Skip to content

个人中心 ​

1. 功能概述 ​

本文介绍如何使用 tio-boot 框架实现个人中心功能,包含以下核心接口:

  • 用户信息查询:获取当前用户的详细信息
  • 用户信息更新:修改用户基本信息
  • 密码更新:安全修改用户密码

2. 接口规范 ​

2.1 用户信息查询接口 ​

请求

http
GET /api/v1/user/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDIwODgzNjgsInVzZXJJZCI6IjQ4ODIzNjI3MjMzODg3MDI3MiJ9.liKfYdWzULDPCeKfktyKyEanZIO3Q7uhZ9mAKMfjWyM

响应示例

json
{
    "error": null,
    "code": 1,
    "msg": null,
    "ok": true,
    "data": {
        "thirdPlatformUrl": null,
        "updater": "",
        "remark": null,
        "createTime": 1752141088649,
        "deleted": 0,
        "creator": "",
        "updateTime": 1752141088649,
        "tenantId": "0",
        "schoolId": null,
        "metadata": "{}",
        "userType": 0,
        "passwordHash": null,
        "passwordSalt": null,
        "bio": null,
        "backgroundUrl": null,
        "updatedProfile": false,
        "birthday": null,
        "phoneNumber": null,
        "emailVerified": false,
        "disabled": false,
        "of": "http://localhost:5173",
        "invitedByUserId": null,
        "coin": "0",
        "mfaInfo": "[]",
        "email": "litongjava@qq.com",
        "photoUrl": null,
        "platform": null,
        "providerData": "[]",
        "id": "532949489378402304",
        "displayName": "litongjava"
    }
}

2.2 用户信息更新接口 ​

请求

http
POST /api/v1/user/update
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDIwODgzNjgsInVzZXJJZCI6IjQ4ODIzNjI3MjMzODg3MDI3MiJ9.liKfYdWzULDPCeKfktyKyEanZIO3Q7uhZ9mAKMfjWyM

# 请求体示例
{"displayName": "新用户名", "bio": "新简介"}

响应示例

json
{
    "error": null,
    "code": 1,
    "msg": null,
    "ok": true,
    "data": 1
}

2.3 密码更新接口 ​

请求

http
POST /api/v1/user/updatePassword
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDIwODgzNjgsInVzZXJJZCI6IjQ4ODIzNjI3MjMzODg3MDI3MiJ9.liKfYdWzULDPCeKfktyKyEanZIO3Q7uhZ9mAKMfjWyM

{
    "old_password": "00000000",
    "new_password": "11111111"
}

响应示例

json
{
    "error": null,
    "code": 1,
    "msg": null,
    "ok": true,
    "data": 1
}

3. 后端实现 ​

3.1 数据模型 ​

密码更新请求对象

java
package nexus.io.tio.boot.admin.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserUpdatePasswordRequest {
  private String oldPassword;
  private String newPassword;
}

3.2 SQL 查询 ​

用户信息查询 SQL (admin_app_user.sql)

sql
-- 获取用户完整信息
SELECT 
  id, email, phone, email_verified, updated_profile,
  display_name, bio, photo_url, background_url, phone_number,
  disabled, birthday, coin, invited_by_user_id, "of", platform,
  third_platform_url, school_id, user_type, provider_data, mfa_info,
  metadata, user_info, google_id, google_info, facebook_id, facebook_info,
  twitter_id, twitter_info, github_id, github_info, wechat_id, wechat_info,
  qq_id, qq_info, weibo_id, weibo_info, remark, creator,
  create_time, updater, update_time, deleted, tenant_id 
FROM app_users 
WHERE ID = ? AND deleted = 0;

3.3 服务层实现 ​

AppUserService.java 用户与密码方法

下面只列出与用户表、密码有关的方法,完整类以框架源码为准。密码的盐与哈希规则由同一组静态方法提供,注册、修改密码、登录校验都调用它们。

java
import java.security.SecureRandom;
import java.util.HexFormat;

import org.apache.commons.codec.digest.DigestUtils;

import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.model.body.RespBodyVo;
import nexus.io.tio.boot.admin.sql.AppUsersSql;
import nexus.io.tio.boot.admin.vo.AppUser;
import nexus.io.tio.boot.admin.vo.UserUpdatePasswordRequest;

public class AppUserService {
  // 查询用户基本信息,不含密码相关字段
  private static final String SQL_SELECT_USER = AppUsersSql.findById;
  // 只查询密码盐与哈希,已删除用户查不到
  private static final String SQL_SELECT_PASSWORD = "SELECT id, password_salt, password_hash FROM app_users WHERE id = ? AND deleted = 0";
  private static final SecureRandom PASSWORD_SALT_RANDOM = new SecureRandom();

  // 生成 16 字节随机盐,十六进制表示
  public static String createPasswordSalt() {
    byte[] bytes = new byte[16];
    PASSWORD_SALT_RANDOM.nextBytes(bytes);
    return HexFormat.of().formatHex(bytes);
  }

