This commit is contained in:
oneao committed 2026-03-30 17:27:19 +08:00
1 parent f9eaacf02a
commit 69eda8b8ad
27 files changed
+2797 -616

No files matched your search

@@ -19,14 +19,14 @@ public class FileConfig {
private String staticPath;
public String getCommonPath() {
return PathUtils.joinFilePath(commonDir, commonDir);
return PathUtils.joinFilePath(baseDir, commonDir);
}
public String getContainerPath() {
return PathUtils.joinFilePath(commonDir, containerDir);
return PathUtils.joinFilePath(baseDir, containerDir);
}
public String getTempPath() {
return PathUtils.joinFilePath(commonDir, tempDir);
return PathUtils.joinFilePath(baseDir, tempDir);
}
}
@@ -50,6 +50,6 @@ public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(fileConfig.getStaticPath() + "/**")
.addResourceLocations("file:" + fileConfig.getUploadDir() + "/");
.addResourceLocations("file:" + fileConfig.getBaseDir() + "/");
}
}
@@ -14,10 +14,7 @@ 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;
import java.util.*;
@RestController
@RequestMapping("/file")
@@ -25,8 +22,6 @@ public class FileController {
@Resource
private DbUtils dbUtils;
@Resource
private FileUploadUtils fileUploadUtils;
@Resource
private FileConfig fileConfig;
/**
@@ -34,9 +29,9 @@ public class FileController {
*/
@PostMapping("/uploadTemp")
public Result<Map<String, String>> uploadTemp(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "fileName", required = false) String fileName)
throws Exception{
@RequestParam("file") MultipartFile file,
@RequestParam(value = "fileName", required = false) String fileName)
throws Exception {
// 1. 校验
FileUtils.checkFile(file);
@@ -46,6 +41,7 @@ public class FileController {
// 3. 物理路径
String basePath = fileConfig.getTempPath(); // D:/data/file/temp/
System.out.println(basePath);
// 4. 保存
FileUtils.saveFile(file, basePath, uuidName);
@@ -69,10 +65,7 @@ public class FileController {
@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
@RequestParam("fileName") String fileName
) throws Exception {
// 1. 校验
FileUtils.checkFile(file);
@@ -97,22 +90,6 @@ public class FileController {
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(),
@@ -129,127 +106,192 @@ public class FileController {
return Result.ok(result);
}
// @PostMapping("/uploadContainer")
// @Transactional(rollbackFor = Exception.class)
// public Result<Map<String, Object>> uploadContainer(
// @RequestParam("file") MultipartFile file,
// @RequestParam("containerNo") String containerNo,
// @RequestParam("fileName") String fileName,
// @RequestParam("jobId") String jobId
// ) throws Exception {
// // 1. 校验
// FileUtils.checkFile(file);
//
// // 2. 日期路径
// String datePath = TimeUtils.todayPath(); // 2026/03/30/
//
// // 3. 根路径
// String basePath = fileConfig.getContainerPath();
//
// // 4. 构建目录:日期 / 箱号 / jobId
// String fullDirPath = PathUtils.joinFilePath(
// basePath,
// datePath,
// containerNo,
// jobId
// );
//
// // 5. 创建目录(幂等)
// File dir = new File(fullDirPath);
// if (!dir.exists()) {
// dir.mkdirs();
// }
//
// // 6. 文件后缀
// String suffix = FileUtils.getSuffix(fileName);
//
// long nextId = UniqueId.nextId();
//
// // 7. 使用雪花ID作为文件名(核心优化)
// String newFileName = nextId + suffix;
//
// // 8. 保存文件
// FileUtils.saveFile(file, fullDirPath, newFileName);
//
// // 9. 相对路径
// String relativePath = PathUtils.joinFilePath(
// fileConfig.getContainerDir(),
// datePath,
// containerNo,
// jobId,
// newFileName
// );
//
// // 10. URL
// String url = PathUtils.joinUrl(
// fileConfig.getServerUrl(),
// fileConfig.getStaticPath(),
// relativePath
// );
//
// // 11. 返回
// Map<String, Object> result = new HashMap<>();
// result.put("url", url);
// result.put("path", relativePath);
// result.put("uid", nextId);
// result.put("fileName", newFileName);
//
// return Result.ok(result);
// }
@PostMapping("/uploadContainer")
@Transactional(rollbackFor = Exception.class)
public Result<Map<String, Object>> uploadContainer(
@RequestParam("file") MultipartFile file,
@RequestParam("containerNo") String containerNo
@RequestParam("fileName") String fileName
) throws Exception {
// 1. 校验
FileUtils.checkFile(file);
// 2. 日期路径
String datePath = TimeUtils.todayPath(); // 2026/03/29/
String datePath = TimeUtils.todayPath(); // 2026/03/30/
// 3. 根路径
String basePath = fileConfig.getContainerPath();
String dayPath = PathUtils.joinFilePath(basePath, datePath);
String relativePath;
String fileName;
String containerDirName;
// 4. 雪花 ID(唯一)
long nextId = UniqueId.nextId();
// 🔥 4. 并发锁(同一天 + container)
String lockKey = (datePath + containerNo).intern();
// 5. 分桶(0-ff)
String bucket = String.format("%02x", nextId & 0xff);
synchronized (lockKey) {
// 6. 构建目录:日期 + 分桶
String fullDirPath = PathUtils.joinFilePath(
basePath,
datePath,
bucket
);
// 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
);
// 7. 创建目录(幂等)
File dir = new File(fullDirPath);
if (!dir.exists()) {
dir.mkdirs();
}
// 5. URL
// 8. 文件后缀
String suffix = FileUtils.getSuffix(fileName);
String newFileName = nextId + suffix;
// 9. 保存文件
FileUtils.saveFile(file, fullDirPath, newFileName);
// 10. 相对路径
String relativePath = PathUtils.joinFilePath(
fileConfig.getContainerDir(),
datePath,
bucket,
newFileName
);
// 11. URL
String url = PathUtils.joinUrl(
fileConfig.getServerUrl(),
fileConfig.getStaticPath(),
relativePath
);
// 6. 返回
// 12. 返回
Map<String, Object> result = new HashMap<>();
result.put("url", url);
result.put("path", relativePath);
result.put("fileName", fileName);
result.put("containerDir", containerDirName);
result.put("uid", nextId);
result.put("fileName", newFileName);
result.put("bucket", bucket);
return Result.ok(result);
}
public static int getNextIndex(String dirPath) {
File dir = new File(dirPath);
@PostMapping("/delete")
@Transactional(rollbackFor = Exception.class)
public Result<Void> deleteFile(@RequestBody Map<String, Object> params) {
try {
// 1. 参数校验
ValidationUtils.checkRequiredFields(params, "subid");
if (!dir.exists()) {
return 1;
}
Long subid = Long.valueOf(params.get("subid").toString());
File[] files = dir.listFiles();
if (files == null || files.length == 0) {
return 1;
}
// 2. 查询文件记录
List<Map<String, Object>> maps = dbUtils.loadData(
"SELECT * FROM bf_file WHERE subid = ?", subid
);
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) {}
if (maps.isEmpty()) {
return Result.ok();
}
}
return max + 1;
}
Map<String, Object> objectMap = maps.get(0);
public static String getAvailableContainerDir(String basePath, String containerNo) {
File baseDir = new File(basePath);
// 3. 获取文件路径
String mxMapfilename = objectMap.get("mx_mapfilename") == null
? ""
: objectMap.get("mx_mapfilename").toString();
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;
if (StringUtils.isBlank(mxMapfilename)) {
return Result.fail("文件路径为空");
}
i++;
// 4. 拼接真实路径
String filePath = PathUtils.joinFilePath(
fileConfig.getBaseDir(),
mxMapfilename
);
// 5. 删除本地文件
boolean deleted = FileUtils.deleteFileByPath(filePath);
if (!deleted) {
System.out.println("文件删除失败或不存在: " + filePath);
}
// 6. 删除数据库
dbUtils.deleteById("bf_file", "subid", subid);
return Result.ok();
} catch (Exception e) {
e.printStackTrace();
return Result.fail("删除失败: " + e.getMessage());
}
}
}
@@ -68,6 +68,7 @@ public class OcrController {
return Result.ok(result);
} catch (Exception e){
e.printStackTrace();
return Result.fail("OCR 识别失败,请联系管理员");
}
}
@@ -41,67 +41,78 @@ public class OssController {
ValidationUtils.checkRequiredFields(params, "father", "mx_xh", "mx_filename", "mx_filesize", "mx_mapfilename", "mx_moduleid");
String userId = UserContext.get().getUserId();
Object subIdObj = params.get("subid");
long nextId = UniqueId.nextId();
Long subid = null;
params.put("subid", nextId);
if (subIdObj != null) {
subid = Long.valueOf(subIdObj.toString());
}
// 如果 subid 为空,生成新ID
if (subid == null) {
long nextId = UniqueId.nextId();
subid = nextId;
}
params.put("subid", subid);
params.put("mx_inputuser_id",userId);
params.put("mx_inputdatetime", TimeUtils.getDateTime());
dbUtils.insert("bf_file","subid",params);
return Result.ok(nextId);
return Result.ok(subid);
}
@PostMapping("/delete")
@Transactional(rollbackFor = Exception.class)
public Result<Void> deleteFile(@RequestBody Map<String, Object> params) {
ValidationUtils.checkRequiredFields(params, "subid", "tableName", "bucketName");
String tableName = params.get("tableName").toString();
String bucketName = params.get("bucketName").toString();
String id = params.get("subid").toString();
// 查询数据
List<Map<String, Object>> maps = dbUtils.loadData("SELECT * FROM " + tableName + " WHERE subid = ?", id);
// 如果没有数据,直接返回删除成功
if (ObjectUtils.isEmpty(maps)) {
return Result.ok("删除成功");
}
String mxMapfilename = maps.get(0).get("mx_mapfilename").toString();
String orgId = UserContext.get().getOrgId();
int index = mxMapfilename.indexOf(orgId + "/");
// 如果没找到,就转换orgId为大写
if (index < 0) {
orgId = orgId.toUpperCase();
index = mxMapfilename.indexOf(orgId + "/");
}
String objectName = mxMapfilename.substring(index);
// 删除记录
Map<String, Object> deleteParams = new HashMap<>();
deleteParams.put("subid", id);
int deleteCount = dbUtils.delete(tableName, deleteParams);
// 如果删除成功,发送删除请求
if (deleteCount > 0) {
if(StringUtils.isBlank(objectName)){
return Result.fail("删除失败,文件格式有误");
}
String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName);
try {
HttpUtils.get(requestUrl);
} catch (Exception e) {
throw new RuntimeException("删除文件失败",e);
}
}
return Result.ok();
}
// @PostMapping("/delete")
// @Transactional(rollbackFor = Exception.class)
// public Result<Void> deleteFile(@RequestBody Map<String, Object> params) {
// ValidationUtils.checkRequiredFields(params, "subid", "tableName", "bucketName");
//
// String tableName = params.get("tableName").toString();
// String bucketName = params.get("bucketName").toString();
// String id = params.get("subid").toString();
//
// // 查询数据
// List<Map<String, Object>> maps = dbUtils.loadData("SELECT * FROM " + tableName + " WHERE subid = ?", id);
//
// // 如果没有数据,直接返回删除成功
// if (ObjectUtils.isEmpty(maps)) {
// return Result.ok("删除成功");
// }
//
// String mxMapfilename = maps.get(0).get("mx_mapfilename").toString();
// String orgId = UserContext.get().getOrgId();
// int index = mxMapfilename.indexOf(orgId + "/");
//
// // 如果没找到,就转换orgId为大写
// if (index < 0) {
// orgId = orgId.toUpperCase();
// index = mxMapfilename.indexOf(orgId + "/");
// }
//
// String objectName = mxMapfilename.substring(index);
//
// // 删除记录
// Map<String, Object> deleteParams = new HashMap<>();
// deleteParams.put("subid", id);
// int deleteCount = dbUtils.delete(tableName, deleteParams);
//
// // 如果删除成功,发送删除请求
// if (deleteCount > 0) {
// if(StringUtils.isBlank(objectName)){
// return Result.fail("删除失败,文件格式有误");
// }
// String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName);
// try {
// HttpUtils.get(requestUrl);
// } catch (Exception e) {
// throw new RuntimeException("删除文件失败",e);
// }
// }
//
// return Result.ok();
// }
@PostMapping("/deleteAliyunFile")
@Transactional(rollbackFor = Exception.class)
@@ -1,99 +0,0 @@
package cn.g3soft.utils;
import cn.g3soft.config.FileConfig;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* 通用文件上传工具类
* 规则:
* - 文件名保持原始或指定
* - 同一天同箱号重复 -> 自动生成 _1/_2/... 文件夹
* - 使用 FileUtils 拼接路径
*/
@Component
public class FileContainerUtils {
@Resource
private FileConfig fileConfig;
/**
* 单文件上传
*/
public String upload(MultipartFile file, String containerNo, String fileName) throws IOException {
if (file == null || file.isEmpty()) {
throw new RuntimeException("文件不能为空");
}
// 1. 日期路径 yyyy/MM/dd
String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
// 2. 基础目录:上传目录 + 日期
String basePath = PathUtils.joinFilePath(fileConfig.getContainerPath(), datePath);
// 3. 计算最终目录(箱号 + 重复批次)
File finalDir = resolveContainerDir(basePath, containerNo);
if (!finalDir.exists()) {
finalDir.mkdirs();
}
// 4. 最终文件
File dest = new File(finalDir, fileName);
file.transferTo(dest);
// 5. 相对路径,用于生成URL
String relativePath = PathUtils.joinFilePath(
datePath,
finalDir.getName(),
fileName
);
return fileConfig.buildFileUrl(relativePath);
}
/**
* 多文件上传
*/
public List<String> uploadBatch(MultipartFile[] files, String containerNo, String[] fileNames) throws IOException {
if (files == null || files.length == 0) {
return new ArrayList<>();
}
List<String> urls = new ArrayList<>();
for (int i = 0; i < files.length; i++) {
String name = (fileNames != null && i < fileNames.length) ? fileNames[i] : files[i].getOriginalFilename();
urls.add(upload(files[i], containerNo, name));
}
return urls;
}
/**
* 解析容器目录
* - 如果同一天已存在该箱号目录 -> 自动加 _1/_2/... 避免覆盖
*/
private File resolveContainerDir(String basePath, String containerNo) {
File containerDir = new File(PathUtils.joinFilePath(basePath, containerNo));
if (!containerDir.exists()) {
return containerDir;
}
// 已存在 -> 找下一个可用序号
int i = 1;
while (true) {
File dir = new File(PathUtils.joinFilePath(basePath, containerNo + "_" + i));
if (!dir.exists()) {
return dir;
}
i++;
}
}
}
@@ -1,87 +0,0 @@
package cn.g3soft.utils;
import cn.g3soft.config.FileConfig;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
@Component
public class FileUploadUtils {
@Resource
private FileConfig fileConfig;
/**
* 上传文件到临时路径
* @param file 上传的文件
* @param fileName 如果前端传递了文件名,使用传递的文件名
* @return 临时文件的URL
*/
public String uploadTemp(MultipartFile file, String fileName) throws IOException {
// 1. 生成最终文件名
String finalFileName = generateTempFileName(file, fileName);
// 2. 临时目录路径
File tempDir = new File(fileConfig.getTempPath());
// 如果临时目录不存在,创建目录
if (!tempDir.exists()) {
tempDir.mkdirs();
}
// 3. 保存文件到临时目录
File tempFile = new File(tempDir, finalFileName);
file.transferTo(tempFile);
// 4. 返回临时文件的 URL(供 OCR 使用)
return fileConfig.buildTempUrl(finalFileName);
}
/**
* 根据文件名生成临时文件名(带时间戳 + UUID + 文件后缀)
* @param file 上传的文件
* @param fileName 传递的文件名(可选)
* @return 生成的最终文件名
*/
private String generateTempFileName(MultipartFile file, String fileName) {
// 优先使用前端传递的 fileName
String originalFilename = (fileName != null && !fileName.isEmpty())
? fileName
: file.getOriginalFilename();
// 如果没有获取到文件名,设置一个默认文件名
if (originalFilename == null) {
originalFilename = "file";
}
// 获取文件后缀
String extension = "";
if (originalFilename.contains(".")) {
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
// 生成带时间戳和 UUID 的文件名
return LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + "_"
+ UUID.randomUUID().toString().replace("-", "") + extension;
}
/**
* 删除临时文件(成功识别后删除)
* @param tempFileName 临时文件的文件名
*/
public void deleteTempFile(String tempFileName) {
String filePath = PathUtils.joinFilePath(fileConfig.getBaseDir(), fileConfig.getTempDir(), tempFileName);
File tempFile = new File(tempFileName);
if (tempFile.exists()) {
tempFile.delete();
}
}
}
@@ -114,4 +114,63 @@ public class FileUtils {
throw new RuntimeException("上传文件不能为空");
}
}
public static boolean deleteFileByPath(String fullPath) {
if (fullPath == null || fullPath.trim().isEmpty()) {
throw new RuntimeException("路径不能为空");
}
File file = new File(fullPath);
// 防止误删
if (!file.exists()) {
return false;
}
if (!file.isFile()) {
throw new RuntimeException("不是文件: " + fullPath);
}
return file.delete();
}
public static boolean deleteDirectory(String dirPath) {
if (dirPath == null || dirPath.trim().isEmpty()) {
throw new RuntimeException("路径不能为空");
}
File dir = new File(dirPath);
if (!dir.exists()) {
return false;
}
if (!dir.isDirectory()) {
throw new RuntimeException("不是文件夹: " + dirPath);
}
return deleteDirRecursively(dir);
}
private static boolean deleteDirRecursively(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
// 递归删除子目录
deleteDirRecursively(file);
} else {
// 删除文件
if (!file.delete()) {
throw new RuntimeException("文件删除失败: " + file.getAbsolutePath());
}
}
}
}
// 删除当前目录
return dir.delete();
}
}
@@ -14,6 +14,9 @@ public class PathUtils {
part = part.trim();
// 替换 Windows 路径分隔符为 URL 的 '/'
part = part.replace("\\", "/");
if (sb.length() == 0) {
// 第一段:保留协议(http://)
sb.append(part.replaceAll("/+$", ""));