# 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` | `

账号信息

` | **角色定义**: - `AdminLayout.vue:65` — `sponsor: '支持方'` - 父路由守卫 `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` — 直接把 `LoginUser` 的 `SysUser` 序列化返回,SQL 层面没专门查询,等同于读取登录态里缓存的用户对象。 ### 4.2 字段映射 | UI label | 控件 | 绑定字段 | 后端 SQL | 命中表.字段 | 选项来源 | 原型对照 | |---|---|---|---|---|---|---| | 姓名 | `el-input` | `form.nickName` | `updateUser` (mapper.xml:229-248) — `nick_name = #{nickName}` | `sys_user.nick_name` | 接口回填 | ⚠️ **原型 readonly,实现可编辑** | | 手机号 | `el-input` | `form.phonenumber` | mapper.xml:235 — `phonenumber = #{phonenumber}` | `sys_user.phonenumber` | 接口回填 | ⚠️ **原型 readonly,实现可编辑** | | 原密码 | `el-input type="password"` | `form.oldPassword` | `SysProfileController.java:106` — `SecurityUtils.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` | 仅前端比对 | — | 用户输入 | ✅ 一致 | **附注 — 前端"留空不改密"逻辑**: ```js // 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 二次核对) ```sql -- 实测 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.java` ↔ `sys_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)**: ```sql 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` | ❌ `checkPhoneUnique` 走 `WHERE 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_name` 和 `idx_phonenumber`。 --- ## 8. 与原型差异 (`proto/html/components/sponsor-account.html`) ### 8.1 实现新增 (原型没有) - **可编辑的姓名 / 手机号** — 原型 `class="form-input" readonly`,实现改成 `` - **前端密码长度上限 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 用 `` 拼接,每次 profile 更新都要重写 update_by / update_time | `SysUserMapper.xml:243,245` | 由 MyBatis 拦截器从 BaseEntity 取,行为正确 ✅ — 仅记录 | --- ## 10. 待修复列表 (按优先级) | # | 问题 | 文件 / 位置 | 修复建议 | 严重度 | |---|---|---|---|---| | 1 | 改手机号**未走短信验证码**,与后端 `/changePhone` 接口意图不一致 | `Account.vue:57` `PUT /system/user/profile` 含 `phonenumber` | 改手机号流程拆为单独按钮 + 短信验证码(已有 `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 重写,与本页面同模式) |