Files
guoju0808/_self/sponsor_account.md
T
郭庆泰andClaude 8aba05aa0f docs(review): sponsor/account 端到端审查报告
11 章穿透审查:
- 四跳定位 (AdminLayout → router → Account.vue → proto)
- 后端链路: SysProfileController → SysUserMapper
- DB 实测 sys_user DDL + 索引
- 与原型 sponsor-account.html 对比
- 发现 P1: 改手机号未走短信验证码 (后端 /changePhone 已实现但前端未用)
- 发现 P3: sys_user 缺 uk_user_name / idx_phonenumber

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-18 20:47:23 +08:00

15 KiB

sponsor / Account — 端到端页面技术审查

审查日期: 2026-08-18 审查范围: 前端 → 后端 → 数据库 → 原型 审查目标: ry-vue3/src/views/sponsor/Account.vue


0. 四跳定位

文件 / 位置 命中
① 角色菜单 ry-vue3/src/layout/AdminLayout.vue:128 { path: '/sponsor/account', title: '账号信息', icon: Setting }
② 路由 ry-vue3/src/router/index.js:113,123 父路由 { path: '/sponsor', component: AdminLayout, meta: { role: 'sponsor' } } → 子路由 { path: 'account', name: 'sponsor-account', component: () => import('@/views/sponsor/Account.vue'), meta: { title: '账号信息' } }
③ 组件 ry-vue3/src/views/sponsor/Account.vue 审查目标
④ 原型 proto/html/components/sponsor-account.html <h1 class="page-title">账号信息</h1>

角色定义:

  • AdminLayout.vue:65sponsor: '支持方'
  • 父路由守卫 meta.role = 'sponsor' — 全局 router.beforeEach (router/index.js:134-) 拦截非 sponsor 角色

1. 主要功能与可见性

1.1 主要功能

功能 前端入口 后端接口 数据表
加载个人信息 loadProfile() (onMounted) GET /system/user/profile sys_user
修改姓名/手机号 onSave() 分支 1 PUT /system/user/profile sys_user (nick_name, phonenumber, sex)
修改密码 onSave() 分支 2 PUT /system/user/profile/updatePwd sys_user (password, pwd_update_date)

1.2 可见性三层过滤

校验 行为
前端菜单 AdminLayout.vue:128 未设 requireMain 主 / 子账号均可访问 (主账号默认就可见;子账号不要求)
路由守卫 router/index.js:113 meta.role = 'sponsor' 仅 sponsor 角色可进入
后端 SysProfileController.java:53,98 — 直接读 getLoginUser() 操作的是"自己",不存在跨账号泄露风险

不能看到本页面的人群:

  • 其他角色映射到自己的同名页面 (admin/leader/manager/doctor/executor) — 各自 layout 都有 account 菜单项
  • 未登录用户 — 路由守卫拦截

数据隔离机制: 不适用 (改自己)。修改接口只接收 nickName/email/phonenumber/sex,用户身份从 LoginUser 取,前端无法注入他人 ID。


2. 筛选项 (filter-form)

本页面无筛选区(非列表页),跳过。


3. 工具栏按钮 (toolbar)

按钮 功能 接口 涉及表 原型对照
保存 提交姓名/手机号,如有新密码则改密 PUT /system/user/profile + (条件) PUT /system/user/profile/updatePwd sys_user 原型有 保存
取消 清空表单,恢复 nickname/phonenumber 到 profile 值 无 (本地操作) 原型有 取消

4. 表单 (form)

4.1 数据来源

SysProfileController.java:52-61 — 直接把 LoginUserSysUser 序列化返回,SQL 层面没专门查询,等同于读取登录态里缓存的用户对象。

4.2 字段映射

UI label 控件 绑定字段 后端 SQL 命中表.字段 选项来源 原型对照
姓名 el-input form.nickName updateUser (mapper.xml:229-248) — <if test="nickName != null and nickName != ''">nick_name = #{nickName}</if> sys_user.nick_name 接口回填 ⚠️ 原型 readonly,实现可编辑
手机号 el-input form.phonenumber mapper.xml:235 — <if test="phonenumber != null">phonenumber = #{phonenumber}</if> sys_user.phonenumber 接口回填 ⚠️ 原型 readonly,实现可编辑
原密码 el-input type="password" form.oldPassword SysProfileController.java:106SecurityUtils.matchesPassword(oldPassword, password) 仅校验,不落库 用户输入 一致
新密码 el-input type="password" form.newPassword mapper.xml:266-268 — resetUserPwd 更新 password + pwd_update_date sys_user.password, pwd_update_date 用户输入 一致
确认密码 el-input type="password" form.confirmPassword 仅前端比对 用户输入 一致

附注 — 前端"留空不改密"逻辑:

// Account.vue:58-60
if (form.newPassword) {
  await request({ url: '/system/user/profile/updatePwd', ... })
}

新密码为空时,根本不发改密请求 — 比原型 save() 直接调 alert('保存成功') 更严谨。


5. 行内操作按钮

本页面无表格 / 无行内操作,跳过。


6. Java Entity ↔ 数据库表 一致性

