用户中心
个人中心资料查询与编辑、个性化配置读写、朋友圈动态列表与发布(图片经 MinIO 预签名访问)。
10.1 接口总览
🔐 本章 6 个接口均不在认证白名单内,必须携带 Authorization(Bearer Token 或 auth_token Cookie,见第 02 章)。
所有「当前用户」语义均由服务端从 UserContext 取得,客户端无法冒充他人查询或修改。
10.2 个人中心(/api/user/profile)
/api/user/profile
获取当前登录用户的个人中心聚合信息:基础资料、翻译统计与最近 5 条成功翻译记录,一次请求全部返回。
| 字段 | 类型 | 说明 |
|---|---|---|
username | String | 用户名(只读展示,不可修改) |
email | String | 邮箱(只读展示,不可修改) |
nickname | String | 原始昵称,可为空(供编辑表单预填) |
displayName | String | 展示名(昵称为空时回退用户名) |
phone | String | 手机号(服务端脱敏后返回) |
bio | String | 个人简介 |
avatarUrl | String | 头像 URL |
createdAt | DateTime | 注册时间 |
lastLoginTime | DateTime | 最后登录时间 |
totalTranslations | Long | 成功翻译总数 |
totalCharacters | Long | 翻译字符总数 |
recentTranslations | List | 最近 5 条成功翻译记录,元素结构见下 |
{
"id": 1001,
"sourceText": "你好,世界",
"translatedText": "Hello, world",
"sourceLanguage": "zh",
"targetLanguage": "en",
"engine": "QWEN",
"createdAt": "2026-09-06T10:00:00"
}
/api/user/profile
更新当前用户的个人资料,返回更新后的完整个人中心数据(结构与 GET 相同,便于前端直接刷新页面)。
| 字段 | 类型 | 必填 | 校验规则 |
|---|---|---|---|
nickname | String | 可选 | 长度 ≤ 100,否则 400「昵称长度不能超过100字符」 |
phone | String | 可选 | 正则 ^(\+?[0-9]{6,20})?$,否则 400「手机号格式不正确」 |
bio | String | 可选 | 长度 ≤ 500,否则 400「个人简介不能超过500字符」 |
null → 不修改该字段(保持原值) "" 空串 → 清空该字段 其他值 → 更新为该值(需通过对应校验)
用户名与邮箱为只读字段,本接口无法修改;校验失败由全局异常处理器统一转为
400 VALIDATION_ERROR(错误明细见第 11 章)。
10.3 个性化配置(/api/user/settings)
/api/user/settings
读取当前用户的个性化配置,返回配置键值对 Map(键为 setting_key,值为字符串形式的配置值)。
{
"settings": {
"defaultSourceLanguage": "zh",
"defaultTargetLanguage": "en",
"defaultEngine": "QWEN",
"autoTranslate": "true",
"useTerminology": "true",
"qualityAssessment": "false",
"theme": "light",
"fontSize": "14",
"notificationEmail": "true"
}
}
/api/user/settings
更新个性化配置(部分字段更新),返回更新后的配置键值对。所有字段均以字符串传值并通过正则校验。
| 字段 | 类型 | 合法值 | 校验失败提示 |
|---|---|---|---|
defaultSourceLanguage | String | zh|en|ja|ko|fr|de|es|ru|ar|pt 十选一 | 默认源语言不支持 |
defaultTargetLanguage | String | 同上十种语言代码 | 默认目标语言不支持 |
defaultEngine | String | QWEN / ALIBABA_CLOUD | 默认翻译引擎不支持 |
autoTranslate | String | "true" / "false"(字符串布尔) | 自动翻译开关值不正确 |
useTerminology | String | "true" / "false" | 术语库匹配开关值不正确 |
qualityAssessment | String | "true" / "false" | 质量评估开关值不正确 |
theme | String | light / dark | 主题值不支持 |
fontSize | String | 12 – 24 的整数 | 字号需为 12-24 的整数 |
notificationEmail | String | "true" / "false" | 邮件通知开关值不正确 |
null → 不修改;非 null → 整体校验后更新该字段 语言代码集合(与全系统支持语言一致,10 种): zh(中文) en(英语) ja(日语) ko(韩语) fr(法语) de(德语) es(西班牙语) ru(俄语) ar(阿拉伯语) pt(葡萄牙语)
⚠️ 布尔型配置传的是字符串 "true"/"false"(非 JSON 布尔),
传 true(无引号)会因类型不匹配被反序列化拒绝;正则校验失败统一 400 VALIDATION_ERROR。
10.4 朋友圈动态(/api/moments)
/api/moments/list
分页查询朋友圈动态(全站动态流,按发布时间倒序),图片映射为可直接访问的 MinIO 预签名 URL。
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
page | 可选 | 0 | 0 起始(服务端自动 +1 适配 MyBatis-Plus,负值按 0 处理) |
size | 可选 | 20 | 每页条数 |
{
"total": 57,
"page": 0, // 回显客户端传入的 0 起始页码
"size": 20,
"items": [
{
"id": 12,
"userId": 1,
"nickname": "小明", // 昵称为空时回退用户名
"content": "今天用 AI 翻了一整份合同,效率翻倍!",
"imageUrl": "https://minio.../moments/xxx?X-Amz-...", // 预签名,可为 null
"createdAt": "2026-09-06T09:30:00"
}
]
}
⚠️ 注意本接口的分页结构与第 11 章的通用 PagedResponse 不同:
这里是 {total, page, size, items},且 items 直接是动态数组。
图片预签名链接获取失败不影响列表返回(对应条目 imageUrl 为 null,仅记 warn 日志)。
/api/moments/create
发布一条动态,支持可选图片上传。multipart/form-data 编码,图片经 MinIO 对象存储持久化。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
content | String | 必填 | 动态内容;缺失或为空抛 IllegalArgumentException → 400 INVALID_ARGUMENT |
image | File | 可选 | 动态配图(MultipartFile),上传后返回预签名访问 URL |
{
"id": 13,
"userId": 1,
"nickname": "小明",
"content": "第一条动态!",
"imageUrl": "https://minio.../moments/xxx?X-Amz-...",
"createdAt": "2026-09-06T10:00:00"
}
| 场景 | HTTP | error 错误码 |
|---|---|---|
| 未登录(Token 缺失但未过拦截器 / 上下文丢失) | 401 | UNAUTHORIZED |
| content 缺失 / 非法参数 | 400 | INVALID_ARGUMENT(message 为具体原因) |
| 列表查询内部异常 | 500 | MOMENTS_LIST_FAILED |
| 发布内部异常(含图片存储失败) | 500 | MOMENTS_CREATE_FAILED |
💰 发布与浏览动态不消耗点数、不校验会员配额;动态功能与翻译计费链路完全隔离。
10.5 调用示例
# 1. 查询个人中心 curl http://localhost:8090/api/user/profile \ -H "Authorization: Bearer $TOKEN" # 2. 更新昵称与简介(手机号传空串 = 清空) curl -X PUT http://localhost:8090/api/user/profile \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"nickname":"新昵称","phone":"","bio":"翻译爱好者"}' # 3. 更新个性化配置(注意布尔值是字符串) curl -X PUT http://localhost:8090/api/user/settings \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"theme":"dark","fontSize":"16","autoTranslate":"true"}' # 4. 发布带图动态 curl -X POST http://localhost:8090/api/moments/create \ -H "Authorization: Bearer $TOKEN" \ -F "content=第一条动态!" \ -F "image=@./screenshot.png" # 5. 翻页浏览动态 curl "http://localhost:8090/api/moments/list?page=0&size=20" \ -H "Authorization: Bearer $TOKEN"
// 更新资料并直接用返回值刷新个人中心 const resp = await fetch('/api/user/profile', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ nickname: '新昵称', bio: '翻译爱好者' }) }).then(r => r.json()); if (!resp.success) throw new Error(resp.message); const { displayName, totalTranslations, recentTranslations } = resp.data; console.log(`欢迎 ${displayName},累计翻译 ${totalTranslations} 次`); recentTranslations.forEach(t => console.log(`· [${t.engine}] ${t.sourceText} → ${t.translatedText}`)); // 发布动态(FormData 自动处理 multipart 边界,勿手动设置 Content-Type) const fd = new FormData(); fd.append('content', '第一条动态!'); fd.append('image', fileInput.files[0]); // 可选 await fetch('/api/moments/create', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: fd });