# 个人头像云端维护 > Makelore 个人资料模块 · 服务端接口与联调说明 | 项目 | 内容 | | --- | --- | | 文档状态 | 服务端接口已完成,待客户端联调 | | 目标读者 | 用户服务、文件存储、API 开发人员 | | 客户端约束 | 客户端统一将头像裁剪为正方形,界面以圆形蒙版展示 | ## 联调前置 - 部署服务端时,先执行 Alembic 迁移:`20260813_user_avatar_cloud_0040`。 - 新客户端只使用以下接口:`POST /api/user/avatar`、`DELETE /api/user/avatar`、`GET /api/user/agent-profile`。 - 旧接口 `/api/auth/me/avatar` 仅保留兼容,新客户端不要继续使用。 ## 1. 需求概述 Makelore 个人资料弹窗需要支持用户维护个人头像,并将头像按账号保存到云端。用户可在个人资料中上传、替换或移除头像;保存后,头像应在不同设备间同步。 **核心结论:**头像通过独立接口维护。服务端提供上传、删除接口,并在个人资料查询响应中返回只读字段 `avatar_url`;`avatar_url` 不得放入 Agent Profile 的 PUT 请求体。 ## 2. 产品范围与行为 - 头像展示位置:侧边栏底部账号按钮、展开后的账号菜单、个人资料弹窗预览。聊天消息布局本期不增加用户头像。 - 上传格式:PNG、JPEG、WebP。客户端会在上传前自动居中裁剪为正方形,并压缩到最长边 512px。 - 展示方式:客户端使用圆形蒙版展示头像;服务端不需要保存圆形遮罩图,保存正方形图片即可。 - 保存时机:用户选择图片或点击移除后只更新弹窗草稿;点击“保存个人资料”时才调用上传或删除接口。 - 默认头像:没有头像或头像加载失败时,客户端回退显示用户姓名首字母。 - 账号隔离:头像必须绑定当前登录用户,不允许通过请求参数指定其他用户。 - URL 生命周期:OSS 环境下 `avatar_url` 为 24 小时有效的签名 URL,客户端启动或刷新资料时重新获取。 - URL 使用方式:客户端直接使用服务端返回的 `avatar_url`,不得持久化为永久地址,不得自行拼接、截断、改写或刷新签名参数。 ## 3. 接口总览 | 方法 | 上游路径 | 用途 | 成功结果 | | --- | --- | --- | --- | | `GET` | `/api/user/agent-profile` | 查询个人资料 | 返回 `avatar_url` 字段 | | `POST` | `/api/user/avatar` | 上传或替换头像 | 返回新的 `avatar_url` | | `DELETE` | `/api/user/avatar` | 删除当前头像 | 返回 `avatar_url: null` | > **路径说明:**上表是服务端上游接口路径。客户端通过 Electron Main 的 Host API 代理访问,服务端无需感知客户端的本地代理路径。 ## 4. 认证与通用约定 - 所有接口使用现有登录态认证:`Authorization: Bearer `。 - 用户身份从 access token 解析,不接受 `user_id`、`username` 等客户端字段作为头像归属依据。 - 成功响应直接返回 JSON 对象;头像 URL 字段统一命名为只读响应字段 `avatar_url`。 - `avatar_url` 只能出现在个人资料查询、上传成功和删除成功响应中,不能出现在 Agent Profile 的 PUT 请求体中。 - OSS 环境下 `avatar_url` 是 24 小时签名 URL;客户端应直接将其用于头像展示,不得持久化为永久地址,也不得自行拼接或修改 URL。 - 接口失败时沿用项目现有错误响应结构,并返回稳定错误码。 ## 5. 接口详细要求 ### 5.1 扩展个人资料查询 现有接口保持不变,在返回的个人资料对象中提供只读字段 `avatar_url`。客户端启动和刷新资料时都应重新调用该接口,获取新的签名 URL。 ```http GET /api/user/agent-profile Authorization: Bearer ``` 成功响应示例: ```json { "display_name": "小明", "age": 18, "gender": "male", "avatar_url": "https://oss.example.com/avatars/user-1-v3.webp?X-Amz-Expires=86400&X-Amz-Signature=", "share_age_with_agents": true, "share_gender_with_agents": true, "analysis_enabled": true, "completed": true, "version": 3, "updated_at": "2026-08-13T00:00:00Z" } ``` 无头像时必须返回 `null`,而不是省略字段: ```json { "avatar_url": null } ``` ### 5.2 上传或替换头像 ```http POST /api/user/avatar Authorization: Bearer Content-Type: multipart/form-data ``` 请求表单字段: | 字段名 | 类型 | 必填 | 要求 | | --- | --- | --- | --- | | `file` | `File` | 是 | 头像图片;支持 `image/png`、`image/jpeg`、`image/webp` | 成功响应: ```json { "avatar_url": "https://oss.example.com/avatars/user-1-v4.webp?X-Amz-Expires=86400&X-Amz-Signature=" } ``` 服务端会对上传内容做最终校验并转换为 WebP。上传接口同时承担新头像上传和旧头像替换;替换成功后,旧文件可以异步清理,但不能影响新 URL 在有效期内的可用性。 ### 5.3 删除头像 ```http DELETE /api/user/avatar Authorization: Bearer ``` 成功响应: ```json { "avatar_url": null } ``` 删除后,个人资料查询接口也必须返回 `avatar_url: null`。 ## 6. 文件校验与存储要求 | 项目 | 要求 | | --- | --- | | 允许格式 | 客户端可上传 PNG、JPEG、WebP;服务端校验后统一转成 WebP。 | | 格式安全 | 不得上传伪装格式、SVG 或动态图片;服务端应校验 MIME、文件头和实际解码结果。 | | 大小限制 | 按服务端配置执行;超过限制时返回 HTTP 413 和 `avatar_file_too_large`。 | | 图片内容 | 拒绝损坏图片、空文件、无法解码文件和伪装格式文件。 | | 归属隔离 | 文件对象、数据库记录和访问 URL 均必须绑定当前用户。 | | URL 处理 | OSS 环境返回 24 小时签名 URL;客户端不得持久化为永久地址,不得自行拼接或修改 URL。 | | 隐私 | 头像属于用户资料,不应出现在公开作品、作品列表或未授权用户的接口响应中。 | > **客户端处理边界:**客户端目前会把图片处理为最长边 512px 的正方形,并可上传 PNG、JPEG 或 WebP;不要上传伪装格式、SVG 或动态图片。服务端仍需做最终校验并转换为 WebP。 ## 7. 错误响应 | 场景 | HTTP 状态 | `code` | 说明 | | --- | ---: | --- | --- | | 格式不支持 | 415 | `avatar_unsupported_type` | 仅允许 PNG/JPEG/WebP | | 文件过大 | 413 | `avatar_file_too_large` | 超过服务端大小限制 | | 图片损坏或无法解码 | 400 | `avatar_invalid_file` | 文件不是有效图片 | | 上传失败 | 503 | `avatar_storage_unavailable` | 对象存储暂时不可用 | 错误响应示例: ```json { "detail": { "code": "avatar_file_too_large", "message": "头像文件超过服务端大小限制" } } ``` ## 8. 客户端调用时序 1. 客户端启动或刷新资料时调用 `GET /api/user/agent-profile`,获取当前资料和新的 `avatar_url`;不要复用已过期的签名 URL。 2. 用户在个人资料弹窗中选择图片,客户端只生成本地预览,不请求服务端。 3. 用户点击保存;如果是新头像,客户端先调用 `POST /api/user/avatar`;如果用户选择移除,则先调用 `DELETE /api/user/avatar`。 4. 头像上传或删除成功后,再调用现有 Agent Profile PUT 接口保存姓名、年龄、性别等资料。PUT 请求体不得包含 `avatar_url`。 5. 如果后续资料 PUT 失败,重试时只重试资料 PUT,不要重复上传或删除已经成功的头像操作。 6. 任一步失败,弹窗保持打开并展示错误;用户可以修正后重新保存。 > **联调注意:**头像接口与个人资料 PUT 是两个独立请求。`avatar_url` 是只读响应字段,服务端不需要在个人资料 PUT 中处理该字段,也不需要客户端为头像 URL 增加版本号或自行处理签名参数。 ## 9. 验收标准 - 登录用户可以通过 `POST /api/user/avatar` 上传 PNG、JPEG 或 WebP,并获得可直接展示的 `avatar_url`。 - 再次上传新图片可以替换旧头像;`GET /api/user/agent-profile` 返回最新 `avatar_url`。 - `DELETE /api/user/avatar` 成功后,`GET /api/user/agent-profile` 返回 `avatar_url: null`。 - 不同用户之间无法读取、覆盖或删除彼此的头像。 - 超过大小限制、格式不支持、图片损坏时返回明确且稳定的错误 `code`。 - 客户端启动或刷新资料时会重新获取 `avatar_url`;OSS 签名 URL 过期后不会继续复用旧 URL。 - 客户端不把 `avatar_url` 放入 Agent Profile PUT 请求体,也不会自行拼接或修改 URL。 - 服务端最终将有效头像转换为 WebP;伪装格式、SVG、动态图片和损坏文件会被拒绝。 - 服务端异常不会返回包含内部存储路径、访问凭据或对象存储密钥的响应。 ## 10. 联调检查清单 - 部署前已执行 Alembic 迁移 `20260813_user_avatar_cloud_0040`。 - `GET /api/user/agent-profile` 在有头像时返回 24 小时签名 `avatar_url`,无头像时返回 `null`。 - `POST /api/user/avatar` 成功返回 `{ "avatar_url": "..." }`。 - `DELETE /api/user/avatar` 成功返回 `{ "avatar_url": null }`。 - Agent Profile PUT 请求体不接受、不处理 `avatar_url`。 - 错误码和 HTTP 状态码与本文档一致:415、413、400、503。 - 旧接口 `/api/auth/me/avatar` 继续兼容,但新客户端不调用。 客户端已按上述接口契约完成接入;服务端接口上线后即可进行联调。