# Users + Admins 表合并设计文档 **日期**: 2026-05-13 **状态**: 已定稿,待实施 ## 1. 背景 当前系统使用两张独立的认证表: - `users` — 小程序用户(家长/孩子/规划师),加密算法 `xzyj_salt_` + MD5 - `admins` — Web管理端用户(管理员/规划师),加密算法 `xzyj_admin_salt_` + MD5 规划师需要在小程序端和 Web 管理端同时使用,但两套认证体系导致: - 规划师审核通过时需同步创建 admins 记录(`AdminController.guide/approve`) - 密码不同步,规划师需要两套密码 - 扩展新角色需要同时修改两套系统 ## 2. 目标 将 admins 表的功能合并到 users 表,统一认证体系,废弃 admins 表。 ## 3. 设计方案(4个模块) ### Module 1: 数据模型合并 #### users 表新增字段 ```sql -- 迁移SQL (DatabaseInitializer) ALTER TABLE users ADD COLUMN username VARCHAR(50) COMMENT '登录用户名(兼容admins迁移)'; ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active' COMMENT '账号状态: active/inactive/actived'; ALTER TABLE users ADD INDEX idx_username (username); -- status 字段说明: -- active - 正常(小程序用户默认) -- actived - 已激活(规划师设置密码后) -- inactive - 已禁用 ``` #### User.java 实体新增字段 ```java private String username; // 登录用户名 private String status; // 账号状态: active/inactive/actived ``` #### 数据迁移逻辑 (DatabaseInitializer.runMigrations) ```text 遍历 admins 表所有记录: 1. 按 phone 查询 users 表 2. 如果 phone 已存在 users 中: - 更新 role(如果 admins.role=teacher 且 users 是 parent,添加 teacher 角色) - 设置 password(用 xzyj_salt_ + MD5 重新加密) - 设置 username = admins.username(如果有) - 设置 status = active 3. 如果 phone 不存在 users 中: - 在 users 表创建新用户 - 设置必要字段(phone, username, password, role, status) - 不创建家庭(admins 没有家庭概念) 4. 跳过没有 phone 的 admins 记录 ``` **注意**: admins 表不删除,仅停止使用,保留作为回滚备份。 ### Module 2: Web登录重建 #### AdminAuthController 重构 - 将 `AdminMapper` 替换为 `UserMapper` - 所有查询从 `admins` 表改为 `users` 表 - 加密算法从 `xzyj_admin_salt_` + MD5 改为 `xzyj_salt_` + MD5(与 UserService 一致) - `loginByPassword` 改为用 phone 查询 users 表,支持 teacher 角色 - 登录时检测 `status = inactive` → 账号被禁用 - 登录时检测 `role=teacher && password 为空/未设置` → 返回特殊标识让前端提示设置密码 #### 删除文件 - `Admin.java` 实体类 - `AdminMapper.java` Mapper 接口 #### AdminController 调整 - 移除 `@Resource AdminMapper` 注入 - `guide/approve` 方法:删除同步创建/更新 admins 表的逻辑,审核通过直接更新 User 即可 - 检查其他引用 `AdminMapper` 或 `Admin` 的地方并清理 #### UserService 修复 - `resetPassword` 方法(第1021行)当前使用 `xzyj_user_` 加密算法,改为统一的 `xzyj_salt_` + MD5 ### Module 3: 规划师首次密码创建 #### 触发场景 规划师审核通过后,登录 Web 管理端时: 1. Web 登录页输入手机号 + 密码 2. `AdminAuthController.loginByPassword` 查询 users 表 3. 检测到 `role=teacher && (password==null || password=="")` 4. 返回 `Result.error(400, "请先设置密码")` 附带 `needsSetup: true` 标识 #### 小程序端设置密码 1. 规划师在小程序 teacher-profile 页点击 "设置网页密码" 2. 输入新密码 + 确认密码 3. 调用后端 API `POST /api/auth/set-web-password` 4. 后端执行: - `UserService.setPassword(userId, newPassword)` — 用 `xzyj_salt_` + MD5 加密 - `UserService.updateUserStatus(userId, "actived")` — 设置状态为已激活 5. 成功后规划师可用密码登录 Web 管理端 #### 新增后端 API ```java // AuthController 或 AdminAuthController 新增 @PostMapping("/api/auth/set-web-password") public Result setWebPassword(@RequestAttribute("userId") Long userId, @RequestBody Map params) { // 1. 校验密码强度 (6位以上) // 2. 设置密码 (xzyj_salt_ + MD5) // 3. 设置 status = actived // 4. 返回成功 } ``` ### Module 4: 规划师密码重置 #### 小程序端修改密码 1. 规划师在 teacher-profile 页 "修改网页密码" 2. 输入旧密码 + 新密码 + 确认新密码 3. 调用后端 API `POST /api/auth/reset-web-password` 4. 后端执行: - 校验旧密码是否正确 - 设置新密码(`xzyj_salt_` + MD5 加密) - 状态保持不变 #### 新增后端 API ```java // AuthController 或 AdminAuthController 新增 @PostMapping("/api/auth/reset-web-password") public Result resetWebPassword(@RequestAttribute("userId") Long userId, @RequestBody Map params) { // 1. 校验旧密码 // 2. 设置新密码 // 3. 返回成功 } ``` ## 4. 加密算法统一 **统一算法**: `DigestUtils.md5DigestAsHex(("xzyj_salt_" + password).getBytes(StandardCharsets.UTF_8))` | 位置 | 修改前 | 修改后 | |------|--------|--------| | AdminAuthController.encryptPassword (L271) | `xzyj_admin_salt_` + MD5 | `xzyj_salt_` + MD5 | | UserService.encryptPassword (L749) | `xzyj_salt_` + MD5 | ✅ 不变 | | UserService.resetPassword (L1021) | `xzyj_user_` + MD5 | `xzyj_salt_` + MD5 | ## 5. 影响范围 ### 后端文件变更 | 文件 | 变更类型 | 说明 | |------|----------|------| | `entity/User.java` | 修改 | 新增 username, status 字段 | | `entity/Admin.java` | 删除 | 废弃 | | `mapper/AdminMapper.java` | 删除 | 废弃 | | `controller/admin/AdminAuthController.java` | 重写 | UserMapper 替代 AdminMapper,统一加密 | | `controller/admin/AdminController.java` | 修改 | 移除 AdminMapper 引用,清理同步逻辑 | | `controller/auth/AuthController.java` | 修改 | 新增 set-web-password, reset-web-password 端点 | | `service/UserService.java` | 修改 | 修复 resetPassword 算法,新增 setStatus 方法 | | `config/DatabaseInitializer.java` | 修改 | 新增 username/status 迁移 + admins→users 数据迁移 | ### 前端文件变更 | 文件 | 变更类型 | 说明 | |------|----------|------| | `zxyj-frontend/pages/teacher/teacher-profile.vue` | 修改 | 拆分密码设置为首次设置和修改密码两个入口 | | `zxyj-frontend/utils/api.js` | 修改 | 新增 set-web-password, reset-web-password API | ### Web端文件变更 | 文件 | 变更类型 | 说明 | |------|----------|------| | `zxyj-web/src/views/Login.vue` | 无需修改 | AdminAuthController 端点不变(logic变化内部处理) | ## 6. 安全考虑 - 所有密码使用 MD5 + 固定 salt 传输(当前系统已有模式,暂不改 BCrypt 以保持兼容) - 小程序端不存储明文密码 - Web 端密码传输使用 HTTPS(生产环境配置) ## 7. 回滚方案 - admins 表保留不删除,数据完整 - 如果合并后出现问题,可停止使用 users 表的新字段 - 钥匙管理器中有 DatabaseInitializer 中的迁移代码,重新部署会自动跳过