yeg
2026-09-05 bc9a169ce3648467136a279d167f72c4fbab9595
完成设备发现登记与状态事实

候选设备继续暂存 Redis,正式检测柜、通道和采集器按最终数据库基线登记。

移除采集器测试占用列写入,派生测试展示状态并保存最近观测时间。

统一适配器及通道在线、工作状态枚举。
11个文件已修改
227 ■■■■■ 已修改文件
ruoyi-system/src/main/java/com/ruoyi/system/domain/TCollector.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/TDetectionCabinet.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/TDeviceAdapter.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TDetectionCabinetMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DeviceAdapterServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DeviceDiscoveryPersistenceService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TCollectorServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TDetectionCabinetServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TCollectorMapper.xml 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TDetectionCabinetMapper.xml 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TDeviceAdapterMapper.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/TCollector.java
@@ -5,6 +5,7 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.Instant;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;
@@ -20,6 +21,12 @@
    /** 采集器主键 */
    private Long collectorId;
    /** 所属设备适配器实例;不表达测试占用。 */
    private Long adapterId;
    /** 适配器最近一次观测该采集器的时间(UTC)。 */
    private Instant lastSeenAtUtc;
    /** 设备综合查询条件 */
    private String device;
@@ -67,13 +74,13 @@
    /** 在线状态:0离线、1在线 */
    private String onlineStatus;
    /** 测试状态:0未测试、1测试中 */
    /** 测试状态展示值,由测试绑定事实派生,不写入采集器表。 */
    private String testStatus;
    /** 当前测试 */
    /** 当前测试展示值,由测试绑定事实派生,不写入采集器表。 */
    private String currentTest;
    /** 当前样品名称 */
    /** 兼容旧页面的派生字段,不参与采集器登记持久化。 */
    private String currentSampleName;
    /** 状态:0启用、1禁用 */
@@ -92,6 +99,14 @@
        this.collectorId = collectorId;
    }
    public Long getAdapterId() { return adapterId; }
    public void setAdapterId(Long adapterId) { this.adapterId = adapterId; }
    public Instant getLastSeenAtUtc() { return lastSeenAtUtc; }
    public void setLastSeenAtUtc(Instant lastSeenAtUtc) { this.lastSeenAtUtc = lastSeenAtUtc; }
    public String getDevice()
    {
        return device;
ruoyi-system/src/main/java/com/ruoyi/system/domain/TDetectionCabinet.java
@@ -74,9 +74,10 @@
    /** 在线状态:0正常,1异常。 */
    private String onlineStatus;
    /** 测试状态:0未测试,1测试中。 */
    /** 测试状态展示值,由通道绑定事实派生,不写入检测柜表。 */
    private String testStatus;
    /** 当前测试展示值,由通道绑定事实派生,不写入检测柜表。 */
    @Size(max = 200, message = "当前测试内容长度不能超过200个字符")
    private String currentTest;
ruoyi-system/src/main/java/com/ruoyi/system/domain/TDeviceAdapter.java
@@ -50,6 +50,7 @@
    private String capabilities;
    private String status;
    /** 适配器连接状态,仅允许 ONLINE/OFFLINE。 */
    private String connectionStatus;
    private String lastErrorCode;
    private String lastErrorMessage;
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TDetectionCabinetMapper.java
@@ -74,6 +74,23 @@
            @Param("onlineStatus") String onlineStatus, @Param("workStatus") String workStatus,
            @Param("lastSeenAtUtc") java.time.Instant lastSeenAtUtc, @Param("updateLastSeen") boolean updateLastSeen);
    /** 发现失败或空拓扑时清除该适配器下已登记设备的过期在线事实。 */
    int markRegisteredFactsUnobservable(@Param("adapterCode") String adapterCode);
    /** 同步检测柜在线事实为离线。 */
    int markRegisteredCabinetsOffline(@Param("adapterCode") String adapterCode);
    /** 仅推进成功发现的通道最近观测时间,不改变工作状态。 */
    int touchRegisteredChannelObservation(@Param("adapterCode") String adapterCode,
            @Param("devtype") int devtype, @Param("devid") int devid,
            @Param("subdevid") int subdevid, @Param("chlid") int chlid,
            @Param("lastSeenAtUtc") java.time.Instant lastSeenAtUtc);
    /** 仅推进成功发现的检测柜最近观测时间,不改变在线状态。 */
    int touchRegisteredCabinetObservation(@Param("adapterCode") String adapterCode,
            @Param("devtype") int devtype, @Param("devid") int devid,
            @Param("lastSeenAtUtc") java.time.Instant lastSeenAtUtc);
    /** 软删除正式检测柜对应的通道拓扑。 */
    int deleteDetectionChannelsByCabinetId(@Param("cabinetId") Long cabinetId);
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DeviceAdapterServiceImpl.java
@@ -71,7 +71,7 @@
        if (capabilities.control())
            throw error("当前里程碑只允许只读设备适配器", DeviceAdapterErrorCodes.INVALID_CONFIG);
        adapter.setStatus(ENABLED);
        adapter.setConnectionStatus("DISCONNECTED");
        adapter.setConnectionStatus("OFFLINE");
        adapter.setDelFlag("0");
        mapper.upsertManagedDeviceAdapter(adapter);
        TDeviceAdapter registered = mapper.selectDeviceAdapterByCode(adapter.getAdapterCode());
