feat(analog): 新增ICC模拟量数据同步 - 重建electricity_data表(迁移20261024_icc_analog_data.sql:删除equip_id与electricity占位列、icc_device_code更名imei、新增analog_type/analog_name/analog_unit/analog_value、collect_time以同步时间填充、按月分区2026_10~2027_03、唯一键(line_code,analog_type,collect_time));同步服务IccAnalogSyncService复用OAuth2鉴权,按已同步线路line_code分批(≤50)拉取最新模拟量(POST /analog/subsystem/page,pageSize=500短页停止)幂等upsert并写icc_sync_log(sync_type=electricity_data);新增POST /api/electricity-data/sync手动同步(RBAC device:list);注册icc_analog_sync每5分钟定时任务与sys_job种子;同步db_init/db_seed与设计文档;无前端改动
This commit is contained in:
1 parent
160ad85051
commit
c92fc7f298
15 files changed
+701
-27
No files matched your search
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* ICC 探测器模拟量数据字段映射(集中配置,便于按 ICC 平台实际返回结构调整)。
|
||||
*
|
||||
* ICC 返回的原始字段名 → 本系统 electricity_data 字段。接入真实 ICC 平台时
|
||||
* 仅需调整本文件与 fetch 路径即可,业务层无需改动。纯映射逻辑,无 SQL 无 DOM。
|
||||
*/
|
||||
export interface IccAnalogRawRecord {
|
||||
[field: string]: any;
|
||||
}
|
||||
|
||||
/** 模拟量分页列表接口路径({version} 由调用方按 ICC_VERSION 替换) */
|
||||
export const ICC_ANALOG_ENDPOINT = "/evo-apigw/evo-fdbu/{version}/analog/subsystem/page";
|
||||
|
||||
/** 原始记录关键字段映射:本系统字段 → ICC 返回字段名 */
|
||||
export const ICC_ANALOG_FIELDS = {
|
||||
deviceCode: "deviceCode", // 探测器编码(→ electric_line.line_code 幂等键)
|
||||
uniqueCode: "uniqueCode", // 设备唯一标识码(→ electricity_data.imei)
|
||||
analogType: "analogType", // 模拟量类型
|
||||
analogName: "analogName", // 模拟量名称(→ electricity_data.analog_name)
|
||||
analogUnit: "analogUnit", // 模拟量单位(→ electricity_data.analog_unit)
|
||||
analogValue: "analogValue", // 模拟量值
|
||||
reportTime: "reportTime" // 上报/采集时间(yyyy-MM-dd HH:mm:ss,接口文档声明字段,保留映射)
|
||||
} as const;
|
||||
|
||||
/** 字符串 trim;null / undefined / 空串统一归一为 undefined。 */
|
||||
function toOptionalString(value: unknown): string | undefined {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
const s = String(value).trim();
|
||||
return s === "" ? undefined : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 ICC 原始模拟量记录映射为本系统 electricity_data 落库字段。
|
||||
* analogValue 用 Number 解析,空值/非法值统一归一为 null(可空落库)。
|
||||
*/
|
||||
export function mapIccAnalogRecord(raw: IccAnalogRawRecord): {
|
||||
deviceCode: string;
|
||||
uniqueCode?: string;
|
||||
analogType?: string;
|
||||
analogName?: string;
|
||||
analogUnit?: string;
|
||||
analogValue: number | null;
|
||||
reportTime?: string;
|
||||
} {
|
||||
const f = ICC_ANALOG_FIELDS;
|
||||
// 匹配键保持空串语义,由调用方判跳
|
||||
const deviceCode = String(raw[f.deviceCode] ?? "").trim();
|
||||
|
||||
// 数值解析:空 / null / 非数值统一返回 null
|
||||
let analogValue: number | null = null;
|
||||
const rawValue = raw[f.analogValue];
|
||||
if (rawValue !== undefined && rawValue !== null && String(rawValue).trim() !== "") {
|
||||
const num = Number(rawValue);
|
||||
analogValue = Number.isFinite(num) ? num : null;
|
||||
}
|
||||
|
||||
return {
|
||||
deviceCode,
|
||||
uniqueCode: toOptionalString(raw[f.uniqueCode]),
|
||||
analogType: toOptionalString(raw[f.analogType]),
|
||||
analogName: toOptionalString(raw[f.analogName]),
|
||||
analogUnit: toOptionalString(raw[f.analogUnit]),
|
||||
analogValue,
|
||||
// reportTime 保留映射(接口文档仍声明该字段;当前采集时间以同步时间为准,见同步服务)
|
||||
reportTime: toOptionalString(raw[f.reportTime])
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析模拟量分页响应(兼容三种形态):
|
||||
* - payload 为数组 → 直接作为列表
|
||||
* - payload.data.pageData 为数组 → 取 pageData
|
||||
* - payload.data.list 为数组 → 取 list
|
||||
* - 结构不匹配或解析异常 → 返回空列表
|
||||
*/
|
||||
export function parseAnalogPage(payload: any): {
|
||||
list: IccAnalogRawRecord[];
|
||||
nextPage?: number;
|
||||
totalCount?: number;
|
||||
} {
|
||||
try {
|
||||
if (Array.isArray(payload)) {
|
||||
return { list: payload };
|
||||
}
|
||||
const page = payload?.data;
|
||||
if (page && Array.isArray(page.pageData)) {
|
||||
return {
|
||||
list: page.pageData,
|
||||
nextPage: Number(page.nextPage) > 0 ? Number(page.nextPage) : undefined,
|
||||
totalCount: Number(page.totalCount) > 0 ? Number(page.totalCount) : undefined
|
||||
};
|
||||
}
|
||||
if (page && Array.isArray(page.list)) {
|
||||
return {
|
||||
list: page.list,
|
||||
nextPage: Number(page.nextPage) > 0 ? Number(page.nextPage) : undefined,
|
||||
totalCount: Number(page.totalCount) > 0 ? Number(page.totalCount) : undefined
|
||||
};
|
||||
}
|
||||
return { list: [] };
|
||||
} catch {
|
||||
return { list: [] };
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user