feat: 添加后台登录验证码、记住密码功能,优化媒体资源与前端规范

- 新增后台登录图形验证码功能,完善登录安全防护
- 新增登录rememberMe参数,控制Refresh Token的会话持久化策略
- 实现OSS私有桶媒体URL自动签名,统一处理图片资源的临时访问签名
- 新增素材库数据库表与上传API,规范媒体资源管理流程
- 统一前端UI图标使用@element-plus/icons-vue,重构布局图标组件
- 登录页新增验证码输入、刷新功能,添加账号记忆与记住密码逻辑
- 更新全套文档,补充API契约、技术决策记录与集成流程说明
- 修复多个业务页面的图标展示问题,新增认证流程相关测试用例
This commit is contained in:
duanshuwen
2026-08-27 07:50:59 +08:00
parent 2c8c327de7
commit 6245159e7c
35 changed files with 914 additions and 79 deletions

View File

@@ -31,6 +31,7 @@ MiniAPP 或 Public API 联调:
| `detail-api.md` | 路线详情管理 API | 后端、管理前端 |
| `concierge-api.md` | 管家顾问管理 API | 后端、管理前端 |
| `public-api.md` | MiniAPP 使用的 Public API 唯一契约 | 后端、MiniAPP |
| `decisions/` | 当前重要技术决策记录 | 全部 |
## 文档边界

View File

@@ -15,17 +15,25 @@
## 通用约定
- API 前缀:`/api/admin`
- 除登录接口外均需 `Authorization: Bearer <admin-jwt>`
- 验证码和登录接口不要求 `Authorization`;其余受保护 Admin API 使用 `Authorization: Bearer <admin-jwt>` 或认证 Cookie 约定
- JSON 请求统一使用 camelCase 字段。
- 变更接口写入审计日志后再提交事务。
- 成功业务结果统一放在 `data`;创建成功为 HTTP/code `201`
- 失败统一返回数字 `code`、用户可读 `msg``data: null`,业务错误码放在可选的 `errorCode`
- 所有持久化资源的 `id` 由后端生成稳定 UUID 字符串。Admin UI 必须保存并复用接口返回的 ID不能根据标题、文案或数组下标自行拼接也不能假设 ID 是可读 slug。
## 管理端图标约定
- `WonderQ-Admin-UI-Vue` 的界面图标统一使用 `@element-plus/icons-vue`,不新增手写 SVG、Emoji 或其他图标库。
- `src/components/layout/LayoutIcon.vue` 是后端菜单图标名与 Element Plus 图标组件之间的受控白名单适配器;未知图标必须回退为默认菜单图标,不能动态执行组件路径。
- 为兼容既有菜单数据,`Route``Chevron` 等 WonderQ 图标名继续保留为前端语义别名,分别映射到 Element Plus 的 `Guide``ArrowDown`
- `ConciergeDetail.icon` 等业务字段属于内容数据,不是管理端界面图标;其值和接口契约不因本规范改变。
## 接口清单
| 方法 | 路径 | 用途 |
| -------- | ----------------------------------------- | -------------------- |
| `GET` | `/api/admin/auth/captcha` | 获取后台登录图形验证码 |
| `POST` | `/api/admin/auth/login` | 后台登录 |
| `POST` | `/api/admin/auth/refresh` | 使用 HttpOnly Cookie 刷新后台访问令牌 |
| `POST` | `/api/admin/auth/logout` | 撤销当前后台会话 |
@@ -70,6 +78,8 @@
| `GET` | `/api/admin/media-assets` | 素材列表 |
| `POST` | `/api/admin/media-assets/upload` | 上传图片 |
媒体上传成功响应中的 `data.url` 是可直接用于图片回显的 HTTP(S) URL。OSS 私有读场景下,该 URL 会包含短时 GET 签名;管理端应直接使用返回值,后续 Admin/Public API 响应会重新生成签名。该接口只写入素材库,不会自动绑定首页配置;绑定首页轮播或用车卡片后,仍需提交对应的站点配置保存接口。
## 站点模块
`SiteModule` 只允许以下值:
@@ -102,9 +112,34 @@ type SiteModule =
`POST /api/admin/auth/login` 请求:
```json
{ "email": "admin@example.test", "password": "<password>" }
{
"email": "admin@example.test",
"password": "<password>",
"captchaId": "<captcha-id>",
"captchaCode": "ABCD",
"rememberMe": false
}
```
登录前先调用 `GET /api/admin/auth/captcha`,接口返回:
```json
{
"code": 200,
"msg": "success",
"data": {
"captchaEnabled": true,
"captchaId": "<captcha-id>",
"image": "data:image/svg+xml;base64,<image-data>",
"expiresIn": 120
}
}
```
验证码只允许消费一次,默认 120 秒过期。验证码答案只以哈希形式保存在 Redis 中;验证码错误、过期或重复使用时登录返回 `401`,管理端应重新获取验证码。验证码接口和登录接口都依赖 RedisRedis 不可用时返回 `503`,不得绕过验证码或会话校验。
`rememberMe` 默认为 `false`。勾选后Refresh Token Cookie 按现有 7 天有效期持久化,刷新令牌轮换时继续保持持久化;未勾选时使用会话级 HttpOnly Cookie刷新时不延长为持久化 Cookie。该字段只控制登录会话生命周期不表示服务端或前端保存密码。
成功响应包裹为 `data: { token, accessToken, expiresIn, user: { id, email, name, role } }``accessToken` 是短时访问令牌,`token` 是当前响应中的同值兼容字段Refresh Token 只通过同域 HttpOnly Cookie 返回,不进入 JSON。
管理员登录、刷新和退出依赖 Redis 会话存储。Refresh Token 轮换后旧令牌立即失效Redis 不可用时认证接口返回 `503`,不降级为无会话校验。

