Files
guoju0808/_self/manager_accounts.md
T
郭庆泰andClaude e496f34393 feat: 多处页面改造 + 新增劳务协议模板配置
主要改动:
- biz_meeting_attendee 中间表 + doctor/expert 角色数据隔离
- biz_meeting_attendee 加 handsign/labor_protocol 字段 (劳务协议改造)
- biz_labor_protocol_template 配置表 + admin 页面 (从 HeguiConstants 迁移)
- biz_expert.expertId 改 Long + IdGenerator 生成 (去自增)
- Login.vue / PublicityDetail.vue / Home.vue 等多处 bug 修复 + UI 改进
- 新增 page-tech-review 报告 3 篇 (_self/*.md)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 21:06:49 +08:00

17 KiB

/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)

CREATE TABLE `sys_user` (
  `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
  `user_name` varchar(30) NOT NULL COMMENT '用户账号',
  `nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
  `user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00系统用户)',
  `account_type` varchar(8) DEFAULT 'MAIN' COMMENT '账号类型 (MAIN=主账号/SUB=子账号)',
  `parent_user_id` bigint DEFAULT NULL COMMENT '主账号ID (子账号关联)',
  `role_type` varchar(20) DEFAULT 'executor',
  `email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
  `phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
  `sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
  `avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
  `password` varchar(100) DEFAULT '' COMMENT '密码',
  `status` char(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
  `del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
  `login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
  `login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
  `pwd_update_date` datetime DEFAULT NULL COMMENT '密码最后更新时间',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`user_id`),
  KEY `idx_parent_user_id` (`parent_user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=138 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户信息表';

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