|
|
@@ -0,0 +1,176 @@
|
|
|
+# 用户头像本地上传与获取接口设计
|
|
|
+
|
|
|
+> 本文档遵循《03-API设计规范》《06-日志和错误处理规范》《Swagger泛型返回类型字段信息不显示问题解决方案》《前端ID精度丢失问题解决方案》等项目规范。
|
|
|
+
|
|
|
+## 概述
|
|
|
+为提升用户体验,系统需支持用户上传、更新和获取头像图片,头像文件存储于本地磁盘,路径可配置,支持文件大小和类型限制,仅依赖MySQL+Spring Boot+本地存储。
|
|
|
+
|
|
|
+## 背景和目标
|
|
|
+- 背景:当前仅支持通过字符串方式设置头像,缺乏本地文件上传与访问能力。
|
|
|
+- 目标:实现安全、灵活的头像上传与获取接口,支持本地存储、路径可变、文件大小限制,接口风格与现有API统一。
|
|
|
+
|
|
|
+## 需求与约束
|
|
|
+1. 认证:接口需走Token认证(AuthInterceptor),依赖`currentUserId`,未认证返回401。
|
|
|
+2. 存储:头像文件存储于本地磁盘,根路径通过配置文件(如`application.yml`)指定。
|
|
|
+3. 文件限制:仅允许图片类型(jpg/png/jpeg/webp),最大文件大小(如2MB)可配置,超限或非法类型返回400。
|
|
|
+4. 数据库:`UserInfo.avatar`字段存储头像相对路径。
|
|
|
+5. 访问控制:仅允许用户本人上传/更新头像,获取接口可公开。
|
|
|
+6. API兼容:接口风格与现有API一致,所有响应均为`R<T>`格式,状态码、错误码、消息体与规范一致。
|
|
|
+7. 日志审计:所有上传/更新操作需用SLF4J记录info级日志,异常需error级,内容含userId、文件名、IP等。
|
|
|
+8. Swagger文档:所有接口需用`@ApiResponse`+`@Content`+`@Schema(implementation=...)`标注,确保泛型返回类型文档化。
|
|
|
+9. ID精度:所有响应中的userId等ID字段在VO层转为String,避免前端精度丢失。
|
|
|
+
|
|
|
+## 设计要点(高层)
|
|
|
+1. Controller层新增接口:
|
|
|
+ - 上传/更新头像:`POST /user/avatar/upload`,参数为`MultipartFile`,仅允许当前用户操作。
|
|
|
+ - 获取头像:`GET /user/avatar/{userId}`,userId为字符串,返回图片流。
|
|
|
+2. Service层实现头像存储、路径生成、文件校验、数据库更新、异常处理、日志记录等逻辑。
|
|
|
+3. 配置文件增加头像存储根路径、最大文件大小、允许类型等配置项。
|
|
|
+4. 文件存储结构建议:`{avatarRootPath}/{userId}/{userId}_{timestamp}.{ext}`,防止文件名冲突。
|
|
|
+ - 文件命名策略:建议使用 `{userId}_{timestamp}.{ext}`(如 `123_1597891234567.jpg`)或基于日期格式的时间戳(如 `123_20200820T153012.jpg`)生成上传文件名。
|
|
|
+ - 优点:保证唯一性、防止并发覆盖;便于前端缓存失效控制(每次上传返回不同 URL);也可以直接用于文件历史管理与清理。
|
|
|
+ - 存储示例:
|
|
|
+ - 完整路径:`D:/avatar-storage/123/123_1597891234567.jpg`
|
|
|
+ - avatar 字段(相对路径)存储:`user/123/123_1597891234567.jpg`
|
|
|
+ - 推荐策略:保存新头像后异步删除旧头像(注意合规和备份需求),避免磁盘堆积;若需要保留历史版本,则保留旧文件并在数据库中标记版本。
|
|
|
+5. 上传接口需校验文件类型与大小,超限或非法类型返回400,未认证401,IO异常500。
|
|
|
+6. 获取接口根据数据库记录返回本地文件流,若无头像返回默认图片,路径防穿越。
|
|
|
+7. 日志记录上传、更新、异常操作,包括userId、操作时间、文件名、IP等,按日志规范分级。
|
|
|
+8. 所有接口响应均为`R<T>`格式,错误用`R.fail`,成功用`R.success`。
|
|
|
+9. Swagger注解需覆盖所有状态码,泛型返回类型需用`@Schema(implementation=...)`。
|
|
|
+10. 响应VO中userId等ID字段类型为String。
|
|
|
+
|
|
|
+
|
|
|
+## API设计示例
|
|
|
+
|
|
|
+### 上传/更新头像接口
|
|
|
+
|
|
|
+```java
|
|
|
+@Operation(summary = "上传/更新用户头像", description = "用户上传或更新自己的头像图片")
|
|
|
+@ApiResponses(value = {
|
|
|
+ @ApiResponse(responseCode = "200", description = "上传成功",
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
+ schema = @Schema(implementation = R.class))),
|
|
|
+ @ApiResponse(responseCode = "400", description = "参数错误/文件类型或大小不合法",
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
+ schema = @Schema(implementation = R.class))),
|
|
|
+ @ApiResponse(responseCode = "401", description = "未认证",
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
+ schema = @Schema(implementation = R.class))),
|
|
|
+ @ApiResponse(responseCode = "500", description = "服务器内部错误",
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
+ schema = @Schema(implementation = R.class)))
|
|
|
+})
|
|
|
+@PostMapping(value = "/user/avatar/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
|
|
+public R<String> uploadAvatar(@RequestPart("file") MultipartFile file, HttpServletRequest request) {
|
|
|
+ String userId = String.valueOf(request.getAttribute("currentUserId"));
|
|
|
+ // 校验文件类型与大小...
|
|
|
+ // 生成保存文件名(示例为 userId + 时间戳):
|
|
|
+ // String originalFilename = file.getOriginalFilename();
|
|
|
+ // String ext = StringUtils.getFilenameExtension(originalFilename); // 或使用 FilenameUtils
|
|
|
+ // String savedFileName = userId + "_" + System.currentTimeMillis() + "." + ext;
|
|
|
+ // String relativePath = "user/" + userId + "/" + savedFileName;
|
|
|
+ // 保存文件到 avatarRootPath + File.separator + relativePath;
|
|
|
+ // 更新数据库:UserInfo.avatar = relativePath;
|
|
|
+ // 可选:删除或异步清理旧文件
|
|
|
+ // 日志示例:logger.info("[AvatarUpload] userId={}, savedFileName={}, ip={}", userId, savedFileName, request.getRemoteAddr());
|
|
|
+ // 错误示例:logger.error("[AvatarUpload] userId={}, error=文件类型不支持", userId);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 获取头像接口
|
|
|
+
|
|
|
+```java
|
|
|
+@Operation(summary = "获取用户头像", description = "根据用户ID获取头像图片")
|
|
|
+@ApiResponses(value = {
|
|
|
+ @ApiResponse(responseCode = "200", description = "获取成功,返回图片流"),
|
|
|
+ @ApiResponse(responseCode = "404", description = "未找到头像,返回默认图片")
|
|
|
+})
|
|
|
+@GetMapping(value = "/user/avatar/{userId}", produces = MediaType.IMAGE_JPEG_VALUE)
|
|
|
+public ResponseEntity<Resource> getAvatar(@PathVariable String userId) {
|
|
|
+ // ...查找头像文件并返回流...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 响应示例(上传成功)
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 200,
|
|
|
+ "message": "上传成功",
|
|
|
+ "data": "/user/avatar/user/123/123_1597891234567.jpg"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 响应示例(参数错误)
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 400,
|
|
|
+ "message": "文件类型不支持",
|
|
|
+ "data": null
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 配置示例(application.yml)
|
|
|
+
|
|
|
+```yaml
|
|
|
+avatar:
|
|
|
+ root-path: D:/avatar-storage/
|
|
|
+ max-size: 2MB
|
|
|
+ allowed-types: jpg,png,jpeg,webp
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+## 数据库字段设计与兼容性
|
|
|
+
|
|
|
+### avatar字段用途
|
|
|
+- `docs/DataBase/t_user_info.txt` 中的 avatar 字段用于存储头像图片的“相对路径”或“文件名”,如 `user/123/avatar.jpg`,不直接存储图片内容或外部URL。
|
|
|
+- 这样既兼容本地存储,也便于后续迁移和扩展。
|
|
|
+
|
|
|
+
|
|
|
+### 统一访问与健壮性
|
|
|
+- 获取头像接口优先查找本地文件(根据avatar字段拼接绝对路径),找不到时可回退到默认头像或兼容历史URL。
|
|
|
+- avatar字段建议varchar(255)或更长,确保能存储完整相对路径。
|
|
|
+- 开发和测试时可手动插入或模拟avatar字段的不同内容(如空、相对路径、异常值),验证接口健壮性。
|
|
|
+
|
|
|
+`UserInfo`表已存在`avatar`字段,无需变更。如需兼容历史数据,可统一迁移旧头像路径。
|
|
|
+
|
|
|
+## 权限与安全
|
|
|
+- 上传/更新接口仅允许当前登录用户操作自己的头像。
|
|
|
+- 获取接口可公开,但需防止路径遍历等安全风险。
|
|
|
+- 文件名、路径需规范化,防止覆盖他人文件。
|
|
|
+- 所有ID字段在响应VO中转为String,避免前端精度丢失。
|
|
|
+
|
|
|
+## 日志与审计
|
|
|
+- 上传/更新/异常操作需用SLF4J记录日志,内容包括userId、文件名、操作时间、IP等,按info/error分级。
|
|
|
+- 日志格式建议:`[AvatarUpload] userId=xxx, fileName=xxx, ip=xxx`。
|
|
|
+ - 建议日志记录 `savedFileName`(含时间戳)和 `relativePath`,以便定位文件与审计:
|
|
|
+ - `logger.info("[AvatarUpload] userId={}, savedFileName={}, relativePath={}, ip={}", userId, savedFileName, relativePath, ip);`
|
|
|
+- 可选:如需合规审计,可建立头像操作日志表。
|
|
|
+
|
|
|
+## 测试建议
|
|
|
+1. 单元测试:覆盖文件类型、大小校验、路径生成(包括userId+timestamp的命名逻辑)、数据库更新等逻辑。
|
|
|
+ - 验证命名规则:同一 userId 连续上传两次应生成不同 `savedFileName`;验证时间戳或日期格式正确。
|
|
|
+ - 验证旧文件处理:上传新头像后老头像是否被删除或保留(依据配置),以及数据库 avatar 字段是否正确地指向新文件。
|
|
|
+2. 集成测试:模拟前端上传、获取流程,验证接口兼容性与安全性。
|
|
|
+3. 性能测试:批量上传、并发获取,评估磁盘IO与接口响应。
|
|
|
+
|
|
|
+## 兼容性与前端要求
|
|
|
+- 前端需以multipart/form-data方式上传头像,文件字段名为`file`。
|
|
|
+- 获取接口返回图片流,前端可直接用于img标签src。
|
|
|
+- 上传成功后返回头像访问URL,便于前端展示。
|
|
|
+
|
|
|
+## 迁移/变更管理
|
|
|
+- 先实现后端接口与配置,测试通过后通知前端接入。
|
|
|
+- 头像存储路径、大小、类型等参数可通过配置灵活调整。
|
|
|
+
|
|
|
+## 参考
|
|
|
+- Spring Boot官方文件上传文档
|
|
|
+- 项目现有认证、响应格式、日志规范
|
|
|
+- docs/DevRule/03-API设计规范.md
|
|
|
+- docs/DevRule/06-日志和错误处理规范.md
|
|
|
+- docs/Swagger泛型返回类型字段信息不显示问题解决方案.md
|
|
|
+- docs/前端ID精度丢失问题解决方案.md
|
|
|
+
|
|
|
+## 责任人
|
|
|
+- 设计/实现:后端开发团队
|
|
|
+- 联调:后端+前端
|