// Controller 在返回头像资源时会尝试使用 Files.probeContentType 识别 mime type 并写到响应头(若识别失败将使用默认且不影响返回)。
@Operation(summary = "获取用户头像", description = "根据用户ID获取头像图片")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "获取成功,返回图片流"),
@ApiResponse(responseCode = "404", description = "未找到头像或文件不存在,返回404")
})
@GetMapping(value = "/user/avatar/{userId}", produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity getAvatar(@PathVariable String userId) {
// ...查找头像文件并返回流...
本文档遵循《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为字符串,返回图片流。说明(基于当前实现):
UserAvatarServiceImpl(work.baiyun.chronicdiseaseapp.service.impl.UserAvatarServiceImpl)负责具体逻辑;控制器为 UserAvatarController。AvatarProperties,@ConfigurationProperties 前缀为 avatar,默认 maxSize 为 2MB,allowedTypes 默认为 "jpg,png,jpeg,webp";若未在配置文件中设置 root-path,则使用默认相对目录 avatar-storage。timestamp.ext(如 1597891234567.jpg)并将文件放到 {root}/{userId}/{timestamp}.{ext} 下(relative = currentUserId + "/" + savedFileName)。normalize() 并检查路径前缀以防止路径穿越;若检测失败,抛出系统异常并记录日志。CustomException 并使用 ErrorCode.PARAMETER_ERROR;若用户不存在抛 ErrorCode.USER_NOT_EXIST;IO 错误返回 ErrorCode.SYSTEM_ERROR。/user/avatar/{userId} 返回 Resource 流;若找不到资源或字段为空则返回 404;若 userId 非法 (非数字),会记录错误并返回 404。{avatarRootPath}/{userId}/{timestamp}.{ext}。
userId 作为文件名前缀,仅把 userId 作为目录名(relative = userId + "/" + timestamp + "." + ext)。userId(便于查找或排障),改为 {userId}_{timestamp}.{ext};当前实现已由目录结构与时间戳保证唯一性。{userId}_{timestamp}.{ext}(如 123_1597891234567.jpg)或基于日期格式的时间戳(如 123_20200820T153012.jpg)生成上传文件名。D:/avatar-storage/123/123_1597891234567.jpguser/123/123_1597891234567.jpgResponseEntity.status(HttpStatus.NOT_FOUND)),路径防穿越。R<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 = System.currentTimeMillis() + "." + ext; // 项目实现使用 timestamp.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 = "未找到头像或文件不存在,返回404")
})
@GetMapping(value = "/user/avatar/{userId}", produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<Resource> getAvatar(@PathVariable String userId) {
// ...查找头像文件并返回流...
}
注意:Controller 在返回头像资源时会尝试使用 Files.probeContentType 自动识别 mime type 并设置到返回的 header(若无法识别则回退到默认处理)。
{
"code": 200,
"message": "上传成功",
"data": "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。