@@ -89,7 +89,7 @@
            throw error("适配器编码已存在", DeviceAdapterErrorCodes.DUPLICATE_CODE);
        adapter.setCapabilities(READ_ONLY_CAPABILITIES);
        adapter.setStatus(DISABLED);
        adapter.setConnectionStatus("DISCONNECTED");
        adapter.setConnectionStatus("OFFLINE");
        adapter.setDelFlag("0");
        try { return mapper.insertDeviceAdapter(adapter); }
        catch (DuplicateKeyException exception) { throw error("适配器编码已存在", DeviceAdapterErrorCodes.DUPLICATE_CODE); }
@@ -143,10 +143,12 @@
        }
        catch (DeviceAdapterException exception)
        {
            persistence.markUnavailable(adapter.getAdapterCode());
            return failDiscovery(adapterId, exception.failureCode(), exception);
        }
        catch (AdapterRegistryException exception)
        {
            persistence.markUnavailable(adapter.getAdapterCode());
            return failDiscovery(adapterId, "ADAPTER_FAILURE", exception);
        }
    }
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DeviceDiscoveryPersistenceService.java
@@ -65,6 +65,13 @@
        return testMapper == null || testMapper.hasReceivingTestForAdapter(adapterCode) > 0;
    }
    /** 发现失败或返回空拓扑时,清除该适配器下已登记设备的过期运行事实。 */
    public void markUnavailable(String adapterCode)
    {
        detectionCabinetMapper.markRegisteredFactsUnobservable(adapterCode);
        detectionCabinetMapper.markRegisteredCabinetsOffline(adapterCode);
    }
    private AdapterDiscoveryResult discoverNow(DeviceAdapterRegistry registry, DiscoverySource source)
    {
        DiscoveryObservation observation = registry.execute(source.adapterCode(), runtime -> {
@@ -97,6 +104,7 @@
                        observedAt, observedAt.plus(lifetime)))
                .toList();
        candidateStore.recordCabinets(candidates);
        if (detected.isEmpty()) markUnavailable(source.adapterCode());
        synchronizeRegisteredFacts(source, detected, snapshots, observedAt);
        return new AdapterDiscoveryResult(grouped.size(), detected.size(), observedAt);
    }
@@ -132,6 +140,9 @@
            detectionCabinetMapper.updateRegisteredChannelObservation(source.adapterCode(), identity.devtype(),
                    identity.devid(), identity.subdevid(), identity.chlid(), channel.available() ? "ONLINE" : "OFFLINE",
                    workStatus.name(), observedAt, successfulObservation);
            if (channel.available() && !successfulObservation)
                detectionCabinetMapper.touchRegisteredChannelObservation(source.adapterCode(), identity.devtype(),
                        identity.devid(), identity.subdevid(), identity.chlid(), observedAt);
            CabinetKey cabinet = new CabinetKey(identity.devtype(), identity.devid());
            cabinetOnline.merge(cabinet, channel.available(), Boolean::logicalOr);
        }
