From 573dcea8435e2a46e5ad254494da55552472a3a6 Mon Sep 17 00:00:00 2001 From: czhqwer Date: Wed, 19 Mar 2025 23:54:58 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/czh/advice/GlobalExceptionHandler.java | 28 ++++++++++++++++ .../src/main/java/cn/czh/base/Result.java | 12 +++++-- .../czh/context/StorageConfigUpdateEvent.java | 17 ++++++++++ .../cn/czh/controller/ConfigController.java | 30 +++++++++++++++++ .../java/cn/czh/entity/StorageConfig.java | 4 +++ .../cn/czh/service/IStorageConfigService.java | 5 +++ .../czh/service/impl/LocalStorageService.java | 23 ++++++++++--- .../czh/service/impl/MinioStorageService.java | 33 ++++++++++--------- .../czh/service/impl/OssStorageService.java | 20 +++++++++-- .../impl/StorageConfigServiceImpl.java | 18 ++++++++++ 10 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 upload-file-backend/src/main/java/cn/czh/advice/GlobalExceptionHandler.java create mode 100644 upload-file-backend/src/main/java/cn/czh/context/StorageConfigUpdateEvent.java create mode 100644 upload-file-backend/src/main/java/cn/czh/controller/ConfigController.java diff --git a/upload-file-backend/src/main/java/cn/czh/advice/GlobalExceptionHandler.java b/upload-file-backend/src/main/java/cn/czh/advice/GlobalExceptionHandler.java new file mode 100644 index 0000000..db045b5 --- /dev/null +++ b/upload-file-backend/src/main/java/cn/czh/advice/GlobalExceptionHandler.java @@ -0,0 +1,28 @@ +package cn.czh.advice; + +import cn.czh.base.BusinessException; +import cn.czh.base.Result; +import org.springframework.http.HttpStatus; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleValidationException(MethodArgumentNotValidException ex) { + FieldError fieldError = ex.getBindingResult().getFieldErrors().get(0); + String errorMessage = fieldError.getDefaultMessage(); + return Result.error(400, errorMessage); + } + + @ExceptionHandler(BusinessException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Result handleBusinessException(BusinessException e) { + return Result.error(500, e.getMessage()); + } +} \ No newline at end of file diff --git a/upload-file-backend/src/main/java/cn/czh/base/Result.java b/upload-file-backend/src/main/java/cn/czh/base/Result.java index 28b344f..f178eb0 100644 --- a/upload-file-backend/src/main/java/cn/czh/base/Result.java +++ b/upload-file-backend/src/main/java/cn/czh/base/Result.java @@ -21,15 +21,21 @@ public class Result implements Serializable { private T data; /** - * 操作失败,无返回值 + * 操作失败,无返回值(自定义状态码) */ - public static Result error(String msg) { + public static Result error(int code, String msg) { Result responseWrapper = new Result<>(); + responseWrapper.setCode(code); responseWrapper.setMsg(msg); - responseWrapper.setCode(500); return responseWrapper; } + /** + * 操作失败,无返回值(默认 500) + */ + public static Result error(String msg) { + return error(500, msg); + } /** * 操作成功,无返回值 diff --git a/upload-file-backend/src/main/java/cn/czh/context/StorageConfigUpdateEvent.java b/upload-file-backend/src/main/java/cn/czh/context/StorageConfigUpdateEvent.java new file mode 100644 index 0000000..3491382 --- /dev/null +++ b/upload-file-backend/src/main/java/cn/czh/context/StorageConfigUpdateEvent.java @@ -0,0 +1,17 @@ +package cn.czh.context; + +import cn.czh.entity.StorageConfig; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class StorageConfigUpdateEvent extends ApplicationEvent { + + private final StorageConfig newConfig; + + public StorageConfigUpdateEvent(Object source, StorageConfig newConfig) { + super(source); + this.newConfig = newConfig; + } + +} \ No newline at end of file diff --git a/upload-file-backend/src/main/java/cn/czh/controller/ConfigController.java b/upload-file-backend/src/main/java/cn/czh/controller/ConfigController.java new file mode 100644 index 0000000..9860252 --- /dev/null +++ b/upload-file-backend/src/main/java/cn/czh/controller/ConfigController.java @@ -0,0 +1,30 @@ +package cn.czh.controller; + +import cn.czh.base.BusinessException; +import cn.czh.base.Result; +import cn.czh.entity.StorageConfig; +import cn.czh.service.IStorageConfigService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +@RestController +@RequestMapping("/config") +public class ConfigController { + + @Resource + private IStorageConfigService storageConfigService; + + @GetMapping + public Result getConfig(String type) { + return Result.success(storageConfigService.getStorageConfigByType(type)); + } + + @PatchMapping + public Result updateConfig(@Valid @RequestBody StorageConfig config) { + storageConfigService.updateStorageConfig(config); + return Result.success(); + } + +} diff --git a/upload-file-backend/src/main/java/cn/czh/entity/StorageConfig.java b/upload-file-backend/src/main/java/cn/czh/entity/StorageConfig.java index aa150c4..84745eb 100644 --- a/upload-file-backend/src/main/java/cn/czh/entity/StorageConfig.java +++ b/upload-file-backend/src/main/java/cn/czh/entity/StorageConfig.java @@ -7,6 +7,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; import java.io.Serializable; @Data @@ -32,6 +34,8 @@ public class StorageConfig implements Serializable { /** * 存储类型:'local','minio','oss','obs' */ + @NotNull(message = "存储类型不能为空") + @Pattern(regexp = "local|minio|oss|obs", message = "存储类型必须是 'local', 'minio', 'oss', 'obs' 之一") private String type; /** diff --git a/upload-file-backend/src/main/java/cn/czh/service/IStorageConfigService.java b/upload-file-backend/src/main/java/cn/czh/service/IStorageConfigService.java index 19ca3a5..37fad05 100644 --- a/upload-file-backend/src/main/java/cn/czh/service/IStorageConfigService.java +++ b/upload-file-backend/src/main/java/cn/czh/service/IStorageConfigService.java @@ -9,4 +9,9 @@ public interface IStorageConfigService { */ StorageConfig getStorageConfigByType(String type); + /** + * 更新存储配置 + */ + void updateStorageConfig(StorageConfig config); + } diff --git a/upload-file-backend/src/main/java/cn/czh/service/impl/LocalStorageService.java b/upload-file-backend/src/main/java/cn/czh/service/impl/LocalStorageService.java index aaf8e75..2731556 100644 --- a/upload-file-backend/src/main/java/cn/czh/service/impl/LocalStorageService.java +++ b/upload-file-backend/src/main/java/cn/czh/service/impl/LocalStorageService.java @@ -1,6 +1,7 @@ package cn.czh.service.impl; import cn.czh.base.BusinessException; +import cn.czh.context.StorageConfigUpdateEvent; import cn.czh.dto.FileRecordDTO; import cn.czh.dto.MyPartSummary; import cn.czh.dto.TaskInfoDTO; @@ -16,12 +17,13 @@ import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.springframework.beans.BeanUtils; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.annotation.PostConstruct; @@ -50,17 +52,28 @@ public class LocalStorageService implements IStorageService { @PostConstruct public void init() { + this.refreshConfig(); + } + + private void refreshConfig() { this.storageConfig = storageConfigService.getStorageConfigByType(StorageConfig.LOCAL); - // Ensure the root directory exists File rootDir = new File(storageConfig.getBucket()); if (!rootDir.exists()) { - boolean mkdirs = rootDir.mkdirs(); - if (!mkdirs) { + if (!rootDir.mkdirs()) { throw new BusinessException("无法创建根目录"); } } + log.info("LocalStorageService refreshed successfully"); } + @EventListener + public void handleConfigUpdate(StorageConfigUpdateEvent event) { + if (StorageConfig.LOCAL.equals(event.getNewConfig().getType())) { + refreshConfig(); + } + } + + @Transactional @Override public FileRecordDTO uploadFile(MultipartFile file, String md5, String objectName) { UploadFile uploadFile = getByIdentifier(md5); @@ -129,6 +142,7 @@ public class LocalStorageService implements IStorageService { return result; } + @Transactional @Override public TaskInfoDTO createMultipartUpload(CreateMultipartUpload param) { String identifier = param.getIdentifier(); @@ -162,6 +176,7 @@ public class LocalStorageService implements IStorageService { .setPath(generateWebUrl(objectKey)); } + @Transactional @Override public UploadFile merge(String identifier) { UploadFile uploadFile = getByIdentifier(identifier); diff --git a/upload-file-backend/src/main/java/cn/czh/service/impl/MinioStorageService.java b/upload-file-backend/src/main/java/cn/czh/service/impl/MinioStorageService.java index 2b4be9b..f318640 100644 --- a/upload-file-backend/src/main/java/cn/czh/service/impl/MinioStorageService.java +++ b/upload-file-backend/src/main/java/cn/czh/service/impl/MinioStorageService.java @@ -1,6 +1,7 @@ package cn.czh.service.impl; import cn.czh.base.BusinessException; +import cn.czh.context.StorageConfigUpdateEvent; import cn.czh.dto.FileRecordDTO; import cn.czh.dto.MyPartSummary; import cn.czh.dto.TaskInfoDTO; @@ -33,10 +34,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import io.minio.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; +import org.springframework.context.event.EventListener; import org.springframework.http.MediaType; import org.springframework.http.MediaTypeFactory; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.annotation.PostConstruct; @@ -79,14 +81,25 @@ public class MinioStorageService implements IStorageService { @PostConstruct public void init() { + this.refreshConfig(); + } + + private void refreshConfig() { this.storageConfig = storageConfigService.getStorageConfigByType(StorageConfig.MINIO); this.minioClient = MinioClient.builder() .endpoint(storageConfig.getEndpoint()) .credentials(storageConfig.getAccessKey(), storageConfig.getSecretKey()) .build(); this.amazonS3 = minioAmazonS3Client(storageConfig); + log.info("MinioStorageService refreshed successfully"); } + @EventListener + public void handleConfigUpdate(StorageConfigUpdateEvent event) { + if (StorageConfig.MINIO.equals(event.getNewConfig().getType())) { + refreshConfig(); + } + } private AmazonS3 minioAmazonS3Client(StorageConfig storageConfig) { // 设置连接时的参数 @@ -107,7 +120,7 @@ public class MinioStorageService implements IStorageService { .build(); } - + @Transactional @Override public FileRecordDTO uploadFile(MultipartFile file, String md5, String objectName) { @@ -179,6 +192,7 @@ public class MinioStorageService implements IStorageService { return result; } + @Transactional @Override public TaskInfoDTO createMultipartUpload(CreateMultipartUpload param) { Date currentDate = new Date(); @@ -203,7 +217,7 @@ public class MinioStorageService implements IStorageService { return new TaskInfoDTO().setFinished(false).setTaskRecord(TaskRecordDTO.convertFromEntity(task)).setPath(getPath(key)); } - + @Transactional @Override public UploadFile merge(String identifier) { UploadFile uploadFile = getByIdentifier(identifier); @@ -273,19 +287,6 @@ public class MinioStorageService implements IStorageService { } - @Scheduled(fixedRate = 60000) // 每分钟检查一次 - public void refreshStorageConfig() { - StorageConfig newConfig = storageConfigService.getStorageConfigByType(StorageConfig.MINIO); - if (!newConfig.equals(this.storageConfig)) { - this.storageConfig = newConfig; - this.minioClient = MinioClient.builder() - .endpoint(newConfig.getEndpoint()) - .credentials(newConfig.getAccessKey(), newConfig.getSecretKey()) - .build(); - log.info("StorageConfig refreshed successfully"); - } - } - /** * 获取文件访问URL */ diff --git a/upload-file-backend/src/main/java/cn/czh/service/impl/OssStorageService.java b/upload-file-backend/src/main/java/cn/czh/service/impl/OssStorageService.java index 216ff3a..aae8a3c 100644 --- a/upload-file-backend/src/main/java/cn/czh/service/impl/OssStorageService.java +++ b/upload-file-backend/src/main/java/cn/czh/service/impl/OssStorageService.java @@ -1,6 +1,7 @@ package cn.czh.service.impl; import cn.czh.base.BusinessException; +import cn.czh.context.StorageConfigUpdateEvent; import cn.czh.dto.FileRecordDTO; import cn.czh.dto.MyPartSummary; import cn.czh.dto.TaskInfoDTO; @@ -22,7 +23,9 @@ import com.aliyun.oss.model.*; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.annotation.PostConstruct; @@ -55,19 +58,30 @@ public class OssStorageService implements IStorageService { @PostConstruct public void init() { - // 获取 OSS 配置 + this.refreshConfig(); + } + + private void refreshConfig() { this.storageConfig = storageConfigService.getStorageConfigByType(StorageConfig.OSS); - // 初始化 OSS 客户端 this.ossClient = new OSSClientBuilder().build( storageConfig.getEndpoint(), storageConfig.getAccessKey(), storageConfig.getSecretKey() ); + log.info("OssStorageService refreshed successfully"); + } + + @EventListener + public void handleConfigUpdate(StorageConfigUpdateEvent event) { + if (StorageConfig.OSS.equals(event.getNewConfig().getType())) { + refreshConfig(); + } } /** * 上传单个文件 */ + @Transactional @Override public FileRecordDTO uploadFile(MultipartFile file, String md5, String objectName) { UploadFile uploadFile = getByIdentifier(md5); @@ -155,6 +169,7 @@ public class OssStorageService implements IStorageService { /** * 创建分片上传任务 */ + @Transactional @Override public TaskInfoDTO createMultipartUpload(CreateMultipartUpload param) { String identifier = param.getIdentifier(); @@ -194,6 +209,7 @@ public class OssStorageService implements IStorageService { /** * 合并分片 */ + @Transactional @Override public UploadFile merge(String identifier) { UploadFile uploadFile = getByIdentifier(identifier); diff --git a/upload-file-backend/src/main/java/cn/czh/service/impl/StorageConfigServiceImpl.java b/upload-file-backend/src/main/java/cn/czh/service/impl/StorageConfigServiceImpl.java index 61400a1..eb90fdc 100644 --- a/upload-file-backend/src/main/java/cn/czh/service/impl/StorageConfigServiceImpl.java +++ b/upload-file-backend/src/main/java/cn/czh/service/impl/StorageConfigServiceImpl.java @@ -1,17 +1,27 @@ package cn.czh.service.impl; import cn.czh.base.BusinessException; +import cn.czh.context.StorageConfigUpdateEvent; import cn.czh.entity.StorageConfig; import cn.czh.mapper.StorageConfigMapper; +import cn.czh.mapper.UploadFileMapper; import cn.czh.service.IStorageConfigService; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; @Service public class StorageConfigServiceImpl extends ServiceImpl implements IStorageConfigService { + + @Resource + private ApplicationEventPublisher eventPublisher; + @Override public StorageConfig getStorageConfigByType(String type) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); @@ -27,4 +37,12 @@ public class StorageConfigServiceImpl extends ServiceImpl