From e38aca8134570513435c003c25dc05f1ae51c9c9 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期四, 08 十月 2026 13:56:28 +0800
Subject: [PATCH] 实现 Ticket04:采集器辅助测量与登记入口,拆除开发模拟器

---
 battery-device-neware/src/main/java/com/battery/device/neware/ReadOnlyVendorDataSource.java |   29 +++++++++++++++++++++++++++--
 1 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/battery-device-neware/src/main/java/com/battery/device/neware/ReadOnlyVendorDataSource.java b/battery-device-neware/src/main/java/com/battery/device/neware/ReadOnlyVendorDataSource.java
index f682c0d..ea088fe 100644
--- a/battery-device-neware/src/main/java/com/battery/device/neware/ReadOnlyVendorDataSource.java
+++ b/battery-device-neware/src/main/java/com/battery/device/neware/ReadOnlyVendorDataSource.java
@@ -33,6 +33,12 @@
 public final class ReadOnlyVendorDataSource implements DataSource
 {
     private static final List<String> ALLOWED_PREFIXES = List.of("select", "show", "describe", "desc");
+    /**
+     * 加锁读会真的阻塞厂商写入,所以它虽然以 {@code select} 开头也要单独拒绝:
+     * 现场实测厂商库 {@code tx_isolation=REPEATABLE-READ},行锁会挡住上位机的写库。
+     */
+    private static final List<String> LOCKING_CLAUSES = List.of("for update", "for share",
+            "lock in share mode", "updlock", "holdlock", "xlock", "tablock");
 
     private final DataSource delegate;
     private final String sourceLabel;
@@ -117,13 +123,17 @@
 
     private void requireQuery(String sql)
     {
-        if (isQuery(sql))
+        boolean query = isQuery(sql);
+        boolean locking = query && containsLockingClause(sql);
+        if (query && !locking)
         {
             audit.recordForwarded(sql);
             return;
         }
         audit.recordRejected(sql);
-        throw new NonQueryStatementException("厂商数据源只允许查询语句:" + abbreviate(sql));
+        throw new NonQueryStatementException(locking
+                ? "厂商数据源禁止加锁读(会阻塞厂商写入):" + abbreviate(sql)
+                : "厂商数据源只允许查询语句:" + abbreviate(sql));
     }
 
     /** 只按语句首关键字判定,避免用正则近似 SQL 语法。 */
@@ -144,6 +154,21 @@
         return false;
     }
 
+    /**
+     * 语句里是否带加锁读子句。这是**语法层的拦截**,不是 SQL 解析器:厂商取数的语句由本模块固定给出,
+     * 不会把加锁子句藏在字符串常量里;新增取数语句时由 {@code ReadOnlyVendorDataSourceTest} 一起覆盖。
+     */
+    static boolean containsLockingClause(String sql)
+    {
+        if (sql == null) return false;
+        String normalized = stripLeadingComments(sql).toLowerCase(Locale.ROOT).replaceAll("\\s+", " ");
+        for (String clause : LOCKING_CLAUSES)
+        {
+            if (normalized.contains(clause)) return true;
+        }
+        return false;
+    }
+
     private static String stripLeadingComments(String sql)
     {
         String remaining = sql.trim();

--
Gitblit v1.7.1