6.1 sys_user DDL (DB 实测, 注释乱码以 dump 二次核对)

-- 实测 2026-08-18, guoju0808.sys_user
CREATE TABLE `sys_user` (
  `user_id`        bigint NOT NULL AUTO_INCREMENT,
  `dept_id`        bigint DEFAULT NULL,
  `user_name`      varchar(30) NOT NULL,
  `nick_name`      varchar(30) NOT NULL,
  `user_type`      varchar(2) DEFAULT '00',
  `account_type`   varchar(8) DEFAULT 'MAIN' COMMENT '账号类型 (MAIN=主账号/SUB=子账号)',
  `parent_user_id` bigint DEFAULT NULL,
  `role_type`      varchar(20) DEFAULT 'executor',
  `email`          varchar(50) DEFAULT '',
  `phonenumber`    varchar(11) DEFAULT '',
  `sex`            char(1) DEFAULT '0',
  `avatar`         varchar(100) DEFAULT '',
  `password`       varchar(100) DEFAULT '',
  `status`         char(1) DEFAULT '0',
  `del_flag`       char(1) DEFAULT '0',
  `login_ip`       varchar(128) DEFAULT '',
  `login_date`     datetime DEFAULT NULL,
  `pwd_update_date` datetime DEFAULT NULL,
  `create_by`      varchar(64) DEFAULT '',
  `create_time`    datetime DEFAULT NULL,
  `update_by`      varchar(64) DEFAULT '',
  `update_time`    datetime DEFAULT NULL,
  `remark`         varchar(500) DEFAULT NULL,
  PRIMARY KEY (`user_id`),
  KEY `idx_parent_user_id` (`parent_user_id`)
)

6.2 Entity 字段对照 (SysUser.javasys_user)

实体字段 Java 类型 表字段 表类型 一致? 备注
userId Long user_id bigint 主键
deptId Long dept_id bigint
userName String user_name varchar(30) NOT NULL
nickName String nick_name varchar(30) NOT NULL @Size(max=30), @Xss
email String email varchar(50) @Email
phonenumber String phonenumber varchar(11) @Size(max=11)
sex String sex char(1)
avatar String avatar varchar(100)
password String password varchar(100) @JsonProperty(WRITE_ONLY)
status String status char(1)
delFlag String del_flag char(1)
accountType String account_type varchar(8) MAIN/SUB
parentUserId Long parent_user_id bigint
loginIp String login_ip varchar(128)
loginDate Date login_date datetime
pwdUpdateDate Date pwd_update_date datetime
roleType String role_type varchar(20) 业务角色 (memory/sessions)
dept, roles, roleIds, postIds, roleId (对象/数组) 关联查询字段,不持久化
— (扩展) user_type varchar(2) ⚠️ 表里有 user_type,Entity 没对应字段 (RuoYi 残留,未使用)

6.3 mapper SQL 字段校对

updateUser (mapper.xml:229-248) — 改 profile 时实际落库的列:

  • dept_id, nick_name, email, phonenumber, sex, avatar, password, status, role_type, login_ip, login_date, update_by, remark, update_time
  • 全部对得上表字段

resetUserPwd (mapper.xml:266-268):

update sys_user set pwd_update_date = sysdate(), password = #{password}, update_time = sysdate() where user_id = #{userId}

落 2 列 + 走 user_id 主键。无 update_by,由 MyBatis 拦截器从 BaseEntity 注入。


7. 索引检查 (sys_user 实测)

Key 用途覆盖 评估
PRIMARY user_id updateUser WHERE user_id = ?, resetUserPwd WHERE user_id = ? OK
idx_parent_user_id parent_user_id 子账号关联 OK
phonenumber checkPhoneUniqueWHERE phonenumber = ? AND del_flag='0',全表扫 暂可接受 (用户 26 行);P3
user_name 登录查 WHERE user_name = ? 全表扫 P3 — sys_user 应有 uk_user_name(RuoYi 标准);标准 RuoYi 模板里有,本项目落地时被裁掉了

注: sys_user 当前 26 行 (mysql -e 'SELECT COUNT(*) FROM sys_user' 推算,Cardinality 26),单条 user_id 精确查询均 OK,索引缺失不会立即暴露性能问题,但用户量破万后会变慢,建议尽早补上 uk_user_nameidx_phonenumber


8. 与原型差异 (proto/html/components/sponsor-account.html)

8.1 实现新增 (原型没有)

  • 可编辑的姓名 / 手机号 — 原型 class="form-input" readonly,实现改成 <el-input v-model>
  • 前端密码长度上限 20 — 原型只校验"≥6",实现多了 max=20 (后端其实没强校)
  • 表单 label 宽度 120px — 原型是 flex 90px
  • 密码提示行 "密码长度 6-20 位,支持数字、字母、特殊字符;留空表示不修改密码" — 原型无

8.2 原型有但实现缺失

  • 顶部 h1 "账号信息" — 实现用 breadcrumb 替代,与项目风格统一(其它 sponsor 页面都是 breadcrumb),按项目风格
  • 手机号 readonly — 这是偏离,需用户确认业务意图(原型为何不让改?是否要走短信验证码改?)

