diff --git a/common/service-utils/src/main/java/cn/bunny/common/service/utils/minio/MinioUtil.java b/common/service-utils/src/main/java/cn/bunny/common/service/utils/minio/MinioUtil.java index eacb2b2..e5fe302 100644 --- a/common/service-utils/src/main/java/cn/bunny/common/service/utils/minio/MinioUtil.java +++ b/common/service-utils/src/main/java/cn/bunny/common/service/utils/minio/MinioUtil.java @@ -4,10 +4,9 @@ import cn.bunny.common.service.exception.BunnyException; import cn.bunny.dao.pojo.common.MinioFilePath; import cn.bunny.dao.pojo.constant.MinioConstant; import cn.bunny.dao.pojo.result.ResultCodeEnum; -import io.minio.GetObjectArgs; -import io.minio.GetObjectResponse; -import io.minio.MinioClient; -import io.minio.PutObjectArgs; +import io.minio.*; +import io.minio.messages.DeleteError; +import io.minio.messages.DeleteObject; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -18,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.UUID; /** @@ -29,13 +29,14 @@ import java.util.UUID; public class MinioUtil { @Autowired private MinioProperties properties; + @Autowired private MinioClient minioClient; /** * 获取Minio文件路径 */ - public static MinioFilePath getMinioFilePath(String buckName, String minioPreType, MultipartFile file) { + public static MinioFilePath initUploadFile4MinioFilePath(String buckName, String minioPreType, MultipartFile file) { String uuid = UUID.randomUUID().toString(); // 定义日期时间格式 LocalDateTime currentDateTime = LocalDateTime.now(); @@ -74,11 +75,10 @@ public class MinioUtil { /** * * 上传文件并返回处理信息 */ - public MinioFilePath getUploadMinioObjectFilePath(MultipartFile file, String minioPreType) throws IOException { - // 如果buckName为空,设置为默认的桶 + public MinioFilePath uploadObject4FilePath(MultipartFile file, String minioPreType) throws IOException { String bucketName = properties.getBucketName(); if (file != null) { - MinioFilePath minioFile = getMinioFilePath(bucketName, minioPreType, file); + MinioFilePath minioFile = initUploadFile4MinioFilePath(bucketName, minioPreType, file); String filepath = minioFile.getFilepath(); // 上传对象 @@ -137,4 +137,39 @@ public class MinioUtil { throw new BunnyException(ResultCodeEnum.UPDATE_ERROR); } } -} \ No newline at end of file + + /** + * * 删除目标文件 + * + * @param list 对象文件列表 + */ + public void removeObjects(List list) { + try { + String bucketName = properties.getBucketName(); + List objectList = list.stream().map(DeleteObject::new).toList(); + + Iterable> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objectList).build()); + for (Result result : results) { + DeleteError error = result.get(); + throw new BunnyException("Error in deleting object " + error.objectName() + "; " + error.message()); + } + } catch (Exception exception) { + exception.printStackTrace(); + } + } + + /** + * * 更新文件 + * + * @param bucketName 桶名称 + * @param filepath 文件路径 + * @param file 文件 + */ + public void updateFile(String bucketName, String filepath, MultipartFile file) { + try { + putObject(bucketName, filepath, file.getInputStream(), file.getSize()); + } catch (IOException e) { + throw new BunnyException(e.getMessage()); + } + } +} diff --git a/dao/src/main/java/cn/bunny/dao/dto/system/files/FilesAddDto.java b/dao/src/main/java/cn/bunny/dao/dto/system/files/FilesAddDto.java index 00ae1a3..faf3393 100644 --- a/dao/src/main/java/cn/bunny/dao/dto/system/files/FilesAddDto.java +++ b/dao/src/main/java/cn/bunny/dao/dto/system/files/FilesAddDto.java @@ -1,12 +1,17 @@ package cn.bunny.dao.dto.system.files; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @Data @AllArgsConstructor @@ -15,22 +20,18 @@ import lombok.NoArgsConstructor; @Schema(name = "FilesAddDto对象", title = "文件", description = "文件管理") public class FilesAddDto { - @Schema(name = "filename", title = "文件的名称") - @NotBlank(message = "文件的名称不能为空") - @NotNull(message = "文件的名称不能为空") - private String filename; - @Schema(name = "filepath", title = "文件在服务器上的存储路径") @NotBlank(message = "存储路径不能为空") @NotNull(message = "存储路径不能为空") private String filepath; - @Schema(name = "fileType", title = "文件的MIME类型") - @NotBlank(message = "文件类型不能为空") - @NotNull(message = "文件类型不能为空") - private String fileType; - @Schema(name = "downloadCount", title = "下载数量") + @Min(value = 0L, message = "最小值为0") private Integer downloadCount = 0; + @Schema(name = "files", title = "文件") + @NotEmpty(message = "文件不能为空") + @NotNull(message = "文件不能为空") + private List files; + } \ No newline at end of file diff --git a/dao/src/main/java/cn/bunny/dao/dto/system/files/FilesUpdateDto.java b/dao/src/main/java/cn/bunny/dao/dto/system/files/FilesUpdateDto.java index cd9ba80..a703882 100644 --- a/dao/src/main/java/cn/bunny/dao/dto/system/files/FilesUpdateDto.java +++ b/dao/src/main/java/cn/bunny/dao/dto/system/files/FilesUpdateDto.java @@ -1,12 +1,14 @@ package cn.bunny.dao.dto.system.files; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.springframework.web.multipart.MultipartFile; @Data @AllArgsConstructor @@ -24,17 +26,16 @@ public class FilesUpdateDto { @NotNull(message = "文件的名称不能为空") private String filename; - @Schema(name = "filepath", title = "文件在服务器上的存储路径") - @NotBlank(message = "存储路径不能为空") - @NotNull(message = "存储路径不能为空") - private String filepath; - @Schema(name = "fileType", title = "文件的MIME类型") @NotBlank(message = "文件类型不能为空") @NotNull(message = "文件类型不能为空") private String fileType; @Schema(name = "downloadCount", title = "下载数量") + @Min(value = 0L, message = "最小值为0") private Integer downloadCount; + @Schema(name = "file", title = "文件") + private MultipartFile files; + } \ No newline at end of file diff --git a/dao/src/main/java/cn/bunny/dao/pojo/constant/MinioConstant.java b/dao/src/main/java/cn/bunny/dao/pojo/constant/MinioConstant.java index c73f4af..729e92e 100644 --- a/dao/src/main/java/cn/bunny/dao/pojo/constant/MinioConstant.java +++ b/dao/src/main/java/cn/bunny/dao/pojo/constant/MinioConstant.java @@ -14,7 +14,7 @@ public class MinioConstant { public static final String feedback = "feedback"; public static final String articleCovers = "articleCovers"; public static final String articleAttachment = "articleAttachment"; - private static final Map typeMap = new HashMap<>(); + public static final Map typeMap = new HashMap<>(); static { typeMap.put(favicon, "/favicon/"); diff --git a/pom.xml b/pom.xml index 21511a3..e2e0427 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,8 @@ 3.1 5.1.0 4.3.1 + 2.12.3 + 2.3.2 @@ -148,7 +150,13 @@ com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.12.3 + ${jackson-dataType.version} + + + + org.quartz-scheduler + quartz + ${quartz-scheduler.version} diff --git a/service/pom.xml b/service/pom.xml index ec262d1..c7e73d8 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -76,6 +76,22 @@ com.baomidou dynamic-datasource-spring-boot3-starter + + + org.quartz-scheduler + quartz + + + com.mchange + c3p0 + 0.9.5.5 + + + + org.springframework + spring-context-support + 6.1.6 + diff --git a/service/src/main/java/cn/bunny/services/controller/FilesController.java b/service/src/main/java/cn/bunny/services/controller/FilesController.java index 0e4a753..45350b3 100644 --- a/service/src/main/java/cn/bunny/services/controller/FilesController.java +++ b/service/src/main/java/cn/bunny/services/controller/FilesController.java @@ -5,6 +5,7 @@ import cn.bunny.dao.dto.system.files.FilesAddDto; import cn.bunny.dao.dto.system.files.FilesDto; import cn.bunny.dao.dto.system.files.FilesUpdateDto; import cn.bunny.dao.entity.system.Files; +import cn.bunny.dao.pojo.constant.MinioConstant; import cn.bunny.dao.pojo.result.PageResult; import cn.bunny.dao.pojo.result.Result; import cn.bunny.dao.pojo.result.ResultCodeEnum; @@ -22,6 +23,8 @@ import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; import java.util.List; +import java.util.Map; +import java.util.Set; /** *

@@ -58,6 +61,22 @@ public class FilesController { return filesService.downloadFilesByFileId(fileId); } + @Operation(summary = "获取所有文件类型", description = "获取所有文件类型") + @GetMapping("getAllMediaTypes") + public Mono>> getAllMediaTypes() { + Set list = filesService.getAllMediaTypes(); + return Mono.just(Result.success(list)); + } + + @Operation(summary = "获取所有文件存储基础路径", description = "获取所有文件存储基础路径") + @GetMapping("getAllFilesStoragePath") + public Mono>> getAllFilesStoragePath() { + Map typeMap = MinioConstant.typeMap; + List list = typeMap.keySet().stream().toList(); + + return Mono.just(Result.success(list)); + } + @Operation(summary = "根据文件名下载文件", description = "根据文件名下载文件") @GetMapping("downloadFilesByFilepath") public ResponseEntity downloadFilesByFilepath(String filepath) { @@ -66,14 +85,14 @@ public class FilesController { @Operation(summary = "更新系统文件表", description = "更新系统文件表") @PutMapping("updateFiles") - public Mono> updateFiles(@Valid @RequestBody FilesUpdateDto dto) { + public Result updateFiles(@Valid FilesUpdateDto dto) { filesService.updateFiles(dto); - return Mono.just(Result.success(ResultCodeEnum.UPDATE_SUCCESS)); + return Result.success(ResultCodeEnum.UPDATE_SUCCESS); } @Operation(summary = "添加系统文件表", description = "添加系统文件表") @PostMapping("addFiles") - public Mono> addFiles(@Valid @RequestBody FilesAddDto dto) { + public Mono> addFiles(@Valid FilesAddDto dto) { filesService.addFiles(dto); return Mono.just(Result.success(ResultCodeEnum.ADD_SUCCESS)); } @@ -87,8 +106,8 @@ public class FilesController { @Operation(summary = "删除系统文件表", description = "删除系统文件表") @DeleteMapping("deleteFiles") - public Mono> deleteFiles(@RequestBody List ids) { + public Result deleteFiles(@RequestBody List ids) { filesService.deleteFiles(ids); - return Mono.just(Result.success(ResultCodeEnum.DELETE_SUCCESS)); + return Result.success(ResultCodeEnum.DELETE_SUCCESS); } } diff --git a/service/src/main/java/cn/bunny/services/factory/FileFactory.java b/service/src/main/java/cn/bunny/services/factory/FileFactory.java index 7a4f008..cde1d44 100644 --- a/service/src/main/java/cn/bunny/services/factory/FileFactory.java +++ b/service/src/main/java/cn/bunny/services/factory/FileFactory.java @@ -43,7 +43,7 @@ public class FileFactory { String filename = file.getOriginalFilename(); // 上传文件 - MinioFilePath minioFIlePath = minioUtil.getUploadMinioObjectFilePath(file, type); + MinioFilePath minioFIlePath = minioUtil.uploadObject4FilePath(file, type); String bucketNameFilepath = minioFIlePath.getBucketNameFilepath(); // 盘读研数据是否过大 diff --git a/service/src/main/java/cn/bunny/services/service/FilesService.java b/service/src/main/java/cn/bunny/services/service/FilesService.java index 1a5730a..4aa0bb3 100644 --- a/service/src/main/java/cn/bunny/services/service/FilesService.java +++ b/service/src/main/java/cn/bunny/services/service/FilesService.java @@ -14,6 +14,7 @@ import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import java.util.List; +import java.util.Set; /** *

@@ -76,4 +77,12 @@ public interface FilesService extends IService { * @return 文件字节数组 */ ResponseEntity downloadFilesByFilepath(String filepath); + + /** + * * 获取所有文件类型 + * + * @return 媒体文件类型列表 + */ + Set getAllMediaTypes(); + } diff --git a/service/src/main/java/cn/bunny/services/service/impl/FilesServiceImpl.java b/service/src/main/java/cn/bunny/services/service/impl/FilesServiceImpl.java index 58c47df..b82978d 100644 --- a/service/src/main/java/cn/bunny/services/service/impl/FilesServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/impl/FilesServiceImpl.java @@ -1,12 +1,14 @@ package cn.bunny.services.service.impl; import cn.bunny.common.service.exception.BunnyException; +import cn.bunny.common.service.utils.minio.MinioProperties; import cn.bunny.common.service.utils.minio.MinioUtil; import cn.bunny.dao.dto.system.files.FileUploadDto; import cn.bunny.dao.dto.system.files.FilesAddDto; import cn.bunny.dao.dto.system.files.FilesDto; import cn.bunny.dao.dto.system.files.FilesUpdateDto; import cn.bunny.dao.entity.system.Files; +import cn.bunny.dao.pojo.common.MinioFilePath; import cn.bunny.dao.pojo.result.PageResult; import cn.bunny.dao.pojo.result.ResultCodeEnum; import cn.bunny.dao.vo.system.files.FileInfoVo; @@ -21,14 +23,20 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import jakarta.validation.Valid; import lombok.SneakyThrows; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** *

@@ -39,15 +47,17 @@ import java.util.List; * @since 2024-10-09 16:28:01 */ @Service +@Transactional public class FilesServiceImpl extends ServiceImpl implements FilesService { - private final FileFactory fileFactory; - private final MinioUtil minioUtil; + @Autowired + private MinioProperties properties; - public FilesServiceImpl(FileFactory fileFactory, MinioUtil minioUtil) { - this.fileFactory = fileFactory; - this.minioUtil = minioUtil; - } + @Autowired + private FileFactory fileFactory; + + @Autowired + private MinioUtil minioUtil; /** * * 系统文件表 服务实现类 @@ -81,11 +91,25 @@ public class FilesServiceImpl extends ServiceImpl implements * @param dto 系统文件表添加 */ @Override - public void addFiles(@Valid FilesAddDto dto) { + public void addFiles(FilesAddDto dto) { + List list = dto.getFiles().stream().map(file -> { + try { + MinioFilePath minioFilePath = minioUtil.uploadObject4FilePath(file, dto.getFilepath()); + + Files files = new Files(); + files.setFileType(file.getContentType()); + files.setFileSize(file.getSize()); + files.setFilepath("/" + properties.getBucketName() + minioFilePath.getFilepath()); + files.setFilename(minioFilePath.getFilename()); + files.setDownloadCount(dto.getDownloadCount()); + return files; + } catch (IOException e) { + throw new BunnyException(e.getMessage()); + } + }).toList(); + // 保存数据 - Files files = new Files(); - BeanUtils.copyProperties(dto, files); - save(files); + saveBatch(list); } /** @@ -95,8 +119,23 @@ public class FilesServiceImpl extends ServiceImpl implements */ @Override public void updateFiles(@Valid FilesUpdateDto dto) { + Long id = dto.getId(); + MultipartFile file = dto.getFiles(); + Files files = getOne(Wrappers.lambdaQuery().eq(Files::getId, id)); + + if (file != null) { + // 文件路径 + String filePath = files.getFilepath().replace("/" + properties.getBucketName() + "/", ""); + minioUtil.updateFile(properties.getBucketName(), filePath, file); + + // 设置文件信息 + files.setFileSize(file.getSize()); + files.setFileType(file.getContentType()); + } + + // 更新内容 - Files files = new Files(); + files = new Files(); BeanUtils.copyProperties(dto, files); updateById(files); } @@ -124,6 +163,19 @@ public class FilesServiceImpl extends ServiceImpl implements @Override public void deleteFiles(List ids) { if (ids.isEmpty()) throw new BunnyException(ResultCodeEnum.REQUEST_IS_EMPTY); + + // 查询文件路径 + List list = list(Wrappers.lambdaQuery().in(Files::getId, ids)).stream() + .map(files -> { + String filepath = files.getFilepath(); + int end = filepath.indexOf("/", 1); + return filepath.substring(end + 1); + }).toList(); + + // 删除目标文件 + minioUtil.removeObjects(list); + + // 删除数据库内容 baseMapper.deleteBatchIdsWithPhysics(ids); } @@ -179,4 +231,30 @@ public class FilesServiceImpl extends ServiceImpl implements return new ResponseEntity<>(bytes, headers, HttpStatus.OK); } + + /** + * * 获取所有文件类型 + * + * @return 媒体文件类型列表 + */ + @Override + public Set getAllMediaTypes() { + Set valueList = new HashSet<>(); + Class mediaTypeClass = MediaType.class; + + try { + for (Field declaredField : mediaTypeClass.getDeclaredFields()) { + // 获取字段属性值 + declaredField.setAccessible(true); + String value = declaredField.get(null).toString(); + + if (value.matches("\\w+/.*")) { + valueList.add(value); + } + } + return valueList; + } catch (Exception exception) { + return Set.of(); + } + } } diff --git a/service/src/main/resources/application-dev.yml b/service/src/main/resources/application-dev.yml index 07b83e5..c890f7a 100644 --- a/service/src/main/resources/application-dev.yml +++ b/service/src/main/resources/application-dev.yml @@ -17,3 +17,22 @@ bunny: accessKey: bunny secretKey: "02120212" bucket-name: auth-admin + +# rabbitmq: +# host: ${bunny.rabbitmq.host} +# port: ${bunny.rabbitmq.port} +# username: ${bunny.rabbitmq.username} +# password: ${bunny.rabbitmq.password} +# virtual-host: ${bunny.rabbitmq.virtual-host} +# publisher-confirm-type: correlated # 交换机确认 +# publisher-returns: true # 队列确认 +# listener: +# simple: +# acknowledge-mode: manual # 手动处理消息 +# connection-timeout: 1s # 设置MQ连接超时时间 +# template: +# retry: +# enabled: true # 失败重试 +# initial-interval: 1000ms # 失败后初始时间 +# multiplier: 1 # 失败后下次等待时长倍数 initial-interval * multiplier +# max-attempts: 3 # 最大重试次数 \ No newline at end of file diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml index 60d7ee6..a7be05d 100644 --- a/service/src/main/resources/application.yml +++ b/service/src/main/resources/application.yml @@ -64,24 +64,13 @@ spring: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 -# rabbitmq: -# host: ${bunny.rabbitmq.host} -# port: ${bunny.rabbitmq.port} -# username: ${bunny.rabbitmq.username} -# password: ${bunny.rabbitmq.password} -# virtual-host: ${bunny.rabbitmq.virtual-host} -# publisher-confirm-type: correlated # 交换机确认 -# publisher-returns: true # 队列确认 -# listener: -# simple: -# acknowledge-mode: manual # 手动处理消息 -# connection-timeout: 1s # 设置MQ连接超时时间 -# template: -# retry: -# enabled: true # 失败重试 -# initial-interval: 1000ms # 失败后初始时间 -# multiplier: 1 # 失败后下次等待时长倍数 initial-interval * multiplier -# max-attempts: 3 # 最大重试次数 + quartz: + job-store-type: jdbc + jdbc: + initialize-schema: always + auto-startup: true + wait-for-jobs-to-complete-on-shutdown: true + overwrite-existing-jobs: false mybatis-plus: mapper-locations: classpath:mapper/*.xml diff --git a/service/src/main/resources/quartz.properties b/service/src/main/resources/quartz.properties new file mode 100644 index 0000000..704442e --- /dev/null +++ b/service/src/main/resources/quartz.properties @@ -0,0 +1,11 @@ +org.quartz.scheduler.instanceName=quartzScheduler +org.quartz.threadPool.threadCount=5 +org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX +org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate +org.quartz.jobStore.tablePrefix=QRTZ_ +org.quartz.jobStore.dataSource=auth_admin +org.quartz.dataSource.auth_admin.driver=com.mysql.cj.jdbc.Driver +org.quartz.dataSource.auth_admin.URL=jdbc:mysql://192.168.3.98:3304/auth_admin?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true +org.quartz.dataSource.auth_admin.user=root +org.quartz.dataSource.auth_admin.password=02120212 +org.quartz.dataSource.auth_admin.maxConnections=5 \ No newline at end of file diff --git a/service/src/test/java/cn/bunny/services/service/impl/FilesServiceImplTest.java b/service/src/test/java/cn/bunny/services/service/impl/FilesServiceImplTest.java index 0a3220e..4fc94fa 100644 --- a/service/src/test/java/cn/bunny/services/service/impl/FilesServiceImplTest.java +++ b/service/src/test/java/cn/bunny/services/service/impl/FilesServiceImplTest.java @@ -1,6 +1,13 @@ package cn.bunny.services.service.impl; +import cn.bunny.dao.pojo.constant.MinioConstant; +import lombok.SneakyThrows; import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; class FilesServiceImplTest { @Test @@ -15,4 +22,23 @@ class FilesServiceImplTest { System.out.println(filename); } + @SneakyThrows + @Test + void getAllMediaTypeTest() { + Class mediaTypeClass = MediaType.class; + for (Field declaredField : mediaTypeClass.getDeclaredFields()) { + declaredField.setAccessible(true); + String value = declaredField.get(null).toString(); + if (value.matches("\\w+/.*")) { + System.out.println(value); + } + } + } + + @Test + void getAllPaths() { + Map typeMap = MinioConstant.typeMap; + List> list = typeMap.entrySet().stream().toList(); + System.out.println(list); + } } \ No newline at end of file