1. 概述

本文将为我们的 Reddit 应用 添加一个 用户个人中心(Profile)功能,让使用者可以配置个性化的默认偏好设置。

目标很明确:用户在每次发布新帖子时,不再需要重复填写相同的配置项。通过在个人中心中预设偏好,系统会自动填充这些默认值。当然,每次发帖时仍可临时修改,但至少——不用每次都从零开始。

这属于典型的“提效型”功能,长期看能显著提升用户体验,属于那种“早该有”的功能。


2. Preference 实体设计

我们将把应用中大多数可配置项,统一收归到用户的个人偏好中进行管理。

先来看核心的 Preference 实体类:

@Entity
public class Preference {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String email;

    private String subreddit;

    private boolean sendReplies;

    // 自动重发相关配置
    private int noOfAttempts;
    private int timeInterval;
    private int minScoreRequired;
    private int minUpvoteRatio;
    private boolean keepIfHasComments;
    private boolean deleteAfterLastAttempt;
}

说明

  • 这个类本质上是用户级的“全局默认配置”
  • email 用于接收系统通知(比如发帖失败、成功等)
  • 后面一堆字段控制自动重发逻辑,比如最多尝试几次、间隔多久、达到多少分数就停止重发等

接下来,把 PreferenceUser 关联起来:

@Entity
public class User {
    ...
    
    @OneToOne
    @JoinColumn(name = "preference_id")
    private Preference preference;
}

⚠️ 踩坑提醒
这里用的是 @OneToOne + @JoinColumn,表示 User 表中有一个外键指向 Preference。别搞反了,否则初始化时容易出现 null 引用问题。


3. 简单的个人中心页面

前端页面我们不追求花里胡哨,目标是快速可用。

<form>
    <input type="hidden" name="id" />
    <input name="email" type="email"/>
    <input name="subreddit"/>
    ...
   <button onclick="editPref()">保存修改</button>
</form>

<script>
$(function() {
    $.get("user/preference", function (data){
        $.each(data, function(key, value) {
            $('*[name="'+key+'"]').val(value);
        });
    });
});

function editPref(){
    var data = {};
    $('form').serializeArray().map(function(x){data[x.name] = x.value;});
    $.ajax({
        url: "user/preference/" + $('input[name="id"]').val(),
        data: JSON.stringify(data),
        type: 'PUT',
        contentType: 'application/json'
    })
    .done(function() { 
        window.location.href = "./"; 
    })
    .fail(function(error) { 
        alert(error.responseText); 
    }); 
}
</script>

📌 关键点解析

  • 页面加载时通过 GET /user/preference 拉取当前用户的偏好
  • 使用 $.each 自动填充表单字段,简单粗暴但有效
  • 提交走 PUT 接口,JSON 格式提交

顺手在首页加个入口:

<h1>Welcome, <a href="profile" sec:authentication="principal.username">username</a></h1>

这样用户一眼就能点进自己的个人中心。


4. 后端接口实现

核心逻辑在 UserPreferenceController,负责读取和更新用户偏好。

@Controller
@RequestMapping(value = "/user/preference")
public class UserPreferenceController {

    @Autowired
    private PreferenceRepository preferenceReopsitory;

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public Preference getCurrentUserPreference() {
        return getCurrentUser().getPreference();
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.OK)
    public void updateUserPreference(@RequestBody Preference pref) {
        preferenceReopsitory.save(pref);
        getCurrentUser().setPreference(pref);
    }
}

注意点

  • getCurrentUser() 是一个工具方法,从 Spring Security Context 中获取当前登录用户
  • 更新时先 save 到数据库,再同步到内存中的 User 对象,避免下次读取不一致

⚠️ 初始化陷阱
别忘了在用户首次登录/注册时,初始化其 Preference

public void loadAuthentication(String name, OAuth2AccessToken token) {
    User user = userReopsitory.findByUsername(name);
    if (user == null) {
        user = new User();
        user.setUsername(name);
        
        // 初始化偏好设置
        Preference pref = new Preference();
        pref.setEmail(name + "@example.com");  // mock 默认邮箱
        preferenceReopsitory.save(pref);
        
        user.setPreference(pref);
        userReopsitory.save(user);
    }
    ...
}

❌ 如果漏了这一步,后续访问 /user/preference 会 NPE,线上踩过的真实坑


5. 偏好数据的加载与使用

有了默认值,就得让它真正发挥作用。

以“发帖调度页”为例,我们在页面加载时自动填充用户偏好:

$(function() {
    $.get("user/preference", function (data){
        $.each(data, function(key, value) {
            $('*[name="'+key+'"]').val(value);
        });
    });
});

📌 效果

  • 用户打开发帖页,subredditemail 等字段已自动填好
  • 只需微调,无需从头输入
  • 修改后仍可临时覆盖,不影响默认值

这种“默认 + 可覆盖”的设计模式,在后台系统中非常常见,属于提升 UX 的基本操作。


6. 测试与总结

最后一步:加点集成测试,确保核心流程不翻车。

我们基于已有的持久层测试基类扩展即可:

@Test
public void whenUserCreated_thenHasPreference() {
    User user = createUser("testuser");
    assertNotNull(user.getPreference());
    assertEquals("testuser@example.com", user.getPreference().getEmail());
}

@Test
public void whenUpdatePreference_thenSaved() {
    Preference pref = user.getPreference();
    pref.setSubreddit("java");
    userPreferenceController.updateUserPreference(pref);
    
    Preference updated = preferenceRepository.findById(pref.getId());
    assertEquals("java", updated.getSubreddit());
}

总结

  • 用户个人中心功能完成
  • 支持偏好设置的读取、更新、初始化
  • 前后端联动,实现默认值自动填充
  • 代码简洁,扩展性强,后续加新字段也很方便

这个功能看似小,但对用户来说是实实在在的便利。别小看这些“小改进”,积少成多才是好产品


原始标题:A User Profile in the Reddit App

« 上一篇: Baeldung周报第25期
» 下一篇: Baeldung周报26