u
This commit is contained in:
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+222
-12
@@ -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
|
||||
Reference in new issue
Block a user