This commit is contained in:
oneao committed 2026-03-29 21:28:08 +08:00
1 parent 9c026f00ad
commit f9eaacf02a
5 files changed
+323 -116

No files matched your search

@@ -11,36 +11,22 @@ import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "file")
public class FileConfig {
private String baseDir;
private String tempDir;
private String commonDir;
private String containerDir;
private String tempDir;
private String staticPath;
private String serverUrl;
private String staticPath;
public String getCommonPath() {
return PathUtils.joinFilePath(commonDir, commonDir);
}
public String getContainerPath() {
return PathUtils.joinFilePath(commonDir, containerDir);
}
public String getTempPath() {
return PathUtils.joinFilePath(baseDir, tempDir);
return PathUtils.joinFilePath(commonDir, tempDir);
}
public String getContainerPath(){
return PathUtils.joinFilePath(baseDir,containerDir);
}
public String buildTempFilePath(String fileName) {
return PathUtils.joinFilePath(baseDir, tempDir, fileName);
}
public String getStaticUrlPrefix() {
return PathUtils.joinUrl(serverUrl, staticPath);
}
public String buildTempUrl(String fileName) {
return PathUtils.joinUrl(serverUrl, staticPath, tempDir, fileName);
}
public String buildFileUrl(String relativePath) {
return PathUtils.joinUrl(serverUrl, staticPath, relativePath);
}
}
@@ -1,19 +1,23 @@
package cn.g3soft.controller;
import cn.g3soft.config.FileConfig;
import cn.g3soft.context.UserContext;
import cn.g3soft.utils.*;
import cn.g3soft.utils.uniqueId.idgen.UniqueId;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.DateUtil;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/file")
@@ -22,24 +26,230 @@ public class FileController {
private DbUtils dbUtils;
@Resource
private FileUploadUtils fileUploadUtils;
@Resource
private FileConfig fileConfig;
/**
* 上传临时文件接口
*/
@PostMapping("/uploadTemp")
public Result<Map<String, String>> uploadTemp(@RequestParam("file") MultipartFile file,
@RequestParam(value = "fileName") String fileName) {
Map<String, String> response = new HashMap<>();
try {
// 调用工具类上传临时文件,返回临时 URL
String tempUrl = fileUploadUtils.uploadTemp(file,fileName);
public Result<Map<String, String>> uploadTemp(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "fileName", required = false) String fileName)
throws Exception{
// 1. 校验
FileUtils.checkFile(file);
// 返回临时文件 URL
response.put("tempUrl", tempUrl);
return Result.ok(response);
} catch (IOException e) {
return Result.fail("文件上传失败:" + e.getMessage());
}
// 2. 生成文件名(只取后缀)
String uuidName = FileUtils.generateUUIDName(fileName);
// 3. 物理路径
String basePath = fileConfig.getTempPath(); // D:/data/file/temp/
// 4. 保存
FileUtils.saveFile(file, basePath, uuidName);
// 5. 相对路径(关键)
String relativePath = PathUtils.joinFilePath(fileConfig.getTempDir(), uuidName);
// temp/xxx.jpg
// 6. URL
String url = PathUtils.joinUrl(fileConfig.getServerUrl(), fileConfig.getStaticPath(), relativePath);
// 7. 返回
Map<String, String> result = new HashMap<>();
result.put("fileName", uuidName);
result.put("url", url);
result.put("path", relativePath);
return Result.ok(result);
}
@PostMapping("/uploadCommon")
@Transactional(rollbackFor = Exception.class)
public Result<Map<String, Object>> uploadCommon(
@RequestParam("file") MultipartFile file,
@RequestParam("moduleId") String moduleId,
@RequestParam("father") Long father,
@RequestParam("fileName") String fileName,
@RequestParam(value = "mxXh", required = false) Integer mxXh
) throws Exception {
// 1. 校验
FileUtils.checkFile(file);
// 2. 生成文件名(UUID + 后缀)
String uuidName = FileUtils.generateUUIDName(fileName);
// 3. 日期路径 2026/03/29/
String datePath = TimeUtils.todayPath();
// 4. 物理路径
String basePath = fileConfig.getCommonPath(); // D:/data/file/common/
String fullPath = PathUtils.joinFilePath(basePath, datePath);
// 5. 保存
FileUtils.saveFile(file, fullPath, uuidName);
// 6. 相对路径(关键)
String relativePath = PathUtils.joinFilePath(
fileConfig.getCommonDir(),
datePath,
uuidName
);
// 保存到数据库
String fileSuffix = FileUtils.getSuffix(fileName);
long fileSize = FileUtils.getFileSize(file);
Map<String, Object> insertMap = new HashMap<>();
insertMap.put("subid", UniqueId.nextId());
insertMap.put("father", father);
insertMap.put("mx_moduleid", moduleId);
insertMap.put("mx_xh", Optional.ofNullable(mxXh).orElse(0));
insertMap.put("mx_filename", fileName);
insertMap.put("mx_filesize", fileSize);
insertMap.put("mx_fileext", fileSuffix);
insertMap.put("mx_mapfilename", relativePath);
dbUtils.insert("bf_file", "subid", insertMap);
// 7. URL
String url = PathUtils.joinUrl(
fileConfig.getServerUrl(),
fileConfig.getStaticPath(),
relativePath
);
// 8. 返回
Map<String, Object> result = new HashMap<>();
result.put("fileName", uuidName);
result.put("url", url);
result.put("path", relativePath);
return Result.ok(result);
}
@PostMapping("/uploadContainer")
@Transactional(rollbackFor = Exception.class)
public Result<Map<String, Object>> uploadContainer(
@RequestParam("file") MultipartFile file,
@RequestParam("containerNo") String containerNo
) throws Exception {
// 1. 校验
FileUtils.checkFile(file);
// 2. 日期路径
String datePath = TimeUtils.todayPath(); // 2026/03/29/
// 3. 根路径
String basePath = fileConfig.getContainerPath();
String dayPath = PathUtils.joinFilePath(basePath, datePath);
String relativePath;
String fileName;
String containerDirName;
// 🔥 4. 并发锁(同一天 + container)
String lockKey = (datePath + containerNo).intern();
synchronized (lockKey) {
// 4.1 获取 container 目录(防重复)
containerDirName = getAvailableContainerDir(dayPath, containerNo);
String fullDirPath = PathUtils.joinFilePath(dayPath, containerDirName);
// 4.2 获取下一个序号(不补位)
int index = getNextIndex(fullDirPath);
// 4.3 后缀
String suffix = FileUtils.getSuffix(file.getOriginalFilename());
// 4.4 文件名(核心规则)
fileName = containerNo + "_" + String.format("%02d", index) + suffix;
// 4.5 保存
FileUtils.saveFile(file, fullDirPath, fileName);
// 4.6 相对路径
relativePath = PathUtils.joinFilePath(
fileConfig.getContainerDir(),
datePath,
containerDirName,
fileName
);
}
// 5. URL
String url = PathUtils.joinUrl(
fileConfig.getServerUrl(),
fileConfig.getStaticPath(),
relativePath
);
// 6. 返回
Map<String, Object> result = new HashMap<>();
result.put("url", url);
result.put("path", relativePath);
result.put("fileName", fileName);
result.put("containerDir", containerDirName);
return Result.ok(result);
}
public static int getNextIndex(String dirPath) {
File dir = new File(dirPath);
if (!dir.exists()) {
return 1;
}
File[] files = dir.listFiles();
if (files == null || files.length == 0) {
return 1;
}
int max = 0;
for (File f : files) {
String name = f.getName();
// 解析 xxx_01.jpg
int underscore = name.lastIndexOf("_");
int dot = name.lastIndexOf(".");
if (underscore > -1 && dot > underscore) {
try {
int num = Integer.parseInt(name.substring(underscore + 1, dot));
max = Math.max(max, num);
} catch (Exception ignored) {}
}
}
return max + 1;
}
public static String getAvailableContainerDir(String basePath, String containerNo) {
File baseDir = new File(basePath);
if (!baseDir.exists()) {
baseDir.mkdirs();
}
File dir = new File(baseDir, containerNo);
if (!dir.exists()) {
return containerNo;
}
int i = 1;
while (true) {
String newName = containerNo + "_" + String.format("%02d", i);
File newDir = new File(baseDir, newName);
if (!newDir.exists()) {
return newName;
}
i++;
}
}
}
@@ -10,108 +10,108 @@ public class FileUtils {
/**
* =========================
* 最简调用:自动生成文件名 + 不覆盖
* 核心保存逻辑
* =========================
*/
public static String upload(MultipartFile file, String uploadPath) throws IOException {
return upload(file, uploadPath, null, false);
}
public static String saveFile(MultipartFile file,
String uploadPath,
String fileName) throws IOException {
/**
* =========================
* 指定文件名 + 不覆盖
* =========================
*/
public static String upload(MultipartFile file,
String uploadPath,
String fileName) throws IOException {
return upload(file, uploadPath, fileName, false);
}
/**
* =========================
* 控制是否覆盖 + 自动生成文件名
* =========================
*/
public static String upload(MultipartFile file,
String uploadPath,
boolean overwrite) throws IOException {
return upload(file, uploadPath, null, overwrite);
}
/**
* =========================
* 核心方法(唯一执行逻辑)
* =========================
*/
public static String upload(MultipartFile file,
String uploadPath,
String fileName,
boolean overwrite) throws IOException {
// 1. 校验文件
if (file == null || file.isEmpty()) {
throw new RuntimeException("上传文件不能为空");
}
// 2. 创建目录
// 创建目录
File dir = new File(uploadPath);
if (!dir.exists()) {
boolean created = dir.mkdirs();
if (!created) {
throw new RuntimeException("目录创建失败: " + uploadPath);
}
if (!dir.exists() && !dir.mkdirs()) {
throw new RuntimeException("目录创建失败: " + uploadPath);
}
// 3. 获取原始文件名
String originalName = file.getOriginalFilename();
// 4. 处理文件名
if (fileName == null || fileName.trim().isEmpty()) {
fileName = generateFileName(originalName);
} else {
// 如果用户传了文件名,但没有后缀,则补上
if (!fileName.contains(".") && originalName != null && originalName.contains(".")) {
fileName = fileName + getSuffix(originalName);
}
}
// 5. 目标文件
// 处理重名
File dest = new File(dir, fileName);
// 6. 处理覆盖逻辑
if (dest.exists() && !overwrite) {
String newName = generateFileName(fileName);
dest = new File(dir, newName);
fileName = newName;
if (dest.exists()) {
fileName = rename(fileName, dir);
dest = new File(dir, fileName);
}
// 7. 保存文件
// 保存文件
file.transferTo(dest);
// 8. 返回文件路径(你也可以改成返回 URL)
return dest.getAbsolutePath();
}
/**
* =========================
* 生成唯一文件名
* 重名处理:a.jpg → a_1.jpg → a_2.jpg
* =========================
*/
private static String generateFileName(String originalName) {
return UUID.randomUUID().toString().replace("-", "")
+ getSuffix(originalName);
public static String rename(String fileName, File dir) {
String name = getNameWithoutSuffix(fileName);
String suffix = getSuffix(fileName);
int i = 1;
File newFile;
do {
String newName = name + "_" + i + suffix;
newFile = new File(dir, newName);
i++;
if (!newFile.exists()) {
return newName;
}
} while (true);
}
/**
* =========================
* 获取文件后缀
* 根据文件名生成 UUID 文件名(保留后缀)
* =========================
*/
private static String getSuffix(String fileName) {
public static String generateUUIDName(String fileName) {
String suffix = getSuffix(fileName);
return UUID.randomUUID().toString().replace("-", "") + suffix;
}
/**
* =========================
* 获取后缀
* =========================
*/
public static String getSuffix(String fileName) {
if (fileName == null || !fileName.contains(".")) {
return "";
}
return fileName.substring(fileName.lastIndexOf("."));
}
}
/**
* =========================
* 获取不带后缀的文件名
* =========================
*/
public static String getNameWithoutSuffix(String fileName) {
if (fileName == null || !fileName.contains(".")) {
return fileName;
}
return fileName.substring(0, fileName.lastIndexOf("."));
}
/**
* =========================
* 获取文件大小(字节)
* =========================
*/
public static long getFileSize(MultipartFile file) {
if (file == null) {
return 0L;
}
return file.getSize();
}
/**
* =========================
* 文件校验
* =========================
*/
public static void checkFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new RuntimeException("上传文件不能为空");
}
}
}
@@ -1,5 +1,6 @@
package cn.g3soft.utils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -15,4 +16,12 @@ public class TimeUtils {
public static String getDateTime() {
return LocalDateTime.now().format(DATE_TIME_FORMATTER);
}
public static String todayPath() {
LocalDate now = LocalDate.now();
return now.getYear() + "/" +
String.format("%02d", now.getMonthValue()) + "/" +
String.format("%02d", now.getDayOfMonth()) + "/";
}
}
@@ -19,9 +19,11 @@ spring:
date-format: yyyy-MM-dd HH:mm:ss
file:
base-dir: D:/data/file
static-path: /static/file
server-url: http://192.168.1.137:80/api
base-dir: D:/data
common-dir: common
container-dir: container
temp-dir: temp
temp-dir: temp