本文档遵循《03-API设计规范》《06-日志和错误处理规范》《Swagger泛型返回类型字段信息不显示问题解决方案》《前端ID精度丢失问题解决方案》等项目规范。
为提升用户体验,系统需支持用户上传、更新和获取头像图片,头像文件存储于本地磁盘,路径可配置,支持文件大小和类型限制,仅依赖MySQL+Spring Boot+本地存储。
currentUserId,未认证返回401。application.yml)指定。UserInfo.avatar字段存储头像相对路径。R<T>格式,状态码、错误码、消息体与规范一致。@ApiResponse+@Content+@Schema(implementation=...)标注,确保泛型返回类型文档化。POST /user/avatar/upload,参数为MultipartFile,仅允许当前用户操作。GET /user/avatar/{userId},userId为字符串,返回图片流。{avatarRootPath}/{userId}/{userId}_{timestamp}.{ext},防止文件名冲突。
{userId}_{timestamp}.{ext}(如 123_1597891234567.jpg)或基于日期格式的时间戳(如 123_20200820T153012.jpg)生成上传文件名。D:/avatar-storage/123/123_1597891234567.jpguser/123/123_1597891234567.jpgR<T>格式,错误用R.fail,成功用R.success。@Schema(implementation=...)。@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);
}
@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) {
// ...查找头像文件并返回流...
}
{
"code": 200,
"message": "上传成功",
"data": "/user/avatar/user/123/123_1597891234567.jpg"
}
{
"code": 400,
"message": "文件类型不支持",
"data": null
}
avatar:
root-path: D:/avatar-storage/
max-size: 2MB
allowed-types: jpg,png,jpeg,webp
docs/DataBase/t_user_info.txt 中的 avatar 字段用于存储头像图片的“相对路径”或“文件名”,如 user/123/avatar.jpg,不直接存储图片内容或外部URL。UserInfo表已存在avatar字段,无需变更。如需兼容历史数据,可统一迁移旧头像路径。
[AvatarUpload] userId=xxx, fileName=xxx, ip=xxx。
savedFileName(含时间戳)和 relativePath,以便定位文件与审计:logger.info("[AvatarUpload] userId={}, savedFileName={}, relativePath={}, ip={}", userId, savedFileName, relativePath, ip);savedFileName;验证时间戳或日期格式正确。file。