diff --git a/pom.xml b/pom.xml index bef4fa3..4444dd4 100644 --- a/pom.xml +++ b/pom.xml @@ -131,6 +131,12 @@ fastjson2 ${fastjson2.version} + + + io.minio + minio + 8.6.0 + diff --git a/src/main/java/com/dc/dc_project/config/MinioConfig.java b/src/main/java/com/dc/dc_project/config/MinioConfig.java new file mode 100644 index 0000000..33a75f0 --- /dev/null +++ b/src/main/java/com/dc/dc_project/config/MinioConfig.java @@ -0,0 +1,34 @@ +package com.dc.dc_project.config; + + +import io.minio.MinioClient; +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Data +public class MinioConfig { + + @Value("${minio.endpoint}") + public static String MINIO_ENDPOINT; + + @Value("${minio.accessKey}") + public static String MINIO_ACCESS_KEY; + + @Value("${minio.secretKey}") + public static String MINIO_SECRET_KEY; + + @Value("${minio.bucketName}") + public static String MINIO_BUCKET_NAME; + + + @Bean + public MinioClient defaultClient(){ + return MinioClient.builder() + .endpoint(MINIO_ENDPOINT) + .credentials(MINIO_ACCESS_KEY, MINIO_SECRET_KEY) + .build(); + } +} diff --git a/src/main/java/com/dc/dc_project/controller/PositionController.java b/src/main/java/com/dc/dc_project/controller/PositionController.java index 745f5eb..ac7f8d6 100644 --- a/src/main/java/com/dc/dc_project/controller/PositionController.java +++ b/src/main/java/com/dc/dc_project/controller/PositionController.java @@ -4,10 +4,8 @@ package com.dc.dc_project.controller; import cn.dev33.satoken.stp.StpUtil; import com.dc.dc_project.common.ResponseResult; import com.dc.dc_project.model.dto.*; -import com.dc.dc_project.model.pojo.PersonnelPosition; import com.dc.dc_project.model.pojo.Position; import com.dc.dc_project.service.PositionService; -import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; diff --git a/src/main/java/com/dc/dc_project/controller/sys/FileController.java b/src/main/java/com/dc/dc_project/controller/sys/FileController.java new file mode 100644 index 0000000..fb1769a --- /dev/null +++ b/src/main/java/com/dc/dc_project/controller/sys/FileController.java @@ -0,0 +1,26 @@ +package com.dc.dc_project.controller.sys; + +import cn.dev33.satoken.stp.StpUtil; +import com.dc.dc_project.common.ResponseResult; +import com.dc.dc_project.model.dto.FileUploadDto; +import com.dc.dc_project.service.FileService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/file") +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class FileController { + + private final FileService fileService; + + @PostMapping("/Upload") + public ResponseResult Upload(@ModelAttribute FileUploadDto fileUploadDto) { + Long userId = StpUtil.getLoginIdAsLong(); + return fileService.upload(fileUploadDto, userId); + } +} diff --git a/src/main/java/com/dc/dc_project/enums/FileType.java b/src/main/java/com/dc/dc_project/enums/FileType.java new file mode 100644 index 0000000..89228a8 --- /dev/null +++ b/src/main/java/com/dc/dc_project/enums/FileType.java @@ -0,0 +1,61 @@ +package com.dc.dc_project.enums; + + +import lombok.Getter; + +@Getter +public enum FileType { + + Unknown(0, "未知", "/unknown"), + UserAvatar(1, "用户头像", "/avatar"), + Certificate(2, "证书", "/certificate"), + Report(3, "报告", "/report"), + File(4, "文件", "/file"), + Image(5, "图片", "/image"), + UserFile(6, "用户文件", "/userFile"), + ReportFile(7, "报告文件", "/reportFile"), + Other(8, "其他", "/other"); + + private final Integer code; + private final String message; + private final String path; + FileType(int code, String message, String path) { + this.code = code; + this.message = message; + this.path = path; + } + public static String getMessage(int code) { + for (FileType item : FileType.values()) { + if (item.getCode() == code) { + return item.getMessage(); + } + } + return null; + } + public static int getCode(String message) { + for (FileType item : FileType.values()) { + if (item.getMessage().equals(message)) { + return item.getCode(); + } + } + return 0; + } + + public static String getPath(int code) { + for (FileType item : FileType.values()) { + if (item.getCode() == code) { + return item.getPath(); + } + } + return null; + } + + public static FileType getFileType(Integer code){ + for (FileType item : FileType.values()) { + if (item.getCode() == code) { + return item; + } + } + return null; + } +} diff --git a/src/main/java/com/dc/dc_project/model/dto/FileUploadDto.java b/src/main/java/com/dc/dc_project/model/dto/FileUploadDto.java new file mode 100644 index 0000000..114c7e7 --- /dev/null +++ b/src/main/java/com/dc/dc_project/model/dto/FileUploadDto.java @@ -0,0 +1,17 @@ +package com.dc.dc_project.model.dto; + + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Data +public class FileUploadDto { + + private Integer type; + + private List files; + + private String remark; +} diff --git a/src/main/java/com/dc/dc_project/service/FileService.java b/src/main/java/com/dc/dc_project/service/FileService.java index 4a2d781..3809727 100644 --- a/src/main/java/com/dc/dc_project/service/FileService.java +++ b/src/main/java/com/dc/dc_project/service/FileService.java @@ -1,8 +1,12 @@ package com.dc.dc_project.service; +import com.dc.dc_project.common.ResponseResult; +import com.dc.dc_project.model.dto.FileUploadDto; import com.dc.dc_project.model.pojo.File; import com.baomidou.mybatisplus.extension.service.IService; +import java.io.IOException; + /** * @author ADMIN * @description 针对表【sys_file(通用文件信息表(文件存储记录))】的数据库操作Service @@ -10,4 +14,11 @@ import com.baomidou.mybatisplus.extension.service.IService; */ public interface FileService extends IService { + /** + * 文件上传 + * @param fileUploadDto + * @param userId + * @return + */ + ResponseResult upload(FileUploadDto fileUploadDto, Long userId); } diff --git a/src/main/java/com/dc/dc_project/service/impl/FileServiceImpl.java b/src/main/java/com/dc/dc_project/service/impl/FileServiceImpl.java index abc644b..2560c2b 100644 --- a/src/main/java/com/dc/dc_project/service/impl/FileServiceImpl.java +++ b/src/main/java/com/dc/dc_project/service/impl/FileServiceImpl.java @@ -1,11 +1,28 @@ package com.dc.dc_project.service.impl; +import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dc.dc_project.common.ResponseResult; +import com.dc.dc_project.config.MinioConfig; +import com.dc.dc_project.config.exception.BusinessException; +import com.dc.dc_project.enums.FileType; +import com.dc.dc_project.model.dto.FileUploadDto; import com.dc.dc_project.model.pojo.File; import com.dc.dc_project.service.FileService; import com.dc.dc_project.mapper.FileMapper; +import com.dc.dc_project.utils.FileUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; /** * @author ADMIN @@ -14,9 +31,73 @@ import lombok.extern.slf4j.Slf4j; */ @Service @Slf4j +@RequiredArgsConstructor(onConstructor_ = @Autowired) public class FileServiceImpl extends ServiceImpl implements FileService{ + private final MinioService minioService; + + @Override + @Transactional + public ResponseResult upload(FileUploadDto fileUploadDto, Long userId) { + log.info("上传文件"); + FileType fileType = FileType.getFileType(fileUploadDto.getType()); + if (fileType == null) { + return ResponseResult.error("文件类型错误"); + } + + List uploadedUrls = new ArrayList<>(); + List files = new ArrayList<>(); + try { + // 1. 先上传文件 + for (MultipartFile file : fileUploadDto.getFiles()) { + try (InputStream inputStream = file.getInputStream()) { + File sysFile = new File(); + sysFile.setFileName(file.getOriginalFilename()); + sysFile.setFileExt(FileUtil.getFileType(inputStream)); + sysFile.setRemark(fileUploadDto.getRemark()); + sysFile.setUploaderId(userId); + sysFile.setFileSize(file.getSize()); + sysFile.setFileType(fileType.getCode()); + + // 上传到 MinIO + String url = minioService.uploadFile( + inputStream, + FileUtil.getRandomFileName(), + MinioConfig.MINIO_BUCKET_NAME, + file.getContentType(), + fileType.getPath() + ); + + sysFile.setFileUrl(url); + files.add(sysFile); + uploadedUrls.add(url); // 记录已上传的 URL + } + } + + // 2. 保存到数据库 + if (!this.saveBatch(files)) { + throw new BusinessException("数据库保存失败"); + } + + return ResponseResult.success(files); + + } catch (Exception e) { + log.error("文件上传失败,开始清理已上传的文件", e); + + // 3. 回滚:删除已上传的文件 + for (String url : uploadedUrls) { + try { + minioService.deleteFile(url, MinioConfig.MINIO_BUCKET_NAME); + log.info("已删除文件: {}", url); + } catch (Exception deleteException) { + log.error("删除文件失败: {}", url, deleteException); + } + } + + throw new BusinessException("文件上传失败: " + e.getMessage()); + } + } } diff --git a/src/main/java/com/dc/dc_project/service/impl/MinioService.java b/src/main/java/com/dc/dc_project/service/impl/MinioService.java new file mode 100644 index 0000000..198887e --- /dev/null +++ b/src/main/java/com/dc/dc_project/service/impl/MinioService.java @@ -0,0 +1,83 @@ +package com.dc.dc_project.service.impl; + + +import com.dc.dc_project.config.MinioConfig; +import io.minio.GetPresignedObjectUrlArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import io.minio.RemoveObjectArgs; +import io.minio.errors.*; +import io.minio.http.Method; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Service +@Slf4j +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class MinioService { + + private final MinioClient minioClient; + + /** + * 上传文件 + */ + public String uploadFile(InputStream inputStream, String fileName, String bucketName, String contentType, String path) { + fileName = path + "/" + fileName; + try { + minioClient.putObject( + PutObjectArgs.builder().bucket(bucketName).object(fileName).stream( + inputStream, -1, 10485760) + .contentType(contentType) + .build()); + } catch (ErrorResponseException | XmlParserException | ServerException | InvalidResponseException | + IOException | NoSuchAlgorithmException | InvalidKeyException | InternalException | + InsufficientDataException e) { + throw new RuntimeException(e); + } + return MinioConfig.MINIO_ENDPOINT + "/" + bucketName + "/" + fileName; + } + + /** + * 获取临时访问连接 + */ + public String getPreSignedUrl(String bucketName, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { + Map reqParams = new HashMap(); + reqParams.put("response-content-type", "application/json"); + + String url = + minioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(objectName) + .expiry(2, TimeUnit.HOURS) + .extraQueryParams(reqParams) + .build()); + return url; + } + + public void deleteFile(String url, String minioBucketName) { + String objectName = url.replace(MinioConfig.MINIO_ENDPOINT + "/" + minioBucketName + "/", ""); + try { + minioClient.removeObject( + RemoveObjectArgs.builder() + .bucket(minioBucketName) + .object(objectName) + .build()); + } catch (ErrorResponseException | XmlParserException | ServerException | InvalidResponseException | + IOException | NoSuchAlgorithmException | InvalidKeyException | InternalException | + InsufficientDataException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/dc/dc_project/service/PositionServiceImpl.java b/src/main/java/com/dc/dc_project/service/impl/PositionServiceImpl.java similarity index 97% rename from src/main/java/com/dc/dc_project/service/PositionServiceImpl.java rename to src/main/java/com/dc/dc_project/service/impl/PositionServiceImpl.java index d89fd7e..1b3791c 100644 --- a/src/main/java/com/dc/dc_project/service/PositionServiceImpl.java +++ b/src/main/java/com/dc/dc_project/service/impl/PositionServiceImpl.java @@ -1,4 +1,4 @@ -package com.dc.dc_project.service; +package com.dc.dc_project.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -13,6 +13,7 @@ import com.dc.dc_project.model.pojo.PersonnelPosition; import com.dc.dc_project.model.pojo.Position; import com.dc.dc_project.mapper.PositionMapper; import com.dc.dc_project.model.vo.PositionVo; +import com.dc.dc_project.service.PositionService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -30,7 +31,7 @@ import static com.dc.dc_project.model.vo.PositionVo.potoVo; @Service @RequiredArgsConstructor(onConstructor_ = @__(@Autowired)) public class PositionServiceImpl extends ServiceImpl - implements PositionService{ + implements PositionService { private final OrgMapper orgMapper; private final PersonnelPositionMapper personnelPositionMapper; diff --git a/src/main/java/com/dc/dc_project/utils/FileUtil.java b/src/main/java/com/dc/dc_project/utils/FileUtil.java new file mode 100644 index 0000000..e646a96 --- /dev/null +++ b/src/main/java/com/dc/dc_project/utils/FileUtil.java @@ -0,0 +1,38 @@ +package com.dc.dc_project.utils; + + +import cn.hutool.core.io.FileTypeUtil; +import com.dc.dc_project.enums.FileType; +import com.dc.dc_project.model.pojo.File; + +import java.io.InputStream; +import java.util.UUID; + +public class FileUtil { + + /** + * 获取随机文件名 + */ + public static String getRandomFileName() { + return UUID.randomUUID().toString().replaceAll("-", ""); + } + + /** + * 获取文件后缀 + * @param fileName + * @date :2023/05/05 + * + **/ + public static String getFileSuffix(String fileName) { + return fileName.substring(fileName.lastIndexOf(".")); + } + + /** + * 获取文件类型 + * + **/ + public static String getFileType(InputStream file) { + return FileTypeUtil.getType(file); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4f0ac28..1535ba6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -16,7 +16,9 @@ spring: shutdown-timeout: 100ms servlet: multipart: - max-request-size: 100MB + max-file-size: 100MB + max-request-size: 200MB + resolve-lazily: true datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver @@ -68,3 +70,11 @@ springdoc: swagger-ui: enabled: true # 开启swagger界面,依赖OpenApi,需要OpenApi同时开启 path: /swagger-ui/index.html # 自定义路径,默认为"/swagger-ui/index.html" + +minio: + endpoint: http://192.168.1.100:9000 + accessKey: minioadmin + secretKey: minioadmin + bucketName: dc-lab-system + +