yeg
2026-09-29 b78295e8da96c3e5e76befeef9582ce5273dbb74
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.battery.business.mapper.TDeviceIngestRecordMapper">
    <resultMap id="TDeviceIngestRecordResultMap" type="com.battery.business.domain.TDeviceIngestRecord">
        <id property="ingestId" column="ingest_id"/>
        <result property="adapterCode" column="adapter_code"/>
        <result property="payloadKind" column="payload_kind"/>
        <result property="devtype" column="devtype"/>
        <result property="devid" column="devid"/>
        <result property="subdevid" column="subdevid"/>
        <result property="chlid" column="chlid"/>
        <result property="recordSequence" column="record_sequence"/>
        <result property="recordKey" column="record_key"/>
        <result property="sourceRef" column="source_ref"/>
        <result property="deviceTimeUtc" column="device_time_utc"/>
        <result property="deviceTimeRaw" column="device_time_raw"/>
        <result property="timezoneId" column="timezone_id"/>
        <result property="cycleNo" column="cycle_no"/>
        <result property="stepNo" column="step_no"/>
        <result property="relativeTimeSeconds" column="relative_time_seconds"/>
        <result property="voltageV" column="voltage_v"/>
        <result property="currentA" column="current_a"/>
        <result property="chargeCapacityAh" column="charge_capacity_ah"/>
        <result property="dischargeCapacityAh" column="discharge_capacity_ah"/>
        <result property="chargeEnergyWh" column="charge_energy_wh"/>
        <result property="dischargeEnergyWh" column="discharge_energy_wh"/>
        <result property="payloadJson" column="payload_json"/>
        <result property="contentSha256" column="content_sha256"/>
        <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"/>
        <result property="devid" column="devid"/>
        <result property="subdevid" column="subdevid"/>
        <result property="chlid" column="chlid"/>
        <result property="lastDataAt" column="last_data_at"/>
    </resultMap>
 
    <!-- 落地行只追加:来源行身份(adapter_code + 类别 + record_key)撞键即视为重复推送,不改动既有事实。 -->
    <insert id="insertIngestRecord" parameterType="com.battery.business.domain.TDeviceIngestRecord"
            useGeneratedKeys="true" keyProperty="ingestId">
        insert ignore into t_device_ingest_record
        (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)
        values (#{adapterCode}, #{payloadKind}, #{devtype}, #{devid}, #{subdevid}, #{chlid}, #{recordSequence},
         #{recordKey}, #{sourceRef}, #{deviceTimeUtc}, #{deviceTimeRaw}, #{timezoneId}, #{cycleNo}, #{stepNo},
         #{relativeTimeSeconds}, #{voltageV}, #{currentA}, #{chargeCapacityAh}, #{dischargeCapacityAh},
         #{chargeEnergyWh}, #{dischargeEnergyWh}, cast(#{payloadJson} as json), #{contentSha256}, #{receivedAtUtc})
    </insert>
 
    <!-- 心跳单独占一行并覆盖接收时间:它只表示"取数程序还在推",不是设备事实,没有保留历史的意义。 -->
    <insert id="upsertHeartbeat" parameterType="com.battery.business.domain.TDeviceIngestRecord">
        insert into t_device_ingest_record
        (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)
        values (#{adapterCode}, #{payloadKind}, #{devtype}, #{devid}, #{subdevid}, #{chlid}, #{recordSequence},
         #{recordKey}, #{sourceRef}, #{deviceTimeUtc}, #{deviceTimeRaw}, #{timezoneId}, #{cycleNo}, #{stepNo},
         #{relativeTimeSeconds}, #{voltageV}, #{currentA}, #{chargeCapacityAh}, #{dischargeCapacityAh},
         #{chargeEnergyWh}, #{dischargeEnergyWh}, cast(#{payloadJson} as json), #{contentSha256}, #{receivedAtUtc})
        on duplicate key update received_at_utc = values(received_at_utc)
    </insert>
 
    <select id="selectIngestRecordHashByKey" resultType="string">
        select lower(hex(content_sha256)) from t_device_ingest_record
        where adapter_code = #{adapterCode} and payload_kind = #{payloadKind} and record_key = #{recordKey}
        limit 1
    </select>
 
    <select id="selectHeartbeatAt" resultType="java.util.Date">
        select max(received_at_utc) from t_device_ingest_record
        where adapter_code = #{adapterCode} and payload_kind = 'HEARTBEAT'
    </select>
 
    <!-- 最近数据时间取该通道自己的采样行:没有新采样行就是没有,不按拓扑上报时间代替。 -->
    <select id="selectTopologyChannels" resultMap="TDeviceIngestChannelResultMap">
        select t.devtype, t.devid, t.subdevid, t.chlid,
        (select max(prior.device_time_utc) from t_device_ingest_record prior
         where prior.adapter_code = t.adapter_code and prior.payload_kind = 'SAMPLE'
           and prior.devtype = t.devtype and prior.devid = t.devid
           and prior.subdevid = t.subdevid and prior.chlid = t.chlid) as last_data_at
        from t_device_ingest_record t
        where t.adapter_code = #{adapterCode} and t.payload_kind = 'TOPOLOGY'
        order by t.devid, t.subdevid, t.chlid
    </select>
 
    <select id="selectLatestSample" resultMap="TDeviceIngestRecordResultMap">
        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
        limit 1
    </select>
 
    <!-- 只取严格晚于水位的行;同秒多行按协议序号各自成立,不按秒合并。 -->
    <select id="selectSamplesSince" resultMap="TDeviceIngestRecordResultMap">
        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">
          and (device_time_utc &gt; #{watermarkTime}
               or (device_time_utc = #{watermarkTime} and record_sequence &gt; #{watermarkSequence}))
        </if>
        order by device_time_utc, record_sequence
        limit #{limit}
    </select>
</mapper>