diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/FileConfig.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/FileConfig.java index 3d055876..c003a2e5 100644 --- a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/FileConfig.java +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/FileConfig.java @@ -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); - } - - - } diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/FileController.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/FileController.java index 109c8ca1..f6f483d5 100644 --- a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/FileController.java +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/FileController.java @@ -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> uploadTemp(@RequestParam("file") MultipartFile file, - @RequestParam(value = "fileName") String fileName) { - Map response = new HashMap<>(); - try { - // 调用工具类上传临时文件,返回临时 URL - String tempUrl = fileUploadUtils.uploadTemp(file,fileName); + public Result> 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 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> 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 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 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> 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 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++; + } + } } diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUtils.java index 84494998..217350b9 100644 --- a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUtils.java +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUtils.java @@ -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("上传文件不能为空"); + } + } +} \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/TimeUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/TimeUtils.java index 35c78e89..bec4df4c 100644 --- a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/TimeUtils.java +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/TimeUtils.java @@ -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()) + "/"; + } } diff --git a/code/container-ocr/ocr-springboot/src/main/resources/application.yml b/code/container-ocr/ocr-springboot/src/main/resources/application.yml index 8071580f..d7810e87 100644 --- a/code/container-ocr/ocr-springboot/src/main/resources/application.yml +++ b/code/container-ocr/ocr-springboot/src/main/resources/application.yml @@ -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 \ No newline at end of file