From 6c286868f667fbeaecd01636d9a8caca670e9deb Mon Sep 17 00:00:00 2001 From: wzp Date: Wed, 25 Dec 2024 19:26:58 +0800 Subject: [PATCH] feat: change the avatar upload controller to separate --- .../club/controller/AvatarController.java | 33 +++++++ .../blue/club/controller/ClubController.java | 1 + .../controller/ServerExceptionHandler.java | 16 ++++ .../blue/club/controller/UserController.java | 17 +--- .../blue/club/entities/vo/data/ClubVo.java | 1 + .../blue/club/exceptions/ServerException.java | 14 +++ .../blue/club/services/AvatarServices.java | 91 +++++++++++++++++++ .../org/blue/club/services/UserServices.java | 77 +--------------- 8 files changed, 161 insertions(+), 89 deletions(-) create mode 100644 src/main/java/org/blue/club/controller/AvatarController.java create mode 100644 src/main/java/org/blue/club/controller/ServerExceptionHandler.java create mode 100644 src/main/java/org/blue/club/exceptions/ServerException.java create mode 100644 src/main/java/org/blue/club/services/AvatarServices.java diff --git a/src/main/java/org/blue/club/controller/AvatarController.java b/src/main/java/org/blue/club/controller/AvatarController.java new file mode 100644 index 0000000..71d7278 --- /dev/null +++ b/src/main/java/org/blue/club/controller/AvatarController.java @@ -0,0 +1,33 @@ +package org.blue.club.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.blue.club.annotation.Auth; +import org.blue.club.services.AvatarServices; +import org.mmga.spring.boot.starter.entities.Result; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/avatar") +@Tag(name = "头像相关接口") +@RequiredArgsConstructor +public class AvatarController { + private final AvatarServices avatarServices; + + @GetMapping("/{sha1}") + @Operation(description = "获取头像文件") + public void getAvatar(HttpServletResponse response, @Schema(description = "头像文件SHA1值") @PathVariable("sha1") String sha1) { + avatarServices.getAvatar(response, sha1); + } + + @PostMapping("/upload") + @Operation(description = "上传头像") + @Auth + public Result changeAvatar(MultipartFile file) { + return avatarServices.changeAvatar(file); + } +} diff --git a/src/main/java/org/blue/club/controller/ClubController.java b/src/main/java/org/blue/club/controller/ClubController.java index aa00556..f68ab3b 100644 --- a/src/main/java/org/blue/club/controller/ClubController.java +++ b/src/main/java/org/blue/club/controller/ClubController.java @@ -54,6 +54,7 @@ public class ClubController { public Result clubRemoveUser(@Auth(permissions = {@PermissionDescription(3), @PermissionDescription(value = 2, type = DescriptionType.CLUB)}) User user, @Schema(description = "社团ID") @RequestParam long clubId, @Schema(description = "需要删除的用户ID") @RequestParam long userId) { return clubServices.clubRemoveUser(user, clubId, userId); } + @Operation(description = "修改社团用户权限") @PutMapping("/user/auth") public Result clubChangeUserAuth(@Auth(permissions = {@PermissionDescription(3), @PermissionDescription(value = 2, type = DescriptionType.CLUB)}) User user, @RequestBody ClubChangeUserAuthRequest request) { diff --git a/src/main/java/org/blue/club/controller/ServerExceptionHandler.java b/src/main/java/org/blue/club/controller/ServerExceptionHandler.java new file mode 100644 index 0000000..9b13b35 --- /dev/null +++ b/src/main/java/org/blue/club/controller/ServerExceptionHandler.java @@ -0,0 +1,16 @@ +package org.blue.club.controller; + +import org.blue.club.exceptions.ServerException; +import org.mmga.spring.boot.starter.entities.Result; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class ServerExceptionHandler { + @ExceptionHandler({ServerException.class}) + public ResponseEntity> handleAuthorizationException(ServerException e) { + Result result = e.getResult(); + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/org/blue/club/controller/UserController.java b/src/main/java/org/blue/club/controller/UserController.java index e04e9b7..164f3ad 100644 --- a/src/main/java/org/blue/club/controller/UserController.java +++ b/src/main/java/org/blue/club/controller/UserController.java @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.blue.club.annotation.Auth; @@ -17,7 +16,6 @@ import org.mmga.spring.boot.starter.annotation.AuthMapping; import org.mmga.spring.boot.starter.entities.PagerData; import org.mmga.spring.boot.starter.entities.Result; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; @Slf4j @RestController @@ -74,16 +72,9 @@ public class UserController { return userServices.changeAuth(userChangeAuthVo); } - @PostMapping("/avatar") - @Operation(description = "上传头像") - @Auth - public Result changeAvatar(MultipartFile file) { - return userServices.changeAvatar(file); - } - @PutMapping("/avatar") @Operation(description = "修改用户头像") - public Result changeAvatar(@RequestParam("code") @Schema(description = "修改头像操作码,可以通过将图片文件上传至([POST] /avatar)接口获取") String avatarOperationCode, @Auth User user) { + public Result changeAvatar(@RequestParam("code") @Schema(description = "修改头像操作码,可以通过将图片文件上传至([POST] /api/avatar/upload)接口获取") String avatarOperationCode, @Auth User user) { return userServices.changeAvatar(avatarOperationCode, user); } @@ -93,12 +84,6 @@ public class UserController { return userServices.getVerifyCode(); } - @GetMapping("/avatar/{sha1}") - @Operation(description = "获取头像文件") - public void getAvatar(HttpServletResponse response, @Schema(description = "头像文件SHA1值") @PathVariable("sha1") String sha1) { - userServices.getAvatar(response, sha1); - } - @GetMapping("/info/{id}") @Operation(description = "获取简略用户信息") public Result getSimpleInfo(@PathVariable("id") int userId) { diff --git a/src/main/java/org/blue/club/entities/vo/data/ClubVo.java b/src/main/java/org/blue/club/entities/vo/data/ClubVo.java index 5f0762f..a3adcc9 100644 --- a/src/main/java/org/blue/club/entities/vo/data/ClubVo.java +++ b/src/main/java/org/blue/club/entities/vo/data/ClubVo.java @@ -13,4 +13,5 @@ public class ClubVo extends BaseDataVo { private Long id; private String name; private String commit; + private String avatar; } diff --git a/src/main/java/org/blue/club/exceptions/ServerException.java b/src/main/java/org/blue/club/exceptions/ServerException.java new file mode 100644 index 0000000..eb53e4e --- /dev/null +++ b/src/main/java/org/blue/club/exceptions/ServerException.java @@ -0,0 +1,14 @@ +package org.blue.club.exceptions; + +import lombok.Getter; +import org.mmga.spring.boot.starter.entities.Result; + +@Getter +public class ServerException extends RuntimeException { + private final Result result; + + public ServerException(Result result) { + super(result.getMsg()); + this.result = result; + } +} diff --git a/src/main/java/org/blue/club/services/AvatarServices.java b/src/main/java/org/blue/club/services/AvatarServices.java new file mode 100644 index 0000000..eac4727 --- /dev/null +++ b/src/main/java/org/blue/club/services/AvatarServices.java @@ -0,0 +1,91 @@ +package org.blue.club.services; + +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.tomcat.util.buf.HexUtils; +import org.blue.club.dao.redis.AvatarOperationDao; +import org.blue.club.entities.vo.data.AvatarTmpVo; +import org.blue.club.exceptions.ServerException; +import org.blue.club.utils.iop.FileUtils; +import org.mmga.spring.boot.starter.entities.Result; +import org.mmga.spring.boot.starter.utils.RandomUtils; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.util.StreamUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.util.Date; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class AvatarServices { + private final RandomUtils randomUtils; + private final File tmpFolder; + private final File avatarFolder; + private final AvatarOperationDao avatarOperationDao; + private final FileUtils fileUtils; + + @SneakyThrows + public Result changeAvatar(MultipartFile file) { + String s = randomUtils.generatorRandomFileName(32); + DigestInputStream digestInputStream = new DigestInputStream(file.getInputStream(), DigestUtils.getSha1Digest()); + File tmpFile = new File(tmpFolder, s); + try (FileOutputStream fos = new FileOutputStream(tmpFile)) { + digestInputStream.transferTo(fos); + } + MessageDigest messageDigest = digestInputStream.getMessageDigest(); + String sha1Hex = HexUtils.toHexString(messageDigest.digest()); + avatarOperationDao.save(new AvatarTmpVo(s, sha1Hex, new Date())); + return Result.success("上传成功", s); + } + + @SneakyThrows + public void getAvatar(HttpServletResponse response, String sha1) { + File targetFile = new File(avatarFolder, sha1); + if (!targetFile.getAbsolutePath().startsWith(avatarFolder.getAbsolutePath())) { + Result.failed(HttpStatus.NOT_FOUND, "文件不存在!").writeToResponse(response); + return; + } + if (!targetFile.exists()) { + Result.failed(HttpStatus.NOT_FOUND, "文件不存在!").writeToResponse(response); + return; + } + try (FileInputStream fileInputStream = new FileInputStream(targetFile)) { + response.setContentType("image/png"); + response.setHeader("Content-Disposition", ContentDisposition.attachment().filename(sha1 + ".png").build().toString()); + ServletOutputStream outputStream = response.getOutputStream(); + StreamUtils.copy(fileInputStream, outputStream); + } + } + + public Optional permanentSaveAvatar(String avatarOperationCode) { + Optional byId = avatarOperationDao.findById(avatarOperationCode); + if (byId.isEmpty()) return Optional.empty(); + AvatarTmpVo avatarTmpVo = byId.get(); + File tmpFile = new File(tmpFolder, avatarOperationCode); + String sha1 = avatarTmpVo.sha1(); + File targetFile = new File(avatarFolder, sha1); + changeAvatarFile(tmpFile, targetFile); + avatarOperationDao.deleteById(avatarOperationCode); + return Optional.of(sha1); + } + + public void changeAvatarFile(File tmpFile, File targetFile) { + if (targetFile.exists()) { + fileUtils.tryDeleteOrDeleteOnExit(tmpFile); + return; + } + if (!tmpFile.renameTo(targetFile)) + throw new ServerException(Result.failed(HttpStatus.INTERNAL_SERVER_ERROR, "移动头像文件失败!请联系管理员处理")); + } +} diff --git a/src/main/java/org/blue/club/services/UserServices.java b/src/main/java/org/blue/club/services/UserServices.java index 7f56ea9..270cd4b 100644 --- a/src/main/java/org/blue/club/services/UserServices.java +++ b/src/main/java/org/blue/club/services/UserServices.java @@ -1,44 +1,26 @@ package org.blue.club.services; import com.mybatisflex.core.paginate.Page; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.tomcat.util.buf.HexUtils; import org.blue.club.dao.UserDao; -import org.blue.club.dao.redis.AvatarOperationDao; import org.blue.club.dao.redis.VerifyDao; import org.blue.club.entities.dto.user.User; import org.blue.club.entities.dto.user.req.*; import org.blue.club.entities.dto.user.resp.VerifyCodeResponse; import org.blue.club.entities.vo.VerifyVo; -import org.blue.club.entities.vo.data.AvatarTmpVo; import org.blue.club.entities.vo.data.UserVo; -import org.blue.club.utils.iop.FileUtils; import org.blue.club.utils.iop.VerifyCodeUtils; import org.mmga.spring.boot.starter.entities.PagerData; import org.mmga.spring.boot.starter.entities.Result; import org.mmga.spring.boot.starter.exception.AuthorizationException; import org.mmga.spring.boot.starter.properties.Env; import org.mmga.spring.boot.starter.properties.MainProperties; -import org.mmga.spring.boot.starter.utils.RandomUtils; import org.mmga.spring.boot.starter.utils.VoUtils; -import org.springframework.http.ContentDisposition; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StreamUtils; -import org.springframework.web.multipart.MultipartFile; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.security.DigestInputStream; -import java.security.MessageDigest; -import java.util.Date; import java.util.List; import java.util.Optional; @@ -52,13 +34,9 @@ public class UserServices { private final UserDao userDao; private final VerifyDao verifyDao; private final VoUtils voUtils; - private final RandomUtils randomUtils; - private final File tmpFolder; - private final File avatarFolder; - private final AvatarOperationDao avatarOperationDao; - private final FileUtils fileUtils; private final VerifyCodeUtils verifyCodeUtils; private final MainProperties mainProperties; + private final AvatarServices avatarServices; private boolean isWrongVerifyCode(String key, String code) { if (mainProperties.getEnv().equals(Env.DEV)) return false; @@ -151,70 +129,23 @@ public class UserServices { return Result.success(true); } - @SneakyThrows - public Result changeAvatar(MultipartFile file) { - String s = randomUtils.generatorRandomFileName(32); - DigestInputStream digestInputStream = new DigestInputStream(file.getInputStream(), DigestUtils.getSha1Digest()); - File tmpFile = new File(tmpFolder, s); - try (FileOutputStream fos = new FileOutputStream(tmpFile)) { - digestInputStream.transferTo(fos); - } - MessageDigest messageDigest = digestInputStream.getMessageDigest(); - String sha1Hex = HexUtils.toHexString(messageDigest.digest()); - avatarOperationDao.save(new AvatarTmpVo(s, sha1Hex, new Date())); - return Result.success("上传成功", s); - } public Result changeAvatar(String avatarOperationCode, User user) { - Optional byId = avatarOperationDao.findById(avatarOperationCode); - if (byId.isEmpty()) return Result.failed(HttpStatus.NOT_FOUND, "操作码错误或过期"); - AvatarTmpVo avatarTmpVo = byId.get(); - File tmpFile = new File(tmpFolder, avatarOperationCode); - String sha1 = avatarTmpVo.sha1(); - File targetFile = new File(avatarFolder, sha1); - Optional> booleanResult = changeAvatarFile(tmpFile, targetFile); - if (booleanResult.isPresent()) return booleanResult.get(); - avatarOperationDao.deleteById(avatarOperationCode); + Optional result = avatarServices.permanentSaveAvatar(avatarOperationCode); + if (result.isEmpty()) return Result.failed(HttpStatus.NOT_FOUND, "操作码错误或过期"); UserVo userVo = new UserVo(); userVo.setId(user.getId()); - userVo.setAvatar(sha1); + userVo.setAvatar(result.get()); userDao.update(userVo); return Result.success(true); } - public Optional> changeAvatarFile(File tmpFile, File targetFile) { - if (targetFile.exists()) { - fileUtils.tryDeleteOrDeleteOnExit(tmpFile); - return Optional.empty(); - } - if (!tmpFile.renameTo(targetFile)) - return Optional.of(Result.failed(HttpStatus.INTERNAL_SERVER_ERROR, "移动头像文件失败!请联系管理员处理")); - return Optional.empty(); - } public Result getVerifyCode() { VerifyCodeResponse verifyCodeResponse = verifyCodeUtils.generateVerifyCode(); return Result.success(verifyCodeResponse); } - @SneakyThrows - public void getAvatar(HttpServletResponse response, String sha1) { - File targetFile = new File(avatarFolder, sha1); - if (!targetFile.getAbsolutePath().startsWith(avatarFolder.getAbsolutePath())) { - Result.failed(HttpStatus.NOT_FOUND, "文件不存在!").writeToResponse(response); - return; - } - if (!targetFile.exists()) { - Result.failed(HttpStatus.NOT_FOUND, "文件不存在!").writeToResponse(response); - return; - } - try (FileInputStream fileInputStream = new FileInputStream(targetFile)) { - response.setContentType("image/png"); - response.setHeader("Content-Disposition", ContentDisposition.attachment().filename(sha1 + ".png").build().toString()); - ServletOutputStream outputStream = response.getOutputStream(); - StreamUtils.copy(fileInputStream, outputStream); - } - } public Result getUserInfo(int userId) { UserVo userVo = userDao.selectOneWithRelationsById(userId);