个人中心
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. 安全注意事项
敏感字段过滤:
- 更新接口中需显式移除
password_salt和password_hash字段 - 查询接口不应返回密码相关字段
- 更新接口中需显式移除
密码安全:
- 使用 SHA-256 加盐哈希存储密码
- 密码更新需验证原始密码
- 前端传输应使用 HTTPS 加密
权限控制:
- 所有接口需通过 JWT 认证
- 用户只能操作自己的数据(通过
TioRequestContext.getUserIdString()获取当前用户)
5. 总结
本文详细介绍了使用 tio-boot 框架实现个人中心功能的全过程,包含:
- 清晰的接口规范定义
- 完整的数据库操作实现
- 安全的密码更新流程
- 控制器与服务的解耦设计
- 路由配置的最佳实践
实现要点:
- 使用
TioRequestContext获取当前用户信息 - 通过
Row对象实现灵活的数据更新 - 采用加盐哈希确保密码安全
- 使用
Aop.get()实现依赖注入
