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:85name: '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.updateProfileuserService.updateUserProfileuserMapper.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