View File

@@ -0,0 +1,24 @@
# 0001 后台登录增加图形验证码
## 状态
已采纳。
## 决策
后台登录沿用 RuoYi 的交互模式:登录页先获取图形验证码,提交账号、密码、验证码 ID 和验证码内容;验证码错误、过期或重复使用时,前端重新获取验证码。
WonderQ 使用 `GET /api/admin/auth/captcha`,返回验证码 ID、短时 SVG 图片 data URL 和有效秒数。验证码答案不进入响应、不写日志,只以哈希形式保存到 Redis并在校验时单次消费。验证码接口、登录限流和管理员会话都依赖 RedisRedis 不可用时返回 `503`,不降级绕过安全校验。
## 原因
- 保留 RuoYi 用户熟悉的登录防护和刷新交互。
- 不新增 Pillow 等图片依赖,使用后端生成的受控 SVG减少 Docker 镜像和部署复杂度。
- 让验证码与现有 Redis 会话、登录限流处于同一安全边界。
## 影响范围
- `WonderQ-Admin` 新增验证码接口、Redis 单次消费存储和登录请求字段。
- `WonderQ-Admin-UI-Vue` 登录页新增验证码图片、刷新和失败重试。
- `WonderQ-MiniAPP` 不使用后台登录,不需要改动。
- 现有 `token/accessToken/user` 登录响应和 Refresh Token Cookie 保持兼容。

View File

@@ -0,0 +1,23 @@
# 0002 后台登录记住登录状态
## 状态
已采纳。
## 决策
登录页提供 RuoYi 风格的“记住密码”勾选项,但 WonderQ 不保存明文密码。`rememberMe` 作为登录请求字段传给后端:勾选时 Refresh Token Cookie 使用现有 7 天持久化策略;未勾选时使用会话级 HttpOnly Cookie。Redis 会话记录保存该状态Refresh Token 轮换时继承原状态。
Vue 管理端只在勾选后保存账号和勾选偏好,用于下次填充账号;密码交由浏览器密码管理器处理,不能写入 localStorage、Cookie 或业务 API。
## 原因
- 保留 RuoYi 用户熟悉的登录体验。
- 避免为了“记住密码”在前端持久化可复用的密码凭据。
- 让“记住登录状态”与现有 HttpOnly Refresh Token 会话模型一致。
## 影响范围
- `WonderQ-Admin` 的登录请求增加可选 `rememberMe`Redis 会话记录兼容旧记录,旧记录默认按未勾选处理。
- `WonderQ-Admin-UI-Vue` 增加复选框和账号记忆逻辑。
- `WonderQ-MiniAPP` 不使用后台登录,不需要改动。

