From b78295e8da96c3e5e76befeef9582ce5273dbb74 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期二, 29 九月 2026 13:10:23 +0800
Subject: [PATCH] 按代码规范检查整改实体注释、Service Javadoc 与分层边界

---
 battery-business/src/main/java/com/battery/business/domain/TBatteryGroup.java                                |   21 +
 battery-business/src/main/java/com/battery/business/domain/TTestConsoleDetail.java                           |    4 
 ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java                                      |    7 
 battery-business/src/main/java/com/battery/business/domain/vo/DiscoveredCabinetVo.java                       |   10 
 battery-business/src/main/java/com/battery/business/domain/vo/TestBatteryOptionVo.java                       |    5 
 battery-business/src/main/java/com/battery/business/service/TodoService.java                                 |   14 
 ruoyi-admin/src/main/java/com/battery/business/web/TodoController.java                                       |    6 
 battery-business/src/main/resources/mapper/battery/TTestMapper.xml                                           |    1 
 battery-device-api/src/main/java/com/battery/device/api/model/DeviceIdentity.java                            |    1 
 battery-business/src/main/java/com/battery/business/service/impl/RoutingTestDataObservationAdapter.java      |   61 +++
 battery-device-bts/src/main/java/com/battery/device/bts/BtsTransportException.java                           |    1 
 battery-business/src/main/resources/mapper/battery/TDeviceIngestRecordMapper.xml                             |   10 
 battery-device-api/src/main/java/com/battery/device/api/model/DeviceWorkStatus.java                          |   17 
 battery-business/src/main/java/com/battery/business/service/impl/TestDataReceptionServiceImpl.java           |   73 +++
 battery-business/src/main/java/com/battery/business/domain/vo/TDeviceIngestChannel.java                      |    5 
 ruoyi-admin/src/main/java/com/battery/business/runtime/RedisBtsSimulatorStateStore.java                      |    5 
 battery-business/src/main/java/com/battery/business/domain/vo/OfflineTestSource.java                         |    7 
 battery-business/src/main/java/com/battery/business/battery/dto/BatteryGroupDto.java                         |    1 
 battery-device-bts/src/main/java/com/battery/device/bts/BtsXmlCodec.java                                     |    4 
 ruoyi-admin/src/main/java/com/battery/business/runtime/DeviceAdapterRuntimeConfig.java                       |    1 
 battery-business/src/main/java/com/battery/business/domain/TTestStepRecord.java                              |   53 ++
 battery-business/src/main/java/com/battery/business/domain/TDeviceIngestRecord.java                          |   25 +
 battery-device-api/src/main/java/com/battery/device/api/registry/DeviceAdapterRegistry.java                  |    2 
 battery-business/src/main/java/com/battery/business/domain/TDetectionCabinet.java                            |    4 
 ruoyi-admin/src/main/java/com/battery/business/web/TBatteryGroupController.java                              |    7 
 ruoyi-admin/src/main/java/com/battery/business/web/TestLiveStream.java                                       |   18 
 battery-business/src/main/java/com/battery/business/domain/TTestLogicStepMapping.java                        |    4 
 ruoyi-admin/src/main/java/com/battery/business/runtime/FlywayMigrationConfig.java                            |    1 
 battery-device-api/src/main/java/com/battery/device/api/model/ChannelIdentity.java                           |    1 
 battery-business/src/main/java/com/battery/business/domain/TBatteryGroupRelationField.java                   |   10 
 battery-business/src/main/java/com/battery/business/domain/TCollector.java                                   |    2 
 ruoyi-admin/src/main/java/com/battery/business/runtime/RedisDeviceDiscoveryCandidateStore.java               |    8 
 ruoyi-admin/src/main/java/com/battery/business/web/TTestWarningExceptionController.java                      |   22 
 battery-business/src/main/java/com/battery/business/domain/vo/TestChannelOptionVo.java                       |   10 
 ruoyi-admin/src/test/java/com/battery/business/service/TodoServiceTest.java                                  |   10 
 battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterRuntimeCoordinator.java        |    2 
 ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentSimulatorConfig.java                       |    6 
 battery-business/src/main/java/com/battery/business/service/impl/DeviceIngestServiceImpl.java                |   16 
 battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapter.java                      |    9 
 battery-device-bts/src/main/java/com/battery/device/bts/BtsTcpTransport.java                                 |    6 
 battery-device-neware/src/main/java/com/battery/device/neware/NewareStepDirectionResolver.java               |    1 
 battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapter.java                                |    5 
 battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java                 |    2 
 battery-device-api/src/main/java/com/battery/device/api/model/DetectedDeviceMetadata.java                    |    1 
 ruoyi-admin/src/main/java/com/battery/business/web/TTestDataRevisionController.java                          |    2 
 battery-business/src/main/java/com/battery/business/domain/TTest.java                                        |   38 +
 ruoyi-admin/src/main/java/com/battery/business/runtime/BtsDevelopmentSimulatorWebConfig.java                 |    1 
 battery-device-api/src/main/java/com/battery/device/api/model/DeviceObservation.java                         |    1 
 battery-device-bts/src/main/java/com/battery/device/bts/BtsProtocolException.java                            |    1 
 battery-business/src/main/java/com/battery/business/domain/TTestWarningThreshold.java                        |   10 
 battery-business/src/main/java/com/battery/business/service/impl/TTestServiceImpl.java                       |  157 +++++++
 battery-business/src/main/java/com/battery/business/domain/TTestWarningException.java                        |   25 +
 battery-device-neware/src/main/java/com/battery/device/neware/SingleConnectionVendorDataSource.java          |    5 
 battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java             |    4 
 ruoyi-admin/src/main/java/com/battery/business/runtime/TestDataReceptionScheduler.java                       |    1 
 battery-business/src/main/java/com/battery/business/domain/TBatteryGroupBattery.java                         |    7 
 battery-business/src/main/java/com/battery/business/domain/vo/TestUserOptionVo.java                          |    3 
 ruoyi-admin/src/test/java/com/battery/business/service/impl/TestWarningExceptionServiceTest.java             |   14 
 battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapterFactory.java                         |    2 
 battery-business/src/main/java/com/battery/business/domain/TBatteryGroupField.java                           |    2 
 battery-business/src/main/java/com/battery/business/domain/TTestChannelBinding.java                          |   21 +
 battery-business/src/main/java/com/battery/business/domain/TTestRealtimeRecord.java                          |   42 ++
 battery-business/src/main/java/com/battery/business/domain/vo/AdapterDiscoveryResult.java                    |    8 
 battery-business/src/main/java/com/battery/business/domain/DataRevisionItem.java                             |    2 
 battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java                      |   20 
 battery-device-api/src/main/java/com/battery/device/api/model/AdapterCapabilities.java                       |    1 
 battery-business/src/main/java/com/battery/business/domain/TDeviceAdapter.java                               |   18 
 battery-business/src/main/java/com/battery/business/domain/vo/DeviceRelatedTestVo.java                       |    4 
 battery-business/src/main/java/com/battery/business/domain/vo/TestConfigurationOptionsVo.java                |    7 
 battery-device-api/src/main/java/com/battery/device/api/discovery/InMemoryDeviceDiscoveryCandidateStore.java |    4 
 battery-device-neware/src/main/java/com/battery/device/neware/ReadOnlyVendorDataSource.java                  |    6 
 battery-business/src/main/java/com/battery/business/domain/TDetectionChannel.java                            |   18 
 battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapterFactory.java               |    7 
 battery-business/src/main/java/com/battery/business/domain/CurrentBatteryField.java                          |    7 
 battery-business/src/main/java/com/battery/business/domain/vo/TestCollectorOptionVo.java                     |    4 
 battery-business/src/main/java/com/battery/business/domain/TTestBattery.java                                 |   13 
 battery-business/src/main/java/com/battery/business/service/impl/TestWarningExceptionService.java            |   78 +++
 battery-device-simulator/src/main/java/com/battery/device/simulator/ScriptedBtsTcpSimulator.java             |   27 +
 battery-business/src/main/java/com/battery/business/domain/TTestCollectorChannel.java                        |    9 
 battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapter.java                        |    3 
 battery-device-simulator/src/main/java/com/battery/device/simulator/InMemoryBtsSimulatorStateStore.java      |    5 
 battery-business/src/main/java/com/battery/business/domain/TTestCycleRecord.java                             |   50 ++
 battery-business/src/main/java/com/battery/business/service/impl/TBatteryGroupTemplateServiceImpl.java       |   36 +
 battery-business/src/main/java/com/battery/business/test/dto/TestBatteryBindingDto.java                      |    2 
 battery-business/src/main/java/com/battery/business/domain/vo/TestTypeOptionVo.java                          |    2 
 battery-device-bts/src/test/java/com/battery/device/bts/ScriptedBtsTcpSimulator.java                         |   19 
 86 files changed, 1,080 insertions(+), 85 deletions(-)

diff --git a/battery-business/src/main/java/com/battery/business/battery/dto/BatteryGroupDto.java b/battery-business/src/main/java/com/battery/business/battery/dto/BatteryGroupDto.java
index 6713cff..1f15fd4 100644
--- a/battery-business/src/main/java/com/battery/business/battery/dto/BatteryGroupDto.java
+++ b/battery-business/src/main/java/com/battery/business/battery/dto/BatteryGroupDto.java
@@ -19,6 +19,7 @@
             List<String> batteryNos, List<FieldValueDto> fields) {
         this(groupId, groupName, model, purpose, status, batteryNos, fields, null, null, null, null, null, null, null, null);
     }
