From 9c026f00ad8d01deb80a0505b8465a268a6aa291 Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Sat, 28 Mar 2026 17:22:49 +0800 Subject: [PATCH] u --- code/container-ocr/ocr-springboot/pom.xml | 11 + .../main/java/cn/g3soft/ApiApplication.java | 4 +- .../java/cn/g3soft/config/FileConfig.java | 46 ++ .../java/cn/g3soft/config/WebMvcConfig.java | 22 +- .../cn/g3soft/controller/FileController.java | 135 +--- .../cn/g3soft/controller/OcrController.java | 2 +- .../cn/g3soft/controller/OssController.java | 140 ++++ .../cn/g3soft/schedule/TempFIleSchedule.java | 44 ++ .../cn/g3soft/utils/FileContainerUtils.java | 99 +++ .../java/cn/g3soft/utils/FileUploadUtils.java | 87 +++ .../main/java/cn/g3soft/utils/FileUtils.java | 117 ++++ .../main/java/cn/g3soft/utils/HttpUtils.java | 364 +++------- .../main/java/cn/g3soft/utils/PathUtils.java | 42 ++ .../java/cn/g3soft/utils/ip/AddressUtils.java | 10 +- .../src/main/resources/application.yml | 12 +- .../main/resources/dbconfigs/g3hd.properties | 8 +- .../src/test/java/cn/g3soft/DEMO.java | 84 +++ .../ocr-uniapp/.vscode/settings.json | 1 - code/container-ocr/ocr-uniapp/env/.env | 2 +- code/container-ocr/ocr-uniapp/src/App.vue | 3 +- .../ocr-uniapp/src/pages-sub/upload/index.vue | 328 +++------ .../src/pages-sub/upload/index_old.vue | 627 ++++++++++++++++++ code/container-ocr/ocr-uniapp/src/pages.json | 2 +- .../ocr-uniapp/src/pages/home/index.vue | 337 +++++++--- .../ocr-uniapp/src/request/api.ts | 24 +- .../ocr-uniapp/src/request/index.ts | 457 +++++++------ .../ocr-uniapp/src/static/db.json | 2 +- .../ocr-uniapp/src/static/tabbar/add.png | Bin 6197 -> 6264 bytes .../ocr-uniapp/src/style/iconfont.css | 20 +- .../ocr-uniapp/src/style/index.scss | 2 +- .../ocr-uniapp/src/utils/file.ts | 64 +- .../container-ocr/ocr-uniapp/src/utils/oss.ts | 116 ++++ .../ocr-uniapp/src/utils/platform.ts | 12 +- code/container-ocr/ocr-vue/src/request/api.js | 4 +- .../ocr-vue/src/request/index.js | 35 + .../ocr-vue/src/views/container/index.vue | 3 + 36 files changed, 2230 insertions(+), 1036 deletions(-) create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/FileConfig.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OssController.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/schedule/TempFIleSchedule.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileContainerUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUploadUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/PathUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/test/java/cn/g3soft/DEMO.java create mode 100644 code/container-ocr/ocr-uniapp/src/pages-sub/upload/index_old.vue create mode 100644 code/container-ocr/ocr-uniapp/src/utils/oss.ts diff --git a/code/container-ocr/ocr-springboot/pom.xml b/code/container-ocr/ocr-springboot/pom.xml index a8510ae8..c2b0e033 100644 --- a/code/container-ocr/ocr-springboot/pom.xml +++ b/code/container-ocr/ocr-springboot/pom.xml @@ -83,6 +83,17 @@ oshi-core 6.10.0 + + + com.squareup.okhttp3 + okhttp + + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/ApiApplication.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/ApiApplication.java index bf35d708..dc4f4020 100644 --- a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/ApiApplication.java +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/ApiApplication.java @@ -7,12 +7,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class , }) -@EnableAsync +@EnableAsync // 异步 +@EnableScheduling // 定时任务 public class ApiApplication { public static void main(String[] args){ // 初始化雪花id 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 new file mode 100644 index 00000000..3d055876 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/FileConfig.java @@ -0,0 +1,46 @@ +package cn.g3soft.config; + + +import cn.g3soft.utils.PathUtils; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "file") +public class FileConfig { + private String baseDir; + private String tempDir; + private String containerDir; + + private String staticPath; + private String serverUrl; + + public String getTempPath() { + return PathUtils.joinFilePath(baseDir, 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/config/WebMvcConfig.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java index b7edcd11..be598ebd 100644 --- a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java @@ -3,28 +3,38 @@ package cn.g3soft.config; import cn.g3soft.interceptor.AuthInterceptor; import cn.g3soft.interceptor.DataSourceInterceptor; import cn.g3soft.interceptor.RequestWrapperFilter; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import javax.annotation.Resource; + @Configuration +@Slf4j public class WebMvcConfig implements WebMvcConfigurer { + @Resource + private FileConfig fileConfig; + @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns("/**") .excludePathPatterns( "/auth/login", - "/error" + "/error", + "/static/**" ); registry.addInterceptor(new DataSourceInterceptor()) .addPathPatterns("/**") .excludePathPatterns( "/auth/login", - "/error" + "/error", + "/static/**" ); } @@ -33,7 +43,13 @@ public class WebMvcConfig implements WebMvcConfigurer { FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setFilter(new RequestWrapperFilter()); registration.addUrlPatterns("/*"); - registration.setOrder(1); // 一定要靠前 + registration.setOrder(1); return registration; } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler(fileConfig.getStaticPath() + "/**") + .addResourceLocations("file:" + fileConfig.getUploadDir() + "/"); + } } \ No newline at end of file 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 c5506a9e..109c8ca1 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 @@ -7,8 +7,10 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; 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.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,123 +20,26 @@ import java.util.Map; public class FileController { @Resource private DbUtils dbUtils; + @Resource + private FileUploadUtils fileUploadUtils; - @PostMapping("/getAliyunKey") - public Result> getAliyunKey(@RequestBody Map params) { - ValidationUtils.checkRequiredFields(params, "bucketName"); - - String bucketName = (String) params.get("bucketName"); - - String orgId = UserContext.get().getOrgId(); - - String requestUrl = "http://g3oss.g3soft.cn:8082/upload?orgid=" + orgId + "&bucket=" + bucketName; - String reqResult = HttpUtils.sendGet(requestUrl); - - Map objectMap = JsonUtils.toMap(reqResult); - - return Result.ok(objectMap); - } - - @PostMapping("/insert") - @Transactional(rollbackFor = Exception.class) - public Result insertFile(@RequestBody Map params) { - ValidationUtils.checkRequiredFields(params, "father", "mx_xh", "mx_filename", "mx_filesize", "mx_mapfilename", "mx_moduleid"); - - String userId = UserContext.get().getUserId(); - - long nextId = UniqueId.nextId(); - - params.put("subid", nextId); - params.put("mx_inputuser_id",userId); - params.put("mx_inputdatetime", TimeUtils.getDateTime()); - - dbUtils.insert("bf_file","subid",params); - - return Result.ok(nextId); - } - - @PostMapping("/delete") - @Transactional(rollbackFor = Exception.class) - public Result deleteFile(@RequestBody Map 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> 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 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.sendGet(requestUrl); - } catch (Exception e) { - throw new RuntimeException("删除文件失败",e); - } - } - - return Result.ok(); - } - - @PostMapping("/deleteAliyunFile") - @Transactional(rollbackFor = Exception.class) - public Result deleteAliyunFile(@RequestBody Map params) { - ValidationUtils.checkRequiredFields(params, "bucketName", "mx_mapfilename"); - - String bucketName = params.get("bucketName").toString(); - String mxMapfilename = params.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); - if(StringUtils.isBlank(objectName)){ - return Result.fail("删除失败,文件格式有误"); - } - - // 发送删除请求 - String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName); - - System.out.println(requestUrl); - + /** + * 上传临时文件接口 + */ + @PostMapping("/uploadTemp") + public Result> uploadTemp(@RequestParam("file") MultipartFile file, + @RequestParam(value = "fileName") String fileName) { + Map response = new HashMap<>(); try { - HttpUtils.sendGet(requestUrl); - } catch (Exception e) { - throw new RuntimeException("删除文件失败",e); - } + // 调用工具类上传临时文件,返回临时 URL + String tempUrl = fileUploadUtils.uploadTemp(file,fileName); - return Result.ok(); + // 返回临时文件 URL + response.put("tempUrl", tempUrl); + return Result.ok(response); + } catch (IOException e) { + return Result.fail("文件上传失败:" + e.getMessage()); + } } + } diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OcrController.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OcrController.java index cc4ec33f..6a1caf7d 100644 --- a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OcrController.java +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OcrController.java @@ -33,7 +33,7 @@ public class OcrController { reqPayload.put("visualize", false); try { - String response = HttpUtils.sendPostJson(requestUrl, reqPayload); + String response = HttpUtils.postJson(requestUrl, reqPayload); ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(response); diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OssController.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OssController.java new file mode 100644 index 00000000..f0c91fa6 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OssController.java @@ -0,0 +1,140 @@ +package cn.g3soft.controller; + +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.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/file") +public class OssController { + @Resource + private DbUtils dbUtils; + + @PostMapping("/getAliyunKey") + public Result> getAliyunKey(@RequestBody Map params) throws Exception { + ValidationUtils.checkRequiredFields(params, "bucketName"); + + String bucketName = (String) params.get("bucketName"); + + String orgId = UserContext.get().getOrgId(); + + String requestUrl = "http://g3oss.g3soft.cn:8082/upload?orgid=" + orgId + "&bucket=" + bucketName; + String reqResult = HttpUtils.get(requestUrl); + + Map objectMap = JsonUtils.toMap(reqResult); + + return Result.ok(objectMap); + } + + @PostMapping("/insert") + @Transactional(rollbackFor = Exception.class) + public Result insertFile(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "father", "mx_xh", "mx_filename", "mx_filesize", "mx_mapfilename", "mx_moduleid"); + + String userId = UserContext.get().getUserId(); + + long nextId = UniqueId.nextId(); + + params.put("subid", nextId); + params.put("mx_inputuser_id",userId); + params.put("mx_inputdatetime", TimeUtils.getDateTime()); + + dbUtils.insert("bf_file","subid",params); + + return Result.ok(nextId); + } + + @PostMapping("/delete") + @Transactional(rollbackFor = Exception.class) + public Result deleteFile(@RequestBody Map 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> 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 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) + public Result deleteAliyunFile(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "bucketName", "mx_mapfilename"); + + String bucketName = params.get("bucketName").toString(); + String mxMapfilename = params.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); + if(StringUtils.isBlank(objectName)){ + return Result.fail("删除失败,文件格式有误"); + } + + // 发送删除请求 + String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName); + + System.out.println(requestUrl); + + try { + HttpUtils.get(requestUrl); + } catch (Exception e) { + throw new RuntimeException("删除文件失败",e); + } + + return Result.ok(); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/schedule/TempFIleSchedule.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/schedule/TempFIleSchedule.java new file mode 100644 index 00000000..ed238e5d --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/schedule/TempFIleSchedule.java @@ -0,0 +1,44 @@ +package cn.g3soft.schedule; + +import cn.g3soft.config.FileConfig; +import cn.g3soft.utils.PathUtils; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.File; + +@Component +public class TempFIleSchedule { + @Resource + private FileConfig fileConfig; + + /** + * 定时清理临时文件(临时文件超过1天后清理) + */ + @Scheduled(cron = "0 0 3 * * ?") // 每天3点清理 + public void cleanTempFiles() { + File tempDir = new File(PathUtils.joinFilePath(fileConfig.getTempPath())); + if (tempDir.exists()) { + File[] files = tempDir.listFiles(); + if (files != null) { + for (File file : files) { + if (isFileExpired(file)) { + file.delete(); + } + } + } + } + } + + /** + * 判断文件是否过期(假设超过1天的文件删除) + * @param file 文件 + * @return 是否过期 + */ + private boolean isFileExpired(File file) { + long lastModified = file.lastModified(); + long now = System.currentTimeMillis(); + return now - lastModified > 86400000L; // 1天的毫秒数 + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileContainerUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileContainerUtils.java new file mode 100644 index 00000000..811f0c0e --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileContainerUtils.java @@ -0,0 +1,99 @@ +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 uploadBatch(MultipartFile[] files, String containerNo, String[] fileNames) throws IOException { + if (files == null || files.length == 0) { + return new ArrayList<>(); + } + List 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++; + } + } +} \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUploadUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUploadUtils.java new file mode 100644 index 00000000..c51b2182 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUploadUtils.java @@ -0,0 +1,87 @@ +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(); + } + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..84494998 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/FileUtils.java @@ -0,0 +1,117 @@ +package cn.g3soft.utils; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +public class FileUtils { + + /** + * ========================= + * 最简调用:自动生成文件名 + 不覆盖 + * ========================= + */ + public static String upload(MultipartFile file, String uploadPath) throws IOException { + return upload(file, uploadPath, null, false); + } + + /** + * ========================= + * 指定文件名 + 不覆盖 + * ========================= + */ + 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); + } + } + + // 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; + } + + // 7. 保存文件 + file.transferTo(dest); + + // 8. 返回文件路径(你也可以改成返回 URL) + return dest.getAbsolutePath(); + } + + /** + * ========================= + * 生成唯一文件名 + * ========================= + */ + private static String generateFileName(String originalName) { + return UUID.randomUUID().toString().replace("-", "") + + getSuffix(originalName); + } + + /** + * ========================= + * 获取文件后缀 + * ========================= + */ + private static String getSuffix(String fileName) { + if (fileName == null || !fileName.contains(".")) { + return ""; + } + return fileName.substring(fileName.lastIndexOf(".")); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/HttpUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/HttpUtils.java index 11396bf9..f0188159 100644 --- a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/HttpUtils.java +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/HttpUtils.java @@ -1,29 +1,12 @@ package cn.g3soft.utils; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.net.ConnectException; -import java.net.SocketTimeoutException; -import java.net.URL; -import java.net.URLConnection; -import java.nio.charset.StandardCharsets; -import java.security.cert.X509Certificate; + import java.util.Map; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; +import java.util.Objects; +import java.util.concurrent.TimeUnit; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.MediaType; +import okhttp3.*; /** * 通用http发送方法 @@ -32,287 +15,92 @@ import org.springframework.http.MediaType; */ public class HttpUtils { - private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + private static final ObjectMapper MAPPER = new ObjectMapper(); - /** - * 向指定 URL 发送GET方法的请求 - * - * @param url 发送请求的 URL - * @return 所代表远程资源的响应结果 - */ - public static String sendGet(String url) - { - return sendGet(url, StringUtils.EMPTY); + private static final OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(5, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES)) + .build(); + + private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); + + // ==================== GET ==================== + public static String get(String url) throws Exception { + Request request = new Request.Builder() + .url(url) + .get() + .build(); + + try (Response response = client.newCall(request).execute()) { + return response.body() != null ? response.body().string() : null; + } } - /** - * 向指定 URL 发送GET方法的请求 - * - * @param url 发送请求的 URL - * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 - * @return 所代表远程资源的响应结果 - */ - public static String sendGet(String url, String param) - { - return sendGet(url, param, "UTF-8"); - } + public static String get(String url, Map params, String contentType) throws Exception { + HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder(); - /** - * 向指定 URL 发送GET方法的请求 - * - * @param url 发送请求的 URL - * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 - * @param contentType 编码类型 - * @return 所代表远程资源的响应结果 - */ - public static String sendGet(String url, String param, String contentType) - { - StringBuilder result = new StringBuilder(); - BufferedReader in = null; - try - { - String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; - log.info("sendGet - {}", urlNameString); - URL realUrl = new URL(urlNameString); - URLConnection connection = realUrl.openConnection(); - connection.setRequestProperty("accept", "*/*"); - connection.setRequestProperty("connection", "Keep-Alive"); - connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); - connection.connect(); - in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); - String line; - while ((line = in.readLine()) != null) - { - result.append(line); - } - log.info("recv - {}", result); - } - catch (ConnectException e) - { - log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); - } - catch (SocketTimeoutException e) - { - log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); - } - catch (IOException e) - { - log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); - } - catch (Exception e) - { - log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); - } - finally - { - try - { - if (in != null) - { - in.close(); + if (params != null) { + for (Map.Entry entry : params.entrySet()) { + if (entry.getValue() != null) { + urlBuilder.addQueryParameter( + entry.getKey(), + String.valueOf(entry.getValue()) + ); } } - catch (Exception ex) - { - log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); - } - } - return result.toString(); - } - - /** - * 向指定 URL 发送POST方法的请求 - * - * @param url 发送请求的 URL - * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 - * @return 所代表远程资源的响应结果 - */ - public static String sendPost(String url, String param) - { - return sendPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE); - } - - /** - * 向指定 URL 发送POST方法的请求 - * - * @param url 发送请求的 URL - * @param param 请求参数 - * @param contentType 内容类型 - * @return 所代表远程资源的响应结果 - */ - public static String sendPost(String url, String param, String contentType) - { - PrintWriter out = null; - BufferedReader in = null; - StringBuilder result = new StringBuilder(); - try - { - log.info("sendPost - {}", url); - URL realUrl = new URL(url); - URLConnection conn = realUrl.openConnection(); - conn.setRequestProperty("accept", "*/*"); - conn.setRequestProperty("connection", "Keep-Alive"); - conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); - conn.setRequestProperty("Accept-Charset", "utf-8"); - conn.setRequestProperty("Content-Type", contentType); - conn.setDoOutput(true); - conn.setDoInput(true); - out = new PrintWriter(conn.getOutputStream()); - out.print(param); - out.flush(); - in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); - String line; - while ((line = in.readLine()) != null) - { - result.append(line); - } - log.info("recv - {}", result); - } - catch (ConnectException e) - { - log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); - } - catch (SocketTimeoutException e) - { - log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); - } - catch (IOException e) - { - log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); - } - catch (Exception e) - { - log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); - } - finally - { - try - { - if (out != null) - { - out.close(); - } - if (in != null) - { - in.close(); - } - } - catch (IOException ex) - { - log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); - } - } - return result.toString(); - } - - /** - * 向指定 URL 发送 POST 请求,内容类型为 JSON - * - * @param url 请求的 URL - * @param reqPayload 请求参数,Map 类型 - * @return 响应结果 - */ - public static String sendPostJson(String url, Map reqPayload) { - // 创建一个 ObjectMapper 实例,用于将 Map 转换为 JSON 字符串 - ObjectMapper objectMapper = new ObjectMapper(); - String jsonParam = null; - try { - // 将 Map 转换为 JSON 字符串 - jsonParam = objectMapper.writeValueAsString(reqPayload); - } catch (Exception e) { - log.error("请求参数转换为 JSON 失败,url=" + url, e); - return null; } - // 调用 sendPost 方法,发送请求并返回响应 - return sendPost(url, jsonParam, "application/json"); - } + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .get(); - public static String sendSSLPost(String url, String param) - { - return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE); - } - - public static String sendSSLPost(String url, String param, String contentType) - { - StringBuilder result = new StringBuilder(); - String urlNameString = url + "?" + param; - try - { - log.info("sendSSLPost - {}", urlNameString); - SSLContext sc = SSLContext.getInstance("SSL"); - sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); - URL console = new URL(urlNameString); - HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); - conn.setRequestProperty("accept", "*/*"); - conn.setRequestProperty("connection", "Keep-Alive"); - conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); - conn.setRequestProperty("Accept-Charset", "utf-8"); - conn.setRequestProperty("Content-Type", contentType); - conn.setDoOutput(true); - conn.setDoInput(true); - - conn.setSSLSocketFactory(sc.getSocketFactory()); - conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); - conn.connect(); - InputStream is = conn.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String ret = ""; - while ((ret = br.readLine()) != null) - { - if (ret != null && !"".equals(ret.trim())) - { - result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); - } - } - log.info("recv - {}", result); - conn.disconnect(); - br.close(); - } - catch (ConnectException e) - { - log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); - } - catch (SocketTimeoutException e) - { - log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); - } - catch (IOException e) - { - log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); - } - catch (Exception e) - { - log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); - } - return result.toString(); - } - - private static class TrustAnyTrustManager implements X509TrustManager - { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) - { + if (contentType != null && !contentType.isEmpty()) { + builder.addHeader("Content-Type", contentType); } - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) - { - } - - @Override - public X509Certificate[] getAcceptedIssuers() - { - return new X509Certificate[] {}; + try (Response response = client.newCall(builder.build()).execute()) { + return response.body() != null ? response.body().string() : null; } } - private static class TrustAnyHostnameVerifier implements HostnameVerifier - { - @Override - public boolean verify(String hostname, SSLSession session) - { - return true; + // ==================== POST FORM ==================== + public static String postForm(String url, Map params) throws Exception { + FormBody.Builder builder = new FormBody.Builder(); + + for (Map.Entry entry : params.entrySet()) { + builder.add(entry.getKey(), entry.getValue()); + } + + Request request = new Request.Builder() + .url(url) + .post(builder.build()) + .build(); + + try (Response response = client.newCall(request).execute()) { + return response.body() != null ? response.body().string() : null; + } + } + + // ==================== POST JSON ==================== + // ==================== POST JSON(Map版本)==================== + public static String postJson(String url, Map params) throws Exception { + + // 1. Map 转 JSON 字符串 + String json = MAPPER.writeValueAsString(params); + + // 2. 构建请求体 + RequestBody body = RequestBody.create(json, JSON); + + Request request = new Request.Builder() + .url(url) + .post(body) + .build(); + + // 3. 执行请求 + try (Response response = client.newCall(request).execute()) { + return response.body() != null ? response.body().string() : null; } } } \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/PathUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/PathUtils.java new file mode 100644 index 00000000..f8c5010b --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/PathUtils.java @@ -0,0 +1,42 @@ +package cn.g3soft.utils; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class PathUtils { + public static String joinUrl(String... parts) { + if (parts == null || parts.length == 0) return ""; + + StringBuilder sb = new StringBuilder(); + + for (String part : parts) { + if (part == null || part.trim().isEmpty()) continue; + + part = part.trim(); + + if (sb.length() == 0) { + // 第一段:保留协议(http://) + sb.append(part.replaceAll("/+$", "")); + } else { + sb.append("/"); + sb.append(part.replaceAll("^/+", "").replaceAll("/+$", "")); + } + } + + return sb.toString(); + } + + public static String joinFilePath(String... paths) { + if (paths == null || paths.length == 0) return ""; + Path result = null; + for (String p : paths) { + if (p == null || p.trim().isEmpty()) continue; + if (result == null) { + result = Paths.get(p.trim()); + } else { + result = result.resolve(p.trim()); + } + } + return (result == null) ? "" : result.normalize().toString(); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/ip/AddressUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/ip/AddressUtils.java index 6f5020ca..acefe9c8 100644 --- a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/ip/AddressUtils.java +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/ip/AddressUtils.java @@ -7,6 +7,9 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; +import java.util.Map; + public class AddressUtils { private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); @@ -28,7 +31,12 @@ public class AddressUtils try { - String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", "GBK"); + + Map params = new HashMap<>(); + params.put("ip",ip); + params.put("json",true); + + String rspStr = HttpUtils.get(IP_URL, params, "GBK"); if (StringUtils.isEmpty(rspStr)) { log.error("获取地理位置异常 {}", ip); 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 0281b373..8071580f 100644 --- a/code/container-ocr/ocr-springboot/src/main/resources/application.yml +++ b/code/container-ocr/ocr-springboot/src/main/resources/application.yml @@ -1,6 +1,6 @@ server: servlet: - context-path: /api # 应用的访问路径 + context-path: /api # 应用的访问路径 tomcat: uri-encoding: UTF-8 # URI编码 accept-count: 1000 # 连接数满后的排队数 @@ -16,4 +16,12 @@ spring: max-request-size: -1 jackson: time-zone: "Asia/Shanghai" - date-format: yyyy-MM-dd HH:mm:ss \ No newline at end of file + date-format: yyyy-MM-dd HH:mm:ss + +file: + static-path: /static/file + server-url: http://192.168.1.137:80/api + + base-dir: D:/data + container-dir: container + temp-dir: temp diff --git a/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties b/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties index 11509013..6aa855da 100644 --- a/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties +++ b/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties @@ -3,8 +3,8 @@ username=sa password=zhang520.. driver=com.microsoft.sqlserver.jdbc.SQLServerDriver +# url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3BoxOCR +# username=sa +# password=123456@aaa +# driver=com.microsoft.sqlserver.jdbc.SQLServerDriver -#url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3BoxOCR -#username=sa -#password=123456@aaa -#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/test/java/cn/g3soft/DEMO.java b/code/container-ocr/ocr-springboot/src/test/java/cn/g3soft/DEMO.java new file mode 100644 index 00000000..1105a726 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/test/java/cn/g3soft/DEMO.java @@ -0,0 +1,84 @@ +package cn.g3soft; + + +import cn.g3soft.utils.HttpUtils; +import cn.g3soft.utils.Result; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +public class DEMO { + @Test + public void testHello() throws Exception { + long startTime = System.nanoTime(); // 开始时间 + + String filePath = "D:\\100全关门-3\\20241005082220A664.jpg"; // 你的图片路径 + + byte[] fileBytes = Files.readAllBytes(Paths.get(filePath)); + +// String fileUrl = Base64.getEncoder().encodeToString(fileBytes); + + String fileUrl = "http://g3hd.oss-cn-hangzhou.aliyuncs.com/g3hd/2026/03/28/1774665878210_960.jpg"; + + String requestUrl = "http://127.0.0.1:8867/ocr"; + + Map reqPayload = new HashMap<>(); + reqPayload.put("file", fileUrl); + reqPayload.put("fileType", 1); + reqPayload.put("visualize", false); + + try { + String response = HttpUtils.postJson(requestUrl, reqPayload); + + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(response); + + JsonNode ocrResults = root.path("result").path("ocrResults"); + if (!ocrResults.isArray() || ocrResults.isEmpty()) { + System.out.println("空"); + } + + String number = ""; + double maxScore = -1.0; + + for (JsonNode item : ocrResults) { + JsonNode pruned = item.path("prunedResult"); + JsonNode recTexts = pruned.path("rec_texts"); + JsonNode recScores = pruned.path("rec_scores"); + + for (int i = 0; i < recTexts.size(); i++) { + double score = recScores.get(i).asDouble(); + if (score > maxScore) { + maxScore = score; + number = recTexts.get(i).asText(); + } + } + } + + Map result = new HashMap<>(); + result.put("boxNumber", number); + result.put("score", maxScore); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + long endTime = System.nanoTime(); // 结束时间 + long durationMs = (endTime - startTime) / 1_000_000; + + System.out.println("耗时: " + durationMs + " ms"); + } + } +} \ No newline at end of file diff --git a/code/container-ocr/ocr-uniapp/.vscode/settings.json b/code/container-ocr/ocr-uniapp/.vscode/settings.json index 4472e953..e3dd1583 100644 --- a/code/container-ocr/ocr-uniapp/.vscode/settings.json +++ b/code/container-ocr/ocr-uniapp/.vscode/settings.json @@ -1,5 +1,4 @@ { - "editor.cursorSmoothCaretAnimation": "on", // Disable the default formatter, use eslint instead "prettier.enable": false, "editor.formatOnSave": false, diff --git a/code/container-ocr/ocr-uniapp/env/.env b/code/container-ocr/ocr-uniapp/env/.env index 24a2090a..f56bdca3 100644 --- a/code/container-ocr/ocr-uniapp/env/.env +++ b/code/container-ocr/ocr-uniapp/env/.env @@ -1,3 +1,3 @@ -VITE_APP_PORT = 8081 +VITE_APP_PORT = 8080 VITE_ALIYUN_BUCKET = 'g3hd' \ No newline at end of file diff --git a/code/container-ocr/ocr-uniapp/src/App.vue b/code/container-ocr/ocr-uniapp/src/App.vue index 5951a969..89a6eec4 100644 --- a/code/container-ocr/ocr-uniapp/src/App.vue +++ b/code/container-ocr/ocr-uniapp/src/App.vue @@ -1,6 +1,7 @@ + + + + diff --git a/code/container-ocr/ocr-uniapp/src/pages.json b/code/container-ocr/ocr-uniapp/src/pages.json index 3a4c703f..990e9357 100644 --- a/code/container-ocr/ocr-uniapp/src/pages.json +++ b/code/container-ocr/ocr-uniapp/src/pages.json @@ -60,7 +60,7 @@ "layout": "default", "style": { "navigationBarTitleText": "个人中心", - "navigationStyle": "default" + "navigationStyle": "custom" } } ], diff --git a/code/container-ocr/ocr-uniapp/src/pages/home/index.vue b/code/container-ocr/ocr-uniapp/src/pages/home/index.vue index aee4e93a..f3fab9f3 100644 --- a/code/container-ocr/ocr-uniapp/src/pages/home/index.vue +++ b/code/container-ocr/ocr-uniapp/src/pages/home/index.vue @@ -1,152 +1,269 @@ - diff --git a/code/container-ocr/ocr-uniapp/src/request/api.ts b/code/container-ocr/ocr-uniapp/src/request/api.ts index 5aa198f7..414533a5 100644 --- a/code/container-ocr/ocr-uniapp/src/request/api.ts +++ b/code/container-ocr/ocr-uniapp/src/request/api.ts @@ -1,4 +1,4 @@ -import { httpPost } from './index' +import { httpPost, httpUpload } from './index' const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET @@ -16,7 +16,7 @@ interface GetaliyunkeyParams { bucketName: string } -export function getaliyunkeyApi(params: GetaliyunkeyParams) { +export function getAliyunkeyApi(params: GetaliyunkeyParams) { return httpPost('/file/getAliyunKey', params) } @@ -92,3 +92,23 @@ interface UpdatePasswordParams { export function updatePasswordApi(params: UpdatePasswordParams) { return httpPost(`/auth/updatePassword`, params) } + +interface PageParams { + page: number + size: number + viewName: string + searchCondition?: string + orderField?: string + searchColumns?: string[] +} + +export function loadPageDataApi(params: PageParams) { + return httpPost( + '/data/page', + params, + ) +} + +export function uploadTempFileApi(filePath: string, fileName: string) { + return httpUpload('/file/uploadTemp', filePath, fileName) +} diff --git a/code/container-ocr/ocr-uniapp/src/request/index.ts b/code/container-ocr/ocr-uniapp/src/request/index.ts index 323d6e3f..44426d4c 100644 --- a/code/container-ocr/ocr-uniapp/src/request/index.ts +++ b/code/container-ocr/ocr-uniapp/src/request/index.ts @@ -5,174 +5,260 @@ import { useUserStore } from '@/store' import { joinUrl } from '@/utils/common' import { isH5 } from '@/utils/platform' -function request(option: UniApp.RequestOptions): Promise> & { abort: () => void } { +/** + * ====================== + * 读取机构配置 + * ====================== + */ +function readDbJson(): Promise> { + return new Promise((resolve, reject) => { + if (isH5) { + const timestamp = Date.now() + uni.request({ + url: `/g3ocr-app/static/db.json?t=${timestamp}`, + method: 'GET', + success: r => resolve(r.data as Record), + fail: err => reject(err), + }) + } + else { + resolve(dbJson as Record) + } + }) +} + +/** + * ====================== + * 获取 baseUrl + headers + * ====================== + */ +async function getBaseConfig(option: UniApp.RequestOptions, isLogin: boolean) { + const dbRes = await readDbJson() + + const userStore = useUserStore() + const { token, userid, orgid } = userStore.userInfo || {} + + if (!dbRes || Object.keys(dbRes).length === 0) { + throw new Error('机构配置获取失败') + } + + let reqUrl = '' + + if (isLogin) { + const dataObj: any = typeof option.data === 'string' + ? parseFormUrlencoded(option.data) + : option.data || {} + + reqUrl = dbRes[dataObj.orgid || ''] + } + else { + reqUrl = dbRes[orgid || ''] + } + + if (!reqUrl) { + handleInvalidOrg() + throw new Error('机构码错误') + } + + const headers: Record = { ...(option.header || {}) } + + if (!isLogin) { + headers.Authorization = `Bearer ${token}` + headers.userid = encodeURIComponent(userid) + headers.orgid = orgid + } + + return { + baseUrl: reqUrl, + headers, + } +} + +/** + * ====================== + * 统一错误处理 + * ====================== + */ +function handleInvalidOrg() { + const pages = getCurrentPages() + const currentPage = pages[pages.length - 1] + const currentRoute = `/${currentPage.route}` + + if (currentRoute !== '/pages-sub/login/index') { + uni.showToast({ title: '当前登录已过期,请重新登录', icon: 'none' }) + uni.reLaunch({ url: '/pages-sub/login/index' }) + } + else { + uni.showToast({ title: '机构码错误', icon: 'none' }) + } +} + +/** + * ====================== + * 统一响应处理 + * ====================== + */ +function handleResponse( + res: any, + resolve: (data: IResData) => void, + reject: (err: any) => void, + aborted: boolean, +) { + if (aborted) + return reject(new Error('请求已取消')) + + if (res.statusCode !== 200) { + toast.fail(`请求失败:${res.statusCode}`) + return reject(new Error(`HTTP错误: ${res.statusCode}`)) + } + + let data: IResData + + try { + data = typeof res.data === 'string' + ? JSON.parse(res.data) + : res.data + + if (data.data && typeof data.data === 'string') { + try { + data.data = JSON.parse(data.data) + } + catch {} + } + } + catch { + toast.fail('响应解析失败') + return reject(new Error('响应解析失败')) + } + + // 401 优先处理(避免被后面拦截) + if (data.code === 401) { + uni.showToast({ title: '请重新登录', icon: 'none' }) + handleInvalidOrg() + return reject(new Error('未授权')) + } + + if (data.code === 200) { + data.success = true + resolve(data) + } + else { + toast.fail(data.message || '请求失败') + reject(new Error(data.message || '请求失败')) + } +} + +/** + * ====================== + * 主请求方法 + * ====================== + */ +function request( + option: UniApp.RequestOptions, +): Promise> & { abort: () => void } { let task: UniApp.RequestTask | undefined let aborted = false - const promise = new Promise>((resolve, reject) => { - // 判断是否是登录请求 + const promise = (async () => { const isLogin = option.url?.includes('login') - const readDbJson = new Promise>((res, rej) => { - if (isH5) { - // H5:用 request 请求,带时间戳保证最新 - const timestamp = Date.now() - uni.request({ - url: `/g3ocr-app/static/db.json?t=${timestamp}`, - method: 'GET', - success: r => res(r.data as Record), - fail: err => rej(err), - }) - } - else { - // 非 H5 直接返回本地导入的 JSON - res(dbJson) - } + const { baseUrl, headers } = await getBaseConfig(option, isLogin) + + option.url = joinUrl(baseUrl, option.url || '') + option.header = headers + + return new Promise>((resolve, reject) => { + task = uni.request({ + ...option, + success: res => + handleResponse(res, resolve, reject, aborted), + fail: (err) => { + if (aborted) + return reject(new Error('请求已取消')) + toast.fail('网络错误') + reject(err) + }, + }) }) + })() - readDbJson - .then((dbRes) => { - if (aborted) - return reject(new Error('请求已取消')) + const finalPromise = promise as Promise> & { abort: () => void } - // 组织请求 URL - let reqUrl = '' - const userStore = useUserStore() - const { token, userid, orgid } = userStore.userInfo || {} - - if (!dbRes || Object.keys(dbRes).length === 0) { - toast.fail('机构配置获取失败,请联系管理人员!') - return reject(new Error('机构配置获取失败,请联系管理人员!')) - } - - if (isLogin) { - const dataObj: any = typeof option.data === 'string' - ? parseFormUrlencoded(option.data) - : option.data || {} - const orgidFromData = dataObj.orgid || '' - - reqUrl = dbRes[orgidFromData] - } - else { - reqUrl = dbRes[orgid || ''] - } - - if (!reqUrl) { - const pages = getCurrentPages() - const currentPage = pages[pages.length - 1] - const currentRoute = `/${currentPage.route}` - - if (currentRoute !== '/pages-sub/login/index') { - uni.showToast({ title: '当前登录已过期,请重新登录', icon: 'error' }) - - uni.reLaunch({ - url: '/pages-sub/login/index', - }) - } - else { - uni.showToast({ title: '机构码错误', icon: 'error' }) - } - - return reject(new Error('机构码错误')) - } - - // 设置 headers - const headers: Record = { ...(option.header || {}) } - if (!isLogin) { - headers.Authorization = `Bearer ${token}` - headers.userid = encodeURIComponent(userid) - headers.orgid = orgid - } - - // 拼接完整 URL - option.url = joinUrl(reqUrl, option.url || '') - option.header = headers - - // 发请求 - task = uni.request({ - ...option, - url: option.url, - success: (res) => { - if (aborted) - return reject(new Error('请求已取消')) - - if (res.statusCode !== 200) { - console.error('HTTP错误', res.statusCode, res.data) - toast.fail(`请求失败:${res.statusCode}`) - return reject(new Error(`HTTP错误: ${res.statusCode}`)) - } - - let data: IResData - - try { - // 解析最外层响应 - data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data - - // 如果 data.data 是字符串,则解析成对象 - if (data.data && typeof data.data === 'string') { - try { - data.data = JSON.parse(data.data) - } - catch { - // 如果解析失败,保持原始字符串即可 - // console.warn('data.data 不是合法 JSON 字符串', data.data) - } - } - } - catch (err) { - console.log(`解析响应JSON失败:${err}`) - toast.fail('响应数据解析失败') - return reject(new Error('响应数据解析失败')) - } - - if (data.code === 200) { - data.success = true - - resolve(data) - } - else { - toast.fail(data.message || '请求错误') - reject(new Error(data.message || '请求错误')) - } - - // 登录失效处理 - if (data.code === 401) { - uni.showToast({ title: '请重新登录', icon: 'none' }) - - const pages = getCurrentPages() - const currentPage = pages[pages.length - 1] - const currentRoute = `/${currentPage.route}` - if (currentRoute !== '/pages-sub/login/index') { - uni.reLaunch({ - url: '/pages-sub/login/index', - }) - } - } - }, - fail: (err) => { - if (aborted) - return reject(new Error('请求已取消')) - toast.fail('网络请求失败,请联系管理人员') - - reject(new Error(err.errMsg || '网络请求失败')) - }, - }) - }) - .catch((err) => { - toast.fail('机构配置获取失败,请联系管理人员!') - console.error(err) - reject(new Error('机构配置获取失败,请联系管理人员!')) - }) - }) as Promise> & { abort: () => void } - - promise.abort = () => { + finalPromise.abort = () => { aborted = true task?.abort() } - return promise + return finalPromise } +/** + * ====================== + * 上传文件 + * ====================== + */ +export function httpUpload( + url: string, + filePath: string, + fileName: string, + formData?: Record, + option?: UniApp.UploadFileOption, +) { + let task: UniApp.UploadTask | undefined + let aborted = false + + const promise = (async () => { + const isLogin = url.includes('login') + + const { baseUrl, headers } = await getBaseConfig( + { url, data: formData } as any, + isLogin, + ) + + const fullUrl = joinUrl(baseUrl, url) + + const mergedFormData = { + ...formData, + fileName, + } + + return new Promise>((resolve, reject) => { + task = uni.uploadFile({ + url: fullUrl, + filePath, + name: 'file', + formData: mergedFormData, + header: { + ...headers, + ...(option?.header || {}), + }, + success: res => + handleResponse(res, resolve, reject, aborted), + fail: (err) => { + if (aborted) + return reject(new Error('请求已取消')) + toast.fail('网络错误') + reject(err) + }, + ...option, + }) + }) + })() + + const finalPromise = promise as Promise> & { abort: () => void } + + finalPromise.abort = () => { + aborted = true + task?.abort() + } + + return finalPromise +} +/** + * ====================== + * 工具函数 + * ====================== + */ function parseFormUrlencoded(str: string) { return str.split('&').reduce((acc, pair) => { const [key, value] = pair.split('=') @@ -181,39 +267,16 @@ function parseFormUrlencoded(str: string) { }, {} as Record) } -// GET -request.get = (url: string, data?: any, option?: UniApp.RequestOptions) => { - return request({ - method: 'GET', - url, - data, - ...option, - }) -} +/** + * ====================== + * 方法封装 + * ====================== + */ +request.get = (url: string, data?: any, option?: UniApp.RequestOptions) => + request({ method: 'GET', url, data, ...option }) -// PUT -request.put = (url: string, data?: any, option?: UniApp.RequestOptions) => { - return request({ - method: 'PUT', - url, - data, - ...option, - }) -} - -// DELETE -request.delete = (url: string, data?: any, option?: UniApp.RequestOptions) => { - return request({ - method: 'DELETE', - url, - data, - ...option, - }) -} - -// POST JSON -request.postJson = (url: string, data?: any, option?: UniApp.RequestOptions) => { - return request({ +request.postJson = (url: string, data?: any, option?: UniApp.RequestOptions) => + request({ method: 'POST', url, data, @@ -223,17 +286,10 @@ request.postJson = (url: string, data?: any, option?: UniApp.RequestOpt }, ...option, }) -} - -// POST 表单 request.postForm = (url: string, data?: any, option?: UniApp.RequestOptions) => { - let payload: string - if (data) { - // 使用 qs.stringify 或 URLSearchParams - payload = qs.stringify(data, { arrayFormat: 'repeat' }) - // 生成 tables=val1&tables=val2 格式 - } + const payload = data ? qs.stringify(data, { arrayFormat: 'repeat' }) : undefined + return request({ method: 'POST', url, @@ -247,16 +303,7 @@ request.postForm = (url: string, data?: any, option?: UniApp.RequestOpt } export function httpPost(url: string, data?: any, option?: UniApp.RequestOptions) { - return request({ - method: 'POST', - url, - data, - header: { - 'Content-Type': 'application/json', - ...(option?.header || {}), - }, - ...option, - }) + return request.postJson(url, data, option) } export default request diff --git a/code/container-ocr/ocr-uniapp/src/static/db.json b/code/container-ocr/ocr-uniapp/src/static/db.json index a3df036c..945efbcd 100644 --- a/code/container-ocr/ocr-uniapp/src/static/db.json +++ b/code/container-ocr/ocr-uniapp/src/static/db.json @@ -1,3 +1,3 @@ { - "g3hd": "http://192.168.5.6:80/api" + "g3hd": "http://192.168.1.137:80/api" } diff --git a/code/container-ocr/ocr-uniapp/src/static/tabbar/add.png b/code/container-ocr/ocr-uniapp/src/static/tabbar/add.png index cb9fa28490695d9a5c6e117a8d9d8a37bed87d9d..f21921e9bf9d44f4e3a9770c46dd66867dc5c8ed 100644 GIT binary patch literal 6264 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 + * 上传文件到阿里云 OSS(优化版) */ -export async function uploadFileToAliyun( - filePath: string, - fileName: string, - fileType: any, -): Promise { - // 获取 OSS 临时上传凭证 - const keyRes = await getaliyunkeyApi({ - bucketName: aliyunBucket, - }) - const { accessid, callback, dir, host, policy, signature } = keyRes.data - // 构建文件在 OSS 的存储路径 - const fileExtension = fileName.split('.').pop()?.toLowerCase() || 'tmp' - const timestamp = Date.now() - const random = Math.floor(Math.random() * 1000) - const key = `${dir}${timestamp + random}.${fileExtension}` - - const formData: Record = { - key, - OSSAccessKeyId: accessid, - policy, - signature, - success_action_status: '200', - name: fileName, - } - if (callback) - formData.callback = callback - - // 使用 Promise 包裹 uni.uploadFile - return new Promise((resolve, reject) => { - uni.uploadFile({ - url: host, - filePath, - name: 'file', - fileName, - fileType, - formData, - success: (res) => { - if (res.statusCode === 200) { - resolve({ - url: `${host}/${key}`, - }) - } - else { - reject(new Error(`上传失败,状态码: ${res.statusCode}`)) - } - }, - fail: (err) => { - reject(err) - }, - }) - }) -} /** =================== 工具函数 =================== */ /** 获取文件名:从 URL 提取最后一段 */ diff --git a/code/container-ocr/ocr-uniapp/src/utils/oss.ts b/code/container-ocr/ocr-uniapp/src/utils/oss.ts new file mode 100644 index 00000000..a269232d --- /dev/null +++ b/code/container-ocr/ocr-uniapp/src/utils/oss.ts @@ -0,0 +1,116 @@ +// utils/ossManager.ts +import { getAliyunkeyApi } from '@/request/api' + +const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET + +interface OssKey { + accessid: string + callback?: string + dir: string + host: string + policy: string + signature: string + expire: number +} + +interface OssUpdateResult { + url: string // 上传后的完整 URL +} + +// ✅ 模块级缓存(天然单例) +let ossKeyCache: OssKey | null = null +let ossKeyPromise: Promise | null = null + +/** + * 获取 OSS Key(缓存 + 并发锁) + */ +export async function getOssKey(): Promise { + const now = Math.floor(Date.now() / 1000) + const SAFE_GAP = 10 // 提前10秒过期 + + // ✅ 命中缓存 + if (ossKeyCache && ossKeyCache.expire - SAFE_GAP > now) { + console.log('===命中缓存===') + return ossKeyCache + } + + // ✅ 并发锁 + if (ossKeyPromise) { + return ossKeyPromise + } + + // ✅ 请求 + ossKeyPromise = getAliyunkeyApi({ + bucketName: aliyunBucket, + }) + .then((res) => { + console.log('===重新请求===') + + const data = res.data + + ossKeyCache = { + accessid: data.accessid, + callback: data.callback, + dir: data.dir, + host: data.host, + policy: data.policy, + signature: data.signature, + expire: Number(data.expire), + } + + return ossKeyCache + }) + .finally(() => { + ossKeyPromise = null + }) + + return ossKeyPromise +} + +export async function uploadToOss( + filePath: string, + fileName: string, + fileType?: 'image' | 'video' | 'file', +): Promise { + // ✅ 使用缓存后的 key + const { accessid, callback, dir, host, policy, signature } = await getOssKey() + + // 生成文件路径 + const ext = fileName.split('.').pop()?.toLowerCase() || 'tmp' + const key = `${dir}${Date.now()}_${Math.floor(Math.random() * 1000)}.${ext}` + + const formData: Record = { + key, + OSSAccessKeyId: accessid, + policy, + signature, + success_action_status: '200', + name: fileName, + } + + if (callback) { + formData.callback = callback + } + + return new Promise((resolve, reject) => { + uni.uploadFile({ + url: host, + filePath, + name: 'file', + fileName, + fileType: 'image', + formData, + success: (res) => { + if (res.statusCode === 200) { + resolve({ + url: `${host}/${key}`, + }) + } + else { + reject(new Error(`上传失败,状态码: ${res.statusCode}`)) + } + }, + fail: reject, + }) + }) +} diff --git a/code/container-ocr/ocr-uniapp/src/utils/platform.ts b/code/container-ocr/ocr-uniapp/src/utils/platform.ts index 86801f17..5ae0b8e1 100644 --- a/code/container-ocr/ocr-uniapp/src/utils/platform.ts +++ b/code/container-ocr/ocr-uniapp/src/utils/platform.ts @@ -1,9 +1,3 @@ -/* - * @Author: 菲鸽 - * @Date: 2024-03-28 19:13:55 - * @Last Modified by: 菲鸽 - * @Last Modified time: 2024-03-28 19:24:55 - */ export const platform = __UNI_PLATFORM__ export const isH5 = __UNI_PLATFORM__ === 'h5' export const isApp = __UNI_PLATFORM__ === 'app' @@ -13,6 +7,11 @@ export const isMpAplipay = __UNI_PLATFORM__.startsWith('mp-alipay') export const isMpToutiao = __UNI_PLATFORM__.startsWith('mp-toutiao') export const isHarmony = __UNI_PLATFORM__.startsWith('app-harmony') +function isInWeChat() { + const ua = navigator.userAgent.toLowerCase() + return ua.includes('micromessenger') +} + const PLATFORM = { platform, isH5, @@ -22,5 +21,6 @@ const PLATFORM = { isMpAplipay, isMpToutiao, isHarmony, + isInWeChat } export default PLATFORM diff --git a/code/container-ocr/ocr-vue/src/request/api.js b/code/container-ocr/ocr-vue/src/request/api.js index 77e4d080..6a70c529 100644 --- a/code/container-ocr/ocr-vue/src/request/api.js +++ b/code/container-ocr/ocr-vue/src/request/api.js @@ -1,4 +1,6 @@ -import { httpPost } from "."; +import { httpPost, httpUpload } from "."; + + // 登录 export function loginApi(orgid, userid, password) { diff --git a/code/container-ocr/ocr-vue/src/request/index.js b/code/container-ocr/ocr-vue/src/request/index.js index cbf33b76..e871c70e 100644 --- a/code/container-ocr/ocr-vue/src/request/index.js +++ b/code/container-ocr/ocr-vue/src/request/index.js @@ -112,6 +112,41 @@ export function httpPost(url, data, config) { .then((res) => res.data); } +/** + * 通用文件上传 + * @param {string} url - 上传接口地址 + * @param {File | File[]} file - 单个或多个文件 + * @param {Object} extraData - 额外参数(可选) + * @param {Object} config - axios 配置(可选) + * @returns {Promise} + */ +export function httpUpload(url, file, extraData = {}, config = {}) { + const formData = new FormData(); + + // 兼容单文件 & 多文件 + if (Array.isArray(file)) { + file.forEach((f) => { + formData.append("file", f); + }); + } else { + formData.append("file", file); + } + + // 追加额外参数 + Object.keys(extraData).forEach((key) => { + formData.append(key, extraData[key]); + }); + + return axiosInstance + .post(url, formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + ...config, + }) + .then((res) => res.data); +} + export function reset401Flag() { hasShown401 = false; } diff --git a/code/container-ocr/ocr-vue/src/views/container/index.vue b/code/container-ocr/ocr-vue/src/views/container/index.vue index 971cbf51..9f143711 100644 --- a/code/container-ocr/ocr-vue/src/views/container/index.vue +++ b/code/container-ocr/ocr-vue/src/views/container/index.vue @@ -94,6 +94,9 @@ async function loadTableColumns() { } } +/** + * 上传到临时文件夹 + */ async function loadTableDatas() { loading.table = true;