|
|
@@ -0,0 +1,438 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.entity.ReportTypeRegistry;
|
|
|
+import com.etotem.cfc.entity.ReportUnknownCluster;
|
|
|
+import com.etotem.cfc.entity.ReportUnknownUpload;
|
|
|
+import com.etotem.cfc.mapper.ReportTypeRegistryMapper;
|
|
|
+import com.etotem.cfc.mapper.ReportUnknownClusterMapper;
|
|
|
+import com.etotem.cfc.mapper.ReportUnknownUploadMapper;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.http.*;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.File;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.Executor;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class ReportScriptGeneratorService {
|
|
|
+
|
|
|
+ @Resource(name = "taskExecutor")
|
|
|
+ private Executor taskExecutor;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportUnknownUploadMapper unknownUploadMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportTypeRegistryMapper typeRegistryMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportUnknownClusterMapper unknownClusterMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private StorageService storageService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private RestTemplate restTemplate;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ObjectMapper objectMapper;
|
|
|
+
|
|
|
+ @Value("${report.collect.opencode-base:http://127.0.0.1:4090}")
|
|
|
+ private String opencodeBase;
|
|
|
+
|
|
|
+ @Value("${report.collect.opencode-user:opencode}")
|
|
|
+ private String opencodeUser;
|
|
|
+
|
|
|
+ @Value("${report.collect.opencode-password:IwinTrue@123}")
|
|
|
+ private String opencodePassword;
|
|
|
+
|
|
|
+ @Value("${report.collect.opencode-workdir:/app/cfc}")
|
|
|
+ private String opencodeWorkdir;
|
|
|
+
|
|
|
+ @Value("${report.collect.opencode-model-id:agnes-2.5-flash}")
|
|
|
+ private String opencodeModelId;
|
|
|
+
|
|
|
+ @Value("${report.collect.opencode-model-provider:agnes-ai}")
|
|
|
+ private String opencodeModelProvider;
|
|
|
+
|
|
|
+ @Value("${python.executable:python3}")
|
|
|
+ private String pythonExecutable;
|
|
|
+
|
|
|
+ @Value("${report.parser.scripts.path:docs/scripts/parsers}")
|
|
|
+ private String scriptsPath;
|
|
|
+
|
|
|
+ private static final long SCRIPT_GEN_TIMEOUT_MS = 300_000L;
|
|
|
+ private static final long SCRIPT_TEST_TIMEOUT_MS = 60_000L;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 异步触发脚本生成。立即返回,结果通过更新 report_type_registry.script_status 通知前端。
|
|
|
+ * 防重入:若已在生成中则跳过。
|
|
|
+ */
|
|
|
+ public void triggerAsyncGenerate(Long clusterId, String typeId) {
|
|
|
+ ReportTypeRegistry type = typeRegistryMapper.selectOne(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ReportTypeRegistry>()
|
|
|
+ .eq(ReportTypeRegistry::getTypeId, typeId));
|
|
|
+ if (type == null) {
|
|
|
+ log.warn("脚本生成跳过:类型不存在 typeId={}", typeId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (ReportTypeRegistry.SCRIPT_STATUS_GENERATING.equals(type.getScriptStatus())) {
|
|
|
+ log.info("脚本生成已在进行中,跳过 typeId={}", typeId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ taskExecutor.execute(() -> {
|
|
|
+ try {
|
|
|
+ generateAndTest(clusterId, typeId, type.getDisplayName());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("脚本生成异常 typeId={}", typeId, e);
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_FAILED);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void generateAndTest(Long clusterId, String typeId, String displayName) {
|
|
|
+ ReportUnknownCluster cluster = unknownClusterMapper.selectById(clusterId);
|
|
|
+ if (cluster == null) {
|
|
|
+ log.warn("脚本生成跳过:聚类不存在 clusterId={}", clusterId);
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_FAILED);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<ReportUnknownUpload> uploads = unknownUploadMapper.selectList(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ReportUnknownUpload>()
|
|
|
+ .eq(ReportUnknownUpload::getClusterId, clusterId)
|
|
|
+ .orderByDesc(ReportUnknownUpload::getUploadedAt)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ if (uploads.isEmpty() || uploads.get(0).getFileUrl() == null) {
|
|
|
+ log.warn("脚本生成跳过:无样例 PDF clusterId={}", clusterId);
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_FAILED);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String samplePdfPath = resolveLocalPath(uploads.get(0).getFileUrl());
|
|
|
+ if (samplePdfPath == null) {
|
|
|
+ log.warn("脚本生成跳过:样例 PDF 本地路径不可达 clusterId={}", clusterId);
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_FAILED);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_GENERATING);
|
|
|
+
|
|
|
+ String scriptContent;
|
|
|
+ try {
|
|
|
+ scriptContent = invokeOpencodeGenerateScript(typeId, displayName,
|
|
|
+ cluster.getCommonSignatures(), samplePdfPath);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("opencode 写脚本失败 typeId={}: {}", typeId, e.getMessage());
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_FAILED);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ File dir = new File(scriptsPath);
|
|
|
+ if (!dir.exists() && !dir.mkdirs()) {
|
|
|
+ log.warn("无法创建脚本目录: {}", scriptsPath);
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_FAILED);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ File scriptFile = new File(dir, typeId + ".py");
|
|
|
+ try {
|
|
|
+ java.nio.file.Files.write(scriptFile.toPath(),
|
|
|
+ scriptContent.getBytes(StandardCharsets.UTF_8));
|
|
|
+ log.info("脚本已写入: {}", scriptFile.getAbsolutePath());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("写脚本文件失败 typeId={}: {}", typeId, e.getMessage());
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_FAILED);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ String stdout = runScriptAndWait(samplePdfPath, scriptFile.getAbsolutePath());
|
|
|
+ validateJsonOutput(stdout, typeId);
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_READY);
|
|
|
+ log.info("脚本生成+测试通过 typeId={} scriptPath={}", typeId, scriptFile.getAbsolutePath());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("脚本独立测试失败 typeId={}: {}", typeId, e.getMessage());
|
|
|
+ setScriptStatus(typeId, ReportTypeRegistry.SCRIPT_STATUS_FAILED);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String invokeOpencodeGenerateScript(String typeId, String displayName,
|
|
|
+ String commonSignatures, String samplePdfPath)
|
|
|
+ throws Exception {
|
|
|
+ String sessionId = opencodeCreateSession("script-gen-" + typeId);
|
|
|
+ if (sessionId == null) {
|
|
|
+ throw new IllegalStateException("opencode 会话创建失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ String prompt = buildGenerateScriptPrompt(typeId, displayName, commonSignatures, samplePdfPath);
|
|
|
+ sendPrompt(sessionId, prompt);
|
|
|
+
|
|
|
+ long deadline = System.currentTimeMillis() + SCRIPT_GEN_TIMEOUT_MS;
|
|
|
+ int before = countMessages(sessionId);
|
|
|
+ while (System.currentTimeMillis() < deadline) {
|
|
|
+ try {
|
|
|
+ Thread.sleep(5000);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> msgs = listMessages(sessionId);
|
|
|
+ if (msgs == null || msgs.size() <= before) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String reply = findLastAssistantText(msgs);
|
|
|
+ if (reply != null && !reply.trim().isEmpty()) {
|
|
|
+ log.info("opencode 回复(前200字符): {}", reply.substring(0, Math.min(200, reply.length())));
|
|
|
+ String code = extractPythonCode(reply);
|
|
|
+ if (code == null || code.trim().isEmpty()) {
|
|
|
+ throw new IllegalStateException("opencode 回复中未找到 Python 代码块");
|
|
|
+ }
|
|
|
+ return code;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw new IllegalStateException("opencode 写脚本超时(" + SCRIPT_GEN_TIMEOUT_MS / 1000 + "s)");
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildGenerateScriptPrompt(String typeId, String displayName,
|
|
|
+ String commonSignatures, String samplePdfPath) {
|
|
|
+ String desc = (commonSignatures != null && !commonSignatures.isEmpty())
|
|
|
+ ? "公共文本特征:\n" + commonSignatures : "无额外描述";
|
|
|
+ return "你是报告解析脚本编写专家。\n\n"
|
|
|
+ + "任务:为报告类型 '" + typeId + "'(" + (displayName != null ? displayName : "未命名") + ")编写 Python 采集脚本。\n\n"
|
|
|
+ + desc + "\n\n"
|
|
|
+ + "样例PDF路径(本地):" + samplePdfPath + "\n"
|
|
|
+ + "目标脚本路径:/app/cfc/" + scriptsPath + "/" + typeId + ".py\n\n"
|
|
|
+ + "要求:\n"
|
|
|
+ + "1. 先用 pdftotext 或 pdfplumber 读取样例PDF,分析报告结构和字段位置。\n"
|
|
|
+ + "2. 编写 Python 脚本(纯算法,不用 LLM/外部 API),用正则 + pdfplumber/pdfminer 提取结构化数据。\n"
|
|
|
+ + "3. 脚本调用约定:python3 " + typeId + ".py <pdf_path>\n"
|
|
|
+ + "4. stdout 必须输出严格 JSON,结构如下:\n"
|
|
|
+ + "{\n"
|
|
|
+ + " \"summary\": {\"overallScore\": 0, \"gutHealthScore\": 0, \"reportNumber\": \"\", \"reportDate\": \"\", \"personName\": \"\", \"gender\": \"\", \"age\": 0},\n"
|
|
|
+ + " \"indicators\": [{\"category\": \"\", \"indicatorName\": \"\", \"indicatorValue\": \"\", \"unit\": \"\", \"refRange\": \"\", \"status\": \"\"}],\n"
|
|
|
+ + " \"gutFlora\": [{\"bacteriaName\": \"\", \"bacteriaValue\": \"\", \"normalRange\": \"\", \"populationLevel\": \"\", \"detectionRate\": \"\", \"description\": \"\", \"category\": \"\", \"level\": \"\"}],\n"
|
|
|
+ + " \"foods\": [{\"name\": \"\", \"category\": \"\", \"score\": 0}],\n"
|
|
|
+ + " \"diseaseRisks\": [{\"diseaseName\": \"\", \"riskValue\": \"\", \"riskLevel\": \"\"}]\n"
|
|
|
+ + "}\n"
|
|
|
+ + "5. 脚本末尾用 try/except 包裹,确保异常时退出码非零。\n"
|
|
|
+ + "6. 写完脚本后,用样例PDF跑一遍(python3 " + typeId + ".py " + samplePdfPath + "),打印 stdout,确认是合法 JSON。\n"
|
|
|
+ + "7. 最后报告脚本路径和测试输出(前500字符)。\n\n"
|
|
|
+ + "注意:只做以上任务。完成后输出完整的 Python 脚本代码(用 ```python 包裹),以及测试结果。";
|
|
|
+ }
|
|
|
+
|
|
|
+ private String runScriptAndWait(String pdfPath, String scriptPath) throws Exception {
|
|
|
+ ProcessBuilder pb = new ProcessBuilder(pythonExecutable, scriptPath, pdfPath);
|
|
|
+ pb.redirectErrorStream(true);
|
|
|
+ Process proc = pb.start();
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ try (BufferedReader reader = new BufferedReader(
|
|
|
+ new InputStreamReader(proc.getInputStream(), StandardCharsets.UTF_8))) {
|
|
|
+ String line;
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
+ sb.append(line).append('\n');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ boolean finished = proc.waitFor(SCRIPT_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
|
|
+ if (!finished) {
|
|
|
+ proc.destroyForcibly();
|
|
|
+ throw new IllegalStateException("脚本测试超时(" + SCRIPT_TEST_TIMEOUT_MS / 1000 + "s)");
|
|
|
+ }
|
|
|
+ if (proc.exitValue() != 0) {
|
|
|
+ throw new IllegalStateException("脚本测试退出码=" + proc.exitValue()
|
|
|
+ + "\nstdout: " + sb.toString().trim());
|
|
|
+ }
|
|
|
+ String stdout = sb.toString().trim();
|
|
|
+ if (stdout.isEmpty()) {
|
|
|
+ throw new IllegalStateException("脚本测试 stdout 为空");
|
|
|
+ }
|
|
|
+ return stdout;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validateJsonOutput(String stdout, String typeId) throws Exception {
|
|
|
+ String json = extractJson(stdout);
|
|
|
+ if (json == null) {
|
|
|
+ throw new IllegalStateException("脚本 stdout 中未找到 JSON\n"
|
|
|
+ + stdout.substring(0, Math.min(500, stdout.length())));
|
|
|
+ }
|
|
|
+ objectMapper.readTree(json);
|
|
|
+ log.info("脚本测试 JSON 校验通过 typeId={} jsonLen={}", typeId, json.length());
|
|
|
+ }
|
|
|
+
|
|
|
+ private String extractJson(String text) {
|
|
|
+ if (text == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ int start = text.indexOf('{');
|
|
|
+ int end = text.lastIndexOf('}');
|
|
|
+ return (start >= 0 && end > start) ? text.substring(start, end + 1) : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String opencodeCreateSession(String title) {
|
|
|
+ try {
|
|
|
+ String url = opencodeBase + "/session?directory="
|
|
|
+ + java.net.URLEncoder.encode(opencodeWorkdir, StandardCharsets.UTF_8.name());
|
|
|
+ Map<String, Object> body = new LinkedHashMap<>();
|
|
|
+ body.put("title", title);
|
|
|
+ body.put("agent", "build");
|
|
|
+ Map<String, Object> model = new LinkedHashMap<>();
|
|
|
+ model.put("id", opencodeModelId);
|
|
|
+ model.put("providerID", opencodeModelProvider);
|
|
|
+ body.put("model", model);
|
|
|
+ Map<String, Object> tools = new LinkedHashMap<>();
|
|
|
+ tools.put("bash", Boolean.TRUE);
|
|
|
+ tools.put("write", Boolean.TRUE);
|
|
|
+ tools.put("edit", Boolean.TRUE);
|
|
|
+ for (String t : new String[]{"apply_patch", "task", "dispatch",
|
|
|
+ "webfetch_post", "chrome_launch"}) {
|
|
|
+ tools.put(t, Boolean.FALSE);
|
|
|
+ }
|
|
|
+ body.put("tools", tools);
|
|
|
+ ResponseEntity<Map> resp = restTemplate.exchange(url, HttpMethod.POST,
|
|
|
+ new HttpEntity<>(body, opencodeHeaders()), Map.class);
|
|
|
+ Map<?, ?> data = resp.getBody();
|
|
|
+ if (data == null || data.get("id") == null) {
|
|
|
+ log.warn("opencode 创建会话未返回 session_id");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return data.get("id").toString();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("opencode 创建会话失败 title={}: {}", title, e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void sendPrompt(String sessionId, String prompt) {
|
|
|
+ List<Map<String, Object>> parts = new ArrayList<>();
|
|
|
+ Map<String, Object> part = new LinkedHashMap<>();
|
|
|
+ part.put("type", "text");
|
|
|
+ part.put("text", prompt);
|
|
|
+ parts.add(part);
|
|
|
+ Map<String, Object> body = new LinkedHashMap<>();
|
|
|
+ body.put("parts", parts);
|
|
|
+ restTemplate.exchange(opencodeBase + "/session/" + sessionId + "/prompt_async",
|
|
|
+ HttpMethod.POST, new HttpEntity<>(body, opencodeHeaders()), Void.class);
|
|
|
+ log.info("opencode prompt 已发送 session={}", sessionId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> listMessages(String sessionId) {
|
|
|
+ try {
|
|
|
+ ResponseEntity<List> resp = restTemplate.exchange(
|
|
|
+ opencodeBase + "/session/" + sessionId + "/message",
|
|
|
+ HttpMethod.GET, new HttpEntity<>(opencodeHeaders()), List.class);
|
|
|
+ return resp.getBody();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("opencode 查询消息失败 session={}: {}", sessionId, e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private int countMessages(String sessionId) {
|
|
|
+ List<Map<String, Object>> msgs = listMessages(sessionId);
|
|
|
+ return msgs == null ? 0 : msgs.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ private HttpHeaders opencodeHeaders() {
|
|
|
+ String token = Base64.getEncoder().encodeToString(
|
|
|
+ (opencodeUser + ":" + opencodePassword).getBytes(StandardCharsets.UTF_8));
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ headers.set("Authorization", "Basic " + token);
|
|
|
+ return headers;
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private String findLastAssistantText(List<Map<String, Object>> msgs) {
|
|
|
+ if (msgs == null || msgs.isEmpty()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ for (int i = msgs.size() - 1; i >= 0; i--) {
|
|
|
+ Map<String, Object> msg = msgs.get(i);
|
|
|
+ Object infoObj = msg.get("info");
|
|
|
+ if (!(infoObj instanceof Map)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Map<String, Object> info = (Map<String, Object>) infoObj;
|
|
|
+ if (!"assistant".equals(info.get("role"))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Object partsObj = msg.get("parts");
|
|
|
+ if (!(partsObj instanceof List)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (Object pObj : (List<Object>) partsObj) {
|
|
|
+ if (!(pObj instanceof Map)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Map<String, Object> p = (Map<String, Object>) pObj;
|
|
|
+ if ("text".equals(p.get("type")) && p.get("text") != null) {
|
|
|
+ String text = p.get("text").toString().trim();
|
|
|
+ if (!text.isEmpty()) {
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String extractPythonCode(String reply) {
|
|
|
+ if (reply == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ int pyStart = reply.indexOf("```python");
|
|
|
+ if (pyStart >= 0) {
|
|
|
+ int codeStart = reply.indexOf('\n', pyStart);
|
|
|
+ int end = reply.lastIndexOf("```");
|
|
|
+ if (end > codeStart) {
|
|
|
+ return reply.substring(codeStart + 1, end).trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ int start = reply.indexOf("```");
|
|
|
+ if (start >= 0) {
|
|
|
+ int codeStart = reply.indexOf('\n', start);
|
|
|
+ int end = reply.lastIndexOf("```");
|
|
|
+ if (end > codeStart) {
|
|
|
+ return reply.substring(codeStart + 1, end).trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return reply.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String resolveLocalPath(String fileUrl) {
|
|
|
+ if (fileUrl == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ String p = storageService.getFilePath(fileUrl);
|
|
|
+ File f = new File(p);
|
|
|
+ return f.exists() ? f.getAbsolutePath() : null;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析文件路径失败 fileUrl={}: {}", fileUrl, e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setScriptStatus(String typeId, String scriptStatus) {
|
|
|
+ try {
|
|
|
+ com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<ReportTypeRegistry> uw =
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<>();
|
|
|
+ uw.eq(ReportTypeRegistry::getTypeId, typeId)
|
|
|
+ .set(ReportTypeRegistry::getScriptStatus, scriptStatus)
|
|
|
+ .set(ReportTypeRegistry::getUpdatedAt, new Date());
|
|
|
+ typeRegistryMapper.update(null, uw);
|
|
|
+ log.info("脚本状态已更新 typeId={} status={}", typeId, scriptStatus);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("更新脚本状态失败 typeId={}: {}", typeId, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|