/manager/accounts 页面端到端审查
审查日期: 2026-08-19
审查范围: 前端 → 后端 → DB → 原型
审查者: Claude Code (page-tech-review skill)
0. 组件定位 (四跳链)
| 跳 |
命中 |
| ① 角色菜单 |
AdminLayout.vue:105 MENU.manager → { path: '/manager/accounts', title: '账号管理' } |
| ② 路由 |
router/index.js:85 → name: 'manager-accounts', component: () => import('@/views/manager/Accounts.vue') |
| ③ 组件 |
ry-vue3/src/views/manager/Accounts.vue |
| ④ 原型 |
proto/html/components/account-manage.html (从 proto/html/manager.html:213 data-page="components/account-manage.html" 链接) |
1. 主要功能 + 可见性
1.1 主要功能
| 功能 |
前端入口 |
后端接口 |
数据表 |
| 查看个人信息 |
Accounts.vue:41-48 loadProfile() |
GET /system/user/profile |
sys_user |
| 修改姓名/手机号 |
Accounts.vue:57 onSave 第 1 个 request |
PUT /system/user/profile |
sys_user (nick_name, phonenumber, sex) |
| 修改密码 |
Accounts.vue:59 onSave 第 2 个 request |
PUT /system/user/profile/updatePwd |
sys_user (password, pwd_update_date) |
页面只支持 编辑自己的资料,没有 CRUD 列表——非典型管理页,本质是"个人中心"。
1.2 可见性 (三层过滤)
| 层 |
来源 |
校验字段 |
备注 |
| 前端菜单 |
AdminLayout.vue:95 MENU.manager |
仅 manager 角色可见 |
其他角色走 /<role>/account 路径 |
| 路由守卫 |
router/index.js:61 meta: { role: 'manager' } + permission.js |
token 角色 |
通过则进 |
| 后端 |
SysProfileController.java |
无 role_type 校验,任何已登录用户均可改自己的资料 |
currentUser = loginUser.getUser() |
不能看到本页面的角色: admin / leader / doctor / executor / sponsor — 他们各自有 /<role>/account 路由(其中 admin/leader 走 /leader/account,doctor 走 /doctor/account,executor/sponsor 没有 account 页)。
数据隔离: 因为是"个人中心",后端用 currentUser = loginUser.getUser() 自动从 token 取本人 userId,前端无法传别人的 userId,所以天然隔离。
2. 筛选项 (filter-form)
本页面无 filter-form,只有一个 el-form 表单,所以筛选项章节不适用。
3. 工具栏按钮 (toolbar)
本页面无 toolbar,只有"保存 / 取消"两个表单按钮,详见第 5 章"表单内操作"。
4. 表格 (el-table)
本页面无表格,跳过。
5. 表单内操作按钮
5.1 字段映射
| UI label |
控件 |
绑定字段 |
后端 SQL |
命中表.字段 |
选项来源 |
原型对照 |
| 姓名 |
el-input |
form.nickName |
updateUser <if test="nickName != null and nickName != ''">nick_name = #{nickName},</if> |
sys_user.nick_name |
— |
✅ 姓名 |
| 手机号 |
el-input (maxlength=11) |
form.phonenumber |
updateUser phonenumber = #{phonenumber}, + checkPhoneUnique |
sys_user.phonenumber (varchar(11)) |
— |
✅ 手机号 |
| 原密码 |
el-input type=password |
form.oldPassword |
updatePwd 校验旧密码 (SecurityUtils.matchesPassword) |
sys_user.password |
— |
✅ 原密码 |
| 新密码 |
el-input type=password |
form.newPassword |
resetUserPwd password = #{password}, pwd_update_date = sysdate() |
sys_user.password / pwd_update_date |
— |
✅ 新密码 |
| 确认密码 |
el-input type=password |
form.confirmPassword |
纯前端校验:必须等于新密码 |
— |
— |
✅ 确认密码 |
Sex 隐式传: Accounts.vue:57 data: { ..., sex: profile.value.sex },虽然 UI 没显示性别控件,但 sex 字段在 PUT 时跟着送。Mapper <if test="sex != null and sex != ''"> 会原值回写(若 sex 是 "0/1/2")。Sex 没被 UI 控制 = 用户改不了 (见 P2-4)。
5.2 表单按钮
| 按钮 |
触发函数 |
接口 |
后端动作 |
涉及表.字段 |
原型对照 |
| 保存 |
onSave |
PUT /system/user/profile (+ /updatePwd 如有填密码) |
SysProfileController.updateProfile → userService.updateUserProfile → userMapper.updateUser |
sys_user.nick_name/phonenumber/sex (+ password/pwd_update_date) |
✅ |
| 取消 |
onCancel |
— |
重置表单为初始 profile 快照 |
— |
✅ |
5.3 真实落点说明
- 修改姓名/手机号/sex: 落到
sys_user.nick_name/phonenumber/sex(Accounts.vue:57)。userId 来自 loginUser 而非表单(后端从 token 取 LoginUser.getUser(),SysProfileController.java:55-74),前端无法改 userId,无法越权改别人。
- 修改密码: 落到
sys_user.password(SysProfileController.java:115)。新密码在 service 层 SecurityUtils.encryptPassword() 加密(BCrypt),不存在明文落库。
- 缓存同步: 修改成功后
tokenService.setLoginUser(loginUser) 更新 Spring Security 缓存,下次请求拿到的 user 是新的(SysProfileController.java:87, 120)。
6. Java Entity ↔ 数据库表 一致性
6.1 实测 sys_user DDL (2026-08-19)
6.2 SysUser Entity ↔ DB 字段对照
| 实体字段 |
中文列名 (DB COMMENT) |
Java 类型 |
表字段 |
DB 类型 |
一致? |
userId |
用户ID |
Long |
user_id |
bigint NOT NULL AUTO_INCREMENT |
✅ |
deptId |
部门ID |
Long |
dept_id |
bigint DEFAULT NULL |
✅ |
userName |
用户账号 |
String |
user_name |
varchar(30) NOT NULL |
✅ |
nickName |
用户昵称 |
String |
nick_name |
varchar(30) NOT NULL |
✅ |
userType |
用户类型(00系统用户) |
String |
user_type |
varchar(2) DEFAULT '00' |
✅ |
accountType |
账号类型 (MAIN=主账号/SUB=子账号) |
String |
account_type |
varchar(8) DEFAULT 'MAIN' |
✅ |
parentUserId |
主账号ID (子账号关联) |
Long |
parent_user_id |
bigint DEFAULT NULL |
✅ |
roleType |
⚠️ 空注释 (DB 没 COMMENT) |
String |
role_type |
varchar(20) DEFAULT 'executor' |
⚠️ 列一致,但 DB 注释缺失 |
email |
用户邮箱 |
String |
email |
varchar(50) DEFAULT '' |
✅ |
phonenumber |
手机号码 |
String |
phonenumber |
varchar(11) DEFAULT '' |
✅ (Java 注解 @Size(max=11)) |
sex |
用户性别(0男 1女 2未知) |
String |
sex |
char(1) DEFAULT '0' |
✅ |
avatar |
头像地址 |
String |
avatar |
varchar(100) DEFAULT '' |
✅ |
password |
密码 |
String |
password |
varchar(100) DEFAULT '' |
✅ |
status |
账号状态(0正常 1停用) |
String |
status |
char(1) DEFAULT '0' |
✅ |
delFlag |
删除标志(0代表存在 2代表删除) |
String |
del_flag |
char(1) DEFAULT '0' |
✅ |
loginIp |
最后登录IP |
String |
login_ip |
varchar(128) DEFAULT '' |
✅ |
loginDate |
最后登录时间 |
Date |
login_date |
datetime DEFAULT NULL |
✅ |
pwdUpdateDate |
密码最后更新时间 |
Date |
pwd_update_date |
datetime DEFAULT NULL |
✅ |
createBy (BaseEntity) |
创建者 |
String |
create_by |
varchar(64) DEFAULT '' |
✅ |
createTime (BaseEntity) |
创建时间 |
Date |
create_time |
datetime DEFAULT NULL |
✅ |
updateBy (BaseEntity) |
更新者 |
String |
update_by |
varchar(64) DEFAULT '' |
✅ |
updateTime (BaseEntity) |
更新时间 |
Date |
update_time |
datetime DEFAULT NULL |
✅ |
remark (BaseEntity) |
备注 |
String |
remark |
varchar(500) DEFAULT NULL |
✅ |
无字段缺失,无类型不一致。
7. 索引检查
7.1 sys_user 索引列表
| Key |
列 |
用途覆盖 |
PRIMARY |
user_id |
✅ 主键,所有 WHERE user_id = ? |
idx_parent_user_id |
parent_user_id |
✅ 子账号查询 WHERE parent_user_id = ? |
7.2 缺口分析
| 查询场景 |
涉及列 |
现有索引 |
评价 |
修改密码 (resetUserPwd) |
user_id |
PRIMARY |
✅ |
修改个人信息 (updateUser) |
user_id |
PRIMARY |
✅ |
手机号唯一性校验 (checkPhoneUnique) |
phonenumber |
❌ 无索引 |
⚠️ 暂可接受,行数 ~138 走全表扫描无压力 |
邮箱唯一性校验 (checkEmailUnique) |
email |
❌ 无索引 |
⚠️ 暂可接受 |
登录态 WHERE user_name = ? |
user_name |
❌ 无索引 |
⚠️ Spring Security 框架一般会走 Redis/缓存,DB 仅冷启动用 |
结论: 数据量小(<200 行),所有查询暂可接受。若 sys_user 未来增长到 10k+,应给 user_name / phonenumber 加唯一索引。
8. 与原型差异
原型: proto/html/components/account-manage.html
实现: ry-vue3/src/views/manager/Accounts.vue
8.1 实现新增 (超出原型)
| 项 |
实现 |
原型 |
评价 |
| 表单校验规则 |
✅ rules 必填 + 手机号正则 /^1[0-9]\d{9}$/ + 密码长度 6-20 + 两次密码一致 |
❌ 原型只放占位 |
改进,推荐保留 |
| 确认密码实时校验 |
✅ validator 函数 |
❌ 无 |
改进 |
maxlength="11" 手机号限长 |
✅ (本次新增) |
❌ 原型不限 |
改进 |
8.2 原型有但实现缺失
| 项 |
原型 |
实现 |
严重度 |
"基本信息 / 修改密码" 分区标题 (form-section-title) |
✅ |
❌ 平铺 5 项 |
P2 |
| page-title 风格 (h1 大标题 + 副标题) |
✅ 页面大标题 |
❌ 只用 breadcrumb |
P3 (项目统一用 breadcrumb,可不改) |
form-actions 顶部 border-top 分隔 |
✅ |
❌ 紧贴"确认密码" |
P2 |
8.3 文字 / 标签差异
| 项 |
原型 |
实现 |
一致? |
| 主标题 |
"账号管理" |
"首页 / 账号管理" (breadcrumb) |
⚠️ 项目规范用 breadcrumb,保持 |
| 副标题 |
"修改个人信息、修改密码" |
— |
P3 |
| 密码提示语 |
"*密码长度 6-20 位,支持数字、字母、特殊字符;留空表示不修改密码" |
同 |
✅ |
8.4 总结
整体方向: 实现超出原型 (校验 + 限长),分区分组缺失 (P2)。其他均与原型一致或为项目规范变体。
9. 字段冗余 / 设计问题
9.1 唯一约束语义
phonenumber 无唯一索引,但 checkPhoneUnique (mapper) 通过全表扫描 + del_flag='0' 过滤做软唯一。如果未来需要硬唯一 (例如登录用手机号),需加 UNIQUE KEY uk_phonenumber (phonenumber) 并处理历史重复数据。
9.2 JOIN vs 持久化字段
- 本表
SysUser 是基础表,所有字段都是持久化字段,无 JOIN 出来的视图字段 ✅
9.3 设计闭环
role_type 列 DEFAULT 'executor',但本页面是 manager 角色,显然不可能 default 出 manager 角色 — 这是 sys-user-role-type-truth 内存里说的"sys_user.role_type 是真相源",新用户创建时需主动赋值。
account_type / parent_user_id 用于主/子账号关系,本页面不涉及,跳过。
9.4 安全闭环
- 手机号修改无短信验证码校验:
SysProfileController.updateProfile 直接 setPhonenumber,对比 changePhone 接口走 smsService.verifyCode,两条路径并存:
- 本页
Accounts.vue:57 → 走 updateProfile → 绕过短信
- 假如有"换绑手机号"流程 → 走 changePhone → 走短信
- 这是 RuoYi 框架默认行为(若系统部署方想强制短信,需要在 updateProfile 里也加校验)。属于已知设计,非 bug。
9.5 表格冗余
10. 待修复列表
| # |
问题 |
文件 |
修复建议 |
严重度 |
| 1 |
表单无分区("基本信息 / 修改密码") |
Accounts.vue |
用 el-divider 或自定义 form-section-title 视觉分组,与原型一致 |
P2 |
| 2 |
保存按钮无顶部分隔线,与上方密码字段视觉粘连 |
Accounts.vue |
给 el-form-item 按钮组加 border-top: 1px solid #f0f0f0 + padding-top: 16px |
P2 |
| 3 |
sex 字段 UI 没暴露,但 onSave 静默带上 profile.value.sex |
Accounts.vue:57 |
若 sex 不可改,前端不要传;若可改,加 radio 控件(0男/1女/2未知) |
P2 |
| 4 |
email UI 没暴露,SysProfileController.updateProfile 接受 email 入参但前端不传 |
Accounts.vue + SysProfileController |
若产品定位不许改邮箱,前端显式删除 email 入参;若许改,UI 加输入框 |
P2 |
| 5 |
手机号修改无短信校验 (updateProfile 不走 smsService.verifyCode,仅 changePhone 走) |
SysProfileController.java:67-91 |
与产品确认:个人中心改手机号是否需要短信?需要则复用 changePhone 逻辑 |
P1 |
| 6 |
role_type 列 DB 注释缺失,只有 account_type 有注释 |
DB sys_user.role_type |
ALTER TABLE sys_user MODIFY COLUMN role_type varchar(20) DEFAULT 'executor' COMMENT '业务角色 (admin/leader/manager/doctor/executor/sponsor)' |
P3 |
| 7 |
phonenumber 无唯一索引,行数大后 checkPhoneUnique 全表扫描 |
sys_user |
加 UNIQUE KEY uk_phonenumber (phonenumber) (需先排查历史重复) |
P3 |
| 8 |
用户名/手机号正则过宽 (/^1[0-9]\d{9}$/ 接受第二位为 0/1/2) |
Accounts.vue:30 |
收紧为 /^1[3-9]\d{9}$/ (大陆手机号第二位规范) |
P3 |
11. 引用清单
| 文件 |
行号 |
说明 |
ry-vue3/src/views/manager/Accounts.vue |
全文 |
审查目标 |
ry-vue3/src/views/manager/Accounts.vue:7 |
— |
手机号 input maxlength=11 (本次新增) |
ry-vue3/src/views/manager/Accounts.vue:30 |
— |
手机号正则 /^1[0-9]\d{9}$/ |
ry-vue3/src/views/manager/Accounts.vue:57 |
— |
保存 profile (PUT /system/user/profile) |
ry-vue3/src/views/manager/Accounts.vue:59 |
— |
保存密码 (PUT /system/user/profile/updatePwd) |
ry-vue3/src/layout/AdminLayout.vue |
95-106 |
MENU.manager 数组,/manager/accounts 在第 10 项 |
ry-vue3/src/router/index.js |
61, 85 |
路由表 /manager 父路由 + manager-accounts 子路由 |
proto/html/manager.html |
213 |
原型菜单链接 data-page="components/account-manage.html" |
proto/html/components/account-manage.html |
全文 |
原型基准 |
ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java |
37-215 |
后端 controller 全部 5 个接口 |
ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java:67-91 |
— |
updateProfile (无短信校验的手机号更新) |
ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java:97-124 |
— |
updatePwd (旧密码校验 + 加密 + 缓存同步) |
ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java:131-162 |
— |
changePhone (带短信校验,本页面未使用) |
ry-api/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java |
21-317 |
SysUser 实体 |
ry-api/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java:149 |
— |
@Size(min=0, max=11) phonenumber |
ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java |
204-214 |
checkPhoneUnique 实现 |
ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java |
377-381 |
updateUserProfile → 调 userMapper.updateUser |
ry-api/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml |
183-185 |
checkPhoneUnique SQL |
ry-api/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml |
231-251 |
updateUser SQL (用 <set><if> 条件更新) |
ry-api/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml |
269-271 |
resetUserPwd SQL (改 password + pwd_update_date) |
ry-api/ruoyi-admin/src/main/resources/application-druid.yml |
10 |
DB 连接信息 (password: cu2oh2co3) |
MySQL guoju0808.sys_user |
— |
24 列 + 1 主键 + 1 副索引,实测 2026-08-19 |