@@ -141,6 +152,15 @@
                        && channel.identity().devtype() == cabinet.devtype()
                        && channel.identity().devid() == cabinet.devid()
                        && snapshots.containsKey(channel.identity()))));
        cabinetOnline.forEach((cabinet, online) -> {
            boolean successfulSnapshot = online && detected.stream().anyMatch(channel -> channel.available()
                    && channel.identity().devtype() == cabinet.devtype()
                    && channel.identity().devid() == cabinet.devid()
                    && snapshots.containsKey(channel.identity()));
            if (online && !successfulSnapshot)
                detectionCabinetMapper.touchRegisteredCabinetObservation(source.adapterCode(), cabinet.devtype(),
                        cabinet.devid(), observedAt);
        });
    }
    private CabinetCandidate toCandidate(DiscoverySource source, CabinetKey key, List<DetectedChannel> channels,
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TCollectorServiceImpl.java
@@ -105,6 +105,7 @@
        collector.setChannelCount(candidate.channelCount());
        validateCollector(collector, false);
        collector.setOnlineStatus(OFFLINE);
        // 测试占用由 t_test_collector_channel + t_test 派生,采集器登记表不写入这些字段。
        collector.setTestStatus(IDLE);
        collector.setCurrentTest(null);
        collector.setCurrentSampleName(null);
@@ -118,7 +119,11 @@
        }
        collector.setDelFlag("0");
        collector.setCreateTime(new Date());
        return collectorMapper.insertCollector(collector);
        int rows = collectorMapper.insertCollector(collector);
        // 登记瞬间同步候选事实,避免正式记录暂时显示为离线且没有最近观测时间。
        collectorMapper.updateRegisteredObservation(collector.getCollectorCode(),
                candidate.online() ? "ONLINE" : "OFFLINE");
        return rows;
    }
    /**
@@ -127,6 +132,11 @@
    @Override
    public int updateCollector(TCollector collector)
    {
        TCollector current = collector == null || collector.getCollectorId() == null ? null
                : collectorMapper.selectCollectorById(collector.getCollectorId());
        if (current == null) throw new ServiceException("采集器不存在");
        if (collector.getCollectorCode() != null && !collector.getCollectorCode().trim().equals(current.getCollectorCode()))
            throw new ServiceException("设备编码不可修改");
        validateCollector(collector, true);
        validateStatus(collector.getStatus(), "状态");
        if (DISABLED.equals(collector.getStatus()))
@@ -354,6 +364,8 @@
    {
        CollectorCandidate candidate = candidates.get(collector.getCollectorCode());
        collector.setOnlineStatus(candidate != null && candidate.online() ? ONLINE : OFFLINE);
        // 候选过期只改变当前在线展示,不能抹掉 MySQL 保存的最近观测时间。
        if (candidate != null) collector.setLastSeenAtUtc(candidate.observedAt());
        return collector;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TDetectionCabinetServiceImpl.java
@@ -164,11 +164,20 @@
                value.setSubdevid(channel.identity().subdevid());
                value.setChlid(channel.identity().chlid());
                value.setDisplayNo(channel.identity().subdevid() + "-" + channel.identity().chlid());
                value.setOnlineStatus("UNKNOWN");
                value.setWorkStatus("UNKNOWN");
                value.setOnlineStatus(channel.online() ? "ONLINE" : "OFFLINE");
                value.setWorkStatus(persistedWorkStatus(channel.workStatus(), channel.online()));
                value.setLastSeenAtUtc(null);
                detectionCabinetMapper.insertDetectionChannel(value);
            }
            // 登记瞬间的候选也是一次有效观测,立即写入正式事实,避免等待下一轮轮询才显示在线。
            detectionCabinetMapper.updateRegisteredCabinetObservation(candidate.adapterCode(), candidate.devtype(),
                    candidate.devid(), candidate.online() ? "ONLINE" : "OFFLINE", candidate.observedAt(),
                    candidate.online());
            candidate.channels().forEach(channel -> detectionCabinetMapper.updateRegisteredChannelObservation(
                    candidate.adapterCode(), channel.identity().devtype(), channel.identity().devid(),
                    channel.identity().subdevid(), channel.identity().chlid(), channel.online() ? "ONLINE" : "OFFLINE",
                    channel.online() ? persistedWorkStatus(channel.workStatus(), true) : "UNOBSERVABLE",
                    channel.observedAt(), channel.online()));
            return rows;
        }
        catch (DuplicateKeyException exception)
@@ -369,16 +378,24 @@
    private Map<String, CabinetCandidate> candidatesByCode()
    {
        return candidateStore.snapshot(Instant.now()).cabinets().stream()
                .collect(java.util.stream.Collectors.toMap(CabinetCandidate::deviceCode, item -> item));
        return candidateStore.snapshot(Instant.now()).cabinets().stream().collect(java.util.stream.Collectors.toMap(
                item -> stableCandidateKey(item.adapterCode(), item.devtype(), item.devid()), item -> item,
                (left, right) -> right));
    }
    private TDetectionCabinet applyRealtimeState(TDetectionCabinet cabinet,
            Map<String, CabinetCandidate> candidates)
    {
        CabinetCandidate candidate = candidates.get(cabinet.getCabinetCode());
        CabinetCandidate candidate = candidates.get(stableCandidateKey(cabinet.getAdapterCode(),
                cabinet.getVendorDeviceType(), cabinet.getVendorDeviceId()));
        if (candidate == null) candidate = candidateStore.findCabinet(cabinet.getCabinetCode(), Instant.now()).orElse(null);
        cabinet.setOnlineStatus(candidate != null && candidate.online() ? ONLINE : OFFLINE);
        return cabinet;
    }
    private String stableCandidateKey(String adapterCode, Integer devtype, Integer devid)
    {
        return adapterCode + "-" + devtype + "-" + devid;
    }
    private boolean isActive(CabinetCandidate candidate)
@@ -386,6 +403,20 @@
        return candidate.channels().stream().anyMatch(channel -> ACTIVE_WORK_STATUSES.contains(channel.workStatus()));
    }
    /** 将设备观测状态转换为最终数据库允许的通道事实状态。 */
    private String persistedWorkStatus(DeviceWorkStatus status, boolean online)
    {
        if (!online || status == null || status == DeviceWorkStatus.UNKNOWN
                || status == DeviceWorkStatus.UNOBSERVABLE) return "UNOBSERVABLE";
        return switch (status)
        {
            case PAUSED -> "PAUSED";
            case PROTECTED -> "ABNORMAL";
            case FINISHED, STOPPED -> "ENDED";
            default -> "RUNNING";
        };
    }
    private ServiceException buildDuplicateException(TDetectionCabinet detectionCabinet, DuplicateKeyException cause)
    {
        String message = checkCabinetCodeUnique(detectionCabinet) ? "MAC地址已存在" : "设备编码已存在";
ruoyi-system/src/main/resources/mapper/system/TCollectorMapper.xml
@@ -6,6 +6,7 @@
    <resultMap type="TCollector" id="TCollectorResult">
        <id property="collectorId" column="collector_id" />
        <result property="adapterId" column="adapter_id" />
        <result property="collectorCode" column="collector_code" />
        <result property="ipAddress" column="ip_address" />
        <result property="macAddress" column="mac_address" />
@@ -16,7 +17,7 @@
        <result property="onlineStatus" column="online_status" />
        <result property="testStatus" column="test_status" />
        <result property="currentTest" column="current_test" />
        <result property="currentSampleName" column="current_sample_name" />
        <result property="lastSeenAtUtc" column="last_seen_at_utc" />
        <result property="status" column="status" />
        <result property="delFlag" column="del_flag" />
        <result property="createBy" column="create_by" />
@@ -27,11 +28,27 @@
    </resultMap>
    <sql id="selectCollectorVo">
        select collector_id, collector_code, ip_address, mac_address, collector_name,
               manufacturer, model, channel_count, online_status, test_status,
               current_test, current_sample_name, status, del_flag, create_by,
               create_time, update_by, update_time, remark
        from t_collector
        select c.collector_id, c.collector_code, c.adapter_id, c.ip_address, c.mac_address, c.collector_name,
               c.manufacturer, c.model, c.channel_count,
               case when upper(c.online_status) = 'ONLINE' or c.online_status = '1' then '1' else '0' end as online_status,
               c.last_seen_at_utc,
               case when exists (
                   select 1 from t_test_collector_channel mapping
                   join t_test test_fact on test_fact.test_id = mapping.test_id
                   where mapping.collector_id = c.collector_id
                     and test_fact.del_flag = 0
                     and test_fact.test_status in ('RUNNING', 'PAUSED')
               ) then '1' else '0' end as test_status,
               case when exists (
                   select 1 from t_test_collector_channel mapping
                   join t_test test_fact on test_fact.test_id = mapping.test_id
                   where mapping.collector_id = c.collector_id
                     and test_fact.del_flag = 0
                     and test_fact.test_status in ('RUNNING', 'PAUSED')
               ) then '待确认' else null end as current_test,
               c.status, c.del_flag, c.create_by,
               c.create_time, c.update_by, c.update_time, c.remark
        from t_collector c
    </sql>
    <select id="selectCollectorById" parameterType="Long" resultMap="TCollectorResult">
@@ -60,7 +77,13 @@
                and online_status = #{onlineStatus}
            </if>
            <if test="testStatus != null and testStatus != ''">
                and test_status = #{testStatus}
                and (case when exists (
                    select 1 from t_test_collector_channel mapping
                    join t_test test_fact on test_fact.test_id = mapping.test_id
                    where mapping.collector_id = c.collector_id
                      and test_fact.del_flag = 0
                      and test_fact.test_status in ('RUNNING', 'PAUSED')
                ) then '1' else '0' end) = #{testStatus}
            </if>
            <if test="status != null and status != ''">
                and status = #{status}
@@ -92,17 +115,19 @@
    <insert id="insertCollector" parameterType="TCollector" useGeneratedKeys="true" keyProperty="collectorId">
        insert into t_collector
        (collector_code, ip_address, mac_address, collector_name, manufacturer, model,
         channel_count, online_status, test_status, current_test, current_sample_name,
         channel_count, online_status, last_seen_at_utc,
         status, del_flag, create_by, create_time, remark)
        values
        (#{collectorCode}, #{ipAddress}, #{macAddress}, #{collectorName}, #{manufacturer}, #{model},
         #{channelCount}, #{onlineStatus}, #{testStatus}, #{currentTest}, #{currentSampleName},
         #{channelCount}, case when #{onlineStatus} in ('1', 'ONLINE') then 'ONLINE' else 'OFFLINE' end,
         #{lastSeenAtUtc},
         #{status}, #{delFlag}, #{createBy}, #{createTime}, #{remark})
    </insert>
    <update id="updateRegisteredObservation">
        update t_collector
        set online_status = #{onlineStatus}, update_time = sysdate()
        set online_status = case when #{onlineStatus} in ('1', 'ONLINE') then 'ONLINE' else 'OFFLINE' end,
            last_seen_at_utc = utc_timestamp(3), update_time = sysdate()
        where collector_code = #{collectorCode} and del_flag = 0
    </update>
ruoyi-system/src/main/resources/mapper/system/TDetectionCabinetMapper.xml
@@ -123,7 +123,21 @@
    <update id="updateRegisteredChannelObservation">
        update t_detection_channel
        set online_status = #{onlineStatus}, work_status = #{workStatus}, update_time = sysdate()
        set online_status = case when #{onlineStatus} in ('ONLINE', '1') then 'ONLINE' else 'OFFLINE' end,
            work_status = case #{workStatus}
                when 'PAUSED' then 'PAUSED'
                when 'PROTECTED' then 'ABNORMAL'
                when 'FINISHED' then 'ENDED'
                when 'STOPPED' then 'ENDED'
                when 'WORKING' then 'RUNNING'
                when 'SYNCHRONIZING' then 'RUNNING'
                when 'LIT' then 'RUNNING'
                when 'WAITING_TIMEOUT' then 'RUNNING'
                when 'WAITING_START' then 'RUNNING'
                when 'LOCKED' then 'RUNNING'
                when 'RUNNING' then 'RUNNING'
                else 'UNOBSERVABLE' end,
            update_time = sysdate()
        <if test="updateLastSeen">, last_seen_at_utc = #{lastSeenAtUtc}</if>
        where adapter_code = #{adapterCode}
          and devtype = #{devtype} and devid = #{devid}
@@ -134,6 +148,35 @@
                        and cabinet.del_flag = 0 and cabinet.registration_status = 'REGISTERED')
    </update>
    <update id="markRegisteredFactsUnobservable">
        update t_detection_channel
        set online_status = 'OFFLINE', work_status = 'UNOBSERVABLE', update_time = sysdate()
        where adapter_code = #{adapterCode} and del_flag = 0
    </update>
    <update id="markRegisteredCabinetsOffline">
        update t_detection_cabinet
        set online_status = 'OFFLINE', update_time = sysdate()
        where adapter_code = #{adapterCode} and del_flag = 0 and registration_status = 'REGISTERED'
    </update>
    <update id="touchRegisteredChannelObservation">
        update t_detection_channel
        set last_seen_at_utc = #{lastSeenAtUtc}, update_time = sysdate()
        where adapter_code = #{adapterCode}
          and devtype = #{devtype} and devid = #{devid}
          and subdevid = #{subdevid} and chlid = #{chlid}
          and online_status = 'ONLINE' and del_flag = 0
    </update>
    <update id="touchRegisteredCabinetObservation">
        update t_detection_cabinet
        set last_seen_at_utc = #{lastSeenAtUtc}, update_time = sysdate()
        where adapter_code = #{adapterCode}
          and vendor_device_type = #{devtype} and vendor_device_id = #{devid}
          and online_status = 'ONLINE' and del_flag = 0 and registration_status = 'REGISTERED'
    </update>
    <update id="updateDetectionCabinet" parameterType="TDetectionCabinet">
        update t_detection_cabinet
        <set>
ruoyi-system/src/main/resources/mapper/system/TDeviceAdapterMapper.xml
@@ -64,11 +64,11 @@
            timezone_id, capabilities, status, connection_status, create_by, create_time, update_by, update_time,
            remark, del_flag)
        values(#{adapterCode}, #{adapterName}, #{adapterType}, #{host}, #{port}, #{pollIntervalMs}, #{timezoneId},
            #{capabilities}, '0', 'DISCONNECTED', 'system', sysdate(), 'system', sysdate(), #{remark}, 0)
            #{capabilities}, '0', 'OFFLINE', 'system', sysdate(), 'system', sysdate(), #{remark}, 0)
        on duplicate key update adapter_name=values(adapter_name), adapter_type=values(adapter_type),
            host=values(host), port=values(port), poll_interval_ms=values(poll_interval_ms),
            timezone_id=values(timezone_id), capabilities=values(capabilities), status='0',
            connection_status='DISCONNECTED', last_error_code=null, last_error_message=null,
            connection_status='OFFLINE', last_error_code=null, last_error_message=null,
            update_by='system', update_time=sysdate(), remark=values(remark), del_flag=0
    </insert>
@@ -76,31 +76,31 @@
        insert into t_device_adapter(adapter_code, adapter_name, adapter_type, host, port, poll_interval_ms,
            timezone_id, capabilities, status, connection_status, create_by, create_time, remark, del_flag)
        values(#{adapterCode}, #{adapterName}, #{adapterType}, #{host}, #{port}, #{pollIntervalMs},
            #{timezoneId}, #{capabilities}, #{status}, 'DISCONNECTED', #{createBy}, sysdate(), #{remark}, 0)
            #{timezoneId}, #{capabilities}, #{status}, 'OFFLINE', #{createBy}, sysdate(), #{remark}, 0)
    </insert>
    <update id="updateDeviceAdapter" parameterType="TDeviceAdapter">
        update t_device_adapter set adapter_name=#{adapterName}, adapter_type=#{adapterType}, host=#{host}, port=#{port},
            poll_interval_ms=#{pollIntervalMs}, timezone_id=#{timezoneId}, capabilities=#{capabilities},
            connection_status='DISCONNECTED', last_error_code=null, last_error_message=null,
            connection_status='OFFLINE', last_error_code=null, last_error_message=null,
            update_by=#{updateBy}, update_time=sysdate()
        where adapter_id=#{adapterId} and del_flag=0
    </update>
    <update id="updateDeviceAdapterStatus">
        update t_device_adapter set status=#{status}, connection_status='DISCONNECTED',
        update t_device_adapter set status=#{status}, connection_status='OFFLINE',
            last_error_code=null, last_error_message=null, update_by=#{updateBy}, update_time=sysdate()
        where adapter_id=#{adapterId} and del_flag=0
    </update>
    <update id="updateDiscoverySuccess">
        update t_device_adapter set connection_status='CONNECTED', last_error_code=null, last_error_message=null,
        update t_device_adapter set connection_status='ONLINE', last_error_code=null, last_error_message=null,
            last_attempt_at_utc=#{observedAt}, last_observed_at_utc=#{observedAt}, update_time=sysdate()
        where adapter_id=#{adapterId} and del_flag=0
    </update>
    <update id="updateDiscoveryFailure">
        update t_device_adapter set connection_status='ERROR', last_error_code=#{errorCode},
        update t_device_adapter set connection_status='OFFLINE', last_error_code=#{errorCode},
            last_error_message=left(#{errorMessage}, 500), last_attempt_at_utc=utc_timestamp(3), update_time=sysdate()
        where adapter_id=#{adapterId} and del_flag=0
    </update>