yeg
2026-09-29 b78295e8da96c3e5e76befeef9582ce5273dbb74
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package com.battery.business.web;
 
import static org.junit.jupiter.api.Assertions.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.DriverManager;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson2.JSON;
import com.battery.business.domain.TTestDataRevisionAction;
import com.battery.business.mapper.TTestMapper;
import com.battery.business.service.ITestConsoleDataService;
import com.battery.business.service.impl.TestConsoleDataServiceImpl;
import com.battery.business.service.impl.TTestServiceImpl;
import com.battery.business.service.impl.TestDataQueryServiceImpl;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
import org.flywaydb.core.Flyway;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
 
/** 仅在脚本提供的随机隔离库内验证真实 MySQL 迁移、事务和甲方全量样例。 */
@EnabledIfEnvironmentVariable(named = "WORKBOOK_TEST_SCHEMA", matches = "ticket04_import_[a-f0-9]{32}")
class ConsoleWorkbookImportMysqlTest
{
    @Test void fullWorkbookMigrationTransactionRevisionAndWarningIsolation() throws Exception
    {
        String schema = System.getenv("WORKBOOK_TEST_SCHEMA");
        String user = System.getenv("WORKBOOK_TEST_USER"), password = System.getenv("WORKBOOK_TEST_PASSWORD");
        String base = "jdbc:mysql://127.0.0.1:6134/";
        String options = "?useSSL=false&allowPublicKeyRetrieval=true&connectionTimeZone=UTC";
        try (var admin = DriverManager.getConnection(base + options, user, password); var sql = admin.createStatement())
        {
            sql.execute("create database " + schema + " character set utf8mb4 collate utf8mb4_0900_ai_ci");
            try
            {
                String url = base + schema + options;
                var configuration = Flyway.configure().dataSource(url, user, password).locations("classpath:db/migration-final").cleanDisabled(true);
                configuration.target("8").load().migrate();
                var flyway = configuration.target("9").load();
                assertEquals(1, flyway.migrate().migrationsExecuted);
                assertEquals(0, flyway.migrate().migrationsExecuted);
                var source = new UnpooledDataSource("com.mysql.cj.jdbc.Driver", url, user, password);
                var jdbc = new JdbcTemplate(source);
                jdbc.execute("insert into t_test(test_id,test_name,creator_id,principal_id,test_type_id,test_purpose,test_status,reception_status) values(1,'三 Sheet 验收',1,1,1,'隔离测试','COMPLETED','ENDED')");
                jdbc.execute("insert into t_detection_cabinet(cabinet_id,adapter_code,cabinet_code,cabinet_name,vendor_device_type,vendor_device_id) values(1,'adapter-a','CAB1','检测柜',27,1)");
                jdbc.execute("insert into t_detection_channel(channel_id,adapter_code,cabinet_id,devtype,devid,subdevid,chlid) values(11,'adapter-a',1,27,1,1,2)");
                jdbc.execute("insert into t_test_battery(test_battery_id,test_id,group_id,battery_no,collector_id) values(4,1,1,'BAT-1',1)");
                jdbc.execute("insert into t_test_channel_binding(binding_id,test_id,test_battery_id,channel_id) values(5,1,4,11)");
                jdbc.execute("insert into t_test_warning_threshold(test_id,metric_code,max_value,unit,severity) values(1,'TEMPERATURE',1,'C','WARNING')");
                var factory = new SqlSessionFactoryBean(); factory.setDataSource(source);
                factory.setTypeAliasesPackage("com.battery.business.domain");
                factory.setMapperLocations(new ClassPathResource("mapper/battery/TTestMapper.xml"));
                var mapper = new SqlSessionTemplate(factory.getObject()).getMapper(TTestMapper.class);
                var proxy = new ProxyFactory(new TestConsoleDataServiceImpl(mapper));
                proxy.addAdvice(new TransactionInterceptor(new DataSourceTransactionManager(source), new AnnotationTransactionAttributeSource()));
                var service = (ITestConsoleDataService) proxy.getProxy();
                var controller = new TTestController() {
                    @Override public Long getUserId() { return 1L; }
                    @Override public String getUsername() { return "验收操作员"; }
                };
                ReflectionTestUtils.setField(controller, "consoleDataService", service);
                var tests = new TTestServiceImpl();
                ReflectionTestUtils.setField(tests, "testMapper", mapper);
                ReflectionTestUtils.setField(controller, "testDataQueryService", new TestDataQueryServiceImpl(mapper, tests));
                var sample = JSON.parseObject(Files.readString(Path.of(System.getenv("TICKET03_SAMPLE_JSON"))));
                Map<String, List<Map<String, Object>>> sheets = new LinkedHashMap<>();
                for (String type : List.of("REALTIME", "STEP", "CYCLE")) sheets.put(type, (List) sample.getJSONArray(type).toJavaList(Map.class));
                // 在最后一类写入制造数据库错误,确认前两类和修订一起回滚。
                jdbc.execute("alter table t_test_cycle_record add constraint workbook_probe_failure check (cycle_no <> 2)");
                assertThrows(RuntimeException.class, () -> submit(controller, sheets));
                assertTrue(mapper.selectPersistedFinalRealtimeRecords(1L).isEmpty());
                assertTrue(mapper.selectPersistedFinalStepRecords(1L).isEmpty());
                assertTrue(mapper.selectPersistedFinalCycleRecords(1L).isEmpty());
                assertTrue(service.selectActions(new TTestDataRevisionAction()).isEmpty());
                jdbc.execute("alter table t_test_cycle_record drop constraint workbook_probe_failure");
                var imported = submit(controller, sheets);
                assertEquals(7767, imported.get("importedCount"));
                assertEquals(7605, mapper.selectPersistedFinalRealtimeRecords(1L).size());
                assertEquals(129, mapper.selectPersistedFinalStepRecords(1L).size());
                assertEquals(33, mapper.selectPersistedFinalCycleRecords(1L).size());
                // 通过正式查询入口验证真实 JDBC 时间类型,避免内存库时间类型掩盖接口偏差。
                var json = tools.jackson.databind.json.JsonMapper.builder().findAndAddModules().build();
                for (var result : List.of(controller.persistedRecords(1L), controller.persistedSteps(1L), controller.persistedCycles(1L)))
                {
                    var rows = json.readTree(json.writeValueAsString(result)).get("data");
                    for (var row : rows) for (String field : List.of("deviceTime", "recordedAt", "absoluteTime", "serverReceivedAt"))
                    {
                        var value = row.get(field);
                        if (value != null && !value.isNull()) assertTrue(value.isIntegralNumber(), field + ": " + value);
                    }
                }
                var firstRecord = json.readTree(json.writeValueAsString(controller.persistedRecords(1L))).get("data").get(7604);
                assertEquals(1683517331000L, firstRecord.get("deviceTime").longValue());
                var actions = service.selectActions(new TTestDataRevisionAction());
                assertEquals(3, actions.size());
                assertEquals(3, ((List<?>) imported.get("actionIds")).size());
                for (var item : actions) {
                    var revision = service.selectActionById(item.getActionId());
                    assertEquals("三 Sheet 共用原因", revision.getReason());
                    int expected = switch (revision.getDataType()) { case "REALTIME" -> 7605; case "STEP" -> 129; default -> 33; };
                    assertEquals((long) expected, revision.getAffectedRecordCount());
                    assertNull(revision.getIdentitySummary());
                    assertNull(revision.getChangesSummary());
                    assertNull(revision.getBeforeContentJson());
                    // 页面只返回各类摘要,数据库仍保留该类全部导入明细。
                    var changes = JSON.parseArray(jdbc.queryForObject("select cast(changes_summary as char) from t_test_data_revision_action where action_id=?", String.class, revision.getActionId()));
                    assertEquals(expected, changes.size());
                }
                for (String type : sheets.keySet()) {
                    var query = new TTestDataRevisionAction(); query.setTestId(1L); query.setDataType(type);
                    assertEquals(1, service.selectActions(query).size());
                }
                var retry = submit(controller, sheets);
                assertEquals(0, retry.get("importedCount")); assertEquals(7767, retry.get("skippedCount")); assertNull(retry.get("actionId"));
                sheets.get("CYCLE").get(32).put("dischargeCapacity", 99);
                assertThrows(RuntimeException.class, () -> submit(controller, sheets));
                assertEquals(3, service.selectActions(new TTestDataRevisionAction()).size());
                assertTrue(mapper.selectWarningExceptions(1L, null).isEmpty());
                assertEquals("COMPLETED", mapper.selectTestById(1L).getTestStatus());
                assertEquals("ENDED", mapper.selectTestById(1L).getReceptionStatus());
            }
            finally { sql.execute("drop database " + schema); }
        }
    }
 
    private com.ruoyi.common.core.domain.AjaxResult submit(TTestController controller, Map<String, List<Map<String, Object>>> sheets)
    {
        return controller.dataAction(1L, new TTestController.ConsoleDataActionRequest("ALL", "IMPORT", "三 Sheet 导入", "[]",
                JSON.toJSONString(sheets), "三 Sheet 共用原因", List.of()));
    }
}