|
|
@@ -3,7 +3,6 @@ package com.siwei.apply.controller;
|
|
|
import com.siwei.apply.domain.res.YdysyxzRes;
|
|
|
import com.siwei.apply.domain.vo.YdysyxzVo;
|
|
|
import com.siwei.apply.service.YdysyxzService;
|
|
|
-import com.siwei.apply.utils.FixedThreadUtil;
|
|
|
import com.siwei.common.core.domain.R;
|
|
|
import com.siwei.common.core.utils.StringUtils;
|
|
|
import com.siwei.common.core.web.controller.BaseController;
|
|
|
@@ -11,11 +10,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
+import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStreamReader;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
/**
|
|
|
* 用地预审与选址 控制器
|
|
|
@@ -24,6 +27,17 @@ import java.util.Map;
|
|
|
@RestController
|
|
|
@RequestMapping("/chen")
|
|
|
public class ChenController extends BaseController {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * OCR 脚本同步执行的最长等待时间(分钟)
|
|
|
+ */
|
|
|
+ private static final long OCR_TIMEOUT_MINUTES = 10;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 返回给前端的异常输出最大长度
|
|
|
+ */
|
|
|
+ private static final int MAX_ERROR_LENGTH = 900;
|
|
|
+
|
|
|
@Autowired
|
|
|
private YdysyxzService ydysyxzService;
|
|
|
|
|
|
@@ -99,53 +113,111 @@ public class ChenController extends BaseController {
|
|
|
}
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
+ * 调用 python 脚本做图像识别(OCR),同步执行并返回识别内容
|
|
|
+ * <p>
|
|
|
+ * 等价于命令行:
|
|
|
+ * python "D:\Tie-Ortho\ocr-tests\20261012-land-application\run_ocr.py"
|
|
|
+ * "C:\Users\siwei\Desktop\tp_test\疑似违法用地情况说明.pdf" --mode free
|
|
|
+ * <p>
|
|
|
+ * 注意:OCR 属于耗时任务,这里同步等待(上限 OCR_TIMEOUT_MINUTES 分钟),
|
|
|
+ * 会一直占用一个 HTTP 工作线程,网关/前端需要相应放宽超时时间。
|
|
|
+ */
|
|
|
@GetMapping("/testPython")
|
|
|
- public R<String> testPython() {
|
|
|
+ public R<Map<String, Object>> testPython() {
|
|
|
+ // python 解释器(conda 虚拟环境)
|
|
|
+ String pythonHome = "E:\\anaconda\\envs\\py310\\python.exe";
|
|
|
+ // 识别脚本
|
|
|
+ String finalPyPath = "D:\\Tie-Ortho\\ocr-tests\\20261012-land-application\\run_ocr.py";
|
|
|
+ // 待识别的图片/文件
|
|
|
+ String imgPath = "C:\\Users\\siwei\\Desktop\\tp_test\\用地规划设计条件.jpg";
|
|
|
+ // 识别模式
|
|
|
+ String mode = "free";
|
|
|
+
|
|
|
+ Process process = null;
|
|
|
+ BufferedReader reader = null;
|
|
|
try {
|
|
|
- //C:\Users\siwei>python "D:\Tie-Ortho\ocr-tests\20261012-land-application\run_ocr.py" "C:\Users\siwei\Desktop\tp_test\疑似违法用地情况说明.pdf" --mode free
|
|
|
- String retString = "测试";
|
|
|
- String pythonHome = "E:\\anaconda\\envs\\py310\\python.exe";
|
|
|
- String finalPyPath = "D:\\Tie-Ortho\\ocr-tests\\20261012-land-application\\run_ocr.py";
|
|
|
- String imgPath = "C:\\Users\\siwei\\Desktop\\tp_test\\用地规划设计条件.jpg";
|
|
|
- FixedThreadUtil.FIXED_THREAD_POOL.execute(() -> {
|
|
|
- try {
|
|
|
- // 更新状态为运行中
|
|
|
- //TODO command ProcessBuilder
|
|
|
- ProcessBuilder pb = new ProcessBuilder(pythonHome, finalPyPath, imgPath, "", "", "", "");
|
|
|
- pb.redirectErrorStream(true);
|
|
|
- Process process = null;
|
|
|
- process = pb.start();
|
|
|
- BufferedReader reader = new BufferedReader(
|
|
|
- new InputStreamReader(process.getInputStream()));
|
|
|
- String line;
|
|
|
- ArrayList<String> datas = new ArrayList<>();
|
|
|
- while ((line = reader.readLine()) != null) {
|
|
|
- if (StringUtils.isNotEmpty(line)) {
|
|
|
- if (line.contains("||")) {
|
|
|
- datas.add(line.split("ERROR")[0]);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if (datas.size() > 0) {
|
|
|
- // 更新状态为成功
|
|
|
- for (String cur : datas) {
|
|
|
- String[] args = cur.split("\\|\\|");
|
|
|
- String id = StringUtils.getUUID();
|
|
|
- // 处理文件路径
|
|
|
- args[1] = StringUtils.replaceEach(args[1], new String[]{"D:", "\\"}, new String[]{"", "/"});
|
|
|
- // 插入制图产品表
|
|
|
- //thematicMapper.addDetails(id, uuid, args[0], args[1], args[2]);
|
|
|
- System.out.println("制图产品表插入成功:" + args[0] + " " + args[1] + " " + args[2]);
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (IOException e) {
|
|
|
- e.printStackTrace();
|
|
|
+ // 注意:不要再传多余的空串参数,脚本按 "脚本 + 文件 + --mode + 模式" 的约定调用
|
|
|
+ ProcessBuilder pb = new ProcessBuilder(pythonHome, finalPyPath, imgPath, "--mode", mode);
|
|
|
+ // stderr 合并进 stdout,避免只开一个流时子进程写满 stderr 缓冲区而阻塞
|
|
|
+ pb.redirectErrorStream(true);
|
|
|
+ // 强制 python 以 UTF-8 输出,解决 Windows 默认 GBK 导致的中文乱码
|
|
|
+ pb.environment().put("PYTHONIOENCODING", "utf-8");
|
|
|
+ // 以脚本所在目录作为工作目录,脚本内的相对路径才不会找不到
|
|
|
+ File scriptFile = new File(finalPyPath);
|
|
|
+ File scriptDir = scriptFile.getParentFile();
|
|
|
+ if (scriptDir != null && scriptDir.exists()) {
|
|
|
+ pb.directory(scriptDir);
|
|
|
+ }
|
|
|
+
|
|
|
+ process = pb.start();
|
|
|
+
|
|
|
+ // 按 UTF-8 读取输出(原写法用平台默认编码,中文必乱码)
|
|
|
+ reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
|
|
|
+ StringBuilder output = new StringBuilder();
|
|
|
+ List<String> items = new ArrayList<>();
|
|
|
+ String line;
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
+ if (StringUtils.isBlank(line)) {
|
|
|
+ continue;
|
|
|
}
|
|
|
- });
|
|
|
- return R.ok(retString);
|
|
|
+ output.append(line).append("\n");
|
|
|
+ // 兼容脚本用 "||" 分隔的结构化输出行
|
|
|
+ if (line.contains("||")) {
|
|
|
+ items.add(line.split("ERROR")[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 必须等待进程退出并校验退出码,否则脚本报错也会被当成成功
|
|
|
+ boolean finished = process.waitFor(OCR_TIMEOUT_MINUTES, TimeUnit.MINUTES);
|
|
|
+ if (!finished) {
|
|
|
+ process.destroyForcibly();
|
|
|
+ return R.fail("OCR 执行超时(超过 " + OCR_TIMEOUT_MINUTES + " 分钟已强制终止)");
|
|
|
+ }
|
|
|
+ int exitCode = process.exitValue();
|
|
|
+ if (exitCode != 0) {
|
|
|
+ return R.fail("OCR 执行失败,exitCode=" + exitCode + ":" + truncate(output.toString()));
|
|
|
+ }
|
|
|
+
|
|
|
+ String content = output.toString().trim();
|
|
|
+ if (StringUtils.isBlank(content)) {
|
|
|
+ return R.fail("python 脚本未返回任何识别内容");
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ data.put("exitCode", exitCode);
|
|
|
+ data.put("content", content); // 脚本完整输出,即识别内容
|
|
|
+ data.put("items", items); // 按 "||" 协议解析出的结构化行(可能为多条)
|
|
|
+ return R.ok(data, "识别成功");
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ return R.fail("OCR 执行被中断:" + e.getMessage());
|
|
|
} catch (Exception e) {
|
|
|
- return R.fail(e.getMessage());
|
|
|
+ e.printStackTrace();
|
|
|
+ return R.fail("OCR 执行异常:" + e.getMessage());
|
|
|
+ } finally {
|
|
|
+ if (reader != null) {
|
|
|
+ try {
|
|
|
+ reader.close();
|
|
|
+ } catch (IOException ignored) {
|
|
|
+ // 关闭失败无需处理
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (process != null && process.isAlive()) {
|
|
|
+ process.destroy();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 截断过长的脚本输出,避免异常信息刷爆日志/响应
|
|
|
+ */
|
|
|
+ private String truncate(String text) {
|
|
|
+ if (StringUtils.isBlank(text)) {
|
|
|
+ return "";
|
|
|
}
|
|
|
+ String trim = text.trim();
|
|
|
+ return trim.length() <= MAX_ERROR_LENGTH ? trim : trim.substring(0, MAX_ERROR_LENGTH) + "...";
|
|
|
}
|
|
|
|
|
|
|