feat:新增存储配置修改功能

This commit is contained in:
czhqwer
2025-03-19 23:54:58 +08:00
parent 93c7ebae86
commit 573dcea843
10 changed files with 165 additions and 25 deletions

View File

@@ -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());
}
}

View File

@@ -21,15 +21,21 @@ public class Result<T> implements Serializable {
private T data;
/**
* 操作失败,无返回值
* 操作失败,无返回值(自定义状态码)
*/
public static <T> Result<T> error(String msg) {
public static <T> Result<T> error(int code, String msg) {
Result<T> responseWrapper = new Result<>();
responseWrapper.setCode(code);
responseWrapper.setMsg(msg);
responseWrapper.setCode(500);
return responseWrapper;
}
/**
* 操作失败,无返回值(默认 500
*/
public static <T> Result<T> error(String msg) {
return error(500, msg);
}
/**
* 操作成功,无返回值

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
/**

View File

@@ -9,4 +9,9 @@ public interface IStorageConfigService {
*/
StorageConfig getStorageConfigByType(String type);
/**
* 更新存储配置
*/
void updateStorageConfig(StorageConfig config);
}

View File

@@ -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);

View File

@@ -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
*/

View File

@@ -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);

View File

@@ -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<StorageConfigMapper, StorageConfig> implements IStorageConfigService {
@Resource
private ApplicationEventPublisher eventPublisher;
@Override
public StorageConfig getStorageConfigByType(String type) {
LambdaQueryWrapper<StorageConfig> queryWrapper = new LambdaQueryWrapper<>();
@@ -27,4 +37,12 @@ public class StorageConfigServiceImpl extends ServiceImpl<StorageConfigMapper, S
}
return config;
}
@Transactional
@Override
public void updateStorageConfig(StorageConfig config) {
super.updateById(config);
eventPublisher.publishEvent(new StorageConfigUpdateEvent(this, config)); // 发布事件
}
}