# 个人信息页 + 头像上传 技术方案 > 状态:方案已确认,待评审后进入开发 > 版本:v0.1 | 日期:2026-08-06 > 范围:桌面端(Electron / Web / 安卓 WebView 复用同一前端) --- ## 1. 需求背景 桌面客户端设置页目前只有 Git 凭证、消息通知、微信 Bot、MCP 服务器、定时任务五项,**缺少用户自己的个人信息管理入口**;顶栏用户区域显示的是固定的占位图标,**不展示真实头像**。 系统数据基础已经具备:`user` 表自 V001 起就有 `avatar_url`、`display_name`、`email` 字段,后端 `GET /v1/users/me` 也已返回这些字段。缺口在于: - 没有「当前用户自助更新资料」的后端接口(现有 `PUT /v1/users/{id}` 是管理员接口,需要 `user:write` 权限) - 桌面端没有个人信息页面 - 顶栏不显示真实头像 本次需求:在桌面端设置页新增「个人信息」子页,支持上传 / 移除头像,并允许修改个人资料(显示名称、邮箱)。 ## 2. 需求描述 ### 2.1 页面结构 在桌面端设置页 `/settings` 下新增子路由 `/settings/profile`(「个人信息」),作为设置页的**默认首页**。页面包含两个区块: **头像区** - 大尺寸圆形头像预览(96px,`el-avatar`) - 「更换头像」:选择图片 → 本地预览 → 点击「保存」统一提交 - 「移除头像」:`avatar_url` 非空时显示,点击后置空并恢复默认占位 - 校验规则:仅允许 `jpg / png / webp`,单文件 ≤ 5MB,不做前端裁剪 **资料区** | 字段 | 展示 | 可编辑 | 说明 | |------|------|--------|------| | 用户名 | 只读文本 | 否 | 登录名,不可修改 | | 显示名称 | 文本输入框 | 是(仅 LOCAL 用户) | LDAP / 飞书用户禁用 | | 邮箱 | 文本输入框 | 是(仅 LOCAL 用户) | LDAP / 飞书用户禁用 | | 登录方式 | 只读标签 | 否 | LOCAL / LDAP / 飞书 | 「保存」按钮统一提交头像 + 资料字段。 ### 2.2 入口 1. 设置页侧边栏顶部新增「个人信息」项(置顶于 Git 凭证之前) 2. `/settings` 默认重定向从 `git-credentials` 改为 `profile` 3. 顶栏右上角下拉菜单在「设置」上方新增「个人信息」直达项 4. 顶栏用户区域头像由固定占位图标改为展示真实头像(`avatar_url` 为空时回退占位图标) ### 2.3 数据流 ``` 用户选择图片 → 本地预览(objectURL) → 点击保存 → ① 若有新头像:uploadImages([file]) 复用现有双模式上传链路,得到 URL → ② PUT /v1/users/me/profile { displayName?, email?, avatarUrl? } 一次提交 → ③ 成功 → authStore.fetchUserInfo() 刷新 → 顶栏 / 页面头像同步更新 ``` 移除头像:`PUT /v1/users/me/profile { avatarUrl: null }`,旧文件不删除,仅更新引用。 ### 2.4 明确不做 | 事项 | 决策 | |------|------| | 管理后台(admin)改动 | **不做**,admin 用户管理不展示/不编辑头像 | | 登录对话框展示头像 | **不做**,保持现状 | | 前端图片裁剪(缩放/裁切/旋转) | **不做**,仅校验格式与大小 | | 后端图片压缩 / 生成缩略图 | **不做**,原图直存直用 | | 密码修改 | **不做**,不在本次范围 | | 删除旧头像文件(本地 / OSS) | **不做**,仅更新引用 | | 数据库变更 | **不做**,`avatar_url` / `display_name` / `email` 字段已存在 | | LDAP / 飞书用户修改显示名称与邮箱 | **不做**,仅 LOCAL 用户可改,LDAP / 飞书只读 | | 飞书 OAuth 头像自动同步 | **不做**,仅支持手动上传 | ## 3. 现状分析 ### 3.1 已有能力(无需重复建设) | 能力 | 位置 | 说明 | |------|------|------| | 用户字段 | `user` 表(V001) | `avatar_url`(512) / `display_name` / `email` / `username` 已存在 | | 获取当前用户 | `GET /v1/users/me`(`UserController.getCurrentUser`) | 返回含 `avatarUrl` 的 `UserInfoVO` | | 本地文件上传 | `POST /v1/files/upload`(`FileController.uploadFile`) | 存本地 `uploads/`,Nginx 映射 `/uploads/`,返回完整 URL | | OSS 直传 | `POST /v1/oss/sts-token` + 前端 ali-oss 直传 | `desktop/src/utils/ossUpload.ts` | | 上传模式选择 | `GET /v1/upload/config`(`UploadController`) | 返回 `storageMode: oss / local` | | 前端上传封装 | `desktop/src/utils/imageUpload.ts` `uploadImages()` | 按 `getUploadConfig()` 自动分流本地 / OSS | | 用户状态 | `desktop/src/stores/auth.ts` | `user` 含 `avatarUrl`;`fetchUserInfo()` 调 `/users/me` | ### 3.2 缺口 1. 后端缺「当前用户自助更新资料」接口(现有 `PUT /v1/users/{id}` 为管理员接口,需 `user:write`) 2. 桌面端缺个人信息页面与路由 3. 顶栏 `TopNav.vue` 头像为固定 ``(`TopNav.vue:66`),未使用 `avatarUrl` 4. 设置页 `SettingsView.vue` 侧边栏无「个人信息」项;`/settings` 默认重定向为 `git-credentials` ## 4. 技术选型 不引入任何新依赖,全部复用现有技术栈: | 层 | 选型 | 说明 | |----|------|------| | 后端 | Spring MVC + MyBatis-Plus(现有) | 新增一个 Controller 端点 + Service 方法 | | 前端 | Vue 3 + Element Plus(现有) | `el-avatar` / `el-upload` / `el-form` / `el-input` | | 上传 | 现有 `uploadImages()` 双模式封装 | 本地模式走 `/files/upload`,OSS 模式走 STS 直传 | | 状态 | Pinia `authStore`(现有) | 保存后 `fetchUserInfo()` 刷新,无需改 store 结构 | ## 5. 后端实现 ### 5.1 新增接口 `PUT /v1/users/me/profile` **位置**:`backend/src/main/java/cn/etarch/mao/user/controller/UserController.java` **请求体**(均为可空,空值不更新;`avatarUrl` 传 null 表示移除头像): ```json { "displayName": "张三", "email": "zhangsan@example.com", "avatarUrl": "https://.../uploads/xxxx.png" } ``` **响应**:更新后的 `UserInfoVO`(与 `GET /v1/users/me` 同构,含 `permissions` / `isAdmin`)。 **处理逻辑**: 1. 通过 `@AuthenticationPrincipal Long userId` 取当前用户 2. 加载 `User` 实体 3. 校验并落库(新增 `UserService.updateOwnProfile` 方法): - `avatarUrl`:**所有登录方式**均可更新;空字符串 / null → 置 `null`(移除头像) - `displayName` / `email`:**仅 LOCAL 用户可更新**(判定:`passwordHash` 非空)。LDAP / 飞书用户若提交这两个字段,直接抛出业务异常(`PARAM_INVALID`,提示「LDAP/飞书账号的资料由系统维护,不可修改」),由前端同时禁用输入框做双保险 - `displayName` 非空且 ≤ 128 字符(与表结构一致) - `email` 若提交则做基本格式校验(非空时) 4. `userMapper.updateById(user)` 5. 复用 `getCurrentUser` 中的 `UserInfoVO` 组装逻辑(抽成私有方法 `buildUserInfoVO(User user)` 供两处复用),返回更新后的完整用户信息 **路由说明**:`PUT /me/profile` 为两段路径,与现有单段 `PUT /{id}` 不冲突;`GET /me` 已有先例,Spring 能正确区分字面量与模板变量。 ### 5.2 拦截器与安全 - 不新增 `@RequirePermission`:当前用户操作自己的资料属于自助行为,与现有 `GET /users/me` 一致,默认放行(由 JWT 认证保证登录态) - `AuditInterceptor` 自动记录本次修改操作,无需额外编码 ### 5.3 数据库 **无迁移脚本**。`avatar_url`、`display_name`、`email` 字段均已存在于 `user` 表。 ## 6. 前端实现(desktop) ### 6.1 新增个人信息页 `desktop/src/views/settings/ProfileView.vue` 结构(`