8.3 文字 / 标签差异

原型 实现 评估
标题样式 h1 "账号信息" breadcrumb "首页 / 账号管理" 与项目其它页面风格统一
新密码 placeholder "请输入新密码" "请输入新密码" 一致
确认密码 placeholder "请再次输入" "请再次输入新密码" 轻微差异,可接受
校验报错样式 统一错误行 #errMsg Element Plus el-form-item 自带红字 等效

8.4 总结

  • 方向: 实现是"扩展" — 把个人信息页从"只读 + 改密"扩成了"全字段可编辑"
  • 姓名 / 手机号可编辑: 取决于业务策略
    • 如果业务上允许用户改昵称 / 手机号,实现
    • 如果业务上手机号要走短信验证才能改(后端已有 POST /system/user/profile/changePhone 接口),那现在的实现绕过了短信流程,这是 P1

9. 表字段冗余 / 设计问题

# 问题 文件 说明
1 sys_user.user_type 字段无对应 Entity 字段 SysUser.java vs sys_user DDL RuoYi 标准字段,未使用,可考虑清理
2 updateUser mapper 接收 password,但前端调改密走的是 resetUserPwd SysUserMapper.xml:238 不冲突,但语义模糊 — 改 profile 也能改密码?易混
3 updateProfile 接收 email,但前端未传 SysProfileController.java:73,80 vs Account.vue:57 邮件字段当前不开放给用户改;若以后放开,前端一行加字段即可
4 每次保存成功后又调一次 loadProfile() Account.vue:63 多余网络往返 — 表单只是回填,没有展示 profile 的其它字段
5 loadProfile 失败 → toast + 空表单 Account.vue:47 用户体感差;可加"重试"按钮
6 后端 updateUser mapper 用 <if> 拼接,每次 profile 更新都要重写 update_by / update_time SysUserMapper.xml:243,245 由 MyBatis 拦截器从 BaseEntity 取,行为正确 — 仅记录

10. 待修复列表 (按优先级)

# 问题 文件 / 位置 修复建议 严重度
1 改手机号未走短信验证码,与后端 /changePhone 接口意图不一致 Account.vue:57 PUT /system/user/profilephonenumber 改手机号流程拆为单独按钮 + 短信验证码(已有 POST /changePhone);若原型刻意要求"普通表单改手机",则与原型一致,跳过 P1 (需业务确认)
2 前端不传 oldPassword 时,后端 updatePwd 会判"旧密码错误" SysProfileController.java:106-109 前端已通过 form.newPassword 守卫避开了 — 但万一未来前端加单独"改手机"按钮,后端要走 /changePhone,不能复用 updatePwd P2
3 loadProfile 失败 → 空表单 + toast Account.vue:42-48 加占位骨架 / "重试"按钮 P2
4 save 成功后多余 loadProfile() Account.vue:63 直接 ElMessage.success 即可,表单已用最新值 P2
5 前端校验规则与原型不一致 — 密码长度上限 20 Account.vue:32 vs 原型 save() 后端 updatePwd 没校验长度,实际限制在 SecurityUtils.encryptPassword(BCrypt),前端 +1 条 max=20 是防御性,可保留 P3
6 uk_user_name 唯一索引 DB 实测 sys_user 仅 PRIMARY + idx_parent_user_id 用户量上去前补 (user_name, del_flag) 唯一索引;且 RuoYi 登录查 user_name 会全表扫 P3
7 idx_phonenumber DB 实测 sys_user 无 phonenumber 索引 checkPhoneUnique 全表扫;26 行 OK,破万后补 P3
8 前端规则 vs 原型"留空不改密"文案不一致 Account.vue:12 "留空表示不修改密码" 已写在表单下方,清晰 — 仅记录

11. 引用清单

用途 文件 行号
角色菜单 ry-vue3/src/layout/AdminLayout.vue 65, 123-128
路由 ry-vue3/src/router/index.js 113, 123
路由守卫 ry-vue3/src/router/index.js 134+
审查目标 ry-vue3/src/views/sponsor/Account.vue 1-83
后端 Controller ry-api/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java 52-61 (profile), 67-91 (updateProfile), 97-124 (updatePwd)
后端 Service 实现 ry-api/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java 377-381 (updateUserProfile), 415-432 (resetUserPwd)
后端 Mapper XML ry-api/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml 183-185 (checkPhoneUnique), 229-248 (updateUser), 266-268 (resetUserPwd), 285-287 (selectUserIdsByParent)
Entity ry-api/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java 21-91 (字段定义), 117-209 (setter/getter)
DDL (DB 实测) mysql -h 127.0.0.1 -e 'USE guoju0808; SHOW CREATE TABLE sys_user\G' 2026-08-18
DDL (历史快照) ry0808_mysql_dump.sql 2944-2969 (sys_user)
DB 配置 (用户名/密码) ry-api/ruoyi-admin/src/main/resources/application-druid.yml root / cu2oh2co3
原型 proto/html/components/sponsor-account.html 全文 40 行
项目 commit 历史 git log --oneline 7573b41, ff8b172 (executor/account 重写,与本页面同模式)