Auth 模块 API 文档
基础信息
- Base URL:
/api/auth - 认证方式: JWT Bearer Token
- 刷新机制: Refresh Token(30天有效期)
数据库表
users 表(相关字段)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | text (PK) | 必填 | 10位随机ID |
| phone | text (UNIQUE) | 必填 | 手机号 |
| text (UNIQUE) | 选填 | 邮箱 | |
| nickname | text | 必填 | 昵称 |
| password_hash | text | 必填 | bcrypt加密密码 |
| avatar_url | text | 选填 | 头像URL |
| role | text | 必填 | 角色,默认 user |
| status | text | 必填 | 状态,默认 active |
| is_guest | integer | 必填 | 0=正式账号, 1=游客 |
| has_onboarded | integer | 必填 | 0=未完成引导, 1=已完成 |
| prefer_green_expense | integer | 必填 | 0=否, 1=是(支出用绿色显示) |
| created_at | datetime | 必填 | 创建时间 |
| updated_at | datetime | 必填 | 更新时间 |
refresh_tokens 表
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | text (PK) | 必填 | 10位随机ID |
| token | text (UNIQUE) | 必填 | 刷新令牌(40位hex) |
| user_id | text (FK→users) | 必填 | 所属用户 |
| expires_at | datetime | 必填 | 过期时间 |
| created_at | datetime | 必填 | 创建时间 |
接口列表
1. 获取当前用户信息
GET /api/auth/me
触发时机: 应用启动时(AuthProvider 初始化),从 localStorage 恢复会话
App 启动 → localStorage 有 accessToken → GET /auth/me 验证Token有效性
请求头: Authorization: Bearer <token>
后端处理:
- 从 JWT token 提取 userId
- 查询
users表,取字段:id,phone,nickname,email,avatar_url,prefer_green_expense,is_guest,has_onboarded,created_at,role avatar_url为空时生成默认头像(makeFallbackAvatar(phone))并回写数据库- 规范化 nickname(
normalizeNickname)
响应:
{
"user": {
"id": "string",
"phone": "string",
"nickname": "string",
"email": "string | null",
"avatar_url": "string",
"prefer_green_expense": 0 | 1,
"is_guest": 0 | 1,
"has_onboarded": 0 | 1,
"role": "user | admin | developer",
"created_at": "ISO8601"
}
}
Schema 注意: `avatar_url` 可能为空字符串,调用方需做兜底展示
Schema 注意: `email` 允许 NULL,但注册时必填
2. 注册账号
POST /api/auth/register
触发时机: 在 注册页面 填写完所有信息后点击「注册」按钮
注册页 → 填写手机/密码/昵称/邮箱/验证码 → 点击"注册" → POST /auth/register
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| phone | string | 必填 | 手机号,+86 前缀或国际格式 |
| password | string | 必填 | 至少8位,字母+数字 |
| passwordConfirm | string | 必填 | 确认密码 |
| nickname | string | 必填 | 昵称,至少2字符 |
| string | 必填 | 邮箱 | |
| emailCode | string | 必填 | 邮箱验证码(6位数字) |
| captchaId | string | 必填 | 图形验证码ID |
| captchaAnswer | string | 必填 | 图形验证码答案 |
后端处理:
- 校验手机格式(+86 11位 / 国际格式)
- 校验密码格式(8位+字母+数字)
- 校验图形验证码(内存 Map,2分钟有效期)
- 校验邮箱验证码
- 校验邮箱未注册
- bcrypt 加密密码
- 生成唯一 userId(10次碰撞检测)
- 事务:
- 插入
users表 - 插入
refresh_tokens表 - 创建默认账单(
bills+bill_members,角色owner) - 插入7个默认分类(
categories)
- 插入
- 删除邮箱验证码记录
- 返回 JWT(2h有效期)+ refreshToken(30天)+ user 对象
Schema 注意: 注册时创建默认账单,账单名固定为「默认账单」,owner_id 指向新用户
响应(201):
{
"token": "jwt_string",
"refreshToken": "hex_string",
"user": { "id", "phone", "nickname", "email", "avatar_url", "prefer_green_expense", "is_guest", "has_onboarded", "role" }
}
3. 用户登录
POST /api/auth/login
触发时机: 在 登录页面 填写手机号+密码后点击「登录」按钮
登录页 → 输入手机号+密码+图形验证码 → 点击"登录" → POST /auth/login
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| phone | string | 必填 | 手机号 |
| password | string | 必填 | 密码 |
| captchaId | string | 必填 | 图形验证码ID |
| captchaAnswer | string | 必填 | 图形验证码答案 |
后端处理:
- 校验手机格式
- 校验图形验证码
- 查询
users表,比对password_hash(bcrypt) - 生成 JWT(2h)+ refreshToken(30天)
- 插入
refresh_tokens表 - 头像兜底 + 更新
last_ip
响应(200):
{
"token": "jwt_string",
"refreshToken": "hex_string",
"user": { ... }
}
4. 刷新 Token
POST /api/auth/refresh
触发时机: 自动,由 api.js 响应拦截器在收到 401 时触发
请求返回 401 → api.js 拦截器检测到 → POST /auth/refresh { refreshToken }
→ 获取新 token → 重试原请求
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| refreshToken | string | 必填 | 刷新令牌 |
后端处理:
- 校验 refreshToken 存在且未过期
- 校验对应用户存在
- 生成新 JWT + 新 refreshToken
- 事务:删除旧 token,插入新 token
Schema 注意: refreshToken 过期后用户强制登出,无任何恢复手段
5. 退出登录
POST /api/auth/logout
触发时机: 点击顶部「退出登录」按钮
点击"退出登录" → 清除 localStorage → 跳转登录页 → 同时发送 POST /auth/logout(静默,不阻塞UI)
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| refreshToken | string | 选填 | 传则只删这条,不传则删该用户全部 |
后端处理: 删除 refresh_tokens 表中对应记录
6. 游客登录
POST /api/auth/guest-login
触发时机: 登录页点击「随便看看」/「游客试用」按钮
登录页 → 点击"随便看看" → POST /auth/guest-login
后端处理:
- 生成 userId + guestPhone(
GUEST_XXXXXXXX) - IP 限制:24小时内同 IP 最多2个游客账号
- 事务:插入
users(is_guest=1) + 默认账单 +refresh_tokens - JWT 有效期 7天(与正式账号的2h不同)
Schema 注意: 游客账号 `is_guest=1`,无密码(`password_hash` 存占位符)
7. 游客转正
POST /api/auth/upgrade-guest
触发时机: 游客账号点击「注册正式账号」/「升级」按钮,填写完整信息后提交
游客模式 → 点击"升级为正式账号" → 填写手机/密码/邮箱/验证码 → POST /auth/upgrade-guest
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| phone | string | 必填 | 新手机号 |
| password | string | 必填 | 新密码 |
| passwordConfirm | string | 必填 | 确认密码 |
| nickname | string | 必填 | 昵称 |
| string | 必填 | 邮箱 | |
| emailCode | string | 必填 | 邮箱验证码 |
| captchaId | string | 必填 | 图形验证码ID |
| captchaAnswer | string | 必填 | 图形验证码答案 |
后端处理:
- 确认当前用户是
is_guest=1 - 确认新手机号+邮箱未被他用户占用
- 校验邮箱验证码
- bcrypt 更新密码
- 更新
users表:is_guest=0 - 生成新 JWT(2h)
8. 修改个人资料
PUT /api/auth/me/profile
触发时机: 在 设置页面 修改昵称/头像/绿色支出偏好后点击保存
设置页 → 修改昵称/头像/绿色支出 → 点击"保存" → PUT /auth/me/profile
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| nickname | string | 选填 | 至少2字符 |
| avatar_url | string | 选填 | 头像URL |
| prefer_green_expense | boolean | 选填 | 支出是否绿色显示 |
约束: `phone` 和 `email` **不可修改**,传了也会被后端拒绝
Schema 注意: nickname 最少2字符,不满足返回 400
9. 完成新手引导
POST /api/auth/me/onboard-done
触发时机: 完成注册后第一次引导流程,点击「开始使用」/「完成引导」按钮
注册成功 → 进入引导流程 → 完成引导 → 点击"开始使用" → POST /auth/me/onboard-done
后端处理: 更新 users.has_onboarded = 1
10. 获取注册图形验证码
GET /api/auth/register-captcha
触发时机: 进入 注册页面 时自动请求(无需用户操作)
打开注册页 → 自动 GET /auth/register-captcha → 展示 SVG 验证码图片
11. 获取登录图形验证码
GET /api/auth/login-captcha
触发时机: 进入 登录页面 时自动请求;注册页切换到登录时也触发
打开登录页 → 自动 GET /auth/login-captcha → 展示 SVG 验证码图片
12. 发送注册邮箱验证码
POST /api/auth/send-register-email
触发时机: 在 注册页面 填写邮箱后点击「发送验证码」
注册页 → 输入邮箱 → 点击"发送验证码" → POST /auth/send-register-email
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| string | 必填 | 邮箱地址 | |
| captchaId | string | 必填 | 当前图形验证码ID |
| captchaAnswer | string | 必填 | 图形验证码答案 |
后端处理:
- 校验邮箱格式
- 校验图形验证码
- 检查邮箱未注册
- 60秒频率限制
- 生成6位数字验证码,发送邮件
- 存储到内存 Map,5分钟有效
Schema 注意: 邮箱必须唯一,已注册返回 400
13. 发送重置密码邮箱验证码
POST /api/auth/send-reset-email
触发时机: 在 登录页 点击「忘记密码」,填写邮箱+手机号+图形验证码后点击「发送」
登录页 → 点击"忘记密码" → 填写邮箱+手机号+图形验证码 → 点击"发送" → POST /auth/send-reset-email
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| string | 必填 | 邮箱地址 | |
| phone | string | 必填 | 注册时的手机号 |
| captchaId | string | 必填 | 图形验证码ID |
| captchaAnswer | string | 必填 | 图形验证码答案 |
关键: email + phone 必须匹配数据库中同一条用户记录,否则 400
14. 重置密码
POST /api/auth/reset-password
触发时机: 在 忘记密码流程 收到邮箱验证码后,输入新密码提交
收到邮箱验证码 → 输入新密码+确认密码 → 点击"确认重置" → POST /auth/reset-password
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| string | 必填 | 邮箱地址 | |
| emailCode | string | 必填 | 6位邮箱验证码 |
| newPassword | string | 必填 | 新密码(8位+字母+数字) |
| newPasswordConfirm | string | 必填 | 确认新密码 |
后端处理:
- 校验两次密码一致
- 校验密码格式
- 校验邮箱验证码(内存 Map,5分钟有效期)
- bcrypt 更新
password_hash - 删除验证码记录
密码格式验证规则
/^(?=.*[A-Za-z])(?=.*\d).{8,}$/
手机号格式验证规则
// 中国: +86 + 11位数字
// 国际: + 5-15位数字
/^(\+86)1\d{10}$|^\+\d{5,15}$/
JWT Payload 结构
{ "userId": "string", "phone": "string", "role": "string" }
AuthContext 登录后持久化字段
localStorage.setItem('accessToken', res.data.token)
localStorage.setItem('refreshToken', res.data.refreshToken)
localStorage.setItem('user', JSON.stringify(res.data.user))
14. 重置 IP 限制(开发者专用)
POST /api/auth/dev/reset-ip
认证: 必需(仅 role = 'developer' 可访问)
后端处理:
- 校验当前用户 role 为
developer - 获取请求 IP(支持
x-forwarded-for) - 将所有
last_ip等于该 IP 的用户 IP 字段清空(用于解除 IP 注册限制)
响应:
{ "success": true, "count": 5 }
数据库表: users
⚠️ 注意: 仅供开发者调试使用,不可暴露给普通用户