View File

@@ -70,14 +70,18 @@ yarn dev:mp-weixin
- `GET /health` 返回健康状态。
- `GET /api/public/site-config` 返回前台站点配置。
- `POST /api/admin/auth/login` 返回统一包裹的登录结果
- `GET /api/admin/auth/captcha` 返回图形验证码和短时 `captchaId`
- `POST /api/admin/auth/login` 携带 `captchaId``captchaCode` 和可选 `rememberMe` 后返回统一包裹的登录结果;验证码错误或过期后重新获取。
- 管理端登录后依次读取 `/api/admin/system/profile``/api/admin/system/routers`;后者按当前管理员权限提供动态导航树。
- 所有成功响应包含数字 `code``msg: "success"``data`;失败响应的 `data` 必须为 `null`
管理端重点检查:
- 登录后请求带 `Authorization: Bearer <access-token>`Refresh Token 只通过 HttpOnly Cookie 传递。
- 登录页应展示验证码图片,点击图片可刷新;验证码失败后自动刷新,不能在前端缓存或记录验证码答案。
- “记住密码”只控制 Refresh Token Cookie 是否持久化;前端最多记住账号和勾选偏好,不得保存明文密码。
- 动态路由只注册 `/api/admin/system/routers` 返回的页面菜单;目录用于组织层级,按钮只用于按钮权限,不注册为页面。
- 管理端界面图标统一由 `@element-plus/icons-vue` 提供;菜单返回的图标名经 `LayoutIcon` 白名单映射,未知值显示默认图标。顾问服务详情中的 `icon` 字段仍按业务内容数据处理。
- 变更角色、菜单或用户关联后,权限缓存失效时要重新登录或重新加载菜单,确认侧栏、路由和按钮权限同步变化。
- 站点模块、玩法、详情、管家、线索和媒体接口按当前契约返回。
- 新增、更新、删除和排序成功后,页面使用接口返回的数据更新状态,不自行生成 ID 或排序结果。

View File

@@ -52,6 +52,7 @@
- `GET /api/admin/site-config` 返回启用和停用的完整记录,前端负责显示状态。
- `sortOrder` 为从 `0` 开始的非负整数,后端负责重新规范化。
- 图片字段保存最终 HTTP(S) URL不接受 base64管理端上传组件通过媒体上传接口先取得 URL再提交配置。
- 图片字段保存最终 HTTP(S) URL不接受 base64管理端上传组件通过媒体上传接口先取得 URL再提交配置。OSS 私有读场景下,接口响应会为 OSS 图片 URL 临时追加短时 GET 签名,供管理端和 Public API 回显;后端会在每次响应时重新签名,签名参数不应由客户端自行拼接或长期缓存。
- `POST /api/admin/media-assets/upload` 只创建素材库记录,不会自动修改 `heroSlides``vehicleOptions`。将图片用于首页配置时,必须把返回的 `data.url` 写入对应编辑表单,并继续提交对应的 `POST``PATCH /api/admin/site-config/{module}/{id}`;成功后才会在 `GET /api/admin/site-config` 中返回该图片。
- 首页玩法推荐、团队共创和极境视界保留独立的新增、编辑、删除、启停和排序能力,保存后由 Public API 直接提供给 MiniAPP。
- 旧需求页主视觉、特色卡片、需求表单、体验推荐、用车服务说明等表结构已由后续 Alembic 迁移删除,不能在新代码中重新声明或调用。

View File

@@ -238,6 +238,8 @@ type HomeWildArchive = {
站点模块通常包含 `id``createdAt``updatedAt``isActive``sortOrder`。客户端按 `sortOrder` 消费排序模块,不依赖固定 ID。
图片字段(如 `image``gallery``avatar``qrImage`)始终返回可直接请求的 HTTP(S) URL。OSS 配置为私有读时WonderQ-Admin 会在响应中生成短时 GET 签名 URL客户端应直接使用返回值不应持久化或自行修改签名参数。
旧需求页主视觉、特色卡片和需求表单已移除;需求页面只通过 `POST /api/public/leads` 提交实时线索,不再读取已删除的站点配置表。
## 登录接口