Auth 模块 API 文档

基础信息


数据库表

users 表(相关字段)

字段 类型 必填 说明
id text (PK) 必填 10位随机ID
phone text (UNIQUE) 必填 手机号
email 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>

后端处理:

  1. 从 JWT token 提取 userId
  2. 查询 users 表,取字段:id, phone, nickname, email, avatar_url, prefer_green_expense, is_guest, has_onboarded, created_at, role
  3. avatar_url 为空时生成默认头像(makeFallbackAvatar(phone))并回写数据库
  4. 规范化 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字符
email string 必填 邮箱
emailCode string 必填 邮箱验证码(6位数字)
captchaId string 必填 图形验证码ID
captchaAnswer string 必填 图形验证码答案

后端处理:

  1. 校验手机格式(+86 11位 / 国际格式)
  2. 校验密码格式(8位+字母+数字)
  3. 校验图形验证码(内存 Map,2分钟有效期)
  4. 校验邮箱验证码
  5. 校验邮箱未注册
  6. bcrypt 加密密码
  7. 生成唯一 userId(10次碰撞检测)
  8. 事务
    • 插入 users
    • 插入 refresh_tokens
    • 创建默认账单(bills + bill_members,角色 owner
    • 插入7个默认分类(categories
  9. 删除邮箱验证码记录
  10. 返回 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 必填 图形验证码答案

后端处理:

  1. 校验手机格式
  2. 校验图形验证码
  3. 查询 users 表,比对 password_hash(bcrypt)
  4. 生成 JWT(2h)+ refreshToken(30天)
  5. 插入 refresh_tokens
  6. 头像兜底 + 更新 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 必填 刷新令牌

后端处理:

  1. 校验 refreshToken 存在且未过期
  2. 校验对应用户存在
  3. 生成新 JWT + 新 refreshToken
  4. 事务:删除旧 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

后端处理:

  1. 生成 userId + guestPhone(GUEST_XXXXXXXX
  2. IP 限制:24小时内同 IP 最多2个游客账号
  3. 事务:插入 usersis_guest=1) + 默认账单 + refresh_tokens
  4. 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 必填 昵称
email string 必填 邮箱
emailCode string 必填 邮箱验证码
captchaId string 必填 图形验证码ID
captchaAnswer string 必填 图形验证码答案

后端处理:

  1. 确认当前用户是 is_guest=1
  2. 确认新手机号+邮箱未被他用户占用
  3. 校验邮箱验证码
  4. bcrypt 更新密码
  5. 更新 users 表:is_guest=0
  6. 生成新 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

请求参数:

参数 类型 必填 说明
email string 必填 邮箱地址
captchaId string 必填 当前图形验证码ID
captchaAnswer string 必填 图形验证码答案

后端处理:

  1. 校验邮箱格式
  2. 校验图形验证码
  3. 检查邮箱未注册
  4. 60秒频率限制
  5. 生成6位数字验证码,发送邮件
  6. 存储到内存 Map,5分钟有效
Schema 注意: 邮箱必须唯一,已注册返回 400

13. 发送重置密码邮箱验证码

POST /api/auth/send-reset-email

触发时机: 在 登录页 点击「忘记密码」,填写邮箱+手机号+图形验证码后点击「发送」

登录页 → 点击"忘记密码" → 填写邮箱+手机号+图形验证码 → 点击"发送" → POST /auth/send-reset-email

请求参数:

参数 类型 必填 说明
email string 必填 邮箱地址
phone string 必填 注册时的手机号
captchaId string 必填 图形验证码ID
captchaAnswer string 必填 图形验证码答案
关键: email + phone 必须匹配数据库中同一条用户记录,否则 400

14. 重置密码

POST /api/auth/reset-password

触发时机: 在 忘记密码流程 收到邮箱验证码后,输入新密码提交

收到邮箱验证码 → 输入新密码+确认密码 → 点击"确认重置" → POST /auth/reset-password

请求参数:

参数 类型 必填 说明
email string 必填 邮箱地址
emailCode string 必填 6位邮箱验证码
newPassword string 必填 新密码(8位+字母+数字)
newPasswordConfirm string 必填 确认新密码

后端处理:

  1. 校验两次密码一致
  2. 校验密码格式
  3. 校验邮箱验证码(内存 Map,5分钟有效期)
  4. bcrypt 更新 password_hash
  5. 删除验证码记录

密码格式验证规则

/^(?=.*[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' 可访问)

后端处理:

  1. 校验当前用户 role 为 developer
  2. 获取请求 IP(支持 x-forwarded-for
  3. 将所有 last_ip 等于该 IP 的用户 IP 字段清空(用于解除 IP 注册限制)

响应:

{ "success": true, "count": 5 }

数据库表: users

⚠️ 注意: 仅供开发者调试使用,不可暴露给普通用户