Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Q
qingdaoMuseumPlatform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
node_server
qingdaoMuseumPlatform
Commits
4114c762
Commit
4114c762
authored
Jun 09, 2026
by
PC-20251223ZVQQ\Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
数据集成和定时任务
parent
0d91348d
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
815 additions
and
2 deletions
+815
-2
dataIntegration.ts
src/biz/dataIntegration.ts
+452
-0
feiyiClient.ts
src/biz/feiyiClient.ts
+179
-2
running.ts
src/biz/running.ts
+17
-0
schedule.ts
src/config/schedule.ts
+144
-0
main.ts
src/main.ts
+3
-0
device.ts
src/routers/device.ts
+20
-0
No files found.
src/biz/dataIntegration.ts
0 → 100644
View file @
4114c762
import
{
getRegionTree
,
getIndoorUnitList
,
getElectricMaterList
,
getIndoorUnitStateList
,
getMaterStateList
,
}
from
"./feiyiClient"
;
import
{
mysqlModelMap
}
from
"../model/sqlModelBind"
;
// ==================== 分页拉取辅助函数 ====================
/**
* 分页拉取所有数据(通用)
* @param fetchFn 分页查询函数,入参 { page, limit },返回 { page, limit, total, rows }
* @param limit 每页条数,默认 100
*/
async
function
fetchAllPages
(
fetchFn
:
(
page
:
number
,
limit
:
number
)
=>
Promise
<
any
>
,
limit
:
number
=
100
):
Promise
<
any
[]
>
{
const
allRows
:
any
[]
=
[];
let
page
=
1
;
let
hasMore
=
true
;
while
(
hasMore
)
{
const
result
=
await
fetchFn
(
page
,
limit
);
if
(
!
result
||
!
result
.
rows
||
result
.
rows
.
length
===
0
)
{
hasMore
=
false
;
break
;
}
allRows
.
push
(...
result
.
rows
);
// 判断是否还有下一页
if
(
page
*
limit
>=
result
.
total
)
{
hasMore
=
false
;
}
else
{
page
++
;
}
}
return
allRows
;
}
// ==================== 区域集成 ====================
/**
* 递归遍历建筑物树,提取所有房间节点(buildingType === 3)
* 同时追溯父节点获取楼层和楼栋信息
*/
function
extractRoomsFromTree
(
tree
:
any
[],
parentFloor
:
{
floorId
:
string
;
floorName
:
string
}
|
null
=
null
,
parentBuilding
:
{
buildingId
:
string
;
buildingName
:
string
}
|
null
=
null
,
):
Array
<
{
roomId
:
string
;
roomName
:
string
;
floorId
:
string
;
floorName
:
string
;
buildingId
:
string
;
buildingName
:
string
;
}
>
{
const
rooms
:
any
[]
=
[];
for
(
const
node
of
tree
)
{
const
buildingType
=
node
.
buildingType
;
const
buildingId
=
node
.
buildingId
;
const
buildingName
=
node
.
buildingName
;
const
children
=
node
.
children
||
[];
if
(
buildingType
===
1
)
{
// 楼栋节点
const
building
=
{
buildingId
,
buildingName
};
rooms
.
push
(...
extractRoomsFromTree
(
children
,
null
,
building
));
}
else
if
(
buildingType
===
2
)
{
// 楼层节点
const
floor
=
{
floorId
:
buildingId
,
floorName
:
buildingName
};
rooms
.
push
(...
extractRoomsFromTree
(
children
,
floor
,
parentBuilding
));
}
else
if
(
buildingType
===
3
)
{
// 房间节点
rooms
.
push
({
roomId
:
buildingId
,
roomName
:
buildingName
,
floorId
:
parentFloor
?.
floorId
||
''
,
floorName
:
parentFloor
?.
floorName
||
''
,
buildingId
:
parentBuilding
?.
buildingId
||
''
,
buildingName
:
parentBuilding
?.
buildingName
||
''
,
});
}
}
return
rooms
;
}
/**
* 数据集成-区域集成
* 按最小节点(房间)平铺集成,一条数据对应一个房间
*/
export
async
function
region
()
{
console
.
log
(
'[区域集成] 开始执行...'
);
try
{
const
regionModel
=
mysqlModelMap
[
'region'
];
if
(
!
regionModel
)
{
console
.
error
(
'[区域集成] region 表模型未初始化,跳过'
);
return
;
}
// 1. 获取建筑物树结构
const
treeData
=
await
getRegionTree
();
if
(
!
treeData
||
!
Array
.
isArray
(
treeData
))
{
console
.
warn
(
'[区域集成] 未获取到建筑物树数据'
);
return
;
}
// 2. 提取所有房间节点
const
rooms
=
extractRoomsFromTree
(
treeData
);
console
.
log
(
`[区域集成] 共提取到
${
rooms
.
length
}
个房间节点`
);
// 3. 逐房间检查并插入
let
insertCount
=
0
;
let
skipCount
=
0
;
for
(
const
room
of
rooms
)
{
// 校验是否已存在(通过 room_id)
const
existing
=
await
regionModel
.
findOne
({
where
:
{
room_id
:
room
.
roomId
}
});
if
(
existing
)
{
skipCount
++
;
continue
;
}
// 插入新区域记录
await
regionModel
.
create
({
room_id
:
room
.
roomId
,
name
:
room
.
roomName
,
floor_id
:
room
.
floorId
,
type
:
room
.
floorName
,
// type 对应楼层名称
building_id
:
room
.
buildingId
,
groups
:
room
.
buildingName
,
// groups 对应楼栋名称
sort_order
:
0
,
});
insertCount
++
;
}
console
.
log
(
`[区域集成] 完成,新增
${
insertCount
}
条,跳过
${
skipCount
}
条已存在`
);
}
catch
(
err
)
{
console
.
error
(
'[区域集成] 执行异常:'
,
err
);
}
}
// ==================== 设备集成 ====================
/** 空调控制参数模板(按设备数据结构.md) */
const
AC_CONTROL_PARAMS
=
{
power
:
[
"on"
,
"off"
],
mode
:
[
"制冷"
,
"制热"
,
"送风"
,
"除湿"
],
fanSpeed
:
[
"自动"
,
"低"
,
"中"
,
"高"
],
setTemp
:
"16~30"
,
roomTemp
:
"-20~50"
,
maxTemp
:
"20~40"
,
minTemp
:
"-10~20"
,
autoControl
:
[
"生效"
,
"解除"
],
};
/** 电表控制参数模板(按设备数据结构.md) */
const
METER_CONTROL_PARAMS
=
{
current
:
"0~9999"
,
voltage
:
"0~500"
,
power
:
"0~99999"
,
energy
:
"0~999999"
,
switch
:
[
"closed"
,
"open"
],
};
/**
* 数据集成-设备集成
* 包含:空调设备集成 + 电表设备集成
*/
export
async
function
device
()
{
console
.
log
(
'[设备集成] 开始执行...'
);
try
{
await
integrateAcDevices
();
await
integrateMeterDevices
();
console
.
log
(
'[设备集成] 完成'
);
}
catch
(
err
)
{
console
.
error
(
'[设备集成] 执行异常:'
,
err
);
}
}
/**
* 空调设备集成
* device_id = indoorUnitAddressFull
* device_ad = indoorUnitId
*/
async
function
integrateAcDevices
()
{
console
.
log
(
'[空调设备集成] 开始...'
);
const
deviceModel
=
mysqlModelMap
[
'device'
];
if
(
!
deviceModel
)
{
console
.
error
(
'[空调设备集成] device 表模型未初始化,跳过'
);
return
;
}
// 分页拉取所有内机数据
const
rows
=
await
fetchAllPages
((
page
,
limit
)
=>
getIndoorUnitList
({
page
,
limit
}));
console
.
log
(
`[空调设备集成] 共获取
${
rows
.
length
}
条内机数据`
);
let
insertCount
=
0
;
let
skipCount
=
0
;
for
(
const
item
of
rows
)
{
const
deviceId
=
item
.
indoorUnitAddressFull
;
// device_id 用四段地址
if
(
!
deviceId
)
{
skipCount
++
;
continue
;
}
// 校验是否已存在
const
existing
=
await
deviceModel
.
findOne
({
where
:
{
device_id
:
deviceId
}
});
if
(
existing
)
{
skipCount
++
;
continue
;
}
// 查找对应的 region_key
let
regionKey
=
0
;
if
(
item
.
roomId
)
{
const
regionModel
=
mysqlModelMap
[
'region'
];
if
(
regionModel
)
{
const
region
=
await
regionModel
.
findOne
({
where
:
{
room_id
:
item
.
roomId
}
});
if
(
region
)
{
regionKey
=
region
.
id
;
}
}
}
// 插入新设备
await
deviceModel
.
create
({
device_id
:
deviceId
,
device_ad
:
item
.
indoorUnitId
||
''
,
region_key
:
regionKey
,
device_type
:
'空调'
,
device_name
:
`空调内机-
${
deviceId
}
`
,
control_params
:
AC_CONTROL_PARAMS
,
});
insertCount
++
;
}
console
.
log
(
`[空调设备集成] 完成,新增
${
insertCount
}
条,跳过
${
skipCount
}
条已存在`
);
}
/**
* 电表设备集成
* device_id = gatewayCode
* device_ad = meterId
*/
async
function
integrateMeterDevices
()
{
console
.
log
(
'[电表设备集成] 开始...'
);
const
deviceModel
=
mysqlModelMap
[
'device'
];
if
(
!
deviceModel
)
{
console
.
error
(
'[电表设备集成] device 表模型未初始化,跳过'
);
return
;
}
// 分页拉取所有电表数据
const
rows
=
await
fetchAllPages
((
page
,
limit
)
=>
getElectricMaterList
({
page
,
limit
}));
console
.
log
(
`[电表设备集成] 共获取
${
rows
.
length
}
条电表数据`
);
let
insertCount
=
0
;
let
skipCount
=
0
;
for
(
const
item
of
rows
)
{
const
deviceId
=
item
.
gatewayCode
;
// device_id 用 gatewayCode
if
(
!
deviceId
)
{
skipCount
++
;
continue
;
}
// 校验是否已存在
const
existing
=
await
deviceModel
.
findOne
({
where
:
{
device_id
:
deviceId
}
});
if
(
existing
)
{
skipCount
++
;
continue
;
}
// 插入新设备
await
deviceModel
.
create
({
device_id
:
deviceId
,
device_ad
:
item
.
meterId
||
''
,
region_key
:
0
,
// 电表无房间关联
device_type
:
'电能监测'
,
device_name
:
`电表-
${
item
.
meterComId
||
item
.
meterId
||
''
}
`
,
control_params
:
METER_CONTROL_PARAMS
,
});
insertCount
++
;
}
console
.
log
(
`[电表设备集成] 完成,新增
${
insertCount
}
条,跳过
${
skipCount
}
条已存在`
);
}
// ==================== 设备数据集成 ====================
/** 工作模式映射:1=制冷 2=制热 3=送风 4=除湿 */
const
WORK_MODE_MAP
:
Record
<
number
,
string
>
=
{
1
:
'制冷'
,
2
:
'制热'
,
3
:
'送风'
,
4
:
'除湿'
,
};
/** 风速映射:1=高风 2=中风 4=低风 */
const
FAN_SPEED_MAP
:
Record
<
number
,
string
>
=
{
1
:
'高'
,
2
:
'中'
,
4
:
'低'
,
};
/**
* 数据集成-空调内机、电表设备数据集成
*/
export
async
function
deviceData
()
{
console
.
log
(
'[设备数据集成] 开始执行...'
);
try
{
await
integrateAcDeviceData
();
await
integrateMeterDeviceData
();
console
.
log
(
'[设备数据集成] 完成'
);
}
catch
(
err
)
{
console
.
error
(
'[设备数据集成] 执行异常:'
,
err
);
}
}
/**
* 空调设备数据集成(开关机记录)
* 用 indoorUnitAddressFull 匹配 device 表的 device_id
*/
async
function
integrateAcDeviceData
()
{
console
.
log
(
'[空调设备数据集成] 开始...'
);
const
deviceModel
=
mysqlModelMap
[
'device'
];
const
deviceDataModel
=
mysqlModelMap
[
'device_data'
];
if
(
!
deviceModel
||
!
deviceDataModel
)
{
console
.
error
(
'[空调设备数据集成] device 或 device_data 表模型未初始化,跳过'
);
return
;
}
// 分页拉取所有内机开关机记录
const
rows
=
await
fetchAllPages
((
page
,
limit
)
=>
getIndoorUnitStateList
({
page
,
limit
}));
console
.
log
(
`[空调设备数据集成] 共获取
${
rows
.
length
}
条开关机记录`
);
let
insertCount
=
0
;
let
skipCount
=
0
;
for
(
const
item
of
rows
)
{
const
deviceId
=
item
.
indoorUnitAddressFull
;
if
(
!
deviceId
)
{
skipCount
++
;
continue
;
}
// 查找对应设备
const
device
=
await
deviceModel
.
findOne
({
where
:
{
device_id
:
deviceId
}
});
if
(
!
device
)
{
skipCount
++
;
continue
;
}
// 构造数据(按设备数据结构.md 空调设备数据格式)
const
data
=
{
power
:
item
.
onOff
===
1
?
'on'
:
'off'
,
mode
:
WORK_MODE_MAP
[
item
.
workMode
]
||
'未知'
,
fanSpeed
:
FAN_SPEED_MAP
[
item
.
fanSpeed
]
||
'自动'
,
setTemp
:
item
.
tempSet
||
0
,
roomTemp
:
item
.
roomTemp
||
0
,
maxTemp
:
item
.
tempSetHi
||
0
,
minTemp
:
item
.
tempSetLo
||
0
,
autoControl
:
'生效'
,
};
await
deviceDataModel
.
create
({
device_id
:
deviceId
,
data
:
data
,
received_time
:
new
Date
(),
device_time
:
item
.
createTime
?
new
Date
(
item
.
createTime
)
:
null
,
});
insertCount
++
;
}
console
.
log
(
`[空调设备数据集成] 完成,新增
${
insertCount
}
条,跳过
${
skipCount
}
条`
);
}
/**
* 电表设备数据集成(抄表记录)
* 用 gatewayCode 匹配 device 表的 device_id
*/
async
function
integrateMeterDeviceData
()
{
console
.
log
(
'[电表设备数据集成] 开始...'
);
const
deviceModel
=
mysqlModelMap
[
'device'
];
const
deviceDataModel
=
mysqlModelMap
[
'device_data'
];
if
(
!
deviceModel
||
!
deviceDataModel
)
{
console
.
error
(
'[电表设备数据集成] device 或 device_data 表模型未初始化,跳过'
);
return
;
}
// 分页拉取所有抄表记录
const
rows
=
await
fetchAllPages
((
page
,
limit
)
=>
getMaterStateList
({
page
,
limit
}));
console
.
log
(
`[电表设备数据集成] 共获取
${
rows
.
length
}
条抄表记录`
);
let
insertCount
=
0
;
let
skipCount
=
0
;
for
(
const
item
of
rows
)
{
const
deviceId
=
item
.
gatewayCode
;
if
(
!
deviceId
)
{
skipCount
++
;
continue
;
}
// 查找对应设备
const
device
=
await
deviceModel
.
findOne
({
where
:
{
device_id
:
deviceId
}
});
if
(
!
device
)
{
skipCount
++
;
continue
;
}
// 构造数据(按设备数据结构.md 电表设备数据格式)
const
data
=
{
current
:
item
.
powerConsumptionAfterTransformationRatio
||
0
,
voltage
:
0
,
power
:
item
.
ammeterReadingAfterTransformationRatio
||
0
,
energy
:
item
.
ammeterReadingAfterTransformationRatio
||
0
,
switch
:
'open'
,
};
await
deviceDataModel
.
create
({
device_id
:
deviceId
,
data
:
data
,
received_time
:
new
Date
(),
device_time
:
item
.
deviceTime
?
new
Date
(
item
.
deviceTime
)
:
null
,
});
insertCount
++
;
}
console
.
log
(
`[电表设备数据集成] 完成,新增
${
insertCount
}
条,跳过
${
skipCount
}
条`
);
}
// ==================== 暂无对应接口的集成函数(保留空实现) ====================
/**
* 数据集成-九合一环境设备数据集成
*/
export
async
function
airDeviceData
()
{
// TODO: 暂无对应接口
}
/**
* 数据集成-客流监测设备数据集成
*/
export
async
function
customerDeviceData
()
{
// TODO: 暂无对应接口
}
/**
* 数据集成-预警工单数据集成
*/
export
async
function
alertWorkData
()
{
// TODO: 暂无对应接口
}
src/biz/feiyiClient.ts
View file @
4114c762
...
...
@@ -177,6 +177,37 @@ export async function getGatewayDeviceList(page: number = 1, limit: number = 10)
/**
* 内机列表分页查询
* @param params
* @returns
{
"code": "00000", //状态码 00000 为正常 其他为异常
"message": "success", //返回状态 success 为成功, 其他为失败
"data": { // 返回数据
"page": 1, //当前页码
"limit": 0, //每页条数
"total": 579, //总数
"rows": [{
"indoorUnitId": "98566729709518848", //内机 ID
"roomId": "98452624688414720", //房间 ID
"floorId": "98452624680026112", //楼层 ID
"buildingId": "34390184208629760", //楼栋 ID
"indoorUnitAddressFull": "11-1-1-1", //内机四段地址
"onOffLock": 1, //是否开关锁定 1 锁定 0 未锁定
"onOff": 0, //开关状态 1 开 0 关
"workModeLock": 0, //是否工作模式锁定 1 锁定 0 未锁定
"workMode": 2, //工作模式 1 制冷 2 制热 3 送风 4 除湿
"tempSetLock": 0, //是否温度锁定 1 锁定 0 未锁定
"tempSet": 16.0, // 设定温度
"tempSetLo": 16.0, // 锁定下限温度
"tempSetHi": 32.0, // 锁定下限温度
"fanSpeed": 4, // 风速 1 高风 2 中风 4 低风
"hasTimingTasks": 0, //是否有定时任务
"alarmCode": "LOST", //空调故障码
"roomTemp": 27 //室内温度(实际为空调回风温度,有一定的误差仅做参考)
"lockedSwitch": 0, // 锁定的开关:0 关 1 开
"lockedMode": 0, // 锁定模式 1 制冷 2 制热 3 送风 4 除湿 10 制热/送风 11 制冷/送风 12 制冷/除湿 13 制冷/除湿/送风 14 除湿/送风
}]
}
}
*/
export
async
function
getIndoorUnitList
(
params
:
any
):
Promise
<
any
>
{
const
path
=
'/openApi/indoorUnit/list'
;
...
...
@@ -200,4 +231,151 @@ export async function controlIndoorUnit(params: any): Promise<any> {
console
.
log
(
'控制内机响应:'
,
result
);
let
success
=
result
.
code
===
'00000'
?
true
:
false
;
return
{
success
,
data
:
result
.
message
};
}
\ No newline at end of file
}
/**
* 建筑物树结构查询
* @param params
* @returns
{
"code": "00000", //状态码 00000 为正常,其他为异常
"message": "success", //状态信息 success 为成功,其他为异常
"data": [{
"buildingId": "97474765970866176", //建筑物 ID
"parentId": "0", //建筑物父 ID
"buildingName": "飞奕 1", // 建筑物名称
"buildingType": 1, //建筑物类型 1 楼栋 2 楼层 3 房间
"children": [ //子建筑物
{
"buildingId": "97474766339964928",
"parentId": "97474765970866176",
"buildingName": "1 层",
"buildingType": 2,
"children": [{
"buildingId": "97474766696480768",
"parentId": "97474766339964928",
"buildingName": "1-0",
"buildingType": 3,
"children": []
}]
}
]
}],
"timestamp": 1714290880301
}
*/
export
async
function
getRegionTree
():
Promise
<
any
>
{
const
path
=
'/openApi/building/tree'
;
const
body
=
{
};
const
result
=
await
feiYiPost
(
path
,
body
);
return
result
.
data
;
}
/**
* 电表分页查询
* @param params
* @returns
{
"code": "00000", //状态码 00000 为成功,其他为失败
"message": "success", //提示信息 success 为成功 其他为失败
"data": {
"page": 0, //当前页码
"limit": 0, //每页条数
"total": 6, //总条数
"rows": [{
"meterId": "110510431701041152", //电表 ID
"gatewayComId": , //电表网关设备通信 ID
"gatewayCode": , //电表网关设备编码
"protocolType": "DLT645-2007", //电表协议类型
"ammeterReadingAfterTransformationRatio": 6547.68, //电表变比后读数
"transformationRatio": 80.0, //电表变比
"meterComId": "210505015447" //电表地址
}, {
"gatewayComId": , //电表网关设备通信 ID
"gatewayCode": , //电表网关设备编码
"meterId": "110510346153885697",
"transformationRatio": 50.0,
"meterComId": "210505015560"
}]
},
"timestamp": 1713245734235
}
*/
export
async
function
getElectricMaterList
(
params
:
any
):
Promise
<
any
>
{
const
path
=
'/openApi/electricMeter/getAll'
;
const
body
=
{
...
params
};
const
result
=
await
feiYiPost
(
path
,
body
);
return
result
.
data
;
}
/**
* 抄表记录查询
* @param params
* @returns
{
"code": "00000", //状态码 00000 表示成功,其他为失败
"message": "success", //提示信息 success 为成功,其他为失败
"data": {
"page": 1, //当前页码
"limit": 0, //每页条数
"total": 6, //总条数
"rows": [{
"electricMeterStateId": "112111594590437376", //抄表记录 ID
"gatewayId": "97477557167063040", //主机设备 ID
"gatewayCode": "fa000001400001240231212000200026" //主机设备编码
"meterComId": "210505015595", //电表地址
"gatewayComId": 11, // 电表网关通信 ID
"transformationRatio": 100.0, //电表变比
"powerConsumptionAfterTransformationRatio": 0.08, //电表变比后用电量
"ammeterReadingAfterTransformationRatio": 19167.4, // 电表变比后读数
"ammeterReadingBeforeTransformationRatio": 191.67, //电表变比前读数
"energyLoss": 0.00, //损耗电量
"deviceTime": 1706371200000, //设备时间
}]
},
"timestamp": 1713145563759
}
*/
export
async
function
getMaterStateList
(
params
:
any
):
Promise
<
any
>
{
const
path
=
'/openApi/electricMeterState/list'
;
const
body
=
{
...
params
};
const
result
=
await
feiYiPost
(
path
,
body
);
return
result
.
data
;
}
/**
* 内机开关机记录查询
* @param params
* @returns
{
"code": "00000",
"message": "success",
"path": null,
"data": {
"page": 1,
"limit": 0,
"total": 278,
"rows": [{
"indoorUnitEventId": "140718238987321344", //内机事件 ID
"indoorUnitId": "97478680942739586", //内机 ID
"gatewayComId": 1, //主机通信 ID
"roomId": "97474774841819136", //内机所在房间 ID
"floorId": "97474774485303296", //内机所在楼层 ID
"buildingId": "97474765970866176", //内机所在楼栋 ID
"indoorUnitAddressFull": "1-8-128-16", //内机四段地址
"onOff": 0, //内机开关标志 0 关 1 开
"workMode": 4, //工作模式 1 制冷 2 制热 3 送风 4 除湿
"tempSet": 18.0, //内机温度设置
"fanSpeed": 2, //内机风速 风速 1 高风 2 中风 4 低风
"createTime": 1713173801018 //事件发生时间
}]
},
"timestamp": 1713173831195
}
*/
export
async
function
getIndoorUnitStateList
(
params
:
any
):
Promise
<
any
>
{
const
path
=
'/openApi/indoorUnit/event/switch'
;
const
body
=
{
...
params
};
const
result
=
await
feiYiPost
(
path
,
body
);
return
result
.
data
;
}
src/biz/running.ts
View file @
4114c762
...
...
@@ -5,6 +5,7 @@ import { selectOneDataByParam, selectDataListByParam } from "../data/findData";
import
{
BizError
}
from
"../util/bizError"
;
import
{
controlIndoorUnit
}
from
"./feiyiClient"
;
import
{
getRegionList
}
from
"./region"
;
import
{
startSchedule
,
stopSchedule
}
from
"../config/schedule"
;
/**
* 智能管控-运行分析
...
...
@@ -200,6 +201,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
{ count: 8, name: "安全事件", proportion: '18%' }];
// 5.2. 预警监控数据
resultAny.alertManagement = {
alertStatus: alertStatus,
alertWorkLevel: alertWorkLevel,
alertWorkType: alertWorkType,
alertWorkOrders: alertWorkOrders
...
...
@@ -732,6 +734,21 @@ async function controlAcRunningApi(acDevices?: any, temperature?: number, worker
}
/**
* 数据集成 - 启动定时任务
*/
export async function startScheduleTask() {
await startSchedule();
return { success: true };
}
/**
* 数据集成 - 停止定时任务
*/
export async function stopScheduleTask() {
await stopSchedule();
return { success: true };
}
/**
* 空调中文到Key的映射
* 1 制冷 2 制热 3 送风 4 除湿
* 0 关-off 1 开-on
...
...
src/config/schedule.ts
0 → 100644
View file @
4114c762
/**
* 定时任务调度器
* - 每分钟执行一次任务
* - 内置锁机制,防止任务重叠执行
* - 任务锁死时(超时未释放),会主动强制释放锁,确保后续任务能正常执行
*/
import
{
dataintegration
}
from
"tencentcloud-sdk-nodejs"
;
import
{
region
,
device
,
deviceData
}
from
"../biz/dataIntegration"
;
// 锁超时时间(毫秒),任务执行超过此时间视为锁死
const
LOCK_TIMEOUT_MS
=
55
*
1000
;
// 55秒,留5秒余量给下一次执行
// 定时器执行间隔(毫秒)
const
TASK_INTERVAL_MS
=
60
*
1000
;
// 1分钟
interface
TaskLock
{
locked
:
boolean
;
lockTime
:
number
;
// 加锁时的时间戳
lockKey
:
string
;
// 锁标识,用于追踪
}
const
taskLock
:
TaskLock
=
{
locked
:
false
,
lockTime
:
0
,
lockKey
:
''
,
};
/**
* 尝试获取任务锁
* @returns 是否成功获取锁
*/
function
tryAcquireLock
():
boolean
{
if
(
taskLock
.
locked
)
{
const
elapsed
=
Date
.
now
()
-
taskLock
.
lockTime
;
if
(
elapsed
>
LOCK_TIMEOUT_MS
)
{
// 锁已超时,强制释放
console
.
warn
(
`[定时任务] 检测到锁死,强制释放锁。已锁定
${
elapsed
}
ms,锁标识:
${
taskLock
.
lockKey
}
`
);
releaseLock
();
return
tryAcquireLock
();
}
// 上一次任务还在执行中,跳过本次
console
.
log
(
`[定时任务] 上一次任务尚未完成,跳过本次执行。已锁定
${
elapsed
}
ms`
);
return
false
;
}
taskLock
.
locked
=
true
;
taskLock
.
lockTime
=
Date
.
now
();
taskLock
.
lockKey
=
`
${
Date
.
now
()}
_
${
Math
.
random
().
toString
(
36
).
substring
(
2
,
8
)}
`
;
return
true
;
}
/**
* 释放任务锁
*/
function
releaseLock
():
void
{
taskLock
.
locked
=
false
;
taskLock
.
lockTime
=
0
;
taskLock
.
lockKey
=
''
;
}
/**
* 获取当前锁状态(供外部查询)
*/
export
function
getLockStatus
():
{
locked
:
boolean
;
lockDuration
:
number
;
lockKey
:
string
}
{
return
{
locked
:
taskLock
.
locked
,
lockDuration
:
taskLock
.
locked
?
Date
.
now
()
-
taskLock
.
lockTime
:
0
,
lockKey
:
taskLock
.
lockKey
,
};
}
/**
* 主动强制释放锁(供外部调用,用于紧急情况手动释放)
*/
export
function
forceReleaseLock
():
void
{
if
(
taskLock
.
locked
)
{
const
elapsed
=
Date
.
now
()
-
taskLock
.
lockTime
;
console
.
warn
(
`[定时任务] 外部主动强制释放锁。已锁定
${
elapsed
}
ms,锁标识:
${
taskLock
.
lockKey
}
`
);
releaseLock
();
}
}
/**
* 定时任务的具体业务逻辑
* 在这里编写需要定时执行的任务代码
*/
async
function
scheduledTask
():
Promise
<
void
>
{
// 尝试获取锁,未获取到则跳过
if
(
!
tryAcquireLock
())
{
return
;
}
try
{
// ====== 在此处编写定时任务的具体逻辑 ======
console
.
log
(
`[定时任务] 开始执行 -
${
new
Date
().
toLocaleString
()}
`
);
// TODO: 替换为实际的业务逻辑
// 例如:数据备份、缓存清理、定时同步等
await
region
();
await
device
();
await
deviceData
();
console
.
log
(
`[定时任务] 执行完成 -
${
new
Date
().
toLocaleString
()}
`
);
// =========================================
}
catch
(
err
)
{
console
.
error
(
'[定时任务] 执行异常:'
,
err
);
}
finally
{
// 无论任务成功或失败,都要释放锁
releaseLock
();
}
}
// 定时器句柄
let
timerHandle
:
NodeJS
.
Timeout
|
null
=
null
;
/**
* 启动定时任务
* 在项目启动时调用
*/
export
function
startSchedule
():
void
{
if
(
timerHandle
)
{
console
.
warn
(
'[定时任务] 定时器已在运行中,无需重复启动'
);
return
;
}
console
.
log
(
`[定时任务] 定时器已启动,间隔
${
TASK_INTERVAL_MS
/
1000
}
秒`
);
// 使用 setInterval 实现每分钟执行一次
timerHandle
=
setInterval
(()
=>
{
scheduledTask
();
},
TASK_INTERVAL_MS
);
}
/**
* 停止定时任务
*/
export
function
stopSchedule
():
void
{
if
(
timerHandle
)
{
clearInterval
(
timerHandle
);
timerHandle
=
null
;
console
.
log
(
'[定时任务] 定时器已停止'
);
}
}
src/main.ts
View file @
4114c762
...
...
@@ -2,6 +2,7 @@ import { initConfig, systemConfig} from "./config/serverConfig";
import
*
as
mysqlDB
from
"./db/mysqlInit"
;
import
{
initMysqlModel
}
from
"./model/sqlModelBind"
;
import
{
httpServer
}
from
"./net/http_server"
;
import
{
startSchedule
}
from
"./config/schedule"
;
async
function
lanuch
()
{
...
...
@@ -12,6 +13,8 @@ async function lanuch() {
await
initMysqlModel
();
/**创建http服务 */
httpServer
.
createServer
(
systemConfig
.
port
);
/**启动定时任务 */
startSchedule
();
console
.
log
(
'This indicates that the server is started successfully.'
);
...
...
src/routers/device.ts
View file @
4114c762
...
...
@@ -33,6 +33,11 @@ export function setRouter(httpServer) {
/** 空调开关 */
httpServer
.
post
(
'/api/qdm/run/monitor/acRun'
,
asyncHandler
(
controlAcRunning
));
/** 启动数据集成 */
httpServer
.
post
(
'/api/qdm/run/start/schedule'
,
asyncHandler
(
startSchedule
));
/** 停止数据集成 */
httpServer
.
post
(
'/api/qdm/run/stop/schedule'
,
asyncHandler
(
stopSchedule
));
}
/**
...
...
@@ -137,6 +142,21 @@ async function controlAcRunning(req, res) {
res
.
success
(
result
);
}
/**
* 启动定时任务
*/
async
function
startSchedule
(
req
,
res
)
{
const
result
=
await
runningBiz
.
startScheduleTask
();
res
.
success
(
result
);
}
/**
* 停止定时任务
*/
async
function
stopSchedule
(
req
,
res
)
{
const
result
=
await
runningBiz
.
stopScheduleTask
();
res
.
success
(
result
);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment