diff --git a/coder-common-thin-modules/coder-common-thin-system/pom.xml b/coder-common-thin-modules/coder-common-thin-system/pom.xml
index cff360b..e3bd83a 100644
--- a/coder-common-thin-modules/coder-common-thin-system/pom.xml
+++ b/coder-common-thin-modules/coder-common-thin-system/pom.xml
@@ -45,6 +45,12 @@
org.springdoc
springdoc-openapi-starter-webmvc-ui
+
+
+ org.leocoder.thin
+ coder-common-thin-oss
+ ${revision}
+
\ No newline at end of file
diff --git a/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/file/FileController.java b/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/file/FileController.java
index 4c47acb..95ba829 100755
--- a/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/file/FileController.java
+++ b/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/file/FileController.java
@@ -3,22 +3,24 @@ package org.leocoder.thin.system.controller.file;
import cn.dev33.satoken.annotation.SaIgnore;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
-import com.alibaba.excel.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.leocoder.thin.common.constants.CoderConstants;
import org.leocoder.thin.common.exception.BusinessException;
import org.leocoder.thin.common.satoken.CoderLoginUtil;
import org.leocoder.thin.common.utils.file.FileTypeUtil;
-import org.leocoder.thin.common.utils.file.UploadUtil;
import org.leocoder.thin.common.utils.ip.IpUtil;
import org.leocoder.thin.common.utils.ip.ServletUtil;
import org.leocoder.thin.domain.pojo.system.SysFile;
import org.leocoder.thin.domain.pojo.system.SysPicture;
+import org.leocoder.thin.oss.service.StorageService;
+import org.leocoder.thin.oss.service.StorageServiceFactory;
+import org.leocoder.thin.oss.utils.OssUtil;
import org.leocoder.thin.system.service.file.SysFileService;
import org.leocoder.thin.system.service.picture.SysPictureService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -40,6 +42,9 @@ public class FileController {
@Value("${coder.filePath}")
private String basePath;
+
+ @Value("${coder.storage.type:local}")
+ private String storageType;
// 允许的图片文件扩展名
private static final List ALLOWED_IMAGE_EXTENSIONS = Arrays.asList(
@@ -62,10 +67,9 @@ public class FileController {
private static final long MAX_FILE_SIZE_10MB = 10485760L;
private final Environment env;
-
private final SysFileService sysFileService;
-
private final SysPictureService sysPictureService;
+ private final StorageServiceFactory storageServiceFactory;
/**
* @param fileSize 文件大小
@@ -80,23 +84,61 @@ public class FileController {
// 文件预检查
validateUploadFile(file, fileSize, folderName);
- Map fileMap = UploadUtil.coderSingleFile(file, basePath + "/" + folderName + "/"+ CoderLoginUtil.getLoginName() + "/", fileSize);
+ Map fileMap;
+
+ try {
+ // 根据配置选择存储服务
+ StorageService storageService = storageServiceFactory.getStorageService(storageType);
+
+ // 生成唯一文件名
+ String fileName = OssUtil.generateUniqueFileName(file.getOriginalFilename());
+
+ // 构建文件夹路径
+ String folderPath = OssUtil.buildFolderPath(folderName, CoderLoginUtil.getLoginName());
+
+ // 上传文件
+ fileMap = storageService.uploadFile(file, fileName, folderPath);
+
+ log.info("文件上传成功: storageType={}, fileName={}, filePath={}",
+ storageType, fileName, fileMap.get("filePath"));
+
+ } catch (Exception e) {
+ log.error("文件上传失败,尝试使用本地存储降级", e);
+
+ // 降级到本地存储
+ try {
+ StorageService localService = storageServiceFactory.getStorageService("local");
+ String fileName = OssUtil.generateUniqueFileName(file.getOriginalFilename());
+ String folderPath = OssUtil.buildFolderPath(folderName, CoderLoginUtil.getLoginName());
+ fileMap = localService.uploadFile(file, fileName, folderPath);
+
+ log.warn("使用本地存储降级成功: fileName={}", fileName);
+
+ } catch (Exception ex) {
+ log.error("本地存储降级也失败", ex);
+ throw new BusinessException(500, "文件上传失败: " + ex.getMessage());
+ }
+ }
+
+ // 获取实际使用的存储服务类型
+ String actualStorageType = determineActualStorageType(fileMap);
// 统一保存到文件表,便于文件管理页面统一显示
- saveUploadFilesInformation(fileMap, CoderConstants.TRUE);
+ saveUploadFilesInformation(fileMap, actualStorageType, CoderConstants.TRUE);
// 如果是图片,同时保存到图库表(保持图库管理功能)
if (CoderConstants.PICTURES.equals(folderName)) {
- saveUploadPicturesInformation(fileMap, fileParam, CoderConstants.TRUE);
+ saveUploadPicturesInformation(fileMap, fileParam, actualStorageType, CoderConstants.TRUE);
}
+
return fileMap;
}
/**
- * @description [保存上传文件信息]
+ * @description [保存上传图片信息]
* @author Leocoder
*/
- private void saveUploadPicturesInformation(Map fileMap, String fileParam, boolean isCreateBy) {
+ private void saveUploadPicturesInformation(Map fileMap, String fileParam, String storageServiceType, boolean isCreateBy) {
log.info("图库上传 ->");
// 新增文件信息
SysPicture sysPicture = new SysPicture();
@@ -105,16 +147,26 @@ public class FileController {
sysPicture.setPictureSize(fileMap.get("fileSize").toString());
sysPicture.setPictureSuffix(fileMap.get("suffixName").toString());
sysPicture.setPictureUpload(fileMap.get("filePath").toString());
- sysPicture.setPictureService(CoderConstants.ONE_STRING);
- String protocol = IpUtil.getProtocol(ServletUtil.getRequest());
- if (StringUtils.isBlank(protocol)) {
- protocol = "http";
+ sysPicture.setPictureService(storageServiceType);
+
+ // 设置文件访问路径
+ String fileUploadPath = (String) fileMap.get("fileUploadPath");
+ if (isFullUrl(fileUploadPath)) {
+ // 如果已经是完整URL(如OSS),直接使用
+ sysPicture.setPicturePath(fileUploadPath);
+ } else {
+ // 如果是相对路径(如本地存储),构建完整URL
+ String protocol = IpUtil.getProtocol(ServletUtil.getRequest());
+ if (StringUtils.isBlank(protocol)) {
+ protocol = "http";
+ }
+ String hostIp = IpUtil.getHostIp(ServletUtil.getRequest());
+ String hostPort = StringUtils.isNotBlank(env.getProperty("server.port")) ? env.getProperty("server.port") : "18099";
+ sysPicture.setPicturePath(protocol + "://" + hostIp + ":" + hostPort + fileUploadPath);
}
- String hostIp = IpUtil.getHostIp(ServletUtil.getRequest());
- String hostPort = StringUtils.isNotBlank(env.getProperty("server.port")) ? env.getProperty("server.port") : "18099";
- log.info("IP地址:{},端口号:{}", hostIp, env.getProperty("server.port"));
- sysPicture.setPicturePath(protocol + "://" + hostIp + ":" + hostPort + fileMap.get("fileUploadPath").toString());
+
log.info("图片回显地址:{}", sysPicture.getPicturePath());
+
if (CoderConstants.MINUS_ONE_STRING.equals(fileParam)) {
sysPicture.setPictureType("9");
} else {
@@ -130,25 +182,35 @@ public class FileController {
* @description [保存上传文件信息]
* @author Leocoder
*/
- private void saveUploadFilesInformation(Map fileMap, boolean isCreateBy) {
+ private void saveUploadFilesInformation(Map fileMap, String storageServiceType, boolean isCreateBy) {
log.info("文件上传 ->");
- // 新增图库信息
+ // 新增文件信息
SysFile sysFile = new SysFile();
sysFile.setFileName(fileMap.get("fileName").toString());
sysFile.setNewName(fileMap.get("newName").toString());
sysFile.setFileSize(fileMap.get("fileSize").toString());
sysFile.setFileSuffix(fileMap.get("suffixName").toString());
sysFile.setFileUpload(fileMap.get("filePath").toString());
- sysFile.setFileService(CoderConstants.ONE_STRING);
- String protocol = IpUtil.getProtocol(ServletUtil.getRequest());
- if (StringUtils.isBlank(protocol)) {
- protocol = "http";
+ sysFile.setFileService(storageServiceType);
+
+ // 设置文件访问路径
+ String fileUploadPath = (String) fileMap.get("fileUploadPath");
+ if (isFullUrl(fileUploadPath)) {
+ // 如果已经是完整URL(如OSS),直接使用
+ sysFile.setFilePath(fileUploadPath);
+ } else {
+ // 如果是相对路径(如本地存储),构建完整URL
+ String protocol = IpUtil.getProtocol(ServletUtil.getRequest());
+ if (StringUtils.isBlank(protocol)) {
+ protocol = "http";
+ }
+ String hostIp = IpUtil.getHostIp(ServletUtil.getRequest());
+ String hostPort = StringUtils.isNotBlank(env.getProperty("server.port")) ? env.getProperty("server.port") : "18099";
+ sysFile.setFilePath(protocol + "://" + hostIp + ":" + hostPort + fileUploadPath);
}
- String hostIp = IpUtil.getHostIp(ServletUtil.getRequest());
- String hostPort = StringUtils.isNotBlank(env.getProperty("server.port")) ? env.getProperty("server.port") : "18088";
- log.info("IP地址:{},端口号:{}", hostIp, env.getProperty("server.port"));
- sysFile.setFilePath(protocol + "://" + hostIp + ":" + hostPort + fileMap.get("fileUploadPath").toString());
+
log.info("文件回显地址:{}", sysFile.getFilePath());
+
String fileType = FileTypeUtil.checkFileExtension(fileMap.get("suffixName").toString());
sysFile.setFileType(fileType);
if (isCreateBy) {
@@ -156,6 +218,33 @@ public class FileController {
}
sysFileService.save(sysFile);
}
+
+ /**
+ * 判断字符串是否为完整URL
+ */
+ private boolean isFullUrl(String url) {
+ return StringUtils.isNotBlank(url) && (url.startsWith("http://") || url.startsWith("https://"));
+ }
+
+ /**
+ * 确定实际使用的存储服务类型
+ */
+ private String determineActualStorageType(Map fileMap) {
+ String fileUploadPath = (String) fileMap.get("fileUploadPath");
+
+ // 根据返回的文件路径判断实际使用的存储类型
+ if (isFullUrl(fileUploadPath)) {
+ // 如果是完整URL,检查是否包含OSS域名
+ if (fileUploadPath.contains(".aliyuncs.com") || fileUploadPath.contains("oss-")) {
+ return "3"; // OSS
+ } else if (fileUploadPath.contains("minio") || fileUploadPath.contains(":9000")) {
+ return "2"; // MinIO
+ }
+ }
+
+ // 默认为本地存储
+ return "1"; // Local
+ }
/**
* @param fileSize 文件大小
@@ -171,15 +260,53 @@ public class FileController {
// 文件预检查
validateUploadFile(file, fileSize, folderName);
- Map fileMap = UploadUtil.coderSingleFile(file, basePath + "/" + folderName + "/", fileSize);
+ Map fileMap;
+
+ try {
+ // 根据配置选择存储服务
+ StorageService storageService = storageServiceFactory.getStorageService(storageType);
+
+ // 生成唯一文件名
+ String fileName = OssUtil.generateUniqueFileName(file.getOriginalFilename());
+
+ // 构建文件夹路径(匿名上传不包含用户名)
+ String folderPath = folderName;
+
+ // 上传文件
+ fileMap = storageService.uploadFile(file, fileName, folderPath);
+
+ log.info("匿名文件上传成功: storageType={}, fileName={}, filePath={}",
+ storageType, fileName, fileMap.get("filePath"));
+
+ } catch (Exception e) {
+ log.error("匿名文件上传失败,尝试使用本地存储降级", e);
+
+ // 降级到本地存储
+ try {
+ StorageService localService = storageServiceFactory.getStorageService("local");
+ String fileName = OssUtil.generateUniqueFileName(file.getOriginalFilename());
+ String folderPath = folderName;
+ fileMap = localService.uploadFile(file, fileName, folderPath);
+
+ log.warn("匿名上传使用本地存储降级成功: fileName={}", fileName);
+
+ } catch (Exception ex) {
+ log.error("本地存储降级也失败", ex);
+ throw new BusinessException(500, "文件上传失败: " + ex.getMessage());
+ }
+ }
+
+ // 获取实际使用的存储服务类型
+ String actualStorageType = determineActualStorageType(fileMap);
// 统一保存到文件表
- saveUploadFilesInformation(fileMap, false);
+ saveUploadFilesInformation(fileMap, actualStorageType, false);
// 如果是图片,同时保存到图库表
if (CoderConstants.PICTURES.equals(folderName)) {
- saveUploadPicturesInformation(fileMap, fileParam, false);
+ saveUploadPicturesInformation(fileMap, fileParam, actualStorageType, false);
}
+
return fileMap;
}
diff --git a/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/file/SysFileController.java b/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/file/SysFileController.java
index 6e64a6c..8cb49e6 100755
--- a/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/file/SysFileController.java
+++ b/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/file/SysFileController.java
@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.leocoder.thin.common.constants.CoderConstants;
import org.leocoder.thin.common.exception.coder.YUtil;
@@ -17,6 +18,8 @@ import org.leocoder.thin.domain.enums.oper.OperType;
import org.leocoder.thin.domain.model.vo.system.SysFileVo;
import org.leocoder.thin.domain.pojo.system.SysFile;
import org.leocoder.thin.operlog.annotation.OperLog;
+import org.leocoder.thin.oss.service.StorageService;
+import org.leocoder.thin.oss.service.StorageServiceFactory;
import org.leocoder.thin.system.service.file.SysFileService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -33,9 +36,12 @@ import java.util.List;
@RequestMapping("/coder")
@RequiredArgsConstructor
@RestController
+@Slf4j
public class SysFileController {
+
private final SysFileService sysFileService;
+ private final StorageServiceFactory storageServiceFactory;
/**
* @description [分页查询]
@@ -124,8 +130,8 @@ public class SysFileController {
SysFile sysFile = sysFileService.getById(id);
if (!ObjectUtil.isEmpty(sysFile)) {
if (StringUtils.isNotBlank(sysFile.getFileUpload())) {
- // 删除文件
- FileUtil.deleteFile(sysFile.getFileUpload());
+ // 根据存储服务类型删除文件
+ deletePhysicalFile(sysFile.getFileUpload(), sysFile.getFileService());
}
}
YUtil.isTrue(!sysFileService.removeById(id), "删除失败,请稍后重试");
@@ -147,12 +153,58 @@ public class SysFileController {
if (!sysFileList.isEmpty()) {
for (SysFile sysFile : sysFileList) {
if (StringUtils.isNotBlank(sysFile.getFileUpload())) {
- // 删除文件
- FileUtil.deleteFile(sysFile.getFileUpload());
+ // 根据存储服务类型删除文件
+ deletePhysicalFile(sysFile.getFileUpload(), sysFile.getFileService());
}
}
}
YUtil.isTrue(!sysFileService.removeBatchByIds(ids), "删除失败,请稍后重试");
}
+
+ /**
+ * 根据存储服务类型删除物理文件
+ */
+ private void deletePhysicalFile(String filePath, String fileService) {
+ try {
+ boolean deleteResult = false;
+
+ switch (fileService) {
+ case "1": // 本地存储
+ deleteResult = FileUtil.deleteFile(filePath);
+ break;
+ case "2": // MinIO存储
+ try {
+ StorageService minioService = storageServiceFactory.getStorageService("minio");
+ deleteResult = minioService.deleteFile(filePath);
+ } catch (Exception e) {
+ log.error("MinIO存储服务不可用,尝试本地删除", e);
+ deleteResult = FileUtil.deleteFile(filePath);
+ }
+ break;
+ case "3": // OSS存储
+ try {
+ StorageService ossService = storageServiceFactory.getStorageService("oss");
+ deleteResult = ossService.deleteFile(filePath);
+ } catch (Exception e) {
+ log.error("OSS存储服务不可用,尝试本地删除", e);
+ deleteResult = FileUtil.deleteFile(filePath);
+ }
+ break;
+ default:
+ log.warn("未知的存储服务类型: {},使用本地删除", fileService);
+ deleteResult = FileUtil.deleteFile(filePath);
+ break;
+ }
+
+ if (deleteResult) {
+ log.info("文件删除成功: filePath={}, fileService={}", filePath, fileService);
+ } else {
+ log.warn("文件删除失败: filePath={}, fileService={}", filePath, fileService);
+ }
+
+ } catch (Exception e) {
+ log.error("删除物理文件时发生异常: filePath={}, fileService={}", filePath, fileService, e);
+ }
+ }
}
diff --git a/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/picture/SysPictureController.java b/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/picture/SysPictureController.java
index d777977..a74b9f7 100755
--- a/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/picture/SysPictureController.java
+++ b/coder-common-thin-modules/coder-common-thin-system/src/main/java/org/leocoder/thin/system/controller/picture/SysPictureController.java
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.leocoder.thin.common.constants.CoderConstants;
import org.leocoder.thin.common.exception.coder.YUtil;
@@ -15,6 +16,8 @@ import org.leocoder.thin.domain.enums.oper.OperType;
import org.leocoder.thin.domain.model.vo.system.SysPictureVo;
import org.leocoder.thin.domain.pojo.system.SysPicture;
import org.leocoder.thin.operlog.annotation.OperLog;
+import org.leocoder.thin.oss.service.StorageService;
+import org.leocoder.thin.oss.service.StorageServiceFactory;
import org.leocoder.thin.system.service.picture.SysPictureService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -31,9 +34,11 @@ import java.util.List;
@RequestMapping("/coder")
@RequiredArgsConstructor
@RestController
+@Slf4j
public class SysPictureController {
private final SysPictureService sysPictureService;
+ private final StorageServiceFactory storageServiceFactory;
/**
* @description [分页查询]
@@ -117,6 +122,13 @@ public class SysPictureController {
@PostMapping("/sysPicture/deleteById/{id}")
@OperLog(value = "删除图库", operType = OperType.DELETE)
public void delete(@PathVariable("id") Long id) {
+ SysPicture sysPicture = sysPictureService.getById(id);
+ if (sysPicture != null) {
+ if (StringUtils.isNotBlank(sysPicture.getPictureUpload())) {
+ // 根据存储服务类型删除图片文件
+ deletePhysicalFile(sysPicture.getPictureUpload(), sysPicture.getPictureService());
+ }
+ }
YUtil.isTrue(!sysPictureService.removeById(id), "删除失败,请稍后重试");
}
@@ -130,7 +142,63 @@ public class SysPictureController {
@PostMapping("/sysPicture/batchDelete")
@OperLog(value = "批量删除图库", operType = OperType.DELETE)
public void batchDelete(@RequestBody List ids) {
+ List sysPictureList = sysPictureService.listByIds(ids);
+
+ // 批量删除物理文件
+ for (SysPicture sysPicture : sysPictureList) {
+ if (StringUtils.isNotBlank(sysPicture.getPictureUpload())) {
+ deletePhysicalFile(sysPicture.getPictureUpload(), sysPicture.getPictureService());
+ }
+ }
+
YUtil.isTrue(!sysPictureService.removeBatchByIds(ids), "删除失败,请稍后重试");
}
+
+ /**
+ * 根据存储服务类型删除物理文件
+ */
+ private void deletePhysicalFile(String filePath, String fileService) {
+ try {
+ boolean deleteResult = false;
+
+ switch (fileService) {
+ case "1": // 本地存储
+ // 对于本地存储,需要使用完整路径
+ deleteResult = new java.io.File(filePath).delete();
+ break;
+ case "2": // MinIO存储
+ try {
+ StorageService minioService = storageServiceFactory.getStorageService("minio");
+ deleteResult = minioService.deleteFile(filePath);
+ } catch (Exception e) {
+ log.error("MinIO存储服务不可用,跳过删除", e);
+ deleteResult = false;
+ }
+ break;
+ case "3": // OSS存储
+ try {
+ StorageService ossService = storageServiceFactory.getStorageService("oss");
+ deleteResult = ossService.deleteFile(filePath);
+ } catch (Exception e) {
+ log.error("OSS存储服务不可用,跳过删除", e);
+ deleteResult = false;
+ }
+ break;
+ default:
+ log.warn("未知的存储服务类型: {},跳过文件删除", fileService);
+ deleteResult = false;
+ break;
+ }
+
+ if (deleteResult) {
+ log.info("图片文件删除成功: filePath={}, fileService={}", filePath, fileService);
+ } else {
+ log.warn("图片文件删除失败: filePath={}, fileService={}", filePath, fileService);
+ }
+
+ } catch (Exception e) {
+ log.error("删除物理图片文件时发生异常: filePath={}, fileService={}", filePath, fileService, e);
+ }
+ }
}