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 cb9fa284..f21921e9 100644 Binary files a/code/container-ocr/ocr-uniapp/src/static/tabbar/add.png and b/code/container-ocr/ocr-uniapp/src/static/tabbar/add.png differ diff --git a/code/container-ocr/ocr-uniapp/src/style/iconfont.css b/code/container-ocr/ocr-uniapp/src/style/iconfont.css index acd3ca5b..97436614 100644 --- a/code/container-ocr/ocr-uniapp/src/style/iconfont.css +++ b/code/container-ocr/ocr-uniapp/src/style/iconfont.css @@ -1,9 +1,9 @@ @font-face { font-family: 'iconfont'; /* Project id 5142758 */ src: - url('data:application/x-font-woff2;charset=utf-8;base64,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') + url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), - url('//at.alicdn.com/t/c/font_5142758_pgr9uja0qt.ttf?t=1773887288142') format('truetype'); + url('//at.alicdn.com/t/c/font_5142758_32yu5am87gx.ttf?t=1774661340978') format('truetype'); } .iconfont { @@ -14,18 +14,18 @@ -moz-osx-font-smoothing: grayscale; } +.iconfont-tupian:before { + content: '\e865'; +} + +.iconfont-iconyuanbanben_OCR:before { + content: '\e6b6'; +} + .iconfont-fasongtijiao:before { content: '\e693'; } -.iconfont-tupian:before { - content: '\e8ba'; -} - -.iconfont-OCRshibie:before { - content: '\e658'; -} - .iconfont-save:before { content: '\e8c7'; } diff --git a/code/container-ocr/ocr-uniapp/src/style/index.scss b/code/container-ocr/ocr-uniapp/src/style/index.scss index 227526d2..b043cb4f 100644 --- a/code/container-ocr/ocr-uniapp/src/style/index.scss +++ b/code/container-ocr/ocr-uniapp/src/style/index.scss @@ -5,7 +5,7 @@ page, .sar-portal { --sar-input-border-color: #ddd; // --sar-primary: #34495e; - --sar-primary: #4d80f0; + --sar-primary: #0052d9; --sar-rounded: 8rpx; --sar-dialog-title-font-size: 42rpx; --sar-dialog-message-font-size: 36rpx; diff --git a/code/container-ocr/ocr-uniapp/src/utils/file.ts b/code/container-ocr/ocr-uniapp/src/utils/file.ts index 2dc31f05..9884c509 100644 --- a/code/container-ocr/ocr-uniapp/src/utils/file.ts +++ b/code/container-ocr/ocr-uniapp/src/utils/file.ts @@ -1,5 +1,5 @@ import { toast } from 'sard-uniapp' -import { getaliyunkeyApi } from '@/request/api' +import { getOssKey } from './oss' import { isApp, isH5, isMpWeixin } from './platform' const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET @@ -115,70 +115,12 @@ export function formatFileSize(size: number): string { else return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB` } -export interface AliyunUploadResult { - url: string // 上传后的完整 URL -} + /** - * 上传文件到阿里云 OSS - * @param filePath 本地文件路径 - * @param fileName 文件名 - * @param fileType 文件类型,可选:image/video/file - * @returns Promise + * 上传文件到阿里云 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;