diff --git a/src/main/java/com/greenorange/promotion/annotation/FileEnumValidator.java b/src/main/java/com/greenorange/promotion/annotation/FileEnumValidator.java new file mode 100644 index 0000000..f0d9b4c --- /dev/null +++ b/src/main/java/com/greenorange/promotion/annotation/FileEnumValidator.java @@ -0,0 +1,20 @@ +package com.greenorange.promotion.annotation; + +import com.greenorange.promotion.model.enums.FileUploadBizEnum; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +// 枚举校验器 +public class FileEnumValidator implements ConstraintValidator { + + + @Override + public void initialize(UserEnumValue constraintAnnotation) { + + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return FileUploadBizEnum.getEnumByValue(value) != null; + } +} diff --git a/src/main/java/com/greenorange/promotion/annotation/FileEnumValue.java b/src/main/java/com/greenorange/promotion/annotation/FileEnumValue.java new file mode 100644 index 0000000..80e4f77 --- /dev/null +++ b/src/main/java/com/greenorange/promotion/annotation/FileEnumValue.java @@ -0,0 +1,20 @@ +package com.greenorange.promotion.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +// 自定义校验注解 +@Constraint(validatedBy = UserEnumValidator.class) +@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface FileEnumValue { + String message() default "文件业务类型错误"; // 错误信息 + Class[] groups() default {}; // 组别 + Class[] payload() default {}; // 负载 + Class> enumClass(); // 枚举类类型 +} diff --git a/src/main/java/com/greenorange/promotion/annotation/EnumValidator.java b/src/main/java/com/greenorange/promotion/annotation/UserEnumValidator.java similarity index 62% rename from src/main/java/com/greenorange/promotion/annotation/EnumValidator.java rename to src/main/java/com/greenorange/promotion/annotation/UserEnumValidator.java index dc81144..a5d2975 100644 --- a/src/main/java/com/greenorange/promotion/annotation/EnumValidator.java +++ b/src/main/java/com/greenorange/promotion/annotation/UserEnumValidator.java @@ -7,16 +7,16 @@ import jakarta.validation.ConstraintValidatorContext; // 枚举校验器 -public class EnumValidator implements ConstraintValidator { +public class UserEnumValidator implements ConstraintValidator { @Override - public void initialize(EnumValue constraintAnnotation) { + public void initialize(UserEnumValue constraintAnnotation) { } @Override public boolean isValid(String value, ConstraintValidatorContext context) { - return UserRoleEnum.getEnumByValues(value) != null; + return UserRoleEnum.getEnumByValue(value) != null; } } diff --git a/src/main/java/com/greenorange/promotion/annotation/EnumValue.java b/src/main/java/com/greenorange/promotion/annotation/UserEnumValue.java similarity index 88% rename from src/main/java/com/greenorange/promotion/annotation/EnumValue.java rename to src/main/java/com/greenorange/promotion/annotation/UserEnumValue.java index e6c2c79..a0d83b2 100644 --- a/src/main/java/com/greenorange/promotion/annotation/EnumValue.java +++ b/src/main/java/com/greenorange/promotion/annotation/UserEnumValue.java @@ -9,10 +9,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 自定义校验注解 -@Constraint(validatedBy = EnumValidator.class) +@Constraint(validatedBy = UserEnumValidator.class) @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) -public @interface EnumValue { +public @interface UserEnumValue { String message() default "无效的用户角色"; // 错误信息 Class[] groups() default {}; // 组别 Class[] payload() default {}; // 负载 diff --git a/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java b/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java index 3675e3e..5e80869 100644 --- a/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java +++ b/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java @@ -57,7 +57,7 @@ public class PermissionCheck { // 接口的权限 String mustRole = requiresPermission.mustRole(); // 获取接口权限的枚举类 - UserRoleEnum mustUserRoleEnum = UserRoleEnum.getEnumByValues(mustRole); + UserRoleEnum mustUserRoleEnum = UserRoleEnum.getEnumByValue(mustRole); ThrowUtils.throwIf(mustUserRoleEnum == null, ErrorCode.NO_AUTH_ERROR); // 获取用户权限 String token = request.getHeader("Authorization"); @@ -79,7 +79,7 @@ public class PermissionCheck { // 获取用户权限的枚举类 String userRole = userInfo.getUserRole(); - UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValues(userRole); + UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(userRole); // 接口权限只能是 ADMIN 或者 BOSS,用户权限是 ADMIN 或者 BOSS,USER,BAN // 校验角色 diff --git a/src/main/java/com/greenorange/promotion/controller/fileInfo/FileInfoController.java b/src/main/java/com/greenorange/promotion/controller/fileInfo/FileInfoController.java index ed3bf48..f01029b 100644 --- a/src/main/java/com/greenorange/promotion/controller/fileInfo/FileInfoController.java +++ b/src/main/java/com/greenorange/promotion/controller/fileInfo/FileInfoController.java @@ -42,7 +42,7 @@ import java.util.List; * 文件 控制器 */ @RestController -@RequestMapping("fileInfo") +@RequestMapping("file") @Slf4j @Tag(name = "文件管理") public class FileInfoController { @@ -63,9 +63,9 @@ public class FileInfoController { @PostMapping("upload") @Operation(summary = "Web端文件上传(服务器)", description = "参数:文件添加请求体,权限:管理员,方法名:addFileInfo") @SysLog(title = "文件管理", content = "Web端文件上传(服务器)") - public BaseResponse uploadFile(@RequestPart("file") MultipartFile multipartFile, UploadFileRequest uploadFileRequest) { + public BaseResponse uploadFile(@RequestPart("file") MultipartFile multipartFile, UploadFileRequest uploadFileRequest) throws IOException{ // 校验文件 - fileInfoService.validFile(multipartFile); + fileInfoService.validFile(multipartFile, uploadFileRequest); // 文件上传 String view = fileInfoService.uploadFile(multipartFile, uploadFileRequest); return ResultUtils.success(view, "上传成功"); @@ -77,13 +77,13 @@ public class FileInfoController { /** * 文件下载(服务器) - * @param filename 文件名 + * @param fileName 文件名 * @throws IOException */ - @GetMapping("/download") + @GetMapping("/download/{fileName}") @Operation(summary = "文件下载(服务器)", description = "参数:文件名,权限:所有人,方法名:downloadFile") - public void downloadFile(@RequestParam @NotBlank String filename, HttpServletResponse response) throws IOException { - fileInfoService.downloadFile(filename, response); + public void downloadFile(@PathVariable @NotBlank String fileName, HttpServletResponse response) throws IOException { + fileInfoService.downloadFile(fileName, response); } } \ No newline at end of file diff --git a/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoAddRequest.java b/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoAddRequest.java index aa1f314..91c3396 100644 --- a/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoAddRequest.java +++ b/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoAddRequest.java @@ -1,10 +1,9 @@ package com.greenorange.promotion.model.dto.fileInfo; -import com.greenorange.promotion.annotation.EnumValue; +import com.greenorange.promotion.annotation.FileEnumValue; import com.greenorange.promotion.model.enums.FileUploadBizEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Min; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -67,10 +66,18 @@ public class FileInfoAddRequest implements Serializable { /** * 文件业务类型(头像,项目,富文本,默认) */ - @EnumValue(enumClass = FileUploadBizEnum.class) + @FileEnumValue(enumClass = FileUploadBizEnum.class) @Schema(description = "文件业务类型(头像,项目,富文本,默认)", example = "avatar") private String biz; + /** + * 文件hash值 + */ + @NotBlank(message = "文件hash值不能为空") + @Schema(description = "文件hash值", example = "3E8U2AM8") + private String hashValue; + + @Serial private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoQueryRequest.java b/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoQueryRequest.java index 51ec52d..2ce421f 100644 --- a/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoQueryRequest.java +++ b/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoQueryRequest.java @@ -1,9 +1,6 @@ package com.greenorange.promotion.model.dto.fileInfo; -import com.greenorange.promotion.annotation.EnumValue; -import com.greenorange.promotion.model.enums.FileUploadBizEnum; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Min; import lombok.Data; @@ -29,7 +26,6 @@ public class FileInfoQueryRequest extends PageRequest implements Serializable { /** * 文件view值 */ - @EnumValue(enumClass = FileUploadBizEnum.class) @Schema(description = "文件view值", example = "3E8U2AM8") private String fileView; diff --git a/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoUpdateRequest.java b/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoUpdateRequest.java index 79d78df..139755f 100644 --- a/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoUpdateRequest.java +++ b/src/main/java/com/greenorange/promotion/model/dto/fileInfo/FileInfoUpdateRequest.java @@ -1,6 +1,6 @@ package com.greenorange.promotion.model.dto.fileInfo; -import com.greenorange.promotion.annotation.EnumValue; +import com.greenorange.promotion.annotation.FileEnumValue; import com.greenorange.promotion.model.enums.FileUploadBizEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -70,10 +70,17 @@ public class FileInfoUpdateRequest implements Serializable { /** * 文件业务类型(头像,项目,富文本,默认) */ - @EnumValue(enumClass = FileUploadBizEnum.class) + @FileEnumValue(enumClass = FileUploadBizEnum.class) @Schema(description = "文件业务类型(头像,项目,富文本,默认)", example = "avatar") private String biz; + /** + * 文件hash值 + */ + @NotBlank(message = "文件hash值不能为空") + @Schema(description = "文件hash值", example = "3E8U2AM8") + private String hashValue; + @Serial private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/greenorange/promotion/model/dto/fileInfo/UploadFileRequest.java b/src/main/java/com/greenorange/promotion/model/dto/fileInfo/UploadFileRequest.java index 06bc1db..83f46d4 100644 --- a/src/main/java/com/greenorange/promotion/model/dto/fileInfo/UploadFileRequest.java +++ b/src/main/java/com/greenorange/promotion/model/dto/fileInfo/UploadFileRequest.java @@ -1,6 +1,6 @@ package com.greenorange.promotion.model.dto.fileInfo; -import com.greenorange.promotion.annotation.EnumValue; +import com.greenorange.promotion.annotation.FileEnumValue; import com.greenorange.promotion.model.enums.FileUploadBizEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -15,7 +15,7 @@ public class UploadFileRequest implements Serializable { /** * 文件业务类型(头像,项目,富文本,默认) */ - @EnumValue(enumClass = FileUploadBizEnum.class) + @FileEnumValue(enumClass = FileUploadBizEnum.class) @Schema(description = "文件业务类型(头像,项目,富文本,默认)", example = "avatar") private String biz; diff --git a/src/main/java/com/greenorange/promotion/model/dto/project/ProjectAddRequest.java b/src/main/java/com/greenorange/promotion/model/dto/project/ProjectAddRequest.java index 8590fdf..3676e3e 100644 --- a/src/main/java/com/greenorange/promotion/model/dto/project/ProjectAddRequest.java +++ b/src/main/java/com/greenorange/promotion/model/dto/project/ProjectAddRequest.java @@ -1,6 +1,6 @@ package com.greenorange.promotion.model.dto.project; -import com.greenorange.promotion.annotation.EnumValue; +import com.greenorange.promotion.annotation.UserEnumValue; import com.greenorange.promotion.model.enums.ProjectStatusEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.DecimalMin; @@ -121,7 +121,7 @@ public class ProjectAddRequest implements Serializable { /** * 项目状态(项目运行|人数已满|项目暂停) */ - @EnumValue(enumClass = ProjectStatusEnum.class) + @UserEnumValue(enumClass = ProjectStatusEnum.class) @Schema(description = "项目状态", example = "项目运行") private String projectStatus; diff --git a/src/main/java/com/greenorange/promotion/model/dto/user/UserInfoAddRequest.java b/src/main/java/com/greenorange/promotion/model/dto/user/UserInfoAddRequest.java index 14bb896..58d64ce 100644 --- a/src/main/java/com/greenorange/promotion/model/dto/user/UserInfoAddRequest.java +++ b/src/main/java/com/greenorange/promotion/model/dto/user/UserInfoAddRequest.java @@ -1,12 +1,11 @@ package com.greenorange.promotion.model.dto.user; -import com.greenorange.promotion.annotation.EnumValue; +import com.greenorange.promotion.annotation.UserEnumValue; import com.greenorange.promotion.model.enums.UserRoleEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; -import org.hibernate.validator.constraints.Length; import java.io.Serial; import java.io.Serializable; @@ -65,7 +64,7 @@ public class UserInfoAddRequest implements Serializable { /** * 用户角色 */ - @EnumValue(enumClass = UserRoleEnum.class) + @UserEnumValue(enumClass = UserRoleEnum.class) @Schema(description = "用户角色", example = "user") private String userRole; diff --git a/src/main/java/com/greenorange/promotion/model/dto/user/UserInfoUpdateRequest.java b/src/main/java/com/greenorange/promotion/model/dto/user/UserInfoUpdateRequest.java index 8e5e49b..1fc518e 100644 --- a/src/main/java/com/greenorange/promotion/model/dto/user/UserInfoUpdateRequest.java +++ b/src/main/java/com/greenorange/promotion/model/dto/user/UserInfoUpdateRequest.java @@ -1,6 +1,6 @@ package com.greenorange.promotion.model.dto.user; -import com.greenorange.promotion.annotation.EnumValue; +import com.greenorange.promotion.annotation.UserEnumValue; import com.greenorange.promotion.model.enums.UserRoleEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Min; @@ -74,7 +74,7 @@ public class UserInfoUpdateRequest implements Serializable { /** * 用户角色 */ - @EnumValue(enumClass = UserRoleEnum.class) + @UserEnumValue(enumClass = UserRoleEnum.class) @Schema(description = "用户角色", example = "user") private String userRole; diff --git a/src/main/java/com/greenorange/promotion/model/entity/FileInfo.java b/src/main/java/com/greenorange/promotion/model/entity/FileInfo.java index 5819e6c..fe73edb 100644 --- a/src/main/java/com/greenorange/promotion/model/entity/FileInfo.java +++ b/src/main/java/com/greenorange/promotion/model/entity/FileInfo.java @@ -51,6 +51,11 @@ public class FileInfo implements Serializable { */ private String biz; + /** + * 文件hash值 + */ + private String hashValue; + /** * 是否删除 */ diff --git a/src/main/java/com/greenorange/promotion/model/enums/UserRoleEnum.java b/src/main/java/com/greenorange/promotion/model/enums/UserRoleEnum.java index b505dd0..2253036 100644 --- a/src/main/java/com/greenorange/promotion/model/enums/UserRoleEnum.java +++ b/src/main/java/com/greenorange/promotion/model/enums/UserRoleEnum.java @@ -39,7 +39,7 @@ public enum UserRoleEnum { /** * 获取值列表 */ - public static UserRoleEnum getEnumByValues(String value) { + public static UserRoleEnum getEnumByValue(String value) { if (StringUtils.isBlank(value)) { return null; } diff --git a/src/main/java/com/greenorange/promotion/model/vo/fileInfo/FileInfoVO.java b/src/main/java/com/greenorange/promotion/model/vo/fileInfo/FileInfoVO.java index f2ec4a6..9740fdc 100644 --- a/src/main/java/com/greenorange/promotion/model/vo/fileInfo/FileInfoVO.java +++ b/src/main/java/com/greenorange/promotion/model/vo/fileInfo/FileInfoVO.java @@ -56,6 +56,12 @@ public class FileInfoVO implements Serializable { @Schema(description = "文件业务类型(头像,项目,富文本,默认)", example = "user_avatar") private String biz; + /** + * 文件hash值 + */ + @Schema(description = "文件hash值", example = "3E8U3A") + private String hashValue; + @Serial private static final long serialVersionUID = 1L; diff --git a/src/main/java/com/greenorange/promotion/service/file/FileInfoService.java b/src/main/java/com/greenorange/promotion/service/file/FileInfoService.java index f345267..46c876e 100644 --- a/src/main/java/com/greenorange/promotion/service/file/FileInfoService.java +++ b/src/main/java/com/greenorange/promotion/service/file/FileInfoService.java @@ -19,13 +19,13 @@ public interface FileInfoService extends IService { /** * 校验文件 */ - void validFile(MultipartFile multipartFile); + void validFile(MultipartFile multipartFile, UploadFileRequest uploadFileRequest); /** * 获取文件保存路径 */ - String uploadFile(MultipartFile multipartFile, UploadFileRequest uploadFileRequest); + String uploadFile(MultipartFile multipartFile, UploadFileRequest uploadFileRequest) throws IOException; /** diff --git a/src/main/java/com/greenorange/promotion/service/file/impl/FileInfoServiceImpl.java b/src/main/java/com/greenorange/promotion/service/file/impl/FileInfoServiceImpl.java index f2292cf..3af2af1 100644 --- a/src/main/java/com/greenorange/promotion/service/file/impl/FileInfoServiceImpl.java +++ b/src/main/java/com/greenorange/promotion/service/file/impl/FileInfoServiceImpl.java @@ -16,7 +16,8 @@ import com.greenorange.promotion.service.file.FileInfoService; import com.greenorange.promotion.mapper.FileInfoMapper; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang.RandomStringUtils; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -51,7 +52,11 @@ public class FileInfoServiceImpl extends ServiceImpl * 校验文件 */ @Override - public void validFile(MultipartFile multipartFile) { + public void validFile(MultipartFile multipartFile, UploadFileRequest uploadFileRequest) { + // 获取业务名称 + String biz = uploadFileRequest.getBiz(); + FileUploadBizEnum fileUploadBizEnum = FileUploadBizEnum.getEnumByValue(biz); + ThrowUtils.throwIf(fileUploadBizEnum == null, ErrorCode.PARAMS_ERROR, "业务名称错误"); // 文件大小 long fileSize = multipartFile.getSize(); // 文件后缀 @@ -67,7 +72,7 @@ public class FileInfoServiceImpl extends ServiceImpl * 获取文件保存路径 */ @Override - public String uploadFile(MultipartFile multipartFile, UploadFileRequest uploadFileRequest) { + public String uploadFile(MultipartFile multipartFile, UploadFileRequest uploadFileRequest) throws IOException { // 获取业务名称 String biz = uploadFileRequest.getBiz(); // 获取文件名 @@ -76,11 +81,20 @@ public class FileInfoServiceImpl extends ServiceImpl String fileType = FileUtil.getSuffix(fileName); // 获取文件路径 // 获取view值 - String view = RandomStringUtils.random(8); + String view = RandomStringUtils.random(8, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); // 获取文件路径 - String filePath = String.format("%s/%s", biz, fileName); + String filePath = String.format("%s-%s", biz, fileName); // 获取文件大小 Double fileSize = multipartFile.getSize() / 1024.0; + fileSize = Double.valueOf(String.format("%.2f", fileSize)); + // 获取文件哈希值 + InputStream inputStream = multipartFile.getInputStream(); + String hashValue = DigestUtils.sha256Hex(inputStream); + // 判断是否存在哈希值相同的图片 + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(FileInfo::getHashValue, hashValue); + FileInfo fileInfo = this.getOne(lambdaQueryWrapper); + if (fileInfo != null) return fileInfo.getFileView(); // 保存文件 FileInfoAddRequest fileInfoAddRequest = FileInfoAddRequest.builder() .name(fileName) @@ -89,8 +103,9 @@ public class FileInfoServiceImpl extends ServiceImpl .size(fileSize) .fileView(view) .biz(biz) + .hashValue(hashValue) .build(); - FileInfo fileInfo = commonService.copyProperties(fileInfoAddRequest, FileInfo.class); + fileInfo = commonService.copyProperties(fileInfoAddRequest, FileInfo.class); this.save(fileInfo); // 创建上传目录,如果不存在 File file = new File(UPLOAD_DIR + filePath); @@ -107,6 +122,7 @@ public class FileInfoServiceImpl extends ServiceImpl } + /** * 文件下载 * @param filename 业务名称/view值 @@ -114,8 +130,8 @@ public class FileInfoServiceImpl extends ServiceImpl */ @Override public void downloadFile(String filename, HttpServletResponse response) throws IOException{ - ThrowUtils.throwIf(!filename.contains("/"), ErrorCode.PARAMS_ERROR); - String[] split = filename.split("/"); + ThrowUtils.throwIf(!filename.contains("-"), ErrorCode.PARAMS_ERROR); + String[] split = filename.split("-"); FileUploadBizEnum fileUploadBizEnum = FileUploadBizEnum.getEnumByValue(split[0]); ThrowUtils.throwIf(fileUploadBizEnum == null, ErrorCode.PARAMS_ERROR, "业务类型错误"); LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); @@ -126,7 +142,7 @@ public class FileInfoServiceImpl extends ServiceImpl File file = new File(UPLOAD_DIR + fileInfo.getPath()); // // 设置response的Header response.setContentType("application/octet-stream"); - response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), StandardCharsets.UTF_8)); + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileInfo.getName(), StandardCharsets.UTF_8)); response.setContentLengthLong(file.length()); // 使用 setContentLengthLong 适应大文件 // 设置缓存相关的 HTTP 头