+    /** 电池组当前字段值;兼容只传模板字段关系 templateFieldRelations 的调用方。 */
     public List<FieldValueDto> currentFields() { return fields != null ? fields : templateFieldRelations; }
     public record FieldValueDto(Long fieldId, String value, Long batteryGroupFieldId, String fieldValue) {
         public FieldValueDto(Long fieldId, String value) { this(fieldId, value, null, null); }
diff --git a/battery-business/src/main/java/com/battery/business/domain/CurrentBatteryField.java b/battery-business/src/main/java/com/battery/business/domain/CurrentBatteryField.java
index cb2b19f..12f0da9 100644
--- a/battery-business/src/main/java/com/battery/business/domain/CurrentBatteryField.java
+++ b/battery-business/src/main/java/com/battery/business/domain/CurrentBatteryField.java
@@ -9,15 +9,22 @@
 {
     private static final long serialVersionUID = 1L;
 
+    /** 字段主键,创建测试时与 fieldDefinitionId 同值写入。 */
     private Long fieldId;
+    /** 旧页面入参别名;本对象以 fieldId 为准。 */
     private Long relationId;
+    /** 字段定义主键,指向电池组的业务自定义字段定义。 */
     private Long fieldDefinitionId;
+    /** 字段类型:text 文本、number 数字、time 时间、remark 备注;决定前端输入控件形态。 */
     private String fieldType;
+    /** 扩展项名称。 */
     @NotBlank(message = "扩展项名称不能为空")
     @Size(max = 100, message = "扩展项名称长度不能超过100个字符")
     private String fieldName;
+    /** 扩展项值,电池组的当前值;测试直接读取,不另存快照。 */
     @Size(max = 500, message = "扩展项值长度不能超过500个字符")
     private String fieldValue;
+    /** 逻辑删除标识:0存在,其他值表示已删除。 */
     private String delFlag;
 
     public Long getFieldId() { return fieldId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/DataRevisionItem.java b/battery-business/src/main/java/com/battery/business/domain/DataRevisionItem.java
index e4a2c4e..bf4b215 100644
--- a/battery-business/src/main/java/com/battery/business/domain/DataRevisionItem.java
+++ b/battery-business/src/main/java/com/battery/business/domain/DataRevisionItem.java
@@ -3,7 +3,9 @@
 /** 一次修订提交的目标和修改值,不是持久化覆盖记录。 */
 public class DataRevisionItem
 {
+    /** 目标记录的稳定身份,用于在测试数据表中定位同一条记录。 */
     private String stableIdentity;
+    /** 本次要写入的字段与值的 JSON。 */
     private String fieldValues;
     public String getStableIdentity() { return stableIdentity; }
     public void setStableIdentity(String value) { stableIdentity = value; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TBatteryGroup.java b/battery-business/src/main/java/com/battery/business/domain/TBatteryGroup.java
index 97dd171..5885385 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TBatteryGroup.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TBatteryGroup.java
@@ -8,30 +8,49 @@
 import jakarta.validation.constraints.Size;
 import com.ruoyi.common.core.domain.BaseEntity;
 
-/** 电池组主档对象。 */
+/** 电池组主档对象,对应表 t_battery_group;电池号不能脱离所属电池组参与测试。 */
 public class TBatteryGroup extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
+    /** 电池组主键。 */
     private Long groupId;
+    /** 电池组名称。 */
     @NotBlank(message = "电池组名称不能为空") @Size(max = 100, message = "电池组名称长度不能超过100个字符")
     private String groupName;
+    /** 电池组型号。 */
     @NotBlank(message = "电池组型号不能为空") @Size(max = 100, message = "电池组型号长度不能超过100个字符")
     private String model;
+    /** 电池组用途。 */
     @Size(max = 500, message = "用途长度不能超过500个字符") private String purpose;
+    /** 化成报表公式用参数 Z。 */
     @Size(max = 100, message = "z字段长度不能超过100个字符") private String z;
+    /** 化成报表公式用参数 H。 */
     @Size(max = 100, message = "h字段长度不能超过100个字符") private String h;
+    /** 化成报表公式用参数 F,全部报表版本共用同一份定义。 */
     @Size(max = 100, message = "f字段长度不能超过100个字符") private String f;
+    /** 化成报表公式用参数 A。 */
     @Size(max = 100, message = "a字段长度不能超过100个字符") private String a;
+    /** 化成报表公式用参数 B。 */
     @Size(max = 100, message = "b字段长度不能超过100个字符") private String b;
+    /** 启用状态:0正常,1停用;已被测试引用的电池组不能删除。 */
     private String status;
+    /** 逻辑删除标识:0存在,其他值表示已删除。 */
     private String delFlag;
+    /** 查询条件:创建时间起始值。 */
     private Date beginCreateTime;
+    /** 查询条件:创建时间结束值。 */
     private Date endCreateTime;
+    /** 本电池组的电池号列表;新增或编辑时整体替换。 */
     @NotEmpty(message = "电池号不能为空") private List<String> batteryNos;
+    /** 所选字段模板主键。 */
     private Long templateId;
+    /** 模板字段关系,落库为电池组自己的字段值。 */
     private List<TBatteryGroupRelationField> templateFieldRelations;
+    /** 电池号关系列表,含排序号。 */
     private List<TBatteryGroupBattery> batteries;
+    /** 查询结果:电池号数量,由查询汇总得出。 */
     private Integer batteryCount;
+    /** 电池组字段值;templateFields 为现有页面兼容名称。 */
     private List<TBatteryGroupRelationField> templateFields;
     /** 电池组当前字段值;templateFields 为现有页面兼容名称。 */
     private List<TBatteryGroupRelationField> fields;
diff --git a/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupBattery.java b/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupBattery.java
index 1677c8c..596cb44 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupBattery.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupBattery.java
@@ -2,14 +2,19 @@
 
 import java.io.Serializable;
 
-/** 电池组与电池号关系对象。 */
+/** 电池组与电池号关系对象,对应表 t_battery_group_battery;电池号在组内业务唯一。 */
 public class TBatteryGroupBattery implements Serializable
 {
     private static final long serialVersionUID = 1L;
+    /** 电池号关系主键。 */
     private Long relationId;
+    /** 所属电池组主键。 */
     private Long groupId;
+    /** 电池号,在所属电池组内业务唯一。 */
     private String batteryNo;
+    /** 组内展示顺序,从 1 起。 */
     private Integer sortOrder;
+    /** 逻辑删除标识:0存在,其他值表示已删除。 */
     private String delFlag;
     public Long getRelationId() { return relationId; } public void setRelationId(Long v) { relationId = v; }
     public Long getGroupId() { return groupId; } public void setGroupId(Long v) { groupId = v; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupField.java b/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupField.java
index c8879af..b440541 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupField.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupField.java
@@ -23,7 +23,7 @@
     @Size(max = 100, message = "字段名称长度不能超过100个字符")
     private String fieldName;
 
-    /** 字段类型编码 */
+    /** 字段类型编码:text 文本、number 数字、time 时间、remark 备注 */
     @NotBlank(message = "字段类型不能为空")
     private String fieldType;
 
diff --git a/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupRelationField.java b/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupRelationField.java
index 36e552a..76394b4 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupRelationField.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TBatteryGroupRelationField.java
@@ -2,17 +2,25 @@
 
 import java.io.Serializable;
 
-/** 电池组与模板字段关系对象。 */
+/** 电池组与模板字段关系对象,对应表 t_battery_group_relation_field;保存该电池组自己的字段值。 */
 public class TBatteryGroupRelationField implements Serializable
 {
     private static final long serialVersionUID = 1L;
+    /** 关系主键。 */
     private Long relationId;
+    /** 所属电池组主键。 */
     private Long groupId;
+    /** 来源字段模板主键。 */
     private Long templateId;
+    /** 引用的业务自定义字段定义主键,创建后不再变更。 */
     private Long batteryGroupFieldId;
+    /** 字段名称,取自字段定义,测试与报表直接读取。 */
     private String fieldName;
+    /** 字段类型:text 文本、number 数字、time 时间、remark 备注;决定前端输入控件形态。 */
     private String fieldType;
+    /** 该电池组的字段值;只保存当前值,不形成测试字段快照。 */
     private String fieldValue;
+    /** 逻辑删除标识:0存在,其他值表示已删除。 */
     private String delFlag;
     public Long getRelationId() { return relationId; } public void setRelationId(Long v) { relationId = v; }
     public Long getGroupId() { return groupId; } public void setGroupId(Long v) { groupId = v; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TCollector.java b/battery-business/src/main/java/com/battery/business/domain/TCollector.java
index e610f70..4196760 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TCollector.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TCollector.java
@@ -73,7 +73,7 @@
     @Max(value = 10000, message = "通道数不能大于10000")
     private Integer channelCount;
 
-    /** 在线状态:0离线、1在线 */
+    /** 接口在线状态:ONLINE 在线、OFFLINE 离线;数据库约束只允许这两个值。 */
     private String onlineStatus;
 
     /** 进行中测试关联标记,供启禁用和删除校验使用,不写入采集器表。 */
diff --git a/battery-business/src/main/java/com/battery/business/domain/TDetectionCabinet.java b/battery-business/src/main/java/com/battery/business/domain/TDetectionCabinet.java
index f784ed2..3467f8b 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TDetectionCabinet.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TDetectionCabinet.java
@@ -28,13 +28,17 @@
 
     /** 所属设备适配器实例。 */
     private Long adapterId;
+    /** 所属设备适配器实例编码,与厂商字段共同构成通道身份。 */
     private String adapterCode;
 
     /** 数据来源名称,仅供展示,用于区分不同机器上的同名柜号;不落库。 */
     private String adapterName;
 
+    /** 厂商设备类型。 */
     private Integer vendorDeviceType;
+    /** 厂商设备号。 */
     private Integer vendorDeviceId;
+    /** 最近一次观测到该设备的时间,以 UTC 保存。 */
     private Instant lastSeenAtUtc;
 
     /** 登记状态:新记录只使用 REGISTERED;DISCOVERED 仅兼容有业务引用的历史记录。 */
diff --git a/battery-business/src/main/java/com/battery/business/domain/TDetectionChannel.java b/battery-business/src/main/java/com/battery/business/domain/TDetectionChannel.java
index c8e598d..b1fef64 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TDetectionChannel.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TDetectionChannel.java
@@ -3,23 +3,39 @@
 import java.time.Instant;
 import com.ruoyi.common.core.domain.BaseEntity;
 
-/** 检测柜通道对象 t_detection_channel。 */
+/** 检测柜通道对象 t_detection_channel,是测试数据归属的最小来源单位。 */
 public class TDetectionChannel extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
 
+    /** 通道主键。 */
     private Long channelId;
+    /** 所属设备适配器实例主键。 */
     private Long adapterId;
+    /** 所属设备适配器实例编码。 */
     private String adapterCode;
+    /** 所属检测柜主键。 */
     private Long cabinetId;
+    /** 厂商设备类型;与下方三个字段共同构成通道身份。 */
     private Integer devtype;
+    /** 厂商设备号。 */
     private Integer devid;
+    /** 厂商单元号。 */
     private Integer subdevid;
+    /** 厂商通道号。 */
     private Integer chlid;
+    /** 通道展示编号,仅供界面展示,不能代替通道身份。 */
     private String displayNo;
+    /** 设备在线状态:ONLINE 在线、OFFLINE 离线;由设备来源可达性观测,不能被测试动作改写。 */
     private String onlineStatus;
+    /**
+     * 通道工作状态:RUNNING 运行、PAUSED 暂停、ABNORMAL 异常、ENDED 结束、UNOBSERVABLE 无法观测。
+     * 读库路线下设备不提供可用状态,离线或读取失败时一律为 UNOBSERVABLE,不沿用上一次状态,也不由数据行推断。
+     */
     private String workStatus;
+    /** 最近一次观测到该通道的时间,以 UTC 保存。 */
     private Instant lastSeenAtUtc;
+    /** 启用状态:0启用,1停用;开始接收时通道必须存在且启用。 */
     private String status;
 
     public Long getChannelId() { return channelId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TDeviceAdapter.java b/battery-business/src/main/java/com/battery/business/domain/TDeviceAdapter.java
index ed700db..b2286a0 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TDeviceAdapter.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TDeviceAdapter.java
@@ -9,57 +9,73 @@
 import jakarta.validation.constraints.Size;
 import com.ruoyi.common.core.domain.BaseEntity;
 
-/** 设备适配器实例对象 t_device_adapter。 */
+/** 设备适配器实例对象 t_device_adapter;实例按「机器 + 上位机版本」划分,不能按厂商划分。 */
 public class TDeviceAdapter extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
 
+    /** 适配器实例主键。 */
     private Long adapterId;
 
+    /** 适配器实例编码,与厂商通道字段共同构成通道身份。 */
     @NotBlank(message = "适配器编码不能为空")
     @Size(max = 64, message = "适配器编码长度不能超过64个字符")
     private String adapterCode;
 
+    /** 适配器实例名称,用于区分不同机器上的同名设备,仅供展示。 */
     @NotBlank(message = "适配器名称不能为空")
     @Size(max = 100, message = "适配器名称长度不能超过100个字符")
     private String adapterName;
 
+    /** 适配器类型,决定使用哪一套来源实现:BTS 协议客户端、NEWARE_MYSQL 新威读库、PUSH_INGEST 读落地表。 */
     @NotBlank(message = "适配器类型不能为空")
     private String adapterType;
 
+    /** 来源主机地址。 */
     @NotBlank(message = "主机不能为空")
     @Size(max = 255, message = "主机长度不能超过255个字符")
     private String host;
 
+    /** 来源端口。 */
     @NotNull(message = "端口不能为空")
     @Min(value = 1, message = "端口必须在1到65535之间")
     @Max(value = 65535, message = "端口必须在1到65535之间")
     private Integer port;
 
+    /** 轮询周期(毫秒);设备来源约 60 秒才产生一行数据,取数不必更快。 */
     @NotNull(message = "轮询周期不能为空")
     @Min(value = 1000, message = "轮询周期不能小于1000毫秒")
     private Integer pollIntervalMs;
 
+    /** 适配器时区标识,用于把设备时间解释为 UTC。 */
     @NotBlank(message = "时区不能为空")
     @Size(max = 64, message = "时区长度不能超过64个字符")
     private String timezoneId;
 
+    /** 只读能力标记,只允许为真:第一版只观测设备,不下发控制命令。 */
     @NotNull(message = "只读能力不能为空")
     @AssertTrue(message = "当前里程碑只允许只读设备适配器")
     private Boolean readOnly;
 
+    /** 适配器声明的能力标记,运行时控制能力默认为不支持。 */
     private String capabilities;
 
     /** 来源形态配置(JSON),承载各存储形态互不相交的非敏感字段;连接口令不入库。 */
     private String sourceConfig;
 
+    /** 启用状态:0启用,1停用。 */
     private String status;
     /** 适配器连接状态,仅允许 ONLINE/OFFLINE。 */
     private String connectionStatus;
+    /** 最近一次连接失败的错误码,供现场排查使用。 */
     private String lastErrorCode;
+    /** 最近一次连接失败的错误说明,供现场排查使用。 */
     private String lastErrorMessage;
+    /** 最近一次尝试连接的时间,以 UTC 保存。 */
     private Instant lastAttemptAtUtc;
+    /** 最近一次成功观测到设备事实的时间,以 UTC 保存。 */
     private Instant lastObservedAtUtc;
+    /** 逻辑删除标识:0存在,其他值表示已删除。 */
     private String delFlag;
 
     public Long getAdapterId() { return adapterId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TDeviceIngestRecord.java b/battery-business/src/main/java/com/battery/business/domain/TDeviceIngestRecord.java
index a01b650..b770c53 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TDeviceIngestRecord.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TDeviceIngestRecord.java
@@ -20,30 +20,55 @@
     /** 载荷类别:存活心跳,只刷新"取数程序还在推"这一条事实。 */
     public static final String KIND_HEARTBEAT = "HEARTBEAT";
 
+    /** 落地行主键。 */
     private Long ingestId;
+    /** 推送来源所属的适配器实例编码。 */
     private String adapterCode;
+    /** 载荷类别:TOPOLOGY 拓扑上报、SAMPLE 逐点数据行、HEARTBEAT 存活心跳。 */
     private String payloadKind;
+    /** 厂商设备类型。 */
     private Integer devtype;
+    /** 厂商设备号。 */
     private Integer devid;
+    /** 厂商单元号。 */
     private Integer subdevid;
+    /** 厂商通道号。 */
     private Integer chlid;
+    /** 来源协议序号,工步切换点可在同一绝对秒内出现多条。 */
     private Long recordSequence;
+    /** 来源行身份,重复推送由它幂等忽略。 */
     private String recordKey;
+    /** 来源侧引用标识,如测试或来源记录编号,用于现场核对。 */
     private String sourceRef;
+    /** 设备时间,按来源时区解释后以 UTC 保存。 */
     private Date deviceTimeUtc;
+    /** 设备时间原始文本,保留厂商原值。 */
     private String deviceTimeRaw;
+    /** 解释设备时间所用的时区标识。 */
     private String timezoneId;
+    /** 设备实际循环号。 */
     private Integer cycleNo;
+    /** 设备实际工步号。 */
     private Integer stepNo;
+    /** 工步内相对时长,单位秒;由成功观测提供,不以接收时间推算。 */
     private BigDecimal relativeTimeSeconds;
+    /** 电压,业务单位 V。 */
     private BigDecimal voltageV;
+    /** 电流,业务单位 A。 */
     private BigDecimal currentA;
+    /** 充电容量,业务单位 Ah。 */
     private BigDecimal chargeCapacityAh;
+    /** 放电容量,业务单位 Ah。 */
     private BigDecimal dischargeCapacityAh;
+    /** 充电能量,业务单位 Wh。 */
     private BigDecimal chargeEnergyWh;
+    /** 放电能量,业务单位 Wh。 */
     private BigDecimal dischargeEnergyWh;
+    /** 厂商原始行内容,按收到的键值原样保存,便于回溯来源事实。 */
     private String payloadJson;
+    /** 记录内容摘要,用于重复推送的幂等与冲突判定。 */
     private byte[] contentSha256;
+    /** 服务器收到该行的时间,以 UTC 保存。 */
     private Date receivedAtUtc;
 
     public Long getIngestId() { return ingestId; } public void setIngestId(Long v) { ingestId = v; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTest.java b/battery-business/src/main/java/com/battery/business/domain/TTest.java
index c558192..ed4901d 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTest.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTest.java
@@ -12,56 +12,92 @@
 import jakarta.validation.constraints.Size;
 import com.ruoyi.common.core.domain.BaseEntity;
 
-/** 测试配置及其独立业务状态。 */
+/**
+ * 测试配置对象 t_test,含测试状态与接收状态两个并列的业务状态。
+ *
+ * <p>测试状态描述测试流程,接收状态描述系统对该测试的数据归属;两者都不得反向改写设备事实。</p>
+ */
 public class TTest extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
 
+    /** 测试主键。 */
     private Long testId;
+    /** 测试名称,由操作员填写,用于在同一电池组内区分测试。 */
     @NotBlank(message = "测试名称不能为空")
     @Size(max = 100, message = "测试名称长度不能超过100个字符")
     private String testName;
+    /** 创建人用户 ID,创建时取当前登录用户,后续修改不可变更。 */
     private Long creatorId;
+    /** 创建人显示名称,仅供列表展示,不作为身份判断依据。 */
     private String creatorName;
+    /** 当前操作人用户 ID,仅由服务入参带入用于校验,不序列化也不落库。 */
     @JSONField(serialize = false)
     private transient Long operatorId;
+    /** 测试负责人用户 ID,负责关闭测试预警异常和停止测试。 */
     @NotNull(message = "测试负责人不能为空")
     private Long principalId;
+    /** 测试负责人显示名称,仅供列表展示,不作为身份判断依据。 */
     private String principalName;
+    /** 测试类型主键,引用已启用的测试类型。 */
     @NotNull(message = "测试类型不能为空")
     private Long testTypeId;
+    /** 测试类型显示名称,仅供列表展示。 */
     private String testTypeName;
+    /** 测试目的,由操作员填写。 */
     @NotBlank(message = "测试目的不能为空")
     private String testPurpose;
+    /**
+     * 测试状态:WAITING 待测试、RUNNING 检测中、PAUSED 暂停、COMPLETED 检测完成。
+     * 历史数据还可能出现 RUNNING_ABNORMAL(检测中(异常))、COMPLETION_PENDING(完成确认中)、
+     * TERMINATED(已终止),只作兼容展示,新流程不再写入。
+     */
     private String testStatus;
+    /**
+     * 接收状态:NOT_STARTED 未开始接收、RECEIVING 接收中、PAUSED 暂停接收、ENDED 已结束接收。
+     * 它表示系统对本次测试的数据归属状态,与设备状态并列,不改变设备事实。
+     */
     private String receptionStatus;
+    /** 监测超时分钟数,创建测试时缺省 30;控制台按该值展示,不参与设备控制。 */
     private Integer monitorTimeoutMinutes;
+    /** 首次开始接收动作建立的服务器时间;暂停后继续接收不再改写。 */
     @JsonFormat(shape = JsonFormat.Shape.NUMBER)
     private Date startedAtUtc;
     /** 最近一次开始或继续接收动作建立的服务器时间边界。 */
     @JsonFormat(shape = JsonFormat.Shape.NUMBER)
     private Date receptionBoundaryAtUtc;
+    /** 停止测试(检测完成)动作的服务器时间;暂停不写入。 */
     @JsonFormat(shape = JsonFormat.Shape.NUMBER)
     private Date endedAtUtc;
     /** 是否从已运行设备的后续观测边界接入。 */
     private Boolean runtimeAttach;
+    /** 逻辑删除标识:0存在,1删除;测试整体删除按 ADR-0047 不写审计、修订或墓碑。 */
     private String delFlag;
+    /** 查询条件:按绑定电池号过滤;不落库。 */
     private String batteryNo;
+    /** 查询条件:创建时间起始值。 */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date beginCreateTime;
+    /** 查询条件:创建时间结束值。 */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date endCreateTime;
+    /** 查询结果:绑定电池号数量,由列表查询汇总得出,不落库。 */
     private Integer batteryCount;
+    /** 测试绑定的电池号及其通道配置;开始接收后配置整体只读。 */
     @Valid
     @NotEmpty(message = "测试至少绑定一个电池号")
     private List<TTestBattery> batteries;
+    /** 测试阈值规则,接收中按当前成功观测判定超限,不参与设备侧保护。 */
     @Valid
     @NotEmpty(message = "阈值配置不能为空")
     private List<TTestWarningThreshold> thresholds;
+    /** 逻辑工步映射的落库 JSON 原文。 */
     private String logicStepMappingsJson;
+    /** 逻辑工步映射对象,由 JSON 解析或查询组装,不单独落库。 */
     private List<TTestLogicStepMapping> logicStepMappings;
     /** 工步图表中的测试补充详情,仅保存当前最新版。 */
     private String consoleDetailJson;
+    /** 测试补充详情对象,由上述 JSON 解析或查询组装,不单独落库。 */
     private TTestConsoleDetail consoleDetail;
 
     /** 测试业务时间输出毫秒,平台实体仍保留原有时间格式。 */
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestBattery.java b/battery-business/src/main/java/com/battery/business/domain/TTestBattery.java
index c94cab2..ac73871 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestBattery.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestBattery.java
@@ -6,28 +6,39 @@
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
 
-/** 测试内的电池号、必填采集器与设备通道配置。 */
+/** 测试内的电池号、必填采集器与设备通道配置,对应表 t_test_battery。 */
 public class TTestBattery implements Serializable
 {
     private static final long serialVersionUID = 1L;
 
+    /** 旧页面入参别名,仅用于兼容;数据库列以 testBatteryId 为准。 */
     private Long relationId;
     /** 最终数据库主键;relationId 仅作为旧页面入参别名保留。 */
     private Long testBatteryId;
+    /** 所属测试主键。 */
     private Long testId;
+    /** 电池号所属电池组主键。 */
     @NotNull(message = "电池组不能为空")
     private Long groupId;
+    /** 电池组显示名称,仅供列表展示,可能来自电池组当前配置。 */
     private String groupName;
+    /** 电池号,在所属电池组内业务唯一。 */
     @NotBlank(message = "电池号不能为空")
     private String batteryNo;
+    /** 本电池号绑定的采集器主键;开始接收前校验其存在且已启用。 */
     private Long collectorId;
+    /** 采集器显示名称,仅供列表展示。 */
     private String collectorName;
+    /** 逻辑删除标识:0存在,1删除。 */
     private String delFlag;
+    /** 主要检测柜通道绑定,开始接收的通道唯一性以此为判据。 */
     @Valid
     @NotNull(message = "主要检测柜通道不能为空")
     private TTestChannelBinding binding;
+    /** 采集器演示通道的测量映射,用于接收期间的温度与电位归属。 */
     @Valid
     private List<TTestCollectorChannel> collectorChannels;
+    /** 电池组当前字段投影,测试直接读取当前值,不保存测试字段快照。 */
     private List<CurrentBatteryField> fields;
 
     public Long getRelationId() { return relationId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestChannelBinding.java b/battery-business/src/main/java/com/battery/business/domain/TTestChannelBinding.java
index 17013e3..190ee05 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestChannelBinding.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestChannelBinding.java
@@ -4,30 +4,49 @@
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Size;
 
-/** 测试电池号与主要检测柜通道的绑定。 */
+/** 测试电池号与主要检测柜通道的绑定,对应表 t_test_channel_binding。 */
 public class TTestChannelBinding implements Serializable
 {
     private static final long serialVersionUID = 1L;
 
+    /** 通道绑定主键。 */
     private Long bindingId;
+    /** 所属测试主键。 */
     private Long testId;
+    /** 旧页面入参别名,仅用于兼容;数据库列以 testBatteryId 为准。 */
     private Long batteryRelationId;
     /** 最终数据库列名对应的测试电池主键。 */
     private Long testBatteryId;
+    /** 绑定的检测柜通道主键,同一次开始接收不允许重复绑定同一通道。 */
     @NotNull(message = "检测柜通道不能为空")
     private Long channelId;
+    /** 操作员填写的预期条码,用于人工核对,不参与数据归属判定。 */
     @Size(max = 100, message = "预期条码长度不能超过100个字符")
     private String expectedBarcode;
+    /**
+     * 通道认领状态,数据库约束只允许四个取值:PENDING 待认领、CLAIMED 已认领、
+     * RELEASED 已释放、CONFLICT 冲突;数据归属只按绑定关系判定,不依赖该值。
+     */
     private String claimStatus;
+    /** 通道所属检测柜主键,由通道查询带出。 */
     private Long cabinetId;
+    /** 检测柜编码,仅供列表展示。 */
     private String cabinetCode;
+    /** 检测柜名称,仅供列表展示。 */
     private String cabinetName;
+    /** 设备适配器实例编码,与下方厂商字段共同构成通道身份。 */
     private String adapterCode;
+    /** 厂商设备类型。 */
     private Integer devtype;
+    /** 厂商设备号。 */
     private Integer devid;
+    /** 厂商单元号。 */
     private Integer subdevid;
+    /** 厂商通道号。 */
     private Integer chlid;
+    /** 通道展示编号,仅供界面展示,不能代替通道身份。 */
     private String displayNo;
+    /** 适配器时区标识,用于解释设备时间;缺省按 Asia/Hong_Kong 处理。 */
     private String adapterTimezoneId;
 
     public Long getBindingId() { return bindingId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestCollectorChannel.java b/battery-business/src/main/java/com/battery/business/domain/TTestCollectorChannel.java
index 623cf4d..f8ee948 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestCollectorChannel.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestCollectorChannel.java
@@ -3,21 +3,28 @@
 import java.io.Serializable;
 import jakarta.validation.constraints.NotBlank;
 
-/** 测试电池号与采集器演示通道的测量映射。 */
+/** 测试电池号与采集器演示通道的测量映射,对应表 t_test_collector_channel。 */
 public class TTestCollectorChannel implements Serializable
 {
     private static final long serialVersionUID = 1L;
 
+    /** 测量映射主键。 */
     private Long mappingId;
+    /** 所属测试主键。 */
     private Long testId;
+    /** 旧页面入参别名,仅用于兼容;数据库列以 testBatteryId 为准。 */
     private Long batteryRelationId;
     /** 最终数据库列名对应的测试电池主键。 */
     private Long testBatteryId;
+    /** 采集器主键。 */
     private Long collectorId;
+    /** 绑定角色编码,如主要温度、正电位、负电位;用于区分主测量与辅助测量。 */
     @NotBlank(message = "采集器测量项编码不能为空")
     private String measurementCode;
+    /** 绑定角色显示名称,仅供界面展示。 */
     @NotBlank(message = "采集器测量项名称不能为空")
     private String measurementName;
+    /** 采集器通道编码,须与设备返回的通道编码一致才归属本绑定。 */
     @NotBlank(message = "采集器通道不能为空")
     private String channelCode;
 
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestConsoleDetail.java b/battery-business/src/main/java/com/battery/business/domain/TTestConsoleDetail.java
index cf2db09..cfeadf2 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestConsoleDetail.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestConsoleDetail.java
@@ -7,9 +7,13 @@
 /** 工步图表中的测试补充详情,仅保留当前测试的一份记录。 */
 public class TTestConsoleDetail
 {
+    /** 实验名称,控制台详情专用;三类报表详情不含该字段。 */
     private String experimentName;
+    /** 从电池组带入的字段名称有序列表,用于决定详情表格的列顺序。 */
     private List<String> batteryFields = new java.util.ArrayList<>();
+    /** 测试内自行添加的特殊字段名称有序列表,不进入跨业务共享的字段定义。 */
     private List<String> specialFields = new java.util.ArrayList<>();
+    /** 人工填写值,按「电池号 → 字段名 → 值」组织;只保存当前最新版,不形成测试字段快照。 */
     private Map<String, Map<String, String>> fieldValues = new LinkedHashMap<>();
 
     public String getExperimentName() { return experimentName; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestCycleRecord.java b/battery-business/src/main/java/com/battery/business/domain/TTestCycleRecord.java
index 49ff31e..4a7ca21 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestCycleRecord.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestCycleRecord.java
@@ -4,15 +4,55 @@
 import java.math.BigDecimal;
 import java.util.Date;
 
-/** 一条循环统计记录;设备没有循环汇总行时由工步层按循环聚合,来源类别随之保存。 */
+/**
+ * 一条循环统计记录,对应表 t_test_cycle_record;设备没有循环汇总行时由工步层按循环聚合,来源类别随之保存。
+ */
 public class TTestCycleRecord implements Serializable
 {
-    private Long cycleRecordId, testId, testBatteryId, bindingId;
-    private String adapterCode, sourceIdentity, sourceRecordId, sourceKind, adapterTimezoneId, dedupeKey, extensionMeasurementsJson;
-    private Date deviceTimeUtc, serverReceivedAtUtc;
+    /** 循环记录主键。 */
+    private Long cycleRecordId;
+    /** 所属测试主键。 */
+    private Long testId;
+    /** 所属测试电池主键。 */
+    private Long testBatteryId;
+    /** 本次观测使用的通道绑定主键。 */
+    private Long bindingId;
+    /** 设备适配器实例编码。 */
+    private String adapterCode;
+    /** 来源身份,由适配器实例与厂商通道字段拼接。 */
+    private String sourceIdentity;
+    /** 来源记录编号,设备未提供时为空并回落到时间组合去重。 */
+    private String sourceRecordId;
+    /**
+     * 循环统计来源类别:DEVICE 设备提供、AGGREGATED 工步层聚合、IMPORTED 文件导入。
+     * 推导值必须能与设备事实区分,不能只靠界面文案说明。
+     */
+    private String sourceKind;
+    /** 解释设备时间所用的适配器时区标识。 */
+    private String adapterTimezoneId;
+    /** 幂等键:优先取来源记录编号,缺失时按设备结束时间与循环号组合。 */
+    private String dedupeKey;
+    /** 扩展测量记录 JSON。 */
+    private String extensionMeasurementsJson;
+    /** 循环结束时间,按适配器时区解释后以 UTC 保存。 */
+    private Date deviceTimeUtc;
+    /** 服务器接收时间。 */
+    private Date serverReceivedAtUtc;
+    /** 设备时间原始文本,保留厂家原值供核对。 */
     private String deviceTimeRaw;
+    /** 设备循环号。 */
     private Integer cycleNo;
-    private BigDecimal chargeCapacityAh, dischargeCapacityAh, chargeEnergyWh, dischargeEnergyWh, efficiency;
+    /** 充电容量,业务单位 Ah。 */
+    private BigDecimal chargeCapacityAh;
+    /** 放电容量,业务单位 Ah。 */
+    private BigDecimal dischargeCapacityAh;
+    /** 充电能量,业务单位 Wh。 */
+    private BigDecimal chargeEnergyWh;
+    /** 放电能量,业务单位 Wh。 */
+    private BigDecimal dischargeEnergyWh;
+    /** 循环效率,业务单位 %,为放电容量 ÷ 充电容量;没有可用充电容量时为空,未知比率不用零代替。 */
+    private BigDecimal efficiency;
+    /** 记录内容摘要,用于重复观测的幂等与冲突判定。 */
     private byte[] contentSha256;
     public Long getCycleRecordId(){return cycleRecordId;} public void setCycleRecordId(Long v){cycleRecordId=v;}
     public String getSourceKind(){return sourceKind;} public void setSourceKind(String v){sourceKind=v;} public Long getTestId(){return testId;} public void setTestId(Long v){testId=v;}
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java b/battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java
index b4e72cd..16965f5 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java
@@ -6,37 +6,55 @@
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonFormat;
 
-/** 一次控制台数据编辑、导入或删除行为。 */
+/** 一次控制台数据编辑、导入或删除行为,对应表 t_test_data_revision_action;只追加,不提供修改或删除能力。 */
 public class TTestDataRevisionAction implements Serializable
 {
     private static final long serialVersionUID = 1L;
+    /** 修订主键,一次用户行为对应一条修订。 */
     private Long actionId;
+    /** 被修订数据所属测试主键。 */
     private Long testId;
+    /**
+     * 修订的数据类型:REALTIME 实时、STEP 工步、CYCLE 循环;
+     * 三 Sheet 一键导入在提交入参中使用 ALL,落库时按各 Sheet 分别记为三类中的一种。
+     */
     private String dataType;
+    /** 操作类型:EDIT 编辑、IMPORT 导入、DELETE 物理删除。 */
     private String operationType;
+    /** 操作范围摘要,例如“导入 3 条实时数据”。 */
     private String scopeSummary;
+    /** 目标记录身份数组的 JSON,用于定位本次行为涉及的记录。 */
     private String identitySummary;
+    /** 变化内容摘要的 JSON;删除行为保存删除前内容,不额外记录改动项。 */
     private String changesSummary;
+    /** 操作前完整内容的 JSON,用于追溯已物理删除的数据。 */
     private String beforeContentJson;
     /** 详情展示数量由持久化层统计,非编辑无需加载完整身份数组;不写入审计事实。 */
     private Long affectedRecordCount;
+    /** 操作人填写的修订原因,必填。 */
     private String reason;
+    /** 操作人用户 ID,取当前登录会话。 */
     private Long operatorId;
+    /** 操作人显示名称,随修订事实一起保存,不随用户改名变化。 */
     private String operatorName;
+    /** 修订发生时间,以 UTC 保存。 */
     @JsonFormat(shape = JsonFormat.Shape.NUMBER)
     private Date createdAtUtc;
     /** 查询展示字段:来源测试名称。 */
     private String testName;
     /** 返回测试控制台时使用的后端接收状态。 */
     private String testReceptionStatus;
+    /** 查询条件:修订时间起始值。 */
     @JsonIgnore
     @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
     private Date beginCreatedAt;
+    /** 查询条件:修订时间结束值。 */
     @JsonIgnore
     @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
     private Date endCreatedAt;
     /** 本次提交结果,不持久化到修订事实。 */
     private int importedCount;
+    /** 本次提交跳过的重复记录数,不持久化到修订事实。 */
     private int skippedCount;
     /** 本次提交生成的全部修订编号,仅用于提交响应,不持久化。 */
     @JsonIgnore
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestLogicStepMapping.java b/battery-business/src/main/java/com/battery/business/domain/TTestLogicStepMapping.java
index fc0d581..6f30b55 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestLogicStepMapping.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestLogicStepMapping.java
@@ -7,9 +7,13 @@
 /** 当前测试循环内工步位置到逻辑工步的映射;只影响展示和统计,不改写原始检测柜数据。 */
 public class TTestLogicStepMapping implements Serializable {
     private static final long serialVersionUID = 1L;
+    /** 逻辑工步编号,按循环内顺序从 1 连续编号。 */
     @Min(value = 1, message = "逻辑工步编号必须大于0") private Integer logicalNo;
+    /** 操作员填写的逻辑工步名称。 */
     @NotBlank(message = "逻辑工步名称不能为空") private String logicalName;
+    /** 本逻辑工步覆盖的循环内起始实际工步位置,含该位置。 */
     @Min(value = 1, message = "循环内起始位置必须大于0") private Integer startStepNo;
+    /** 本逻辑工步覆盖的循环内结束实际工步位置,含该位置。 */
     @Min(value = 1, message = "循环内结束位置必须大于0") private Integer endStepNo;
     public Integer getLogicalNo() { return logicalNo; }
     public void setLogicalNo(Integer value) { logicalNo = value; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestRealtimeRecord.java b/battery-business/src/main/java/com/battery/business/domain/TTestRealtimeRecord.java
index aa7683b..93c53de 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestRealtimeRecord.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestRealtimeRecord.java
@@ -4,44 +4,84 @@
 import java.math.BigDecimal;
 import java.util.Date;
 
-/** 一次测试电池轮询槽位的检测柜与采集器组合观测。 */
+/**
+ * 一次测试电池轮询槽位的检测柜与采集器组合观测,对应表 t_test_realtime_record。
+ *
+ * <p>槽位为空的记录来自授权历史导入或柜端逐行接收,同一秒可以存在多条不同来源编号的记录。</p>
+ */
 public class TTestRealtimeRecord implements Serializable
 {
+    /** 实时记录主键。 */
     private Long recordId;
+    /** 所属测试主键。 */
     private Long testId;
+    /** 所属测试电池主键,数据按电池号归属。 */
     private Long testBatteryId;
+    /** 本次观测使用的通道绑定主键。 */
     private Long bindingId;
+    /** 设备适配器实例编码。 */
     private String adapterCode;
+    /** 来源身份,由适配器实例与厂商通道字段拼接,是幂等与冲突判定的组成部分。 */
     private String sourceIdentity;
+    /** 来源记录编号:柜端为协议序号或来源主键,采集器为采集端序号;配合来源身份执行幂等。 */
     private String sourceRecordId;
+    /** 柜端厂商设备类型,采集器来源或导入行为空。 */
     private Integer cabinetDevtype;
+    /** 柜端厂商设备号。 */
     private Integer cabinetDevid;
+    /** 柜端厂商单元号。 */
     private Integer cabinetSubdevid;
+    /** 柜端厂商通道号。 */
     private Integer cabinetChlid;
+    /** 柜端协议序号,工步切换点在同一绝对秒内可写入多条记录,靠该值区分。 */
     private Long cabinetSequenceId;
+    /** 柜端设备时间,按适配器时区解释后以 UTC 保存。 */
     private Date cabinetDeviceTimeUtc;
+    /** 柜端设备时间原始文本,保留厂家原值供核对。 */
     private String cabinetDeviceTimeRaw;
+    /** 解释柜端设备时间所用的适配器时区标识。 */
     private String cabinetAdapterTimezoneId;
+    /** 采集器主键,柜端单来源记录为空。 */
     private Long collectorId;
+    /** 采集器通道编码。 */
     private String collectorChannelCode;
+    /** 采集端记录序号。 */
     private Long collectorSequenceId;
+    /** 采集器设备时间,按适配器时区解释后以 UTC 保存。 */
     private Date collectorDeviceTimeUtc;
+    /** 采集器设备时间原始文本。 */
     private String collectorDeviceTimeRaw;
+    /** 解释采集器设备时间所用的适配器时区标识。 */
     private String collectorAdapterTimezoneId;
+    /** 系统轮询槽位;柜端逐行接收和授权历史导入的记录槽位为空,同秒多行因此各自成立。 */
     private Date captureSlotUtc;
+    /** 服务器接收时间,仅用于展示接收节奏,不参与重复观测的内容比较。 */
     private Date serverReceivedAtUtc;
+    /** 设备实际循环号。 */
     private Integer cycleNo;
+    /** 设备实际工步号。 */
     private Integer stepNo;
+    /** 设备工步类型,未知取值原样保留,不按电流或容量猜测方向。 */
     private String stepType;
+    /** 设备工步名称。 */
     private String stepName;
+    /** 电压,业务单位 V。 */
     private BigDecimal voltageV;
+    /** 电流,业务单位 A。 */
     private BigDecimal currentA;
+    /** 容量,业务单位 Ah。 */
     private BigDecimal capacityAh;
+    /** 能量,业务单位 Wh。 */
     private BigDecimal energyWh;
+    /** 主温度,业务单位 ℃;多路同类测量按绑定角色区分,辅助温度不覆盖主温度。 */
     private BigDecimal temperature;
+    /** 正电位,业务单位 V。 */
     private BigDecimal positivePotential;
+    /** 负电位,业务单位 V。 */
     private BigDecimal negativePotential;
+    /** 扩展测量记录 JSON,按指标编码保存动态辅助通道或 DBC 指标及单位。 */
     private String extensionMeasurementsJson;
+    /** 记录内容摘要,用于重复观测的幂等与冲突判定;比较时排除服务器接收时间。 */
     private byte[] contentSha256;
 
     public Long getRecordId() { return recordId; } public void setRecordId(Long v) { recordId = v; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestStepRecord.java b/battery-business/src/main/java/com/battery/business/domain/TTestStepRecord.java
index 627fe93..317d5af 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestStepRecord.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestStepRecord.java
@@ -4,15 +4,56 @@
 import java.math.BigDecimal;
 import java.util.Date;
 
-/** 检测柜直接返回的一条实际工步统计记录。 */
+/**
+ * 检测柜直接返回的一条实际工步统计记录,对应表 t_test_step_record。
+ *
+ * <p>只保存设备给出的实际工步,逻辑工步合并仅用于派生视图,不回写本表。</p>
+ */
 public class TTestStepRecord implements Serializable
 {
-    private Long stepRecordId, testId, testBatteryId, bindingId;
-    private String adapterCode, sourceIdentity, sourceRecordId, adapterTimezoneId, dedupeKey, stepType, stepName, extensionMeasurementsJson;
-    private Date deviceTimeUtc, serverReceivedAtUtc;
+    /** 工步记录主键。 */
+    private Long stepRecordId;
+    /** 所属测试主键。 */
+    private Long testId;
+    /** 所属测试电池主键。 */
+    private Long testBatteryId;
+    /** 本次观测使用的通道绑定主键。 */
+    private Long bindingId;
+    /** 设备适配器实例编码。 */
+    private String adapterCode;
+    /** 来源身份,由适配器实例与厂商通道字段拼接。 */
+    private String sourceIdentity;
+    /** 来源记录编号,设备未提供时为空并回落到时间组合去重。 */
+    private String sourceRecordId;
+    /** 解释设备时间所用的适配器时区标识。 */
+    private String adapterTimezoneId;
+    /** 幂等键:优先取来源记录编号,缺失时按结束时间、循环号、工步号组合。 */
+    private String dedupeKey;
+    /** 设备工步类型,未知取值原样保留,不按电流或容量猜测方向。 */
+    private String stepType;
+    /** 设备工步名称。 */
+    private String stepName;
+    /** 扩展测量记录 JSON,保存设备提供的充电/放电中值电压等额外指标。 */
+    private String extensionMeasurementsJson;
+    /** 工步结束时间,按适配器时区解释后以 UTC 保存。 */
+    private Date deviceTimeUtc;
+    /** 服务器接收时间。 */
+    private Date serverReceivedAtUtc;
+    /** 设备时间原始文本,保留厂家原值供核对。 */
     private String deviceTimeRaw;
-    private Integer cycleNo, stepNo;
-    private BigDecimal voltageV, currentA, capacityAh, energyWh;
+    /** 设备实际循环号。 */
+    private Integer cycleNo;
+    /** 设备实际工步号。 */
+    private Integer stepNo;
+    /** 工步结束电压,业务单位 V。 */
+    private BigDecimal voltageV;
+    /** 工步结束电流,业务单位 A。 */
+    private BigDecimal currentA;
+    /** 工步容量,业务单位 Ah;设备未提供时为空,不写成零。 */
+    private BigDecimal capacityAh;
+    /** 工步能量,业务单位 Wh;设备未提供时为空,不写成零。 */
+    private BigDecimal energyWh;
+    /** 记录内容摘要,用于重复观测的幂等与冲突判定。 */
     private byte[] contentSha256;
     public Long getStepRecordId(){return stepRecordId;} public void setStepRecordId(Long v){stepRecordId=v;}
     public Long getTestId(){return testId;} public void setTestId(Long v){testId=v;} public Long getTestBatteryId(){return testBatteryId;} public void setTestBatteryId(Long v){testBatteryId=v;}
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestWarningException.java b/battery-business/src/main/java/com/battery/business/domain/TTestWarningException.java
index d152392..9ad9348 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestWarningException.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestWarningException.java
@@ -4,27 +4,50 @@
 import java.math.BigDecimal;
 import java.util.Date;
 
-/** 测试阈值超限或接收期间设备离线的异常事实。 */
+/**
+ * 测试阈值超限或接收期间设备离线的异常事实,对应表 t_test_warning_exception。
+ *
+ * <p>它只是本系统的观测提醒,不能表述或实现成设备安全保护的替代品。</p>
+ */
 public class TTestWarningException implements Serializable {
     private static final long serialVersionUID = 1L;
+    /** 异常主键。 */
     private Long exceptionId;
+    /** 所属测试主键。 */
     private Long testId;
+    /** 异常类型:THRESHOLD 阈值超限、DEVICE_OFFLINE 接收期间设备离线。 */
     private String exceptionType = "THRESHOLD";
+    /** 所属测试名称,查询时带出,仅供展示。 */
     private String testName;
+    /** 来源类型:CABINET 检测柜通道、CABINET_CHANNEL 绑定来源离线、COLLECTOR 采集器。 */
     private String sourceType;
+    /** 来源身份:检测柜按适配器实例与厂商通道字段拼接,采集器按设备 ID。 */
     private String sourceIdentity;
+    /** 指标编码;离线异常固定为 DEVICE_ONLINE。 */
     private String metricCode;
+    /** 触发异常时的测量值;离线异常不伪造测量值,保持为空。 */
     private BigDecimal abnormalValue;
+    /** 测量值单位;离线异常为空字符串。 */
     private String unit;
+    /** 触发时阈值规则的内容快照 JSON,规则后续修改不回写历史异常。 */
     private String ruleSnapshot;
+    /** 严重程度:WARNING 警告;离线异常固定为 WARNING。 */
     private String severity;
+    /** 异常状态:OPEN 开放、CLOSED 已关闭;同一聚合键人工关闭前只保留一条开放异常。 */
     private String exceptionStatus;
+    /** 首次触发时间,以 UTC 保存。 */
     private Date firstOccurredAtUtc;
+    /** 最近一次触发时间,以 UTC 保存;正常观测不会自动关闭异常。 */
     private Date lastOccurredAtUtc;
+    /** 累计触发次数。 */
     private Long occurrenceCount;
+    /** 处理人用户 ID,由测试负责人关闭时写入。 */
     private Long handledBy;
+    /** 处理人显示名称,随异常事实保存。 */
     private String handledByName;
+    /** 关闭时间,以 UTC 保存。 */
     private Date handledAtUtc;
+    /** 负责人填写的处理结果,关闭时必填。 */
     private String handlingNote;
     public Long getExceptionId(){return exceptionId;} public void setExceptionId(Long v){exceptionId=v;}
     public Long getTestId(){return testId;} public void setTestId(Long v){testId=v;}
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestWarningThreshold.java b/battery-business/src/main/java/com/battery/business/domain/TTestWarningThreshold.java
index fdd214e..fcfbdbc 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestWarningThreshold.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestWarningThreshold.java
@@ -5,21 +5,29 @@
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.Size;
 
-/** 使用 SI 业务单位保存的测试告警阈值。 */
+/** 使用 SI 业务单位保存的测试告警阈值,对应表 t_test_warning_threshold;供接收中的实时超限判定使用。 */
 public class TTestWarningThreshold implements Serializable
 {
     private static final long serialVersionUID = 1L;
 
+    /** 阈值主键。 */
     private Long thresholdId;
+    /** 所属测试主键。 */
     private Long testId;
+    /** 指标编码,须与观测指标匹配,如电压、电流、容量、能量、温度;支持主要测量前缀形式。 */
     @NotBlank(message = "指标编码不能为空")
     @Size(max = 100, message = "指标编码长度不能超过100个字符")
     private String metricCode;
+    /** 下限,为空表示不判下限;单位与该指标的业务单位一致。 */
     private BigDecimal minValue;
+    /** 上限,为空表示不判上限;单位与该指标的业务单位一致。 */
     private BigDecimal maxValue;
+    /** 阈值单位,只允许 A、V、Ah、Wh、℃。 */
     @NotBlank(message = "阈值单位不能为空")
     private String unit;
+    /** 严重程度:WARNING 警告;随异常事实保存为触发时的规则快照。 */
     private String severity;
+    /** 逻辑删除标识:0存在,其他值表示已删除。 */
     private String delFlag;
 
     public Long getThresholdId() { return thresholdId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/AdapterDiscoveryResult.java b/battery-business/src/main/java/com/battery/business/domain/vo/AdapterDiscoveryResult.java
index 847de4b..6462cbf 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/AdapterDiscoveryResult.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/AdapterDiscoveryResult.java
@@ -2,7 +2,13 @@
 
 import java.time.Instant;
 
-/** 单次设备发现写入 Redis 候选后的结果。 */
+/**
+ * 单次设备发现写入 Redis 候选后的结果。
+ *
+ * @param cabinetCount  本次写入的检测柜候选数量
+ * @param channelCount  本次写入的通道候选数量
+ * @param observedAtUtc 本次观测时间,以 UTC 保存
+ */
 public record AdapterDiscoveryResult(int cabinetCount, int channelCount, Instant observedAtUtc)
 {
 }
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/DeviceRelatedTestVo.java b/battery-business/src/main/java/com/battery/business/domain/vo/DeviceRelatedTestVo.java
index 7a3b30b..9d36341 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/DeviceRelatedTestVo.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/DeviceRelatedTestVo.java
@@ -6,9 +6,13 @@
 /** 设备关联的测试及该测试绑定的本设备通道,不写入设备表。 */
 public class DeviceRelatedTestVo
 {
+    /** 测试主键,设备内按该值去重。 */
     private Long testId;
+    /** 测试名称,界面上作为测试详情链接。 */
     private String testName;
+    /** 测试自己的测试状态,不表示设备状态。 */
     private String testStatus;
+    /** 该测试绑定到本设备的通道展示编号。 */
     private List<String> channels = new ArrayList<>();
 
     public Long getTestId() { return testId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/DiscoveredCabinetVo.java b/battery-business/src/main/java/com/battery/business/domain/vo/DiscoveredCabinetVo.java
index ff8346e..d24bcd5 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/DiscoveredCabinetVo.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/DiscoveredCabinetVo.java
@@ -6,6 +6,16 @@
  * 检测柜选择弹窗使用的设备摘要。
  * {@code lastDataAt} 是该设备最近一次采样行的设备时间(来源里没有采样行时为空),
  * 由操作员据此判断哪几台柜子现在真的在产出数据;登记与否仍由操作员决定。
+ *
+ * @param deviceCode   发现候选的设备编码,由适配器按来源规则生成,供操作员核对
+ * @param ip           设备网络地址
+ * @param mac          设备 MAC 地址
+ * @param name         设备显示名称
+ * @param manufacturer 设备制造商
+ * @param model        设备型号
+ * @param channelCount 该设备在来源里登记且发现的通道数量
+ * @param onlineStatus 界面展示用的在线文案:在线 / 离线
+ * @param lastDataAt   最近一次采样行的设备时间;只作为观测事实,不判定设备是否在用
  */
 public record DiscoveredCabinetVo(
         String deviceCode,
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/OfflineTestSource.java b/battery-business/src/main/java/com/battery/business/domain/vo/OfflineTestSource.java
index 142d848..ae0a80d 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/OfflineTestSource.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/OfflineTestSource.java
@@ -1,4 +1,9 @@
 package com.battery.business.domain.vo;
 
-/** 测试绑定来源的持久化离线事实;检测柜使用完整协议通道身份。 */
+/**
+ * 测试绑定来源的持久化离线事实;检测柜使用完整协议通道身份。
+ *
+ * @param sourceType     来源类型:CABINET_CHANNEL 检测柜通道、COLLECTOR 采集器
+ * @param sourceIdentity 来源身份:检测柜按适配器实例与厂商通道字段拼接,采集器按设备 ID
+ */
 public record OfflineTestSource(String sourceType, String sourceIdentity) {}
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/TDeviceIngestChannel.java b/battery-business/src/main/java/com/battery/business/domain/vo/TDeviceIngestChannel.java
index 2f19310..27d0f04 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/TDeviceIngestChannel.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/TDeviceIngestChannel.java
@@ -9,10 +9,15 @@
  */
 public class TDeviceIngestChannel implements Serializable
 {
+    /** 厂商设备类型。 */
     private Integer devtype;
+    /** 厂商设备号。 */
     private Integer devid;
+    /** 厂商单元号。 */
     private Integer subdevid;
+    /** 厂商通道号。 */
     private Integer chlid;
+    /** 该通道在落地表里最近一条采样行的时间;只作为观测事实,不判定通道是否在用。 */
     private Date lastDataAt;
 
     public Integer getDevtype() { return devtype; } public void setDevtype(Integer v) { devtype = v; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/TestBatteryOptionVo.java b/battery-business/src/main/java/com/battery/business/domain/vo/TestBatteryOptionVo.java
index 9c022a1..a751344 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/TestBatteryOptionVo.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/TestBatteryOptionVo.java
@@ -3,10 +3,15 @@
 /** 启用电池组内可选的电池号。 */
 public class TestBatteryOptionVo
 {
+    /** 电池号关系主键,旧页面入参别名。 */
     private Long relationId;
+    /** 最终数据库主键,与 relationId 同值。 */
     private Long testBatteryId;
+    /** 电池号所属电池组主键。 */
     private Long groupId;
+    /** 电池组名称,供下拉分组展示。 */
     private String groupName;
+    /** 电池号。 */
     private String batteryNo;
 
     public Long getRelationId() { return relationId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/TestChannelOptionVo.java b/battery-business/src/main/java/com/battery/business/domain/vo/TestChannelOptionVo.java
index a493651..6c71b18 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/TestChannelOptionVo.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/TestChannelOptionVo.java
@@ -3,15 +3,25 @@
 /** 已正式登记检测柜的稳定通道选项。 */
 public class TestChannelOptionVo
 {
+    /** 通道主键,绑定测试时提交该值。 */
     private Long channelId;
+    /** 通道所属检测柜主键。 */
     private Long cabinetId;
+    /** 检测柜编码,供下拉展示。 */
     private String cabinetCode;
+    /** 检测柜名称,供下拉展示。 */
     private String cabinetName;
+    /** 设备适配器实例编码。 */
     private String adapterCode;
+    /** 厂商设备类型。 */
     private Integer devtype;
+    /** 厂商设备号。 */
     private Integer devid;
+    /** 厂商单元号。 */
     private Integer subdevid;
+    /** 厂商通道号。 */
     private Integer chlid;
+    /** 通道展示编号,仅供界面展示,不能代替通道身份。 */
     private String displayNo;
 
     public Long getChannelId() { return channelId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/TestCollectorOptionVo.java b/battery-business/src/main/java/com/battery/business/domain/vo/TestCollectorOptionVo.java
index 600c55c..dda43f2 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/TestCollectorOptionVo.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/TestCollectorOptionVo.java
@@ -3,9 +3,13 @@
 /** 可选的启用采集器。 */
 public class TestCollectorOptionVo
 {
+    /** 采集器主键,绑定测试电池时提交该值。 */
     private Long collectorId;
+    /** 采集器编码,供下拉展示。 */
     private String collectorCode;
+    /** 采集器名称,供下拉展示。 */
     private String collectorName;
+    /** 已登记的采集器通道数量,用于校验绑定映射是否完整。 */
     private Integer channelCount;
 
     public Long getCollectorId() { return collectorId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/TestConfigurationOptionsVo.java b/battery-business/src/main/java/com/battery/business/domain/vo/TestConfigurationOptionsVo.java
index f3b8db9..8bb1418 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/TestConfigurationOptionsVo.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/TestConfigurationOptionsVo.java
@@ -2,13 +2,18 @@
 
 import java.util.List;
 
-/** 创建或编辑测试时由后端提供的有效选项。 */
+/** 创建或编辑测试时由后端提供的有效选项:只返回启用且已正式登记的记录。 */
 public class TestConfigurationOptionsVo
 {
+    /** 可选的测试负责人,取启用用户。 */
     private List<TestUserOptionVo> users;
+    /** 可选的测试类型,取启用类型。 */
     private List<TestTypeOptionVo> testTypes;
+    /** 可选的电池号,取启用电池组内的电池号。 */
     private List<TestBatteryOptionVo> batteries;
+    /** 可选的检测柜通道,取已正式登记的通道。 */
     private List<TestChannelOptionVo> channels;
+    /** 可选的采集器,取启用并已登记的采集器。 */
     private List<TestCollectorOptionVo> collectors;
 
     public List<TestUserOptionVo> getUsers() { return users; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/TestTypeOptionVo.java b/battery-business/src/main/java/com/battery/business/domain/vo/TestTypeOptionVo.java
index a41ca58..d84aa1b 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/TestTypeOptionVo.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/TestTypeOptionVo.java
@@ -3,7 +3,9 @@
 /** 启用的测试类型选项。 */
 public class TestTypeOptionVo
 {
+    /** 测试类型主键。 */
     private Long testTypeId;
+    /** 测试类型名称。 */
     private String testTypeName;
 
     public Long getTestTypeId() { return testTypeId; }
diff --git a/battery-business/src/main/java/com/battery/business/domain/vo/TestUserOptionVo.java b/battery-business/src/main/java/com/battery/business/domain/vo/TestUserOptionVo.java
index 9f8a883..cc4dde6 100644
--- a/battery-business/src/main/java/com/battery/business/domain/vo/TestUserOptionVo.java
+++ b/battery-business/src/main/java/com/battery/business/domain/vo/TestUserOptionVo.java
@@ -3,8 +3,11 @@
 /** 可担任测试负责人的启用用户选项。 */
 public class TestUserOptionVo
 {
+    /** 用户主键,提交测试时作为负责人 ID。 */
     private Long userId;
+    /** 登录账号,账号为空时不展示。 */
     private String userName;
+    /** 用户昵称,优先作为负责人的展示名称。 */
     private String nickName;
 
     public Long getUserId() { return userId; }
diff --git a/battery-business/src/main/java/com/battery/business/service/TodoService.java b/battery-business/src/main/java/com/battery/business/service/TodoService.java
index b7d36e6..5912fe5 100644
--- a/battery-business/src/main/java/com/battery/business/service/TodoService.java
+++ b/battery-business/src/main/java/com/battery/business/service/TodoService.java
@@ -52,8 +52,18 @@
         return key.getKey().longValue();
     }
 
-    /** 负责人只能将自己的待处理事项完成一次;写入条件同时校验身份和当前状态。 */
-    public void handle(long id, long ownerId, String remark) {
+    /**
+     * 负责人把自己的待处理事项标记为已处理,并保存处理说明。
+     *
+     * <p>前置条件:操作人就是该待办的负责人,处理说明非空且不超过 500 个字符。
+     * 写入条件同时校验身份和当前状态,已处理的事项不能重复处理。</p>
+     *
+     * @param id      待办主键
+     * @param ownerId 操作人用户 ID
+     * @param remark  处理说明
+     * @throws ServiceException 处理说明为空或超长、待办不存在、已处理或不属于该负责人
+     */
+    public void completeTodo(long id, long ownerId, String remark) {
         String value = required(remark, 500, "处理说明");
         if (jdbc.update("update t_todo set status='已处理',handle_remark=?,operated_at_utc=? where todo_id=? and owner_id=? and status='待处理'",
                 value, new Timestamp(System.currentTimeMillis()), id, ownerId) != 1)
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterRuntimeCoordinator.java b/battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterRuntimeCoordinator.java
index 60d1746..39d45a4 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterRuntimeCoordinator.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/DeviceAdapterRuntimeCoordinator.java
@@ -17,9 +17,11 @@
 
     public DeviceAdapterRuntimeCoordinator(IDeviceAdapterService service) { this.service = service; }
 
+    /** 应用就绪时恢复已启用的适配器实例;不限定 profile,真实来源也要在非开发 profile 下运行。 */
     @EventListener(ApplicationReadyEvent.class)
     public void restoreEnabledAdapters() { service.synchronizeEnabledAdapters(); }
 
+    /** 每约 1 秒检查一次到期实例并执行只读发现,实际取数节奏由各实例自己的轮询周期决定。 */
     @Scheduled(fixedDelay = 1000)
     public void pollDueAdapters() { service.pollDueAdapters(); }
 }
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/DeviceIngestServiceImpl.java b/battery-business/src/main/java/com/battery/business/service/impl/DeviceIngestServiceImpl.java
index 09f2ff8..bb44aeb 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/DeviceIngestServiceImpl.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/DeviceIngestServiceImpl.java
@@ -83,12 +83,27 @@
         this.credentials = credentials;
     }
 
+    /**
+     * 接收推式来源程序的一批载荷:校验来源与令牌,按载荷类别写入落地表,并刷新该来源的心跳。
+     *
+     * <p>前置条件:来源已登记为启用中的推式实例,端点令牌与本地凭据一致,载荷为合法 JSON 且类别已知。
+     * 整批在同一事务内落库,任一行失败则整批回滚;重复推送按来源行身份幂等忽略,内容不一致的行记为冲突
+     * 而不覆盖既有事实。心跳、拓扑与采样都会刷新心跳时间。</p>
+     *
+     * @param adapterCode 来源实例编码,取自接收端点路径
+     * @param token       端点令牌,取自请求头
+     * @param body        载荷正文,含 kind 与对应的行数据
+     * @return 本批的接收计数:成功写入、幂等忽略、内容冲突的行数
+     * @throws ServiceException 来源不是启用的推式实例、令牌不匹配、载荷格式非法或行数超出单批上限
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public IngestResult ingest(String adapterCode, String token, String body)
     {
+        // STEP 1: 校验来源是启用中的推式实例,并校验端点令牌。
         TDeviceAdapter adapter = requirePushSource(adapterCode);
         requireToken(adapter, token);
+        // STEP 2: 解析载荷并按 kind 分流;未知类别直接拒绝,不按猜测处理。
         JSONObject payload = parse(body);
         String kind = payload.getString("kind");
         Instant receivedAt = clock.instant();
@@ -109,6 +124,7 @@
         {
             throw payloadInvalid("未知的载荷类别 kind:" + kind);
         }
+        // STEP 3: 刷新心跳时间;每一批成功接收都是"程序还在推"的证据。
         // 每一批成功接收都刷新心跳:收到采样行同样是"程序还在推"的证据,缺一次心跳不会误判离线。
         touchHeartbeat(adapterCode, receivedAt);
         return new IngestResult(counters.accepted, counters.ignored, counters.conflicts);
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapter.java b/battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapter.java
index ac014a2..7645d61 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapter.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapter.java
@@ -83,12 +83,14 @@
         this.clock = clock;
     }
 
+    /** 返回本实例登记的适配器编码;推送端点与通道身份都用它定位来源。 */
     @Override
     public String adapterCode()
     {
         return config.adapterCode();
     }
 
+    /** 返回本实例登记的能力标记;推式来源同样只观测,不提供控制能力。 */
     @Override
     public AdapterCapabilities capabilities()
     {
@@ -135,6 +137,12 @@
         return Map.copyOf(result);
     }
 
+    /**
+     * 返回该通道在落地表里最近一条采样行投影成的实时观测。
+     *
+     * @param channel 检测柜通道身份
+     * @return 最近一条采样行的实时观测;该通道还没有采样行时为空
+     */
     @Override
     public CabinetRealtime latestCabinet(ChannelIdentity channel)
     {
@@ -169,6 +177,7 @@
         return List.of();
     }
 
+    /** 返回推式来源固定的持久化槽位间隔;采样行按来源自己的落库节奏到达。 */
     @Override
     public long persistenceIntervalMillis(ChannelIdentity channel)
     {
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapterFactory.java b/battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapterFactory.java
index f1bc3f3..892cbe1 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapterFactory.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapterFactory.java
@@ -20,12 +20,19 @@
         this.records = records;
     }
 
+    /** 返回本工厂负责的存储形态编码 PUSH_INGEST。 */
     @Override
     public String adapterType()
     {
         return PushIngestAdapter.ADAPTER_TYPE;
     }
 
+    /**
+     * 按登记的实例配置构造读落地表的适配器。
+     *
+     * @param config 实例配置,含编码、时区、轮询周期与来源形态配置
+     * @return 读落地表的推式来源适配器
+     */
     @Override
     public DeviceAdapter create(AdapterInstanceConfig config)
     {
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/RoutingTestDataObservationAdapter.java b/battery-business/src/main/java/com/battery/business/service/impl/RoutingTestDataObservationAdapter.java
index 067c86b..99adf2a 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/RoutingTestDataObservationAdapter.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/RoutingTestDataObservationAdapter.java
@@ -10,6 +10,7 @@
 import com.battery.device.api.model.ChannelIdentity;
 import com.battery.device.api.model.DetectedChannel;
 import com.battery.device.api.model.DeviceObservation;
+import com.battery.device.api.registry.AdapterRegistryException;
 import com.battery.device.api.registry.DeviceAdapterRegistry;
 
 /**
@@ -36,12 +37,14 @@
         this.registry = registry;
     }
 
+    /** 返回聚合路由的固定编码,仅用于说明"按通道身份路由",不是任何来源实例的编码。 */
     @Override
     public String adapterCode()
     {
         return ADAPTER_CODE;
     }
 
+    /** 返回聚合适配器声明的能力:只观测,不提供控制能力。 */
     @Override
     public AdapterCapabilities capabilities()
     {
@@ -77,12 +80,27 @@
         return grouped;
     }
 
+    /**
+     * 按通道所属来源实例请求该通道最近一次实时观测。
+     *
+     * @param channel 检测柜通道身份,其中的适配器编码决定请求送到哪个实例
+     * @return 最近一次实时观测;没有成功观测时为空
+     * @throws AdapterRegistryException 该通道所属的实例未启用,或实例未提供测试数据观测契约
+     */
     @Override
     public CabinetRealtime latestCabinet(ChannelIdentity channel)
     {
         return registry.executeTestData(channel.adapterCode(), adapter -> adapter.latestCabinet(channel));
     }
 
+    /**
+     * 按来源实例请求采集器的辅助测量快照。
+     *
+     * @param adapterCode   来源实例编码;为空表示来源未知,直接返回空值而不猜也不回退
+     * @param collectorCode 采集器编码
+     * @return 采集器实时快照;来源未知时为空
+     * @throws AdapterRegistryException 来源实例未启用,或实例未提供测试数据观测契约
+     */
     @Override
     public CollectorRealtime latestCollector(String adapterCode, String collectorCode)
     {
@@ -92,36 +110,79 @@
         return registry.executeTestData(adapterCode, adapter -> adapter.latestCollector(adapterCode, collectorCode));
     }
 
+    /**
+     * 按通道所属来源实例请求设备直接提供的工步统计。
+     *
+     * @param channel 检测柜通道身份
+     * @return 该通道的设备工步统计;设备未提供时为空列表
+     * @throws AdapterRegistryException 该通道所属的实例未启用,或实例未提供测试数据观测契约
+     */
     @Override
     public List<StepRecord> stepRecords(ChannelIdentity channel)
     {
         return registry.executeTestData(channel.adapterCode(), adapter -> adapter.stepRecords(channel));
     }
 
+    /**
+     * 按通道所属来源实例请求循环统计,来源类别由实例写在记录上。
+     *
+     * @param channel 检测柜通道身份
+     * @return 该通道的循环统计;设备未提供且无法聚合时为空列表
+     * @throws AdapterRegistryException 该通道所属的实例未启用,或实例未提供测试数据观测契约
+     */
     @Override
     public List<CycleRecord> cycleRecords(ChannelIdentity channel)
     {
         return registry.executeTestData(channel.adapterCode(), adapter -> adapter.cycleRecords(channel));
     }
 
+    /**
+     * 按通道所属来源实例取该来源定义的实时持久化槽位间隔。
+     *
+     * @param channel 检测柜通道身份
+     * @return 持久化槽位间隔,单位毫秒
+     * @throws AdapterRegistryException 该通道所属的实例未启用,或实例未提供测试数据观测契约
+     */
     @Override
     public long persistenceIntervalMillis(ChannelIdentity channel)
     {
         return registry.executeTestData(channel.adapterCode(), adapter -> adapter.persistenceIntervalMillis(channel));
     }
 
+    /**
+     * 按通道所属来源实例取设备计划方案,只用于实时展示。
+     *
+     * @param channel 检测柜通道身份
+     * @return 设备计划方案;设备未提供时为空
+     * @throws AdapterRegistryException 该通道所属的实例未启用,或实例未提供测试数据观测契约
+     */
     @Override
     public TestPlan testPlan(ChannelIdentity channel)
     {
         return registry.executeTestData(channel.adapterCode(), adapter -> adapter.testPlan(channel));
     }
 
+    /**
+     * 按通道所属来源实例读取接收水位,用于把开始接收与继续接收切在这一时刻之后。
+     *
+     * @param channel 检测柜通道身份
+     * @return 通道当前接收水位;来源不按设备数据行接收时为空
+     * @throws AdapterRegistryException 该通道所属的实例未启用,或实例未提供测试数据观测契约
+     */
     @Override
     public SampleWatermark currentWatermark(ChannelIdentity channel)
     {
         return registry.executeTestData(channel.adapterCode(), adapter -> adapter.currentWatermark(channel));
     }
 
+    /**
+     * 按通道所属来源实例读取严格晚于水位的数据行,同秒多行各自成立、不按秒合并。
+     *
+     * @param channel   检测柜通道身份
+     * @param watermark 上次取得的水位;为空表示尚未取水位,此时返回空列表而不并入设备历史
+     * @return 水位之后的设备数据行,按设备时间与协议序号升序
+     * @throws AdapterRegistryException 该通道所属的实例未启用,或实例未提供测试数据观测契约
+     */
     @Override
     public List<CabinetRealtime> samplesSince(ChannelIdentity channel, SampleWatermark watermark)
     {
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/TBatteryGroupTemplateServiceImpl.java b/battery-business/src/main/java/com/battery/business/service/impl/TBatteryGroupTemplateServiceImpl.java
index 114e6fe..492ac43 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/TBatteryGroupTemplateServiceImpl.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/TBatteryGroupTemplateServiceImpl.java
@@ -29,6 +29,13 @@
     @Autowired
     private TBatteryGroupTemplateMapper batteryGroupTemplateMapper;
 
+    /**
+     * 按主键读取电池组模板,并装配模板的字段关系与显示顺序。
+     *
+     * @param templateId 模板主键
+     * @return 模板及其字段关系
+     * @throws ServiceException 模板ID为空或模板不存在
+     */
     @Override
     public TBatteryGroupTemplate selectBatteryGroupTemplateById(Long templateId)
     {
@@ -45,12 +52,27 @@
         return template;
     }
 
+    /**
+     * 返回正常状态的模板选项,供电池组选择字段模板。
+     *
+     * @return 模板选项列表;没有可用模板时为空列表
+     */
     @Override
     public List<TBatteryGroupTemplate> selectBatteryGroupTemplateOptions()
     {
         return batteryGroupTemplateMapper.selectBatteryGroupTemplateOptions();
     }
 
+    /**
+     * 新增电池组模板,在同一事务内写入模板主记录与字段关系。
+     *
+     * <p>前置条件:模板名称在当前数据中唯一,字段不能为空且不能重复,字段须启用,
+     * 排序数量与字段数量一致。字段关系整体按提交的字段与顺序写入。</p>
+     *
+     * @param template 模板配置,含字段主键与可选的显示顺序
+     * @return 写入的模板主记录行数
+     * @throws ServiceException 模板信息为空、名称重复、字段为空或重复、字段不存在或已禁用、排序不合法
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int insertBatteryGroupTemplate(TBatteryGroupTemplate template)
@@ -62,11 +84,23 @@
             template.setUpdateBy(template.getCreateBy());
         }
         template.setCreateTime(new Date());
+        // STEP 1: 写入模板主记录,字段关系依赖本次生成的主键。
         int rows = batteryGroupTemplateMapper.insertBatteryGroupTemplate(template);
+        // STEP 2: 在同一事务内整体写入字段关系与显示顺序。
         replaceFields(template);
         return rows;
     }
 
+    /**
+     * 修改电池组模板,字段关系按提交内容整体替换。
+     *
+     * <p>前置条件:模板存在,模板名称不与其它模板重复(允许与自身同名),
+     * 字段与排序的校验要求同新增。已有电池组引用的只是字段定义,模板变更不改写电池组当前字段值。</p>
+     *
+     * @param template 模板配置,须带模板主键
+     * @return 更新的模板主记录行数
+     * @throws ServiceException 模板ID为空、模板不存在、名称重复或字段与排序校验不通过
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int updateBatteryGroupTemplate(TBatteryGroupTemplate template)
@@ -82,7 +116,9 @@
         validateTemplate(template, true);
         template.setTemplateName(trimToNull(template.getTemplateName()));
         template.setUpdateTime(new Date());
+        // STEP 1: 更新模板主记录。
         int rows = batteryGroupTemplateMapper.updateBatteryGroupTemplate(template);
+        // STEP 2: 字段关系整体替换:先清理旧关系再按提交顺序写入。
         replaceFields(template);
         return rows;
     }
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/TTestServiceImpl.java b/battery-business/src/main/java/com/battery/business/service/impl/TTestServiceImpl.java
index 690bcb5..e7f29ad 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/TTestServiceImpl.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/TTestServiceImpl.java
@@ -45,6 +45,13 @@
     @Autowired
     private TTestMapper testMapper;
 
+    /**
+     * 按测试主键读取测试配置,同时装配电池号、阈值、通道绑定等配置关系。
+     *
+     * @param testId 测试主键
+     * @return 测试配置及其业务状态
+     * @throws ServiceException 测试ID为空或测试不存在
+     */
     @Override
     public TTest selectTestById(Long testId)
     {
@@ -55,6 +62,15 @@
         return test;
     }
 
+    /**
+     * 按正式测试状态、接收状态及配置条件查询测试列表。
+     *
+     * <p>查询条件中的测试状态与接收状态都须是现行取值,历史兼容状态不参与筛选。</p>
+     *
+     * @param test 查询条件;为空时按无条件查询处理
+     * @return 测试列表,含各测试绑定的电池号
+     * @throws ServiceException 传入的测试状态或接收状态不是现行取值
+     */
     @Override
     public List<TTest> selectTestList(TTest test)
     {
@@ -68,6 +84,11 @@
         return tests;
     }
 
+    /**
+     * 返回创建或编辑测试时可选的来源:启用用户、启用测试类型、启用电池组的电池号、已正式登记的通道与采集器。
+     *
+     * @return 测试配置选项,五类选项都可能为空列表
+     */
     @Override
     public TestConfigurationOptionsVo selectConfigurationOptions()
     {
@@ -80,36 +101,62 @@
         return options;
     }
 
+    /**
+     * 创建一条待测试记录,在同一事务内写入测试配置和电池号、阈值、通道绑定关系。
+     *
+     * <p>前置条件:调用方已从登录会话取得操作人,测试配置通过基本信息与配置关系校验。
+     * 创建人取当前操作人,监测超时缺省 30 分钟,初始状态固定为“待测试 + 未开始接收”。</p>
+     *
+     * @param test 测试配置,含电池号、阈值、检测柜通道绑定与采集器映射
+     * @return 新建测试主键
+     * @throws ServiceException 测试信息为空、操作人缺失或配置校验不通过
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createTest(TTest test)
     {
         if (test == null) throw new ServiceException("测试信息不能为空");
+        // STEP 1: 规范化系统赋值字段,创建人取当前操作人,监测超时缺省 30 分钟。
         test.setCreatorId(test.getOperatorId());
         if (test.getMonitorTimeoutMinutes() == null) test.setMonitorTimeoutMinutes(30);
+        // STEP 2: 校验基本信息与电池号、阈值、通道绑定等配置关系。
         validateBase(test, false);
         validateConfiguration(test);
+        // STEP 3: 初始化为“待测试 + 未开始接收”,测试状态只在开始接收时流转。
         test.setTestStatus(WAITING);
         test.setReceptionStatus(NOT_STARTED);
         test.setDelFlag("0");
         test.setCreateTime(new Date());
         testMapper.insertTest(test);
+        // STEP 4: 在同一事务内写入电池号、阈值、通道绑定与采集器映射。
         saveConfiguration(test);
         // 最终基线移除了 t_test_audit;Controller 的 @Log 负责记录创建操作,业务表只保存配置事实。
         return test.getTestId();
     }
 
+    /**
+     * 仅在开始接收之前整体替换测试配置,测试状态与接收状态保持不变。
+     *
+     * <p>前置条件:测试处于“待测试 + 未开始接收”。开始接收后设备数据已按配置归属,
+     * 配置不再允许修改;替换范围包括电池号、阈值和通道绑定,在同一事务内完成。</p>
+     *
+     * @param test 新的测试配置,须带测试主键
+     * @return 更新行数,成功固定为 1
+     * @throws ServiceException 测试ID为空、测试不存在、已开始接收或配置校验不通过
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int updateTest(TTest test)
     {
         if (test == null || test.getTestId() == null) throw new ServiceException("测试ID不能为空");
+        // STEP 1: 取测试行锁并确认仍处于“待测试 + 未开始接收”。
         TTest current = testMapper.selectTestByIdForUpdate(test.getTestId());
         if (current == null) throw new ServiceException("测试不存在");
         loadConfiguration(current);
         if (!stateMatches(current, WAITING, NOT_STARTED))
             throw new ServiceException("开始接收后不能修改测试配置");
 
+        // STEP 2: 保留创建人与两个状态字段,只接受配置类字段的修改。
         test.setCreatorId(current.getCreatorId());
         test.setTestStatus(current.getTestStatus());
         test.setReceptionStatus(current.getReceptionStatus());
@@ -121,7 +168,7 @@
         int rows = testMapper.updateTest(test);
         if (rows != 1) throw new ServiceException("测试配置已变更,请刷新后重试");
 
-        // 开始接收前没有数据归属事实,配置关系可在同一事务内整体替换;操作审计由 Controller @Log 追加。
+        // STEP 3: 开始接收前没有数据归属事实,配置关系可在同一事务内整体替换;操作审计由 Controller @Log 追加。
         testMapper.deleteTestCollectorChannelsByTestId(test.getTestId());
         testMapper.deleteTestBindingsByTestId(test.getTestId());
         testMapper.deleteTestBatteriesByTestId(test.getTestId());
@@ -131,7 +178,17 @@
     }
 
     /**
-     * 建立测试接收边界;来源离线不阻断状态流转,只有成功观测数据才会写入测试事实。
+     * 开始接收:校验全部绑定来源后建立接收边界,边界之后的成功观测才归属本测试。
+     *
+     * <p>前置条件:测试处于“待测试 + 未开始接收”,每个电池号都绑定启用的检测柜通道与采集器,
+     * 且同一检测柜通道在本测试内不重复。来源离线不阻断状态流转,允许从已运行设备中途接入;
+     * 本动作只改变系统数据请求和测试状态,不向设备发送控制命令。</p>
+     *
+     * @param testId     测试主键
+     * @param operatorId 操作人用户 ID
+     * @param updateBy   更新人账号,写入测试行的 update_by
+     * @return 更新行数,成功固定为 1
+     * @throws ServiceException 入参为空、测试不存在、状态不匹配、来源校验失败或数据来源已被其他测试占用
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -139,6 +196,7 @@
     {
         if (testId == null) throw new ServiceException("测试ID不能为空");
         if (operatorId == null) throw new ServiceException("操作人不能为空");
+        // STEP 1: 取测试行锁,确认状态为“待测试 + 未开始接收”并加载绑定配置。
         TTest current = testMapper.selectTestByIdForUpdate(testId);
         if (current == null) throw new ServiceException("测试不存在");
         if (!stateMatches(current, WAITING, NOT_STARTED))
@@ -146,6 +204,7 @@
         loadConfiguration(current);
         if (current.getBatteries().isEmpty()) throw new ServiceException("测试未配置电池号");
 
+        // STEP 2: 逐电池号校验检测柜通道唯一、通道启用、采集器存在且映射完整。
         Date actionAt = new Date();
         Set<Long> channelIds = new HashSet<>();
         Set<String> collectorChannels = new HashSet<>();
@@ -171,6 +230,7 @@
 
         }
 
+        // STEP 3: 在测试行锁内推进为“检测中 + 接收中”,并把此刻写为接收边界。
         try
         {
             if (testMapper.updateReceptionStarted(testId, actionAt, runtimeAttach, updateBy) != 1)
@@ -179,21 +239,36 @@
         }
         catch (DuplicateKeyException exception)
         {
+            // 检测柜通道已被其他接收中的测试占用,唯一索引保证不会同时归属两个测试。
             throw new ServiceException("数据来源已被其他测试占用,请刷新后重试");
         }
     }
 
+    /**
+     * 暂停接收:停止为本次测试请求实时数据,保留已持久化的历史事实。
+     *
+     * <p>前置条件:测试处于“检测中 + 接收中”。设备不接收暂停命令,
+     * 暂停期间停止请求数据且不补采,暂停空档不视为数据缺口。</p>
+     *
+     * @param testId     测试主键
+     * @param operatorId 操作人用户 ID
+     * @param updateBy   更新人账号,写入测试行的 update_by
+     * @return 更新行数,成功固定为 1
+     * @throws ServiceException 入参为空、测试不存在或当前不是接收中状态
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int pauseReception(Long testId, Long operatorId, String updateBy)
     {
         if (testId == null) throw new ServiceException("测试ID不能为空");
         if (operatorId == null) throw new ServiceException("操作人不能为空");
+        // STEP 1: 取测试行锁,确认状态为“检测中 + 接收中”。
         TTest current = testMapper.selectTestByIdForUpdate(testId);
         if (current == null) throw new ServiceException("测试不存在");
         if (!stateMatches(current, "RUNNING", "RECEIVING"))
             throw new ServiceException("当前测试不是接收中状态");
 
+        // STEP 2: 推进为“暂停 + 暂停接收”;不记录接收边界,恢复时再重置边界。
         Date actionAt = new Date();
         if (testMapper.updateReceptionPaused(testId, updateBy, actionAt) != 1)
             throw new ServiceException("测试状态已变更,请刷新后重试");
@@ -201,35 +276,63 @@
         return 1;
     }
 
+    /**
+     * 继续接收:为全部绑定来源恢复实时数据请求,只追加新的成功接收事实。
+     *
+     * <p>前置条件:测试处于“暂停 + 暂停接收”,且绑定来源仍可用。
+     * 继续接收会刷新接收边界,暂停空档不补采;设备已经结束时不得继续接收。</p>
+     *
+     * @param testId     测试主键
+     * @param operatorId 操作人用户 ID
+     * @param updateBy   更新人账号,写入测试行的 update_by
+     * @return 更新行数,成功固定为 1
+     * @throws ServiceException 入参为空、测试不存在或当前不是暂停接收状态
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int resumeReception(Long testId, Long operatorId, String updateBy)
     {
         if (testId == null) throw new ServiceException("测试ID不能为空");
         if (operatorId == null) throw new ServiceException("操作人不能为空");
+        // STEP 1: 取测试行锁,确认状态为“暂停 + 暂停接收”。
         TTest current = testMapper.selectTestByIdForUpdate(testId);
         if (current == null) throw new ServiceException("测试不存在");
         if (!stateMatches(current, "PAUSED", "PAUSED"))
             throw new ServiceException("当前测试不是暂停接收状态");
-        // 加锁查询只返回测试主表,继续接收前重新加载绑定配置以校验来源仍可用。
+        // STEP 2: 加锁查询只返回测试主表,继续接收前重新加载绑定配置以校验来源仍可用。
         loadConfiguration(current);
+        // STEP 3: 推进为“检测中 + 接收中”,并把此刻刷新为新的接收边界。
         Date actionAt = new Date();
         if (testMapper.updateReceptionResumed(testId, updateBy, actionAt) != 1)
             throw new ServiceException("测试状态已变更,请刷新后重试");
         return 1;
     }
 
+    /**
+     * 停止测试:停止数据请求并进入“检测完成 + 已结束接收”的不可恢复终态。
+     *
+     * <p>前置条件:测试处于“检测中 + 接收中”或“暂停 + 暂停接收”。数据缺失、序号跳跃和
+     * 采样间隔异常不阻止完成;不向检测柜或采集器发送停止命令,设备仍按现场事实独立运行。</p>
+     *
+     * @param testId     测试主键
+     * @param operatorId 操作人用户 ID
+     * @param updateBy   更新人账号,写入测试行的 update_by
+     * @return 更新行数,成功固定为 1
+     * @throws ServiceException 入参为空、测试不存在或当前状态不允许停止
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int stopTest(Long testId, Long operatorId, String updateBy)
     {
         if (testId == null) throw new ServiceException("测试ID不能为空");
         if (operatorId == null) throw new ServiceException("操作人不能为空");
+        // STEP 1: 取测试行锁,确认处于可停止的两种状态之一。
         TTest current = testMapper.selectTestByIdForUpdate(testId);
         if (current == null) throw new ServiceException("测试不存在");
         if (!(stateMatches(current, "RUNNING", "RECEIVING")
                 || stateMatches(current, "PAUSED", "PAUSED")))
             throw new ServiceException("当前测试不能停止");
+        // STEP 2: 推进为“检测完成 + 已结束接收”并记录结束时间,不执行完整性门禁。
         Date actionAt = new Date();
         if (testMapper.updateReceptionStopped(testId, updateBy, actionAt) != 1)
             throw new ServiceException("测试状态已变更,请刷新后重试");
@@ -248,6 +351,19 @@
                 && expectedTestStatus.equals(test.getTestStatus());
     }
 
+    /**
+     * 在任意生命周期整体删除测试及其全部子数据,删除后不可恢复。
+     *
+     * <p>前置条件:调用方已确认操作人身份。逐条测试加测试行锁后清理绑定、阈值、三类数据、
+     * 逻辑工步、预警异常和修订记录,最后删除测试主记录;按 ADR-0047 不写操作日志、修订记录或墓碑,
+     * 也不删除电池组、设备登记和独立保存的报表。</p>
+     *
+     * @param testIds    待删除的测试主键数组
+     * @param operatorId 操作人用户 ID
+     * @param updateBy   更新人账号
+     * @return 实际删除的测试数量
+     * @throws ServiceException 未选择测试或测试不存在
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int deleteTestByIds(Long[] testIds, Long operatorId, String updateBy)
@@ -256,9 +372,10 @@
         int rows = 0;
         for (Long testId : testIds)
         {
+            // STEP 1: 加测试行锁,与接收事务互斥;删除提交后不能再向该测试写入数据。
             TTest current = testMapper.selectTestByIdForUpdate(testId);
             if (current == null) throw new ServiceException("测试不存在");
-            // 与接收事务使用同一测试行锁,删除提交后不能再向该测试写入数据;整体删除不留审计或墓碑。
+            // STEP 2: 在同一事务内清理全部测试子数据,整体删除不留审计或墓碑。
             testMapper.deleteTestRealtimeRecordsByTestId(testId);
             testMapper.deleteTestStepRecordsByTestId(testId);
             testMapper.deleteTestCycleRecordsByTestId(testId);
@@ -269,6 +386,7 @@
             testMapper.deleteTestBindingsByTestId(testId);
             testMapper.deleteTestThresholdRowsByTestId(testId);
             testMapper.deleteTestBatteriesByTestId(testId);
+            // STEP 3: 最后删除测试主记录,行数不为 1 说明配置已被并发改动。
             int deleted = testMapper.deleteTestById(testId, updateBy);
             if (deleted != 1) throw new ServiceException("测试配置已变更,请刷新后重试");
             rows += deleted;
@@ -276,6 +394,19 @@
         return rows;
     }
 
+    /**
+     * 保存当前测试的逻辑工步合并规则,整体替换原有映射。
+     *
+     * <p>前置条件:映射须按循环内连续位置升序、互不重叠、编号从 1 起,且每项都有名称。
+     * 逻辑工步只用于统计图表和报表等派生视图,不改写原始采样记录。</p>
+     *
+     * @param testId     测试主键
+     * @param mappings   逻辑工步映射列表,为空表示清空
+     * @param operatorId 操作人用户 ID
+     * @param updateBy   更新人账号
+     * @return 更新行数,成功固定为 1
+     * @throws ServiceException 测试或操作人为空、测试不存在或映射不满足连续升序且互不重叠
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int saveLogicStepMappings(Long testId, List<TTestLogicStepMapping> mappings, Long operatorId, String updateBy)
@@ -283,6 +414,7 @@
         if (testId == null || operatorId == null) throw new ServiceException("测试和操作人不能为空");
         TTest current = testMapper.selectTestByIdForUpdate(testId);
         if (current == null) throw new ServiceException("测试不存在");
+        // STEP 1: 校验映射为连续的循环内位置区间,编号从 1 起且互不重叠。
         List<TTestLogicStepMapping> normalized = mappings == null ? List.of() : mappings;
         int previousEnd = 0;
         int expectedNo = 1;
@@ -297,25 +429,42 @@
             previousEnd = mapping.getEndStepNo();
             expectedNo++;
         }
+        // STEP 2: 整体替换映射 JSON,作用于该测试的全部循环。
         String newJson = JSON.toJSONString(normalized);
         int rows = testMapper.updateLogicStepMappings(testId, newJson, updateBy, new Date());
         if (rows != 1) throw new ServiceException("工步设置已变更,请刷新后重试");
         return rows;
     }
 
+    /**
+     * 保存测试详情当前最新版,允许测试处于任意状态。
+     *
+     * <p>前置条件:实验名称不超过 200 个字符,字段名称与字段值通过校验。
+     * 详情只保留当前最新版,直接读取电池组当前字段值,不创建测试字段快照,也不产生修订记录。</p>
+     *
+     * @param testId     测试主键
+     * @param detail     测试补充详情,含实验名称、字段名称与人工填写值
+     * @param operatorId 操作人用户 ID
+     * @param updateBy   更新人账号
+     * @return 更新行数,成功固定为 1
+     * @throws ServiceException 测试或操作人为空、测试不存在、实验名称或字段校验不通过
+     */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int saveConsoleDetail(Long testId, TTestConsoleDetail detail, Long operatorId, String updateBy)
     {
         if (testId == null || operatorId == null || detail == null) throw new ServiceException("测试详情不能为空");
+        // STEP 1: 加测试行锁并确认测试存在。
         TTest current = testMapper.selectTestByIdForUpdate(testId);
         if (current == null) throw new ServiceException("测试不存在");
+        // STEP 2: 规范化并校验实验名称、字段名称与字段值。
         String experimentName = trimToNull(detail.getExperimentName());
         if (experimentName != null && experimentName.length() > 200) throw new ServiceException("实验名称长度不能超过200个字符");
         detail.setExperimentName(experimentName);
         validateConsoleFields(detail.getBatteryFields());
         validateConsoleFields(detail.getSpecialFields());
         validateConsoleValues(testId, detail);
+        // STEP 3: 覆盖保存最新版详情,不追加修订记录。
         int rows = testMapper.updateConsoleDetail(testId, JSON.toJSONString(detail), updateBy, new Date());
         if (rows != 1) throw new ServiceException("测试详情保存失败,请刷新后重试");
         return rows;
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/TestDataReceptionServiceImpl.java b/battery-business/src/main/java/com/battery/business/service/impl/TestDataReceptionServiceImpl.java
index a5a484e..5a77983 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/TestDataReceptionServiceImpl.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/TestDataReceptionServiceImpl.java
@@ -70,19 +70,28 @@
             TestWarningExceptionService warningExceptions, Clock clock)
     { this.mapper = mapper; this.tests = tests; this.adapter = adapter; this.warningExceptions = warningExceptions; this.clock = clock; }
 
-    /** 轮询接收中测试,判定实时超限和来源离线异常,并按独立周期保存成功观测。 */
+    /**
+     * 接收调度入口:对全部接收中的测试判定实时超限与来源离线异常,并按各自节奏保存成功观测。
+     *
+     * <p>前置条件:测试处于“检测中 + 接收中”。预警判定按约 5 秒节奏,柜端逐行接收和工步、
+     * 循环汇总按来源写库节奏限流;只有成功观测且可归属本测试的数据才写入事实表,不执行历史补采。</p>
+     *
+     * @throws org.springframework.dao.DataAccessException 任一写入失败时整个接收事务回滚,本轮不保存任何测试的数据
+     */
     @Override @Transactional(rollbackFor = Exception.class)
     public void captureReceivedData()
     {
         for (TTest test : receivingTests()) {
             Instant now = clock.instant();
             Instant previous = lastRealtimeCheckAt.get(test.getTestId());
+            // STEP 1: 按约 5 秒节奏判定当前来源离线事实与实时超限,预警不受采样入库周期限制。
             // 实时预警独立于持久化槽位,并与暂停、停止和删除共享测试事务锁。
             if (previous == null || !now.isBefore(previous.plusSeconds(5))) {
                 warningExceptions.recordOfflineSources(test, Date.from(now));
                 for (TTestBattery battery : batteries(test)) saveRealtime(test, battery);
                 lastRealtimeCheckAt.put(test.getTestId(), now);
             }
+            // STEP 2: 单独按来源写库节奏读取工步与循环汇总,避免每秒重查厂商库。
             for (TTestBattery battery : batteries(test)) saveDeviceAggregates(test, battery);
         }
     }
@@ -99,12 +108,21 @@
     }
     private List<TTestBattery> batteries(TTest test) { return test.getBatteries() == null ? List.of() : test.getBatteries(); }
 
+    /**
+     * 保存单个测试电池的一次实时观测:来源支持设备数据行时逐行落库,否则回落到轮询槽位。
+     *
+     * <p>前置条件:测试正在接收且绑定通道完整。柜端与采集器分别判断成功,失败来源的字段保持为空。</p>
+     *
+     * @param test   接收中的测试
+     * @param battery 测试内的一个电池号及其通道绑定
+     */
     private void saveRealtime(TTest test, TTestBattery battery)
     {
         if (adapter == null) return;
         TTestChannelBinding binding = battery.getBinding(); if (binding == null || binding.getDevid() == null || binding.getChlid() == null) return;
-        // 来源以设备数据行为接收单位时,柜端事实逐行落库(同秒多行都保留),槽位只留给采集器端。
+        // STEP 1: 优先按来源水位逐行接收柜端事实(同秒多行都保留),槽位只留给采集器端。
         boolean rowBased = persistCabinetSamples(test, battery, binding);
+        // STEP 2: 来源不按设备数据行接收时,落到轮询槽位,保持原来的组合观测语义。
         saveRealtimeSlot(test, battery, binding, !rowBased);
     }
 
@@ -112,6 +130,12 @@
      * 按来源水位逐行接收柜端事实。水位是"开始接收"或"继续接收"这一刻来源里的最新一行,
      * 因此水位之前的数据行即使仍在来源里也不进本测试,暂停期间的空档也不会被补回来。
      *
+     * <p>前置条件:调用方持有该测试电池的接收游标。读取节奏按来源写库周期限流;
+     * 实例缺失、来源不可达或读取失败时明确失败并留痕,不回退到其他实例。</p>
+     *
+     * @param test    接收中的测试
+     * @param battery 测试内的一个电池号
+     * @param binding 该电池号绑定的检测柜通道
      * @return 该来源是否按设备数据行接收;来源不支持或被拒绝时返回 false,调用方回落到轮询槽位
      */
     private boolean persistCabinetSamples(TTest test, TTestBattery battery, TTestChannelBinding binding)
@@ -121,7 +145,7 @@
         SampleCursor cursor = sampleCursors.computeIfAbsent(key, ignored -> new SampleCursor());
         synchronized (cursor)
         {
-            // 开始接收与继续接收都会推进接收边界:边界变化即重新取水位,暂停期间的数据不补采。
+            // STEP 1: 开始接收与继续接收都会推进接收边界,边界变化即重新取水位,暂停期间的数据不补采。
             if (!java.util.Objects.equals(cursor.boundary, boundary))
             {
                 cursor.boundary = boundary; cursor.watermark = null; cursor.rowBased = null; cursor.sampledAt = null;
@@ -135,6 +159,7 @@
                 return Boolean.TRUE.equals(cursor.rowBased);
             try
             {
+                // STEP 2: 首次接触该边界时只记录水位,不把水位之前的历史行带入本测试。
                 if (cursor.watermark == null)
                 {
                     cursor.sampledAt = now;
@@ -149,6 +174,7 @@
                     cursor.rowBased = true;
                     return true;
                 }
+                // STEP 3: 逐行写入水位之后的设备事实,每行成功后推进水位。
                 List<CabinetRealtime> rows = adapter.samplesSince(channel, cursor.watermark);
                 cursor.sampledAt = now;
                 for (CabinetRealtime row : rows)
@@ -173,15 +199,22 @@
      * 把一行设备事实写成一条实时记录;来源行身份保证重复接收幂等、同身份不同内容报冲突。
      * 归属边界由水位在设备时间里判定(见 {@link #persistCabinetSamples}),
      * 这里不用服务器取数时间反推设备时间。
+     *
+     * @param test    接收中的测试
+     * @param battery 测试内的一个电池号
+     * @param binding 该电池号绑定的检测柜通道
+     * @param row     来源返回的一行柜端事实;设备时间或服务器接收时间缺失时丢弃不写
      */
     private void insertCabinetSample(TTest test, TTestBattery battery, TTestChannelBinding binding, CabinetRealtime row)
     {
         if (row.deviceTime() == null || row.serverReceivedAt() == null) return;
+        // STEP 1: 先用本次成功观测判定实时超限,异常不依赖采样入库周期。
         Date observedAt = Date.from(row.serverReceivedAt());
         evaluateCabinet(test, binding, "VOLTAGE", BigDecimal.valueOf(row.voltageV()), "V", observedAt);
         evaluateCabinet(test, binding, "CURRENT", BigDecimal.valueOf(row.currentA()), "A", observedAt);
         evaluateCabinet(test, binding, "CAPACITY", BigDecimal.valueOf(row.capacityAh()), "Ah", observedAt);
         evaluateCabinet(test, binding, "ENERGY", BigDecimal.valueOf(row.energyWh()), "Wh", observedAt);
+        // STEP 2: 按来源身份组装记录,槽位留空以允许同秒多行各自成立。
         String identity = sourceIdentity(binding);
         String recordId = row.sourceRecordId() == null || row.sourceRecordId().isBlank()
                 ? "cabinet:" + row.sequenceId() : row.sourceRecordId();
@@ -205,7 +238,7 @@
         value.setExtensionMeasurementsJson(JSON.toJSONString(extension));
         value.setSourceIdentity(identity); value.setSourceRecordId(recordId);
         value.setContentSha256(hash(value));
-        // 重复接收的同内容记录幂等忽略;同身份但内容不同时保留原事实,不覆盖。
+        // STEP 3: 按来源行身份幂等;同内容重复接收忽略,同身份但内容不同时保留原事实并告警,不覆盖。
         String existing = mapper.selectRealtimeRecordHashBySourceRecord(battery.getTestBatteryId(), identity, recordId);
         if (existing != null)
         {
@@ -217,16 +250,27 @@
         mapper.insertRealtimeRecord(value);
     }
 
+    /**
+     * 向轮询槽位写入一条实时采样记录,柜端与采集器端的成功观测在同一行合并保存。
+     *
+     * <p>前置条件:调用方已判定该来源不按设备数据行接收。槽位按持久化周期对齐,同一槽位只写一行;
+     * 柜端和采集器端分别判断成功,失败来源的身份、时间和指标保持为空。</p>
+     *
+     * @param test          接收中的测试
+     * @param battery       测试内的一个电池号
+     * @param binding       该电池号绑定的检测柜通道
+     * @param includeCabinet 本次是否把柜端事实合并进该槽位
+     */
     private void saveRealtimeSlot(TTest test, TTestBattery battery, TTestChannelBinding binding, boolean includeCabinet)
     {
-        // 柜端和采集器端分别判断成功;同一槽位只写一行,失败来源的身份、时间和指标保持为空。
+        // STEP 1: 柜端和采集器端分别判断成功,各取最近一次成功观测;同一槽位只写一行,失败来源的身份、时间和指标保持为空。
         Instant now = clock.instant(); long interval = persistenceInterval(binding); String key = test.getTestId() + ":" + battery.getTestBatteryId();
         CabinetRealtime cabinet = includeCabinet ? latestCabinet(binding) : null;
         if (cabinet != null && !accepted(test, cabinet.deviceTime(), cabinet.serverReceivedAt())) cabinet = null;
         CollectorRealtime collector = latestCollector(battery);
         Map<String, CollectorMeasurement> mapped = mappedCollectorMetrics(test, battery, collector);
         if (cabinet == null && (collector == null || !collector.online() || mapped.isEmpty())) return;
-        // 当前成功测量先判定预警;槽位尚未到期或历史行被删除,都不能阻断实时告警。
+        // STEP 2: 槽位尚未到期或历史行被删除都不能阻断实时告警,当前成功测量先判定预警。
         if (cabinet != null) {
             Date observedAt = Date.from(cabinet.serverReceivedAt());
             evaluateCabinet(test, binding, "VOLTAGE", BigDecimal.valueOf(cabinet.voltageV()), "V", observedAt);
@@ -238,11 +282,12 @@
             warningExceptions.evaluate(test, "COLLECTOR", battery.getCollectorId() + ":" + measurement.channelCode(),
                     measurement.metricCode(), measurement.value(), measurement.unit(), Date.from(measurement.serverReceivedAt()));
         }
+        // STEP 3: 按持久化周期限流并对齐槽位;删除修订随数据库保留,重启后同一槽位也不能重新出现。
         Instant previous = lastSnapshotAt.get(key); if (previous != null && now.isBefore(previous.plusMillis(interval))) return;
         Instant collectorTime = mapped.values().stream().map(CollectorMeasurement::deviceTime).max(Instant::compareTo).orElse(null);
         Instant slot = Instant.ofEpochMilli((now.toEpochMilli() / interval) * interval);
-        // 删除修订随数据库保留;重启后同一槽位也不能重新出现。
         if (mapper.wasRealtimeSlotDeleted(test.getTestId(), battery.getTestBatteryId(), slot.toEpochMilli())) return;
+        // STEP 4: 组装组合观测行,只写成功来源的字段。
         TTestRealtimeRecord row = new TTestRealtimeRecord(); row.setTestId(test.getTestId()); row.setTestBatteryId(battery.getTestBatteryId()); row.setBindingId(binding.getBindingId());
         row.setAdapterCode(binding.getAdapterCode()); row.setCaptureSlotUtc(Date.from(slot)); row.setServerReceivedAtUtc(Date.from(now));
         if (cabinet != null) { row.setCabinetDevtype(binding.getDevtype()); row.setCabinetDevid(binding.getDevid()); row.setCabinetSubdevid(binding.getSubdevid()); row.setCabinetChlid(binding.getChlid()); row.setCabinetAdapterTimezoneId(cabinet.timezoneId()); }
@@ -261,6 +306,7 @@
         row.setExtensionMeasurementsJson(JSON.toJSONString(extension)); row.setSourceIdentity(sourceIdentity(binding));
         row.setSourceRecordId(sourceRecordId(cabinet, mapped));
         row.setContentSha256(hash(row));
+        // STEP 5: 同一槽位内容不相同报冲突并保留原事实;插入被忽略时再核对一次并发写入。
         String old = mapper.selectRealtimeRecordContentHash(test.getTestId(), battery.getTestBatteryId(), row.getCaptureSlotUtc());
         if (old != null) { if (!old.equalsIgnoreCase(HexFormat.of().formatHex(row.getContentSha256()))) log.warn("实时采样内容冲突,拒绝覆盖 testId={}, testBatteryId={}, captureSlotUtc={}", test.getTestId(), battery.getTestBatteryId(), slot); return; }
         int inserted = mapper.insertRealtimeRecord(row);
@@ -278,8 +324,18 @@
         if (value != null) warningExceptions.evaluate(test, "CABINET", sourceIdentity(binding), metricCode, value, unit, occurredAt);
     }
 
+    /**
+     * 保存单个测试电池的工步与循环汇总事实,来源编号或明确的时间组合保证重复观测幂等。
+     *
+     * <p>前置条件:测试正在接收且绑定通道完整。只接受来源返回的统计记录,不从实时采样推算;
+     * 已按授权物理删除的汇总不会因为重复观测被写回。</p>
+     *
+     * @param test    接收中的测试
+     * @param battery 测试内的一个电池号及其通道绑定
+     */
     private void saveDeviceAggregates(TTest test, TTestBattery battery)
     {
+        // STEP 1: 按来源写库节奏限流,避免接收调度每秒重查厂商库。
         // 工步、循环只接受来源返回的统计记录,并以来源编号或明确的时间组合执行幂等。
         if (adapter == null) return; TTestChannelBinding binding = battery.getBinding(); if (binding == null || binding.getDevid() == null || binding.getChlid() == null) return;
         String key = test.getTestId() + ":" + battery.getTestBatteryId();
@@ -287,9 +343,11 @@
         Instant now = clock.instant(); Instant previous = lastAggregateAt.get(key);
         if (previous != null && now.isBefore(previous.plusMillis(persistenceInterval(binding)))) return;
         lastAggregateAt.put(key, now);
+        // STEP 2: 读取来源的工步与循环汇总;来源不可达时本轮放弃,下一轮重试。
         ChannelIdentity channel = new ChannelIdentity(binding.getAdapterCode(), binding.getDevtype(), binding.getDevid(), binding.getSubdevid(), binding.getChlid());
         List<StepRecord> steps; List<CycleRecord> cycles; try { steps = adapter.stepRecords(channel); cycles = adapter.cycleRecords(channel); } catch (RuntimeException ex) { return; }
         String identity = sourceIdentity(binding);
+        // STEP 3: 逐条写入可归属的实际工步,已删除的来源行跳过,内容不一致时保留原事实。
         for (StepRecord source : steps == null ? List.<StepRecord>of() : steps)
         {
             if (!accepted(test, source.endedAt(), source.serverReceivedAt())) continue;
@@ -305,6 +363,7 @@
                     log.warn("工步记录内容冲突,拒绝覆盖 testId={}, dedupeKey={}", test.getTestId(), row.getDedupeKey());
             }
         }
+        // STEP 4: 逐条写入可归属的循环汇总,来源类别随记录保存以便区分推导值与设备事实。
         for (CycleRecord source : cycles == null ? List.<CycleRecord>of() : cycles)
         {
             if (!accepted(test, source.endedAt(), source.serverReceivedAt())) continue;
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/TestWarningExceptionService.java b/battery-business/src/main/java/com/battery/business/service/impl/TestWarningExceptionService.java
index bd6c174..29d0a6c 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/TestWarningExceptionService.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/TestWarningExceptionService.java
@@ -7,6 +7,7 @@
 import com.alibaba.fastjson2.JSON;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import com.ruoyi.common.exception.ServiceException;
 import com.battery.business.domain.TTest;
 import com.battery.business.domain.TTestWarningException;
 import com.battery.business.domain.TTestWarningThreshold;
@@ -17,17 +18,43 @@
 public class TestWarningExceptionService {
     private final TTestMapper mapper;
     public TestWarningExceptionService(TTestMapper mapper) { this.mapper = mapper; }
-    /** 调用方须持有测试事务锁;仅在接收中记录当前绑定来源的离线事实。 */
+
+    /**
+     * 记录接收中测试的绑定来源离线事实。
+     *
+     * <p>前置条件:调用方持有该测试的事务锁。只有测试处于“检测中 + 接收中”才判定离线,
+     * 离线不伪造测量值;暂停、停止、未开始接收时不新增或累加。</p>
+     *
+     * @param test       接收中的测试
+     * @param occurredAt 离线事实发生时间
+     */
     public void recordOfflineSources(TTest test, Date occurredAt) {
+        // STEP 1: 只有“检测中 + 接收中”才判定离线,其他状态不新增也不累加。
         if (test == null || !"RUNNING".equals(test.getTestStatus()) || !"RECEIVING".equals(test.getReceptionStatus())) return;
+        // STEP 2: 按测试和来源身份聚合离线异常,不写入测量值或阈值。
         for (var source : mapper.selectOfflineTestSources(test.getTestId())) {
             mapper.upsertOfflineException(test.getTestId(), source.sourceType(), source.sourceIdentity(), occurredAt);
         }
     }
 
-    /** 在持有测试事务锁时,对接收中的成功实时测量聚合开放异常,不受采样入库周期限制。 */
+    /**
+     * 对接收中的一次成功实时测量判定超限并按聚合键写入开放异常。
+     *
+     * <p>前置条件:调用方持有该测试的事务锁。判定只依据成功观测值,不受采样入库周期限制,
+     * 正常观测不会自动关闭已有异常。</p>
+     *
+     * @param test        接收中的测试,须已加载阈值规则
+     * @param sourceType  来源类型:CABINET 检测柜、COLLECTOR 采集器
+     * @param sourceIdentity 来源身份
+     * @param metricCode  指标编码
+     * @param value       本次成功观测值;为空时不判定
+     * @param unit        观测值单位
+     * @param occurredAt  观测时间,为空时取当前时间
+     */
     public void evaluate(TTest test, String sourceType, String sourceIdentity, String metricCode, BigDecimal value, String unit, Date occurredAt) {
+        // STEP 1: 按阈值规则筛出被本次成功观测超限的规则,缺失测量不参与判定。
         for (TTestWarningThreshold rule : exceededRules(test, metricCode, value)) {
+            // STEP 2: 以测试、指标和规则为聚合键原子累加开放异常,并保存触发时的规则快照。
             mapper.upsertWarningException(test.getTestId(), rule.getThresholdId(), sourceType, sourceIdentity, metricCode, value, unit,
                     JSON.toJSONString(rule), rule.getSeverity(), occurredAt == null ? new Date() : occurredAt);
         }
@@ -41,14 +68,47 @@
                 && outside(value, rule.getMinValue(), rule.getMaxValue())).toList();
     }
 
-    /** 由测试负责人关闭开放异常,处理内容保存在异常事实中。 */
-    @Transactional(rollbackFor = Exception.class)
-    public int close(Long exceptionId, Long actorId, String actorName, String handlingNote) {
-        TTestWarningException exception = mapper.selectWarningExceptionById(exceptionId);
-        if (exception == null) return 0;
-        int rows = mapper.closeWarningException(exceptionId, actorId, actorName, new Date(), handlingNote);
-        return rows;
+    /**
+     * 按测试和异常状态查询已持久化的阈值与离线异常事实。
+     *
+     * @param testId 测试主键,为空表示不限测试
+     * @param status 异常状态:OPEN 开放、CLOSED 已关闭;为空表示不限状态
+     * @return 按最近触发时间倒序排列的异常事实
+     */
+    public List<TTestWarningException> list(Long testId, String status) {
+        return mapper.selectWarningExceptions(testId, status);
     }
+
+    /**
+     * 测试负责人关闭开放异常:校验归属与处理结果后写入,返回关闭后的异常事实。
+     *
+     * <p>前置条件:操作人必须是该异常所属测试的测试负责人,且处理结果必填。
+     * 只有开放异常可以关闭;关闭只改变处理状态,原始异常事实与累计触发次数仍保留。</p>
+     *
+     * @param exceptionId  异常主键
+     * @param actorId      操作人用户 ID,同时作为处理人写入
+     * @param actorName    操作人显示名称,随异常事实保存
+     * @param handlingNote 处理结果,必填
+     * @return 关闭后的异常事实
+     * @throws ServiceException 处理结果为空、异常不存在、操作人不是测试负责人或异常已关闭
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public TTestWarningException closeByPrincipal(Long exceptionId, Long actorId, String actorName, String handlingNote) {
+        // STEP 1: 处理结果为必填,先拒绝空提交。
+        String note = handlingNote == null ? null : handlingNote.trim();
+        if (note == null || note.isEmpty()) throw new ServiceException("处理结果不能为空");
+        // STEP 2: 异常与所属测试必须存在,且操作人就是该测试的测试负责人。
+        TTestWarningException exception = mapper.selectWarningExceptionById(exceptionId);
+        if (exception == null) throw new ServiceException("异常不存在");
+        TTest test = mapper.selectTestById(exception.getTestId());
+        if (test == null || actorId == null || !actorId.equals(test.getPrincipalId()))
+            throw new ServiceException("仅测试负责人可以关闭预警异常");
+        // STEP 3: 仅开放异常可关闭,行数不为 1 说明已被并发关闭。
+        if (mapper.closeWarningException(exceptionId, actorId, actorName, new Date(), note) != 1)
+            throw new ServiceException("异常已关闭或不存在");
+        return mapper.selectWarningExceptionById(exceptionId);
+    }
+
     private static boolean outside(BigDecimal value, BigDecimal min, BigDecimal max) { return (min != null && value.compareTo(min) < 0) || (max != null && value.compareTo(max) > 0); }
     static boolean matchesCollectorMetric(String configured, String actual) {
         if (configured == null || actual == null) return false;
diff --git a/battery-business/src/main/java/com/battery/business/test/dto/TestBatteryBindingDto.java b/battery-business/src/main/java/com/battery/business/test/dto/TestBatteryBindingDto.java
index 651e416..c799500 100644
--- a/battery-business/src/main/java/com/battery/business/test/dto/TestBatteryBindingDto.java
+++ b/battery-business/src/main/java/com/battery/business/test/dto/TestBatteryBindingDto.java
@@ -16,7 +16,9 @@
             CabinetChannelDto cabinetChannel, List<CollectorChannelDto> collectorChannels) {
         this(testBatteryId, null, groupId, batteryNo, collectorId, cabinetChannel, null, collectorChannels);
     }
+    /** 测试电池主键;兼容只传旧页面入参别名 relationId 的调用方。 */
     public Long resolvedTestBatteryId() { return testBatteryId != null ? testBatteryId : relationId; }
+    /** 检测柜通道绑定;兼容只传旧页面入参别名 binding 的调用方。 */
     public CabinetChannelDto resolvedCabinetChannel() { return cabinetChannel != null ? cabinetChannel : binding; }
     public record CabinetChannelDto(Long channelId, String expectedBarcode) { }
     public record CollectorChannelDto(String measurementCode, String measurementName, String channelCode) { }
diff --git a/battery-business/src/main/resources/mapper/battery/TDeviceIngestRecordMapper.xml b/battery-business/src/main/resources/mapper/battery/TDeviceIngestRecordMapper.xml
index c7eeca2..b33e572 100644
--- a/battery-business/src/main/resources/mapper/battery/TDeviceIngestRecordMapper.xml
+++ b/battery-business/src/main/resources/mapper/battery/TDeviceIngestRecordMapper.xml
@@ -30,6 +30,12 @@
         <result property="receivedAtUtc" column="received_at_utc"/>
     </resultMap>
 
+    <!-- 落地行的显式列名,顺序与建表脚本一致;列集合不随运行期表结构变化。 -->
+    <sql id="ingestColumns">ingest_id, adapter_code, payload_kind, devtype, devid, subdevid, chlid, record_sequence, record_key,
+        source_ref, device_time_utc, device_time_raw, timezone_id, cycle_no, step_no, relative_time_seconds,
+        voltage_v, current_a, charge_capacity_ah, discharge_capacity_ah, charge_energy_wh, discharge_energy_wh,
+        payload_json, content_sha256, received_at_utc</sql>
+
     <!-- 驼峰映射未开启(见 mybatis-config.xml),列名带下划线的查询一律显式映射。 -->
     <resultMap id="TDeviceIngestChannelResultMap" type="com.battery.business.domain.vo.TDeviceIngestChannel">
         <result property="devtype" column="devtype"/>
@@ -91,7 +97,7 @@
     </select>
 
     <select id="selectLatestSample" resultMap="TDeviceIngestRecordResultMap">
-        select * from t_device_ingest_record
+        select <include refid="ingestColumns"/> from t_device_ingest_record
         where adapter_code = #{adapterCode} and payload_kind = 'SAMPLE'
           and devtype = #{devtype} and devid = #{devid} and subdevid = #{subdevid} and chlid = #{chlid}
         order by device_time_utc desc, record_sequence desc
@@ -100,7 +106,7 @@
 
     <!-- 只取严格晚于水位的行;同秒多行按协议序号各自成立,不按秒合并。 -->
     <select id="selectSamplesSince" resultMap="TDeviceIngestRecordResultMap">
-        select * from t_device_ingest_record
+        select <include refid="ingestColumns"/> from t_device_ingest_record
         where adapter_code = #{adapterCode} and payload_kind = 'SAMPLE'
           and devtype = #{devtype} and devid = #{devid} and subdevid = #{subdevid} and chlid = #{chlid}
         <if test="watermarkTime != null">
diff --git a/battery-business/src/main/resources/mapper/battery/TTestMapper.xml b/battery-business/src/main/resources/mapper/battery/TTestMapper.xml
index b9dc5dc..2daa454 100644
--- a/battery-business/src/main/resources/mapper/battery/TTestMapper.xml
+++ b/battery-business/src/main/resources/mapper/battery/TTestMapper.xml
@@ -657,6 +657,7 @@
               'capture_slot_utc', #{captureSlotMillis})))
     </select>
     <sql id="dataId"><choose><when test="dataType == 'REALTIME'">record_id</when><when test="dataType == 'STEP'">step_record_id</when><otherwise>cycle_record_id</otherwise></choose></sql>
+    <!-- 三类数据表按 dataType 动态取表名,resultMap 映射全列:显式列名需随每次迁移同步,故保留 SELECT *。 -->
     <select id="selectDataRecord" resultType="map">
         select * from <include refid="dataTable"/> where test_id=#{testId} and <include refid="dataId"/>=#{recordId} for update
     </select>
diff --git a/battery-device-api/src/main/java/com/battery/device/api/discovery/InMemoryDeviceDiscoveryCandidateStore.java b/battery-device-api/src/main/java/com/battery/device/api/discovery/InMemoryDeviceDiscoveryCandidateStore.java
index 37eea0f..2426a24 100644
--- a/battery-device-api/src/main/java/com/battery/device/api/discovery/InMemoryDeviceDiscoveryCandidateStore.java
+++ b/battery-device-api/src/main/java/com/battery/device/api/discovery/InMemoryDeviceDiscoveryCandidateStore.java
@@ -11,12 +11,14 @@
     private final Map<String, CabinetCandidate> cabinets = new LinkedHashMap<>();
     private final Map<String, CollectorCandidate> collectors = new LinkedHashMap<>();
 
+    /** 按设备编码 upsert 本轮检测柜观测;本轮未返回的候选保留到逻辑过期。 */
     @Override
     public synchronized void recordCabinets(List<CabinetCandidate> candidates)
     {
         candidates.forEach(item -> cabinets.put(item.deviceCode(), item));
     }
 
+    /** 整体替换指定适配器的采集器候选,使该适配器下已不存在的采集器立即消失。 */
     @Override
     public synchronized void replaceCollectors(String adapterCode, List<CollectorCandidate> candidates)
     {
@@ -24,6 +26,7 @@
         candidates.forEach(item -> collectors.put(item.deviceCode(), item));
     }
 
+    /** 删除指定适配器名下的检测柜与采集器候选;仅供人工重置或联调清理。 */
     @Override
     public synchronized void removeAdapter(String adapterCode)
     {
@@ -31,6 +34,7 @@
         collectors.values().removeIf(item -> item.adapterCode().equals(adapterCode));
     }
 
+    /** 返回指定时刻尚未过期的候选,并清理已过期条目。 */
     @Override
     public synchronized CandidateSnapshot snapshot(Instant now)
     {
diff --git a/battery-device-api/src/main/java/com/battery/device/api/model/AdapterCapabilities.java b/battery-device-api/src/main/java/com/battery/device/api/model/AdapterCapabilities.java
index 9752a71..515ace8 100644
--- a/battery-device-api/src/main/java/com/battery/device/api/model/AdapterCapabilities.java
+++ b/battery-device-api/src/main/java/com/battery/device/api/model/AdapterCapabilities.java
@@ -6,6 +6,7 @@
         boolean historyDownload,
         boolean control)
 {
+    /** 只观测的能力组合:具备发现与实时观测,不提供历史下载与控制。 */
     public static AdapterCapabilities observeOnly()
     {
         return new AdapterCapabilities(true, true, false, false);
diff --git a/battery-device-api/src/main/java/com/battery/device/api/model/ChannelIdentity.java b/battery-device-api/src/main/java/com/battery/device/api/model/ChannelIdentity.java
index 812bb8d..c9d5fd2 100644
--- a/battery-device-api/src/main/java/com/battery/device/api/model/ChannelIdentity.java
+++ b/battery-device-api/src/main/java/com/battery/device/api/model/ChannelIdentity.java
@@ -17,6 +17,7 @@
             throw new IllegalArgumentException("BTS channel identity values must be non-negative");
     }
 
+    /** 返回不含适配器实例的厂商通道键,由设备类型、设备号、单元号和通道号拼成。 */
     public String vendorKey()
     {
         return devtype + "-" + devid + "-" + subdevid + "-" + chlid;
diff --git a/battery-device-api/src/main/java/com/battery/device/api/model/DetectedDeviceMetadata.java b/battery-device-api/src/main/java/com/battery/device/api/model/DetectedDeviceMetadata.java
index 8bec7a3..da7817f 100644
--- a/battery-device-api/src/main/java/com/battery/device/api/model/DetectedDeviceMetadata.java
+++ b/battery-device-api/src/main/java/com/battery/device/api/model/DetectedDeviceMetadata.java
@@ -3,6 +3,7 @@
 /** 设备发现响应可选携带的展示元数据;空值表示协议未提供。 */
 public record DetectedDeviceMetadata(String name, String manufacturer, String model, String macAddress)
 {
+    /** 返回各字段均为空、表示协议未提供元数据的取值。 */
     public static DetectedDeviceMetadata empty()
     {
         return new DetectedDeviceMetadata(null, null, null, null);
diff --git a/battery-device-api/src/main/java/com/battery/device/api/model/DeviceIdentity.java b/battery-device-api/src/main/java/com/battery/device/api/model/DeviceIdentity.java
index 538c790..f2e2b62 100644
--- a/battery-device-api/src/main/java/com/battery/device/api/model/DeviceIdentity.java
+++ b/battery-device-api/src/main/java/com/battery/device/api/model/DeviceIdentity.java
@@ -13,6 +13,7 @@
             throw new IllegalArgumentException("device identity values must be non-negative");
     }
 
+    /** 取通道身份中的设备部分,丢弃通道号。 */
     public static DeviceIdentity from(ChannelIdentity channel)
     {
         Objects.requireNonNull(channel, "channel");
diff --git a/battery-device-api/src/main/java/com/battery/device/api/model/DeviceObservation.java b/battery-device-api/src/main/java/com/battery/device/api/model/DeviceObservation.java
index 749e1b9..1e8d540 100644
--- a/battery-device-api/src/main/java/com/battery/device/api/model/DeviceObservation.java
+++ b/battery-device-api/src/main/java/com/battery/device/api/model/DeviceObservation.java
@@ -78,6 +78,7 @@
                 reason == null ? ObservationFailureReason.OFFLINE : reason);
     }
 
+    /** 按失败分类创建不携带过期设备事实的离线观测;通道工作状态一律为 UNOBSERVABLE。 */
     public static DeviceObservation offline(ChannelIdentity channel, ObservationFailureReason failureReason)
     {
         Objects.requireNonNull(channel, "channel");
diff --git a/battery-device-api/src/main/java/com/battery/device/api/model/DeviceWorkStatus.java b/battery-device-api/src/main/java/com/battery/device/api/model/DeviceWorkStatus.java
index 615d83e..5c24b1c 100644
--- a/battery-device-api/src/main/java/com/battery/device/api/model/DeviceWorkStatus.java
+++ b/battery-device-api/src/main/java/com/battery/device/api/model/DeviceWorkStatus.java
@@ -2,22 +2,39 @@
 
 import java.util.Locale;
 
+/**
+ * 设备侧观测到的通道工作状态,是设备事实,独立于设备在线状态和测试接收状态。
+ *
+ * <p>落库前会归并为 RUNNING、PAUSED、ABNORMAL、ENDED、UNOBSERVABLE 五种,见业务层的通道状态映射。</p>
+ */
 public enum DeviceWorkStatus
 {
+    /** 设备运行中。*/
     WORKING,
+    /** 设备通道已结束。*/
     FINISHED,
+    /** 设备通道已停止。*/
     STOPPED,
+    /** 设备保护动作触发。*/
     PROTECTED,
+    /** 设备通道暂停。*/
     PAUSED,
+    /** 设备同步控制中。*/
     SYNCHRONIZING,
+    /** 设备指示灯状态。*/
     LIT,
+    /** 设备等待超时。*/
     WAITING_TIMEOUT,
+    /** 设备等待开始。*/
     WAITING_START,
+    /** 设备通道锁定。*/
     LOCKED,
     /** 设备离线或请求失败时不得沿用上一次通道状态。 */
     UNOBSERVABLE,
+    /** 协议返回无法识别的状态值。 */
     UNKNOWN;
 
+    /** 将厂商协议状态文本映射为通道工作状态;无法识别的取值和 null 归为 UNKNOWN。 */
     public static DeviceWorkStatus fromBts(String value)
     {
         if (value == null) return UNKNOWN;
diff --git a/battery-device-api/src/main/java/com/battery/device/api/registry/DeviceAdapterRegistry.java b/battery-device-api/src/main/java/com/battery/device/api/registry/DeviceAdapterRegistry.java
index f2c51c5..e6fb6d3 100644
--- a/battery-device-api/src/main/java/com/battery/device/api/registry/DeviceAdapterRegistry.java
+++ b/battery-device-api/src/main/java/com/battery/device/api/registry/DeviceAdapterRegistry.java
@@ -119,11 +119,13 @@
         return registered;
     }
 
+    /** 返回指定编码的适配器实例当前是否已注册。 */
     public synchronized boolean contains(String adapterCode)
     {
         return adapters.containsKey(adapterCode);
     }
 
+    /** 关闭全部已注册实例并清空实例表;逐个等待在途操作结束。 */
     @Override
     public void close()
     {
diff --git a/battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapter.java b/battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapter.java
index cb1ed00..f18583f 100644
--- a/battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapter.java
+++ b/battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapter.java
@@ -31,24 +31,28 @@
         this.capabilities = capabilities;
     }
 
+    /** 返回本实例登记的适配器编码。 */
     @Override
     public String adapterCode()
     {
         return adapterCode;
     }
 
+    /** 返回本实例登记的能力标记;BTS 协议路线按只观测装配。 */
     @Override
     public AdapterCapabilities capabilities()
     {
         return capabilities;
     }
 
+    /** 通过设备信息命令读取通道登记事实,作为发现候选返回。 */
     @Override
     public List<DetectedChannel> discoverChannels()
     {
         return codec.parseDeviceInfo(transport.exchange(codec.getDeviceInfoRequest()));
     }
 
+    /** 按给定通道集合请求一次实时状态;通道为空时不发请求。 */
     @Override
     public Map<ChannelIdentity, RealtimeSnapshot> inquire(Set<ChannelIdentity> channels)
     {
@@ -58,6 +62,7 @@
 
 
 
+    /** 关闭底层传输连接。 */
     @Override
     public void close()
     {
diff --git a/battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapterFactory.java b/battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapterFactory.java
index 58de5d7..c5399ea 100644
--- a/battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapterFactory.java
+++ b/battery-device-bts/src/main/java/com/battery/device/bts/BtsDeviceAdapterFactory.java
@@ -12,12 +12,14 @@
     private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(3);
     private static final Duration RESPONSE_TIMEOUT = Duration.ofSeconds(30);
 
+    /** 返回本工厂的适配器类型标识;注册表按它匹配实例配置。 */
     @Override
     public String adapterType()
     {
         return "BTS";
     }
 
+    /** 按实例配置装配 TCP 传输与报文编解码器;连接与响应超时取本类的固定常量。 */
     @Override
     public DeviceAdapter create(AdapterInstanceConfig config)
     {
diff --git a/battery-device-bts/src/main/java/com/battery/device/bts/BtsProtocolException.java b/battery-device-bts/src/main/java/com/battery/device/bts/BtsProtocolException.java
index 9311a74..0b0dc56 100644
--- a/battery-device-bts/src/main/java/com/battery/device/bts/BtsProtocolException.java
+++ b/battery-device-bts/src/main/java/com/battery/device/bts/BtsProtocolException.java
@@ -39,6 +39,7 @@
         this.reason = reason;
     }
 
+    /** 返回协议失败分类;构造时未指定分类的取 {@code INVALID_RESPONSE}。 */
     public Reason reason()
     {
         return reason;
diff --git a/battery-device-bts/src/main/java/com/battery/device/bts/BtsTcpTransport.java b/battery-device-bts/src/main/java/com/battery/device/bts/BtsTcpTransport.java
index b0bbc22..8bf78fb 100644
--- a/battery-device-bts/src/main/java/com/battery/device/bts/BtsTcpTransport.java
+++ b/battery-device-bts/src/main/java/com/battery/device/bts/BtsTcpTransport.java
@@ -51,6 +51,11 @@
         if (maxResponseBytes < 1) throw new IllegalArgumentException("maxResponseBytes must be positive");
     }
 
+    /**
+     * 在长连接上串行完成一次「单请求、单响应」交换:请求后追加报文结束符 {@code #\r\n},
+     * 读到同一结束符即视为响应完整。并发调用由锁串行化;任何传输失败都作废当前连接,
+     * 并把失败分类为 {@link BtsTransportException} 抛出,下一次调用重新建连。
+     */
     @Override
     public String exchange(String request)
     {
@@ -137,6 +142,7 @@
         }
     }
 
+    /** 关闭当前连接;与交换共用同一把锁,下一次交换按需重新建连。 */
     @Override
     public void close()
     {
diff --git a/battery-device-bts/src/main/java/com/battery/device/bts/BtsTransportException.java b/battery-device-bts/src/main/java/com/battery/device/bts/BtsTransportException.java
index 0e56279..2146284 100644
--- a/battery-device-bts/src/main/java/com/battery/device/bts/BtsTransportException.java
+++ b/battery-device-bts/src/main/java/com/battery/device/bts/BtsTransportException.java
@@ -27,6 +27,7 @@
         this.reason = reason;
     }
 
+    /** 返回本次传输失败的分类。 */
     public Reason reason()
     {
         return reason;
diff --git a/battery-device-bts/src/main/java/com/battery/device/bts/BtsXmlCodec.java b/battery-device-bts/src/main/java/com/battery/device/bts/BtsXmlCodec.java
index 65225b8..16c5b49 100644
--- a/battery-device-bts/src/main/java/com/battery/device/bts/BtsXmlCodec.java
+++ b/battery-device-bts/src/main/java/com/battery/device/bts/BtsXmlCodec.java
@@ -50,11 +50,13 @@
         this.deviceZone = deviceZone;
     }
 
+    /** 构造通道发现的请求报文({@code getdevinfo})。 */
     public String getDeviceInfoRequest()
     {
         return envelope("<cmd>getdevinfo</cmd>");
     }
 
+    /** 构造按通道批量查询实时快照的请求报文({@code inquire});只接受属于本实例的通道身份。 */
     public String inquireRequest(Set<ChannelIdentity> channels, boolean includeBarcode)
     {
         StringBuilder list = new StringBuilder("<cmd>inquire</cmd><list count=\"").append(channels.size()).append("\">");
@@ -72,6 +74,7 @@
 
 
 
+    /** 解析 {@code getdevinfo_resp},按本实例的适配器编码生成检测柜通道清单。 */
     public List<DetectedChannel> parseDeviceInfo(String xml)
     {
         Document document = parse(xml, "getdevinfo_resp");
@@ -92,6 +95,7 @@
         return List.copyOf(channels);
     }
 
+    /** 解析 {@code inquire_resp} 并按通道身份归集实时快照;{@code receivedAt} 记为服务器接收时间。 */
     public Map<ChannelIdentity, RealtimeSnapshot> parseSnapshots(String xml, Instant receivedAt)
     {
         Document document = parse(xml, "inquire_resp");
diff --git a/battery-device-bts/src/test/java/com/battery/device/bts/ScriptedBtsTcpSimulator.java b/battery-device-bts/src/test/java/com/battery/device/bts/ScriptedBtsTcpSimulator.java
index 4683702..3376a76 100644
--- a/battery-device-bts/src/test/java/com/battery/device/bts/ScriptedBtsTcpSimulator.java
+++ b/battery-device-bts/src/test/java/com/battery/device/bts/ScriptedBtsTcpSimulator.java
@@ -13,9 +13,11 @@
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 
 /**
@@ -27,7 +29,9 @@
 
     private final ServerSocket serverSocket;
     private final int port;
-    private final ExecutorService executor = Executors.newSingleThreadExecutor();
+    /** 连接服务线程显式创建并命名。该池只提交一个常驻服务任务,队列容量 1 只是声明不再接受更多任务。 */
+    private final ExecutorService executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
+            new LinkedBlockingQueue<>(1), simulatorThreadFactory());
     private final BlockingQueue<ScriptedAction> actions = new LinkedBlockingQueue<>();
     private final List<String> requests = new CopyOnWriteArrayList<>();
     private final Function<String, String> responseProvider;
@@ -145,6 +149,17 @@
         executor.awaitTermination(2, TimeUnit.SECONDS);
     }
 
+    /** 模拟器连接服务线程统一命名并设为守护线程,避免测试退出时残留。 */
+    private static ThreadFactory simulatorThreadFactory()
+    {
+        AtomicInteger sequence = new AtomicInteger();
+        return runnable -> {
+            Thread thread = new Thread(runnable, "bts-simulator-pool-" + sequence.incrementAndGet());
+            thread.setDaemon(true);
+            return thread;
+        };
+    }
+
     private record ScriptedAction(Duration delay, byte[] payload, boolean disconnect)
     {
     }
diff --git a/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapter.java b/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapter.java
index 475afe0..d122581 100644
--- a/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapter.java
+++ b/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapter.java
@@ -114,12 +114,14 @@
                 source::invalidate, Clock.systemUTC(), SAMPLE_INTERVAL_MS);
     }
 
+    /** 返回本实例登记的适配器编码。 */
     @Override
     public String adapterCode()
     {
         return config.adapterCode();
     }
 
+    /** 返回实例配置登记的能力标记。 */
     @Override
     public AdapterCapabilities capabilities()
     {
@@ -166,6 +168,7 @@
         return Map.copyOf(result);
     }
 
+    /** 取该通道最新测试的最新一行采样作为柜端实时观测;没有测试或没有采样行时返回空值。 */
     @Override
     public CabinetRealtime latestCabinet(ChannelIdentity channel)
     {
diff --git a/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java b/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java
index 8526d37..6792927 100644
--- a/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java
+++ b/battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapterFactory.java
@@ -31,12 +31,14 @@
     /** 凭据文件相对进程工作目录定位,使开发(仓库根)与交付(应用目录)用同一个位置。 */
     static final Path CREDENTIALS_FILE = LocalCredentials.DEFAULT_FILE;
 
+    /** 返回本形态的适配器类型标识 {@code NEWARE_MYSQL}。 */
     @Override
     public String adapterType()
     {
         return NewareMysqlAdapter.ADAPTER_TYPE;
     }
 
+    /** 从实例配置取库名、只读账号与口令来源,装配单连接厂商数据源和只读适配器。 */
     @Override
     public DeviceAdapter create(AdapterInstanceConfig config)
     {
diff --git a/battery-device-neware/src/main/java/com/battery/device/neware/NewareStepDirectionResolver.java b/battery-device-neware/src/main/java/com/battery/device/neware/NewareStepDirectionResolver.java
index 05ed408..6b14118 100644
--- a/battery-device-neware/src/main/java/com/battery/device/neware/NewareStepDirectionResolver.java
+++ b/battery-device-neware/src/main/java/com/battery/device/neware/NewareStepDirectionResolver.java
@@ -10,6 +10,7 @@
  */
 public final class NewareStepDirectionResolver implements StepDirectionResolver
 {
+    /** 只按已确认的协议取值给出方向;其余取值与空值一律保持未知。 */
     @Override
     public Direction resolve(String stepType)
     {
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 8562e73..f682c0d 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
@@ -25,6 +25,10 @@
  *
  * <p>防线是结构性的,不是数学上的:任何绕过本数据源另开连接的代码仍能写。
  * 服务端只读账号(见现场对接清单第七节)是第一道防线,本层是第二道。
+ *
+ * <p>{@link DataSource} 接口中与只读边界无关的 JDK 样板方法(日志写出器、登录超时、
+ * 父日志器、{@code unwrap} / {@code isWrapperFor} / {@code toString})直接转发或拒绝驱动对象,
+ * 不承载业务语义,因此不单独加方法注释。
  */
 public final class ReadOnlyVendorDataSource implements DataSource
 {
@@ -47,12 +51,14 @@
         return audit;
     }
 
+    /** 取一条受只读约束的连接;语句白名单在返回的连接代理上生效。 */
     @Override
     public Connection getConnection() throws SQLException
     {
         return guard(delegate.getConnection());
     }
 
+    /** 同上,按给定账号取连接;账号仍受只读白名单约束。 */
     @Override
     public Connection getConnection(String username, String password) throws SQLException
     {
diff --git a/battery-device-neware/src/main/java/com/battery/device/neware/SingleConnectionVendorDataSource.java b/battery-device-neware/src/main/java/com/battery/device/neware/SingleConnectionVendorDataSource.java
index 9c03559..7613f32 100644
--- a/battery-device-neware/src/main/java/com/battery/device/neware/SingleConnectionVendorDataSource.java
+++ b/battery-device-neware/src/main/java/com/battery/device/neware/SingleConnectionVendorDataSource.java
@@ -15,6 +15,9 @@
  *
  * <p>连接复用一条并在查询失败时作废,避免厂商服务端重启后长期持有失效连接;
  * 厂商库实测按固定 60 秒写库,取数频率远低于连接复用带来的收益门槛。
+ *
+ * <p>{@link DataSource} 接口中与连接获取无关的 JDK 样板方法(日志写出器、登录超时、父日志器、
+ * {@code unwrap} / {@code isWrapperFor})只转发驱动对象,不承载业务语义,因此不单独加方法注释。
  */
 public final class SingleConnectionVendorDataSource implements DataSource
 {
@@ -31,6 +34,7 @@
         this.credentials.setProperty("password", password == null ? "" : password);
     }
 
+    /** 返回复用的单条连接,必要时按登记凭据新建;厂商库只读,连接不做写事务。 */
     @Override
     public synchronized Connection getConnection() throws SQLException
     {
@@ -41,6 +45,7 @@
         return connection;
     }
 
+    /** 按给定账号临时新建一条连接,不复用也不替换已缓存的那条。 */
     @Override
     public Connection getConnection(String username, String password) throws SQLException
     {
diff --git a/battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java b/battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java
index 5fa4690..1dea589 100644
--- a/battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java
+++ b/battery-device-simulator/src/main/java/com/battery/device/simulator/BtsDevelopmentSimulator.java
@@ -68,12 +68,14 @@
     private static final ZoneId DEVICE_ZONE = ZoneId.of("Asia/Hong_Kong");
     private static final DateTimeFormatter DEVICE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
 
+    /** 返回固定的适配器实例代码,用于把模拟观测归属到 dev-sim 这一实例。 */
     @Override
     public String adapterCode()
     {
         return ADAPTER_CODE;
     }
 
+    /** 返回只观测能力:支持发现与实时观测,不支持历史下载,也不支持向设备下发控制命令。 */
     @Override
     public AdapterCapabilities capabilities()
     {
@@ -172,6 +174,7 @@
         simulator = ScriptedBtsTcpSimulator.responding(port, this::respond);
     }
 
+    /** 返回模拟器 TCP 端点实际监听的端口。 */
     public int port()
     {
         return simulator.port();
@@ -890,6 +893,7 @@
             throw new IllegalStateException("Invalid BTS simulator numbering or tombstone state in Redis");
     }
 
+    /** 停止模拟器:标记为不再运行并释放 TCP 端点;释放失败时以运行时异常报告。 */
     @Override
     public synchronized void close()
     {
diff --git a/battery-device-simulator/src/main/java/com/battery/device/simulator/InMemoryBtsSimulatorStateStore.java b/battery-device-simulator/src/main/java/com/battery/device/simulator/InMemoryBtsSimulatorStateStore.java
index 722b799..f7903f7 100644
--- a/battery-device-simulator/src/main/java/com/battery/device/simulator/InMemoryBtsSimulatorStateStore.java
+++ b/battery-device-simulator/src/main/java/com/battery/device/simulator/InMemoryBtsSimulatorStateStore.java
@@ -11,6 +11,7 @@
     public InMemoryBtsSimulatorStateStore() { }
     public InMemoryBtsSimulatorStateStore(SimulatorState state) { this.state = state; }
 
+    /** 返回已有状态;尚未保存过任何状态时以 defaults 初始化并复用同一实例。 */
     @Override
     public synchronized SimulatorState loadOrInitialize(SimulatorState defaults)
     {
@@ -18,12 +19,16 @@
         return state;
     }
 
+    /** 保存状态引用,不做拷贝、校验或版本迁移。 */
     @Override
     public synchronized void save(SimulatorState state) { this.state = state; }
+    /** 返回最近请求列表;尚未保存过时为空列表。 */
     @Override
     public synchronized List<RecentRequest> loadRecentRequests() { return recentRequests; }
+    /** 保存最近请求的不可变副本,调用方后续修改原列表不影响这里。 */
     @Override
     public synchronized void saveRecentRequests(List<RecentRequest> requests) { recentRequests = List.copyOf(requests); }
+    /** 用 defaults 覆盖主状态并清空最近请求。 */
     @Override
     public synchronized void reset(SimulatorState defaults)
     {
diff --git a/battery-device-simulator/src/main/java/com/battery/device/simulator/ScriptedBtsTcpSimulator.java b/battery-device-simulator/src/main/java/com/battery/device/simulator/ScriptedBtsTcpSimulator.java
index 2f7a75a..762ff2c 100644
--- a/battery-device-simulator/src/main/java/com/battery/device/simulator/ScriptedBtsTcpSimulator.java
+++ b/battery-device-simulator/src/main/java/com/battery/device/simulator/ScriptedBtsTcpSimulator.java
@@ -13,9 +13,11 @@
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 
 /**
@@ -27,7 +29,9 @@
 
     private final ServerSocket serverSocket;
     private final int port;
-    private final ExecutorService executor = Executors.newSingleThreadExecutor();
+    /** 连接服务线程显式创建并命名。该池只提交一个常驻服务任务,队列容量 1 只是声明不再接受更多任务。 */
+    private final ExecutorService executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
+            new LinkedBlockingQueue<>(1), simulatorThreadFactory());
     private final BlockingQueue<ScriptedAction> actions = new LinkedBlockingQueue<>();
     private final List<String> requests = new CopyOnWriteArrayList<>();
     private final Function<String, String> responseProvider;
@@ -51,6 +55,7 @@
             serverSocket.close();
     }
 
+    /** 创建一启动就关闭监听、任何连接都会被拒绝的模拟器。 */
     public static ScriptedBtsTcpSimulator refusingConnections() throws IOException
     {
         return new ScriptedBtsTcpSimulator(0, false, null);
@@ -64,32 +69,38 @@
         return new ScriptedBtsTcpSimulator(port, true, responseProvider);
     }
 
+    /** 返回该模拟器绑定的本机端口;构造时传入 0 时返回的是系统分配端口而非 0。 */
     public int port()
     {
         return port;
     }
 
+    /** 向脚本队列追加一条零延迟应答,按 UTF-8 编码,由下一次请求按顺序取用。 */
     public void enqueue(String response)
     {
         actions.add(new ScriptedAction(Duration.ZERO, response.getBytes(StandardCharsets.UTF_8), false));
     }
 
+    /** 追加一条延迟应答:连接服务线程收到请求后先等待 delay 再写出;delay 不得为负。 */
     public void enqueueDelayed(Duration delay, String response)
     {
         if (delay.isNegative()) throw new IllegalArgumentException("delay must not be negative");
         actions.add(new ScriptedAction(delay, response.getBytes(StandardCharsets.UTF_8), false));
     }
 
+    /** 追加一条零延迟的原始字节应答:字节原样写出后追加帧结束符,不做字符编码转换。 */
     public void enqueueRaw(byte[] response)
     {
         actions.add(new ScriptedAction(Duration.ZERO, response.clone(), false));
     }
 
+    /** 追加一条断开动作:处理到该条时结束当前连接,且不写出任何应答。 */
     public void enqueueDisconnect()
     {
         actions.add(new ScriptedAction(Duration.ZERO, null, true));
     }
 
+    /** 返回已接收请求帧的只读快照;只有脚本模式(未配置 responseProvider)才记录。 */
     public List<String> requests()
     {
         return List.copyOf(requests);
@@ -166,6 +177,7 @@
         }
     }
 
+    /** 关闭监听并终止连接服务线程,最多等待 2 秒,避免测试退出时残留线程。 */
     @Override
     public void close() throws Exception
     {
@@ -174,6 +186,17 @@
         executor.awaitTermination(2, TimeUnit.SECONDS);
     }
 
+    /** 模拟器连接服务线程统一命名并设为守护线程,避免测试退出时残留。 */
+    private static ThreadFactory simulatorThreadFactory()
+    {
+        AtomicInteger sequence = new AtomicInteger();
+        return runnable -> {
+            Thread thread = new Thread(runnable, "bts-simulator-pool-" + sequence.incrementAndGet());
+            thread.setDaemon(true);
+            return thread;
+        };
+    }
+
     private record ScriptedAction(Duration delay, byte[] payload, boolean disconnect)
     {
     }
diff --git a/ruoyi-admin/src/main/java/com/battery/business/runtime/BtsDevelopmentSimulatorWebConfig.java b/ruoyi-admin/src/main/java/com/battery/business/runtime/BtsDevelopmentSimulatorWebConfig.java
index 988e48d..092b525 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/runtime/BtsDevelopmentSimulatorWebConfig.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/runtime/BtsDevelopmentSimulatorWebConfig.java
@@ -12,6 +12,7 @@
 @Configuration
 public class BtsDevelopmentSimulatorWebConfig implements WebMvcConfigurer
 {
+    /** 只为模拟器页面和控制 API 挂本机拦截器,其余路径不受影响。 */
     @Override
     public void addInterceptors(InterceptorRegistry registry)
     {
diff --git a/ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentSimulatorConfig.java b/ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentSimulatorConfig.java
index 53c5461..d1aa1f0 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentSimulatorConfig.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/runtime/DevelopmentSimulatorConfig.java
@@ -10,6 +10,7 @@
 @Configuration
 @Profile("dev-simulator")
 public class DevelopmentSimulatorConfig {
+    /** 按配置的监听端口装配开发模拟器;仅 dev-simulator 生效,容器关闭时释放其端口。 */
     @Bean(destroyMethod = "close")
     @Profile("dev-simulator")
     public BtsDevelopmentSimulator btsDevelopmentSimulator(BtsSimulatorStateStore stateStore,
@@ -18,6 +19,11 @@
         return new BtsDevelopmentSimulator(simulatorPort, stateStore);
     }
 
+    /**
+     * 将固定开发适配器按代码装配注册进运行时实例表,不写 t_device_adapter。
+     *
+     * <p>其运行时能力只有发现与实时观测,控制能力为不支持,不向设备发送控制命令。</p>
+     */
     @Bean
     @Profile("dev-simulator")
     public ApplicationRunner registerDevelopmentSimulatorAdapter(DeviceAdapterRegistry registry,
diff --git a/ruoyi-admin/src/main/java/com/battery/business/runtime/DeviceAdapterRuntimeConfig.java b/ruoyi-admin/src/main/java/com/battery/business/runtime/DeviceAdapterRuntimeConfig.java
index 5f42435..ffb819d 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/runtime/DeviceAdapterRuntimeConfig.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/runtime/DeviceAdapterRuntimeConfig.java
@@ -24,6 +24,7 @@
 @EnableScheduling
 public class DeviceAdapterRuntimeConfig
 {
+    /** 装配持有全部可用工厂的适配器注册表;推式来源工厂使用业务落地表 Mapper,实例由配置或代码另行注册。 */
     @Bean(destroyMethod = "close")
     public DeviceAdapterRegistry deviceAdapterRegistry(TDeviceIngestRecordMapper ingestRecords)
     {
diff --git a/ruoyi-admin/src/main/java/com/battery/business/runtime/FlywayMigrationConfig.java b/ruoyi-admin/src/main/java/com/battery/business/runtime/FlywayMigrationConfig.java
index a0555ae..8bca0cd 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/runtime/FlywayMigrationConfig.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/runtime/FlywayMigrationConfig.java
@@ -16,6 +16,7 @@
 @Configuration
 public class FlywayMigrationConfig
 {
+    /** 只取主数据源的连接参数执行业务库迁移;厂商库只读,不迁移也不建表。 */
     @Bean(name = "flywayMigrationInitializer", initMethod = "migrate")
     public FlywayMigrationInitializer flywayMigrationInitializer(@Qualifier("masterDataSource") DataSource dataSource)
     {
diff --git a/ruoyi-admin/src/main/java/com/battery/business/runtime/RedisBtsSimulatorStateStore.java b/ruoyi-admin/src/main/java/com/battery/business/runtime/RedisBtsSimulatorStateStore.java
index 16c34e8..61dba9d 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/runtime/RedisBtsSimulatorStateStore.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/runtime/RedisBtsSimulatorStateStore.java
@@ -32,6 +32,7 @@
         this.redisTemplate = redisTemplate;
     }
 
+    /** 读取模拟器主状态,不存在时原子写入默认拓扑;并发初始化时以 Redis 中先写入的那份为准。 */
     @Override
     public SimulatorState loadOrInitialize(SimulatorState defaultState)
     {
@@ -44,12 +45,14 @@
         return restoreState(stored);
     }
 
+    /** 整体覆盖模拟器主状态;该键不设普通 TTL。 */
     @Override
     public void save(SimulatorState state)
     {
         redisTemplate.opsForValue().set(STATE_KEY, JSON.toJSONString(state));
     }
 
+    /** 读取短期保留的最近请求;键不存在时返回空列表。 */
     @Override
     public List<RecentRequest> loadRecentRequests()
     {
@@ -58,6 +61,7 @@
         return List.copyOf(JSON.parseArray(stored.toString(), RecentRequest.class));
     }
 
+    /** 覆盖保存最近请求,整键按小时级 TTL 自动过期。 */
     @Override
     public void saveRecentRequests(List<RecentRequest> requests)
     {
@@ -65,6 +69,7 @@
                 RECENT_REQUESTS_TTL_HOURS, TimeUnit.HOURS);
     }
 
+    /** 人工重置:删除最近请求并写回默认主状态。 */
     @Override
     public void reset(SimulatorState defaultState)
     {
diff --git a/ruoyi-admin/src/main/java/com/battery/business/runtime/RedisDeviceDiscoveryCandidateStore.java b/ruoyi-admin/src/main/java/com/battery/business/runtime/RedisDeviceDiscoveryCandidateStore.java
index a21b4b2..d6b3231 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/runtime/RedisDeviceDiscoveryCandidateStore.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/runtime/RedisDeviceDiscoveryCandidateStore.java
@@ -28,6 +28,7 @@
         this.redisTemplate = redisTemplate;
     }
 
+    /** 按设备编码 upsert 本轮检测柜候选,本轮未返回的候选保留至逻辑过期。 */
     @Override
     public synchronized void recordCabinets(List<CabinetCandidate> candidates)
     {
@@ -38,6 +39,7 @@
         save(new StoredState(CURRENT_VERSION, List.copyOf(merged.values()), current.collectors()));
     }
 
+    /** 替换指定设备适配器实例的全部采集器候选,其它实例的候选保持不变。 */
     @Override
     public synchronized void replaceCollectors(String adapterCode, List<CollectorCandidate> candidates)
     {
@@ -49,6 +51,7 @@
         save(new StoredState(CURRENT_VERSION, current.cabinets(), merged));
     }
 
+    /** 删除指定设备适配器实例的检测柜与采集器候选;仅供人工重置或实机联调清理。 */
     @Override
     public synchronized void removeAdapter(String adapterCode)
     {
@@ -60,6 +63,11 @@
         save(new StoredState(CURRENT_VERSION, cabinets, collectors));
     }
 
+    /**
+     * 返回指定时刻尚未过期的发现候选,并顺带清掉已逻辑过期的条目。
+     *
+     * <p>是否“在用”由操作员判断,适配器不自动判定。</p>
+     */
     @Override
     public synchronized CandidateSnapshot snapshot(Instant now)
     {
diff --git a/ruoyi-admin/src/main/java/com/battery/business/runtime/TestDataReceptionScheduler.java b/ruoyi-admin/src/main/java/com/battery/business/runtime/TestDataReceptionScheduler.java
index f0766d2..3f7e7d5 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/runtime/TestDataReceptionScheduler.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/runtime/TestDataReceptionScheduler.java
@@ -17,6 +17,7 @@
 
     public TestDataReceptionScheduler(ITestDataReceptionService reception) { this.reception = reception; }
 
+    /** 每秒触发一次数据接收,由业务服务保存成功观测;只推进请求和保存,不向设备发送控制命令。 */
     @Scheduled(fixedDelay = 1000)
     public void capture() { reception.captureReceivedData(); }
 }
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/TBatteryGroupController.java b/ruoyi-admin/src/main/java/com/battery/business/web/TBatteryGroupController.java
index 73e35f3..2b8311f 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/web/TBatteryGroupController.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/TBatteryGroupController.java
@@ -29,24 +29,31 @@
     private final ITBatteryGroupService batteryGroupService;
     @Autowired public TBatteryGroupController(ITBatteryGroupService batteryGroupService) { this.batteryGroupService = batteryGroupService; }
 
+    /** 分页查询电池组列表。 */
     @PreAuthorize("@ss.hasPermi('system:batteryGroup:list')") @GetMapping("/list")
     public TableDataInfo list(TBatteryGroup group) { startPage(); List<TBatteryGroup> list = batteryGroupService.selectBatteryGroupList(group); return getDataTable(list); }
 
+    /** 返回可作为创建人的启用用户选项。 */
     @PreAuthorize("@ss.hasPermi('system:batteryGroup:list')") @GetMapping("/creatorOptions")
     public AjaxResult creatorOptions() { return success(batteryGroupService.selectBatteryGroupCreatorOptions()); }
 
+    /** 按电池组主键查询电池组及其当前字段值。 */
     @PreAuthorize("@ss.hasPermi('system:batteryGroup:query')") @GetMapping("/{groupId}")
     public AjaxResult getInfo(@PathVariable Long groupId) { return success(batteryGroupService.selectBatteryGroupById(groupId)); }
 
+    /** 新增电池组及电池号与字段值。 */
     @PreAuthorize("@ss.hasPermi('system:batteryGroup:add')") @Log(title = "电池组管理", businessType = BusinessType.INSERT) @PostMapping
     public AjaxResult add(@Validated @RequestBody BatteryGroupDto request) { TBatteryGroup group = toDomain(request); group.setCreateBy(getUsername()); return toAjax(batteryGroupService.insertBatteryGroup(group)); }
 
+    /** 修改电池组;字段值按提交内容整体替换为当前值。 */
     @PreAuthorize("@ss.hasPermi('system:batteryGroup:edit')") @Log(title = "电池组管理", businessType = BusinessType.UPDATE) @PutMapping
     public AjaxResult edit(@Validated @RequestBody BatteryGroupDto request) { TBatteryGroup group = toDomain(request); group.setUpdateBy(getUsername()); return toAjax(batteryGroupService.updateBatteryGroup(group)); }
 
+    /** 启用或停用电池组;已被测试引用的电池组不能删除,但可以停用。 */
     @PreAuthorize("@ss.hasPermi('system:batteryGroup:changeStatus')") @Log(title = "电池组管理", businessType = BusinessType.UPDATE) @PutMapping("/changeStatus")
     public AjaxResult changeStatus(@RequestBody TBatteryGroup group) { group.setUpdateBy(getUsername()); return toAjax(batteryGroupService.updateBatteryGroupStatus(group)); }
 
+    /** 删除电池组;电池号与字段关系一并清理,被测试引用的电池组由服务层拒绝。 */
     @PreAuthorize("@ss.hasPermi('system:batteryGroup:remove')") @Log(title = "电池组管理", businessType = BusinessType.DELETE) @DeleteMapping("/{groupIds}")
     public AjaxResult remove(@PathVariable Long[] groupIds) { return toAjax(batteryGroupService.deleteBatteryGroupByIds(groupIds, getUsername())); }
 
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java b/ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java
index e57caa6..5a8cc59 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java
@@ -48,6 +48,7 @@
     @Autowired
     private ITestDataQueryService testDataQueryService;
 
+    /** 创建或编辑测试时可选的来源选项。 */
     @PreAuthorize("@ss.hasAnyPermi('system:test:add,system:test:edit')")
     @GetMapping("/options")
     public AjaxResult options()
@@ -55,6 +56,7 @@
         return success(testService.selectConfigurationOptions());
     }
 
+    /** 按测试状态、接收状态及配置条件分页查询测试列表。 */
     @PreAuthorize("@ss.hasPermi('system:test:list')")
     @GetMapping("/list")
     public TableDataInfo list(TTest test)
@@ -64,6 +66,7 @@
         return getDataTable(list);
     }
 
+    /** 按测试主键查询测试配置及其业务状态。 */
     @PreAuthorize("@ss.hasPermi('system:test:query')")
     @GetMapping("/{testId}")
     public AjaxResult getInfo(@PathVariable Long testId)
@@ -71,6 +74,7 @@
         return success(testService.selectTestById(testId));
     }
 
+    /** 创建待测试记录;创建人取当前登录用户,初始为未开始接收。 */
     @PreAuthorize("@ss.hasPermi('system:test:add')")
     @Log(title = "测试管理", businessType = BusinessType.INSERT)
     @PostMapping
@@ -84,6 +88,7 @@
         return success(testService.selectTestById(testId));
     }
 
+    /** 修改尚未开始接收的测试配置;开始接收后由服务层拒绝。 */
     @PreAuthorize("@ss.hasPermi('system:test:edit')")
     @Log(title = "测试管理", businessType = BusinessType.UPDATE)
     @PutMapping
@@ -96,6 +101,7 @@
         return success(testService.selectTestById(test.getTestId()));
     }
 
+    /** 开始接收动作;只改变系统数据归属,不向设备发送控制命令。 */
     @PreAuthorize("@ss.hasPermi('system:test:start')")
     @Log(title = "测试管理", businessType = BusinessType.UPDATE)
     @PutMapping("/start")
@@ -220,6 +226,7 @@
     public record ConsoleDataActionRequest(String dataType, String operationType, String scopeSummary,
             String identitySummary, String changesSummary, String reason, List<DataRevisionItem> overrides) { }
 
+    /** 整体删除测试及其全部子数据;不可恢复,不写操作日志、修订或墓碑。 */
     @PreAuthorize("@ss.hasPermi('system:test:remove')")
     @DeleteMapping("/{testIds}")
     public AjaxResult remove(@PathVariable Long[] testIds)
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/TTestDataRevisionController.java b/ruoyi-admin/src/main/java/com/battery/business/web/TTestDataRevisionController.java
index 076058d..3e9bb3b 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/web/TTestDataRevisionController.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/TTestDataRevisionController.java
@@ -21,6 +21,7 @@
     @Autowired
     private ITestConsoleDataService service;
 
+    /** 按测试、数据类型、操作类型和时间范围分页查询修订行为。 */
     @PreAuthorize("@ss.hasPermi('system:dataRevision:list')")
     @GetMapping("/list")
     public TableDataInfo list(TTestDataRevisionAction query)
@@ -30,6 +31,7 @@
         return getDataTable(rows);
     }
 
+    /** 按修订主键返回一次用户行为的详情,含操作前完整内容。 */
     @PreAuthorize("@ss.hasPermi('system:dataRevision:query')")
     @GetMapping("/{actionId}")
     public AjaxResult detail(@PathVariable Long actionId)
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/TTestWarningExceptionController.java b/ruoyi-admin/src/main/java/com/battery/business/web/TTestWarningExceptionController.java
index 54c4b98..4552fda 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/web/TTestWarningExceptionController.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/TTestWarningExceptionController.java
@@ -10,38 +10,28 @@
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.common.enums.BusinessType;
 import com.battery.business.domain.TTestWarningException;
-import com.battery.business.mapper.TTestMapper;
-import com.battery.business.service.ITTestService;
 import com.battery.business.service.impl.TestWarningExceptionService;
 
-/** 测试预警异常查询与人工关闭接口。 */
+/** 测试预警异常查询与人工关闭接口;归属校验和关闭规则由服务层负责。 */
 @RestController
 @RequestMapping("/system/test-warning-exceptions")
 public class TTestWarningExceptionController extends BaseController {
-    @Autowired private TTestMapper mapper;
-    @Autowired private ITTestService testService;
     @Autowired private TestWarningExceptionService warningExceptionService;
     /** 按测试和状态查询后端异常事实。 */
     @PreAuthorize("@ss.hasPermi('system:exception:query')")
     @GetMapping("/list")
     public TableDataInfo list(@RequestParam(required=false) Long testId, @RequestParam(required=false) String status) {
         startPage();
-        List<TTestWarningException> rows = mapper.selectWarningExceptions(testId, status);
+        List<TTestWarningException> rows = warningExceptionService.list(testId, status);
         return getDataTable(rows);
     }
-    /** 校验测试负责人身份并关闭异常,通过应用操作日志记录处理动作。 */
+    /** 关闭异常:处理结果必填且只有测试负责人可操作,通过应用操作日志记录处理动作。 */
     @PreAuthorize("@ss.hasPermi('system:exception:handle')")
     @Log(title = "测试预警异常", businessType = BusinessType.UPDATE)
     @PutMapping("/{exceptionId}/close")
-    public AjaxResult close(@PathVariable Long exceptionId, @RequestBody CloseRequest request) {
-        if (request == null || request.handlingNote() == null || request.handlingNote().trim().isEmpty()) return error("处理结果不能为空");
-        TTestWarningException exception = mapper.selectWarningExceptionById(exceptionId);
-        if (exception == null) return error("异常不存在");
-        var test = testService.selectTestById(exception.getTestId());
-        if (test == null || !getUserId().equals(test.getPrincipalId()))
-            return error("仅测试负责人可以关闭预警异常");
-        int rows = warningExceptionService.close(exceptionId, getUserId(), getUsername(), request.handlingNote().trim());
-        return rows == 1 ? success(mapper.selectWarningExceptionById(exceptionId)) : error("异常已关闭或不存在");
+    public AjaxResult close(@PathVariable Long exceptionId, @RequestBody(required=false) CloseRequest request) {
+        return success(warningExceptionService.closeByPrincipal(exceptionId, getUserId(), getUsername(),
+                request == null ? null : request.handlingNote()));
     }
     public record CloseRequest(String handlingNote) {}
 }
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/TestLiveStream.java b/ruoyi-admin/src/main/java/com/battery/business/web/TestLiveStream.java
index 9420d2d..3e8dd81 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/web/TestLiveStream.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/TestLiveStream.java
@@ -3,6 +3,7 @@
 import java.util.Map;
 import java.util.concurrent.*;
 import jakarta.annotation.PreDestroy;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 import org.springframework.stereotype.Component;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 import com.battery.business.service.ITestLiveService;
@@ -11,8 +12,23 @@
 @Component
 public class TestLiveStream {
     private final ITestLiveService live;
-    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
+    /**
+     * SSE 推送线程池显式创建并命名,便于按连接排查推送任务。
+     *
+     * <p>连接关闭后立即移除已取消的定点任务,避免取消任务在延迟队列里滞留到下一个周期。</p>
+     */
+    private final ScheduledThreadPoolExecutor scheduler = scheduler();
     public TestLiveStream(ITestLiveService live) { this.live = live; }
+
+    private static ScheduledThreadPoolExecutor scheduler()
+    {
+        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2,
+                BasicThreadFactory.builder().namingPattern("sse-live-pool-%d").daemon(true).build());
+        executor.setRemoveOnCancelPolicy(true);
+        return executor;
+    }
+
+    /** 建立指定测试的 SSE 连接,先推一次当前状态,随后每 3 秒推一次;连接完成、超时或出错时取消该推送任务。 */
     public SseEmitter stream(Long testId)
     {
         SseEmitter emitter = createEmitter();
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/TodoController.java b/ruoyi-admin/src/main/java/com/battery/business/web/TodoController.java
index 339f6b0..1dbd993 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/web/TodoController.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/TodoController.java
@@ -24,10 +24,10 @@
         if (request == null || request.ownerId() == null) return error("负责人不能为空");
         return success(service.create(request.content(), request.ownerId(), getUserId(), getUsername()));
     }
-    /** 仅负责人可完成自己的待办,已处理不能再次修改。 */
+    /** 仅负责人可处理自己的待办,已处理不能再次修改。 */
     @PutMapping("/{id}/handle") @PreAuthorize("@ss.hasPermi('todo:list:handle')")
-    public AjaxResult handle(@PathVariable long id, @RequestBody HandleRequest request) {
-        service.handle(id, getUserId(), request == null ? null : request.remark()); return success();
+    public AjaxResult complete(@PathVariable long id, @RequestBody(required = false) HandleRequest request) {
+        service.completeTodo(id, getUserId(), request == null ? null : request.remark()); return success();
     }
     /** 删除只作用于当前负责人自己的记录。 */
     @DeleteMapping("/{id}") @PreAuthorize("@ss.hasPermi('todo:list:remove')")
diff --git a/ruoyi-admin/src/test/java/com/battery/business/service/TodoServiceTest.java b/ruoyi-admin/src/test/java/com/battery/business/service/TodoServiceTest.java
index 594f717..b17339f 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/service/TodoServiceTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/service/TodoServiceTest.java
@@ -10,7 +10,7 @@
 
 class TodoServiceTest {
     @Test
-    void onlyOwnerCanReadHandleAndRemoveEvenWhenNamesMatch() throws Exception {
+    void onlyOwnerCanReadCompleteAndRemoveEvenWhenNamesMatch() throws Exception {
         var database = new JdbcDataSource();
         database.setURL("jdbc:h2:mem:todo_" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1");
         var jdbc = new JdbcTemplate(database);
@@ -25,14 +25,14 @@
         assertEquals(id, service.list(2).get(0).id());
         assertEquals(2, service.owners().size());
         assertThrows(ServiceException.class, () -> service.create("事项", 3, 1, "创建人"));
-        assertThrows(ServiceException.class, () -> service.handle(id, 1, "管理员或创建人不可越权"));
+        assertThrows(ServiceException.class, () -> service.completeTodo(id, 1, "管理员或创建人不可越权"));
         assertThrows(ServiceException.class, () -> service.remove(id, 1));
-        assertThrows(ServiceException.class, () -> service.handle(id, 2, " "));
-        service.handle(id, 2, "已完成");
+        assertThrows(ServiceException.class, () -> service.completeTodo(id, 2, " "));
+        service.completeTodo(id, 2, "已完成");
         var reopened = new TodoService(database).list(2).get(0);
         assertEquals("已处理", reopened.status());
         assertEquals("已完成", reopened.handleRemark());
-        assertThrows(ServiceException.class, () -> service.handle(id, 2, "再次处理"));
+        assertThrows(ServiceException.class, () -> service.completeTodo(id, 2, "再次处理"));
         service.remove(id, 2);
         assertTrue(service.list(2).isEmpty());
     }
diff --git a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TestWarningExceptionServiceTest.java b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TestWarningExceptionServiceTest.java
index 9291f0d..6760c0c 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TestWarningExceptionServiceTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TestWarningExceptionServiceTest.java
@@ -3,11 +3,13 @@
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 import org.junit.jupiter.api.Test;
+import com.ruoyi.common.exception.ServiceException;
 import com.battery.business.domain.TTest;
 import com.battery.business.domain.TTestWarningException;
 import com.battery.business.domain.TTestWarningThreshold;
@@ -68,18 +70,24 @@
     }
 
     @Test
-    void closesOpenExceptionAndAppendsAuditInServiceOperation() {
+    void closesOpenExceptionOnlyForTestPrincipalWithHandlingNote() {
         TTestMapper mapper = mock(TTestMapper.class);
         TestWarningExceptionService service = new TestWarningExceptionService(mapper);
         TTestWarningException exception = new TTestWarningException();
         exception.setExceptionId(7L);
         exception.setTestId(1L);
+        TTest test = new TTest();
+        test.setTestId(1L);
+        test.setPrincipalId(22L);
         when(mapper.selectWarningExceptionById(7L)).thenReturn(exception);
+        when(mapper.selectTestById(1L)).thenReturn(test);
         when(mapper.closeWarningException(eq(7L), eq(22L), eq("负责人"), any(Date.class), eq("已核实"))).thenReturn(1);
 
-        int rows = service.close(7L, 22L, "负责人", "已核实");
+        assertEquals(exception, service.closeByPrincipal(7L, 22L, "负责人", "已核实"));
 
-        assertEquals(1, rows);
+        // 归属校验与处理结果必填都由服务层负责,Controller 不再判断业务规则。
+        assertThrows(ServiceException.class, () -> service.closeByPrincipal(7L, 23L, "其他人", "已核实"));
+        assertThrows(ServiceException.class, () -> service.closeByPrincipal(7L, 22L, "负责人", " "));
     }
 
     private static TTest testWithThreshold(String metric, BigDecimal min, BigDecimal max) {

--
Gitblit v1.7.1