From bcd5879a0878c620563e4691d47626d58fb52489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 00:06:44 +0800 Subject: [PATCH] =?UTF-8?q?task-194:=20=E5=AD=A4=E5=84=BF=20task=5Ffile=5F?= =?UTF-8?q?job=20=E5=B7=A1=E6=A3=80=20SQL=EF=BC=8801=5Forphan=5Fjob.sql?= =?UTF-8?q?=EF=BC=8C=E5=8F=AA=E8=AF=BB=20SELECT=20=E5=8F=8D=E8=BF=9E?= =?UTF-8?q?=E6=97=A0=E4=BB=BB=E5=8A=A1/=E7=BB=93=E6=9E=9C=EF=BC=9Bbase=20?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=208=20=E9=A1=B9=E9=9D=99=E6=80=81=E5=A5=91?= =?UTF-8?q?=E7=BA=A6=EF=BC=89+=208=20=E6=9D=A1=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/inspection/01_orphan_job.sql | 17 ++++ .../config/InspectionSqlFileTestBase.java | 97 +++++++++++++++++++ .../config/OrphanJobSqlInspectionTest.java | 9 ++ 3 files changed, 123 insertions(+) create mode 100644 backend-java/src/main/resources/inspection/01_orphan_job.sql create mode 100644 backend-java/src/test/java/com/nanri/aiimage/config/InspectionSqlFileTestBase.java create mode 100644 backend-java/src/test/java/com/nanri/aiimage/config/OrphanJobSqlInspectionTest.java diff --git a/backend-java/src/main/resources/inspection/01_orphan_job.sql b/backend-java/src/main/resources/inspection/01_orphan_job.sql new file mode 100644 index 00000000..af04924e --- /dev/null +++ b/backend-java/src/main/resources/inspection/01_orphan_job.sql @@ -0,0 +1,17 @@ +-- ============================================================= +-- 巡检 01:孤儿 task_file_job(job 无对应任务/结果)— 只读报表(task-194) +-- 对应 Java 巡检:OrphanJobInspector(module-09);仅副本库只读执行,无副作用。 +-- 结果按 updated_at 升序;每批建议 limit <= 500。 +-- ============================================================= +SELECT j.id AS job_id, + j.task_id, + j.result_id, + j.module_type, + j.status, + j.updated_at +FROM biz_task_file_job j +WHERE NOT EXISTS (SELECT 1 FROM biz_file_task t WHERE t.id = j.task_id) + AND (j.result_id IS NULL + OR NOT EXISTS (SELECT 1 FROM biz_file_result r WHERE r.id = j.result_id)) +ORDER BY j.updated_at ASC +LIMIT 200; diff --git a/backend-java/src/test/java/com/nanri/aiimage/config/InspectionSqlFileTestBase.java b/backend-java/src/test/java/com/nanri/aiimage/config/InspectionSqlFileTestBase.java new file mode 100644 index 00000000..030bd050 --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/config/InspectionSqlFileTestBase.java @@ -0,0 +1,97 @@ +package com.nanri.aiimage.config; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * 巡检只读 SQL 报表静态契约基座(task-194..199)。 + * 校验各 inspection/*.sql:以 SELECT 开头、无任何写语句/锁语句、含 NOT EXISTS/JOIN 关联、 + * 带 LIMIT、分号结尾、头注释含目的与只读声明。子类提供文件名。 + */ +abstract class InspectionSqlFileTestBase { + + abstract String fileName(); + + private Path path() { + return Path.of(System.getProperty("user.dir"), "src/main/resources/inspection", fileName()); + } + + private String sql() throws IOException { + return Files.readString(path()); + } + + private String statements() throws IOException { + // 去掉整行注释与空行后的可执行 SQL 文本(头注释说明可能含"清理/删除"等字样,须排除) + StringBuilder sb = new StringBuilder(); + for (String line : sql().split("\n")) { + String t = line.trim(); + if (t.isEmpty() || t.startsWith("--")) { + continue; + } + sb.append(line).append('\n'); + } + return sb.toString(); + } + + @Test + void fileExists() throws IOException { + assertTrue(Files.exists(path()), "巡检 SQL 应存在: " + fileName()); + } + + @Test + void startsWithSelect() throws IOException { + String first = statements().stripLeading(); + assertTrue(first.toUpperCase().startsWith("SELECT"), "可执行 SQL 应以 SELECT 开头: " + fileName()); + } + + private static final String[] FORBIDDEN_WRITE_TOKENS = { + "INSERT", "UPDATE", "DELETE", "DROP", "TRUNCATE", "ALTER", "CREATE", + "GRANT", "REPLACE", "CALL", "LOAD", "DO" + }; + + @Test + void readOnlyNoWriteKeywords() throws IOException { + String upper = statements().toUpperCase(); + for (String kw : FORBIDDEN_WRITE_TOKENS) { + java.util.regex.Pattern p = java.util.regex.Pattern.compile("(?