package com.battery.device.neware; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.math.BigDecimal; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; /** * 可控的假厂商库:按新威 7.6 实测表结构({@code channel} / {@code test} / {@code data_*})回答适配器的查询, * 并把每一条到达"驱动"的语句原样记录下来。 * *

它只回答适配器实际使用的查询形态,因此测试断言的是外部行为(发现清单、最新测试、分表定位、 * 单位换算、水位过滤),不是 SQL 文本本身;记录下来的语句用于断言只读防线没有漏过非查询语句。 */ final class FakeVendorDatabase { record ChannelRow(long devUid, int unitId, int chlId) { } record TestRow(long devUid, int unitId, int chlId, long testId, String startTime, String firstTable, String secondTable) { } record SampleRow(String table, long testId, int unitId, int chlId, long seqId, int cycle, int stepId, String stepType, long testTimeMillis, int voltage, int current, int capacityCharge, int energyCharge, int capacityDischarge, int energyDischarge, String testAtime) { } /** 工步层一行:工步层不随采样分表滚动,所以没有表名。 */ record CapStepRow(long testId, long devUid, int unitId, int chlId, long seqId, int cycle, int stepId, String stepType, long testTimeMillis, int voltage, int current, int capacityCharge, int energyCharge, int capacityDischarge, int energyDischarge, int middleVoltage, String testAtime) { } private final List statements = new java.util.concurrent.CopyOnWriteArrayList<>(); private final List channels = new ArrayList<>(); private final List tests = new ArrayList<>(); private final List samples = new ArrayList<>(); private final List capSteps = new ArrayList<>(); FakeVendorDatabase registeredChannel(long devUid, int unitId, int chlId) { channels.add(new ChannelRow(devUid, unitId, chlId)); return this; } FakeVendorDatabase test(TestRow row) { tests.add(row); return this; } FakeVendorDatabase sample(SampleRow row) { samples.add(row); return this; } FakeVendorDatabase capStep(CapStepRow row) { capSteps.add(row); return this; } /** 到达"驱动"的语句原文,按调用顺序。 */ List statements() { return List.copyOf(statements); } DataSource dataSource() { return new DataSource() { @Override public Connection getConnection() { return connection(); } @Override public Connection getConnection(String username, String password) { return connection(); } @Override public java.io.PrintWriter getLogWriter() { return null; } @Override public void setLogWriter(java.io.PrintWriter out) { } @Override public void setLoginTimeout(int seconds) { } @Override public int getLoginTimeout() { return 0; } @Override public java.util.logging.Logger getParentLogger() { return java.util.logging.Logger.getLogger("fake-vendor"); } @Override public T unwrap(Class iface) throws SQLException { throw new SQLException("不支持解包"); } @Override public boolean isWrapperFor(Class iface) { return false; } }; } private Connection connection() { return (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Connection.class }, (proxy, method, args) -> { if ("prepareStatement".equals(method.getName())) return statement((String) args[0]); if ("createStatement".equals(method.getName())) return plainStatement(); return defaultValue(method); }); } /** 语句级执行:SQL 在执行时才出现,因此在这里才记录;探测类语句不回数据。 */ private java.sql.Statement plainStatement() { return (java.sql.Statement) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { java.sql.Statement.class }, (proxy, method, args) -> { if ("executeQuery".equals(method.getName()) || "execute".equals(method.getName()) || "executeUpdate".equals(method.getName())) { statements.add((String) args[0]); return resultSet(List.of()); } return defaultValue(method); }); } private PreparedStatement statement(String sql) { statements.add(sql); List parameters = new ArrayList<>(); return (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { PreparedStatement.class }, (proxy, method, args) -> { switch (method.getName()) { case "setObject", "setString", "setLong", "setInt" -> { int index = (Integer) args[0]; while (parameters.size() < index) parameters.add(null); parameters.set(index - 1, args[1]); return null; } case "executeQuery" -> { return resultSet(resolve(sql, parameters)); } case "close" -> { return null; } default -> { return defaultValue(method); } } }); } /** 按适配器使用的查询形态分派;参数顺序与适配器绑定顺序一致。 */ private List> resolve(String sql, List parameters) { String normalized = sql.toLowerCase().replaceAll("\\s+", " "); if (normalized.contains("from channel")) return channels.stream().sorted(Comparator.comparingLong(ChannelRow::devUid) .thenComparingInt(ChannelRow::unitId).thenComparingInt(ChannelRow::chlId)) .map(row -> { Map value = new LinkedHashMap<>(); value.put("dev_uid", row.devUid()); value.put("unit_id", row.unitId()); value.put("chl_id", row.chlId()); return value; }) .toList(); if (normalized.contains("from test") && normalized.contains("max(start_time)")) return latestTests(longValue(parameters, 0)); if (normalized.contains("from test")) return latestTestForChannel(longValue(parameters, 0), intValue(parameters, 1), intValue(parameters, 2)); if (normalized.contains("from cap_step")) return capStepRows(longValue(parameters, 0), longValue(parameters, 1), intValue(parameters, 2), intValue(parameters, 3)); if (normalized.contains("order by test_atime, seq_id limit")) return samplesAfter(tableOf(sql), parameters); return lastSampleOfTest(tableOf(sql), intValue(parameters, 0), intValue(parameters, 1), longValue(parameters, 2)); } private List> latestTests(long devUid) { Map latest = new LinkedHashMap<>(); tests.stream().filter(row -> row.devUid() == devUid) .sorted(Comparator.comparing(TestRow::startTime).reversed().thenComparingLong(TestRow::testId)) .forEach(row -> latest.putIfAbsent(row.unitId() + ":" + row.chlId(), row)); return latest.values().stream().sorted(Comparator.comparingInt(TestRow::unitId) .thenComparingInt(TestRow::chlId)).map(this::testColumns).toList(); } private List> latestTestForChannel(long devUid, int unitId, int chlId) { return tests.stream().filter(row -> row.devUid() == devUid && row.unitId() == unitId && row.chlId() == chlId) .sorted(Comparator.comparing(TestRow::startTime).reversed().thenComparingLong(TestRow::testId)) .limit(1).map(this::testColumns).toList(); } private List> lastSampleOfTest(String table, int unitId, int chlId, long testId) { return samples.stream().filter(row -> row.table().equals(table) && row.unitId() == unitId && row.chlId() == chlId && row.testId() == testId) .sorted(Comparator.comparingLong(SampleRow::seqId).reversed()).limit(1) .map(this::sampleColumns).toList(); } private List> samplesAfter(String table, List parameters) { int unitId = intValue(parameters, 0), chlId = intValue(parameters, 1); // 没有带时间的下界时(水位表示界时来源里没有行)返回该通道的全部行。 boolean bounded = parameters.size() >= 5; String watermark = bounded ? String.valueOf(parameters.get(2)) : null; long watermarkSeq = bounded ? longValue(parameters, 4) : 0L; return samples.stream().filter(row -> row.table().equals(table) && row.unitId() == unitId && row.chlId() == chlId && (!bounded || after(row.testAtime(), row.seqId(), watermark, watermarkSeq))) .sorted(Comparator.comparing(SampleRow::testAtime).thenComparingLong(SampleRow::seqId)) .map(this::sampleColumns).toList(); } private List> capStepRows(long testId, long devUid, int unitId, int chlId) { return capSteps.stream().filter(row -> row.testId() == testId && row.devUid() == devUid && row.unitId() == unitId && row.chlId() == chlId) .sorted(Comparator.comparingLong(CapStepRow::seqId)).map(this::capStepColumns).toList(); } private Map capStepColumns(CapStepRow row) { Map value = new LinkedHashMap<>(); value.put("seq_id", row.seqId()); value.put("cycle", row.cycle()); value.put("step_id", row.stepId()); value.put("step_type", row.stepType()); value.put("test_time", row.testTimeMillis()); value.put("test_vol", row.voltage()); value.put("test_cur", row.current()); value.put("test_capchg", row.capacityCharge()); value.put("test_engchg", row.energyCharge()); value.put("test_capdchg", row.capacityDischarge()); value.put("test_engdchg", row.energyDischarge()); value.put("middle_vol", row.middleVoltage()); value.put("test_atime", row.testAtime()); return value; } private static boolean after(String atime, long seqId, String watermark, long watermarkSeq) { int time = atime.compareTo(watermark); return time > 0 || (time == 0 && seqId > watermarkSeq); } private static String tableOf(String sql) { java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("from\\s+`([A-Za-z0-9_]+)`") .matcher(sql.toLowerCase()); if (!matcher.find()) throw new IllegalStateException("假厂商库无法识别分表名:" + sql); return matcher.group(1); } private Map testColumns(TestRow row) { Map value = new LinkedHashMap<>(); value.put("unit_id", row.unitId()); value.put("chl_id", row.chlId()); value.put("test_id", row.testId()); value.put("main_first_table", row.firstTable()); value.put("main_second_table", row.secondTable()); return value; } private Map sampleColumns(SampleRow row) { Map value = new LinkedHashMap<>(); value.put("test_id", row.testId()); value.put("seq_id", row.seqId()); value.put("cycle", row.cycle()); value.put("step_id", row.stepId()); value.put("step_type", row.stepType()); value.put("test_time", row.testTimeMillis()); value.put("test_vol", row.voltage()); value.put("test_cur", row.current()); value.put("test_capchg", row.capacityCharge()); value.put("test_engchg", row.energyCharge()); value.put("test_capdchg", row.capacityDischarge()); value.put("test_engdchg", row.energyDischarge()); value.put("test_atime", row.testAtime()); return value; } private static ResultSet resultSet(List> rows) { List labels = rows.isEmpty() ? List.of() : List.copyOf(rows.get(0).keySet()); int[] cursor = { -1 }; return (ResultSet) Proxy.newProxyInstance(FakeVendorDatabase.class.getClassLoader(), new Class[] { ResultSet.class }, (proxy, method, args) -> { switch (method.getName()) { case "next" -> { cursor[0]++; return cursor[0] < rows.size(); } case "close" -> { return null; } case "getLong", "getInt", "getString", "getBigDecimal" -> { Object value = value(rows.get(cursor[0]), labels, args[0]); if (value == null) return null; return switch (method.getName()) { case "getLong" -> ((Number) value).longValue(); case "getInt" -> ((Number) value).intValue(); case "getString" -> String.valueOf(value); default -> value instanceof BigDecimal decimal ? decimal : BigDecimal.valueOf(((Number) value).longValue()); }; } default -> { return defaultValue(method); } } }); } private static Object value(Map row, List labels, Object key) { if (key instanceof Integer index) return row.get(labels.get(index - 1)); return row.get(((String) key).toLowerCase()); } private static int intValue(List parameters, int index) { return ((Number) parameters.get(index)).intValue(); } private static long longValue(List parameters, int index) { return ((Number) parameters.get(index)).longValue(); } private static Object defaultValue(Method method) { Class type = method.getReturnType(); if (!type.isPrimitive() || type == void.class) return null; if (type == boolean.class) return false; if (type == long.class) return 0L; if (type == double.class) return 0d; if (type == float.class) return 0f; if (type == char.class) return (char) 0; return 0; } }