  // 哈希规则:sha256(密码 + 盐),注册、修改密码、登录校验共用
  public static String hashPassword(String password, String salt) {
    return DigestUtils.sha256Hex(password + salt);
  }

  public AppUser getUserById(String userId) {
    return Db.findFirst(AppUser.class, SQL_SELECT_USER, userId);
  }

  public AppUser getUserPasswordById(String userId) {
    return Db.findFirst(AppUser.class, SQL_SELECT_PASSWORD, userId);
  }

  // 空输入直接返回 false,避免 null 参与拼接后误判
  public boolean verifyPassword(AppUser user, String password) {
    if (user == null || password == null || user.getPasswordHash() == null) {
      return false;
    }
    return hashPassword(password, user.getPasswordSalt()).equals(user.getPasswordHash());
  }

  // 每次设置密码都生成新盐,盐与哈希在同一条 UPDATE 中写入
  public boolean setPassword(String userId, String password) {
    String salt = createPasswordSalt();
    String sql = "UPDATE app_users SET password_salt = ?, password_hash = ? WHERE id = ? AND deleted = 0";
    return Db.updateBySql(sql, salt, hashPassword(password, salt), userId) == 1;
  }

  // 先校验旧密码,通过后用 setPassword 写入新密码
  public RespBodyVo updatePassword(String userId, UserUpdatePasswordRequest request) {
    AppUser user = getUserPasswordById(userId);
    if (user == null || !verifyPassword(user, request.getOldPassword())) {
      return RespBodyVo.fail();
    }
    int effected = setPassword(userId, request.getNewPassword()) ? 1 : 0;
    return RespBodyVo.ok(effected);
  }

  // 按字段更新用户。Row 中的 password_salt、password_hash 不会被移除,调用方需要自行过滤
  public boolean updateById(String userId, Row row) {
    row.set("id", userId);
    return Db.update("app_users", row);
  }
}

注册接口(registerUser、registerUserByUserId)同样使用 createPasswordSalt() 与 hashPassword(),因此注册后的账号可以直接用 verifyPassword 登录。调用 setPassword 或 updatePassword 成功后,旧盐和旧哈希一并被替换,新密码立即生效。

3.4 控制器实现 ​

AppUserHandler.java 请求处理

java
public class AppUserHandler {
  
  // 获取用户信息
  public HttpResponse profile(HttpRequest request) {
    String userId = TioRequestContext.getUserIdString();
    AppUser user = Aop.get(AppUserService.class).getUserById(userId);
    return TioRequestContext.getResponse().body(RespBodyVo.ok(user));
  }

  // 更新用户信息
  public HttpResponse update(HttpRequest request) {
    String userId = TioRequestContext.getUserIdString();
    Map<String, Object> params = FastJson2Utils.parseToMap(
        request.getBodyString(), 
        String.class, 
        Object.class
    );
    return TioRequestContext.getResponse().body(
        RespBodyVo.ok(Aop.get(AppUserService.class).updateById(userId, Row.fromMap(params)))
    );
  }

  // 更新密码
  public HttpResponse updatePassword(HttpRequest request) {
    String userId = TioRequestContext.getUserIdString();
    UserUpdatePasswordRequest req = FastJson2Utils.parse(
        request.getBodyString(), 
        UserUpdatePasswordRequest.class
    );
    RespBodyVo result = Aop.get(AppUserService.class).updatePassword(userId, req);
    return TioRequestContext.getResponse().body(result);
  }
}

3.5 路由配置 ​

接口路由注册

java
public class AppRoutes {
  public void config) {
    //...
    AppUserHandler userHandler = new AppUserHandler();
    
    r.add("/api/v1/user/profile", userHandler::profile);
    r.add("/api/v1/user/update", userHandler::update);
    r.add("/api/v1/user/updatePassword", userHandler::updatePassword);
  }
}

4. 安全注意事项 ​

  1. 敏感字段过滤:

    • 更新接口中需显式移除 password_salt 和 password_hash 字段
    • 查询接口不应返回密码相关字段
  2. 密码安全:

    • 使用 SHA-256 加盐哈希存储密码
    • 密码更新需验证原始密码
    • 前端传输应使用 HTTPS 加密
  3. 权限控制:

    • 所有接口需通过 JWT 认证
    • 用户只能操作自己的数据(通过 TioRequestContext.getUserIdString() 获取当前用户)

5. 总结 ​

本文详细介绍了使用 tio-boot 框架实现个人中心功能的全过程,包含:

  1. 清晰的接口规范定义
  2. 完整的数据库操作实现
  3. 安全的密码更新流程
  4. 控制器与服务的解耦设计
  5. 路由配置的最佳实践

实现要点:

  • 使用 TioRequestContext 获取当前用户信息
  • 通过 Row 对象实现灵活的数据更新
  • 采用加盐哈希确保密码安全
  • 使用 Aop.get() 实现依赖注入

MIT Licensed