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
a29676d4
Commit
a29676d4
authored
Jul 16, 2026
by
PC-20251223ZVQQ\Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
预警工单处理优化和空调自控调整
parent
05624613
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
67 additions
and
40 deletions
+67
-40
dataIntegration.ts
src/biz/dataIntegration.ts
+37
-16
feiyiClient.ts
src/biz/feiyiClient.ts
+4
-4
mqttClient.ts
src/biz/mqttClient.ts
+19
-13
running.ts
src/biz/running.ts
+6
-6
mysqlTableConfig.ts
src/config/mysqlTableConfig.ts
+1
-1
No files found.
src/biz/dataIntegration.ts
View file @
a29676d4
...
...
@@ -920,24 +920,34 @@ export async function processFaultStatus() {
return
;
}
// ② 去重 device_id + 取最早故障时间(扫描范围起点)
// ② 去重 device_id + 建立每个设备的最早故障时间映射
// deviceFaultTimeMap: 每个设备最早的活跃故障发生时间,用于内存中精确过滤
// globalMinOccurredTime: 全局最早故障时间,仅作为 SQL 查询范围下限
const
deviceSet
=
new
Set
<
string
>
();
let
minOccurredTime
:
Date
|
null
=
null
;
let
globalMinOccurredTime
:
Date
|
null
=
null
;
const
deviceFaultTimeMap
=
new
Map
<
string
,
Date
>
();
for
(
const
f
of
activeFaults
as
any
[])
{
deviceSet
.
add
(
f
.
device_id
);
const
t
=
f
.
occurred_time
?
new
Date
(
f
.
occurred_time
)
:
null
;
if
(
t
&&
(
!
minOccurredTime
||
t
<
minOccurredTime
))
{
minOccurredTime
=
t
;
if
(
!
t
)
continue
;
// 全局最早时间(SQL 查询范围)
if
(
!
globalMinOccurredTime
||
t
<
globalMinOccurredTime
)
{
globalMinOccurredTime
=
t
;
}
// 每个设备的最早故障时间(内存过滤用)
const
existing
=
deviceFaultTimeMap
.
get
(
f
.
device_id
);
if
(
!
existing
||
t
<
existing
)
{
deviceFaultTimeMap
.
set
(
f
.
device_id
,
t
);
}
}
const
deviceIds
=
[...
deviceSet
];
// ③ 一次拉回所有相关设备的
开机记录(故障最早
时间之后)
// ③ 一次拉回所有相关设备的
运行记录(全局最早故障
时间之后)
// 不做时间兜底,宕机重启后也能覆盖全部历史
const
deviceDataRecords
=
await
deviceDataModel
.
findAll
({
where
:
{
device_id
:
{
[
Op
.
in
]:
deviceIds
},
...(
minOccurredTime
?
{
device_time
:
{
[
Op
.
gte
]:
m
inOccurredTime
}
}
:
{}),
...(
globalMinOccurredTime
?
{
device_time
:
{
[
Op
.
gte
]:
globalM
inOccurredTime
}
}
:
{}),
},
attributes
:
[
'device_id'
,
'data'
,
'device_time'
],
order
:
[[
'device_id'
,
'ASC'
],
[
'device_time'
,
'ASC'
]],
...
...
@@ -945,8 +955,10 @@ export async function processFaultStatus() {
});
console
.
log
(
`[故障状态更新] 共
${
deviceDataRecords
.
length
}
条相关设备数据记录`
);
// ④ 内存过滤:只保留开机记录(power='on'),按 device_id 分组取最早开机时间
const
powerOnMap
=
new
Map
<
string
,
Date
>
();
// ④ 内存过滤:只保留各设备最早故障时间之后的开机记录(power='on')
// 关键修复:不以全局最早时间过滤,而是用每个设备自己的最早故障时间,
// 避免将故障发生前的开机记录纳入比对,导致永远无法判定"已解决"
const
powerOnTimesMap
=
new
Map
<
string
,
Date
[]
>
();
for
(
const
r
of
deviceDataRecords
as
any
[])
{
let
power
:
string
|
undefined
;
const
rawData
=
r
.
data
;
...
...
@@ -960,22 +972,31 @@ export async function processFaultStatus() {
const
dt
=
r
.
device_time
?
new
Date
(
r
.
device_time
)
:
null
;
if
(
!
dt
)
continue
;
if
(
!
powerOnMap
.
has
(
r
.
device_id
)
||
dt
<
powerOnMap
.
get
(
r
.
device_id
)
!
)
{
powerOnMap
.
set
(
r
.
device_id
,
dt
);
// 只保留该设备最早故障时间之后的记录
const
deviceFaultTime
=
deviceFaultTimeMap
.
get
(
r
.
device_id
);
if
(
deviceFaultTime
&&
dt
<=
deviceFaultTime
)
continue
;
if
(
!
powerOnTimesMap
.
has
(
r
.
device_id
))
{
powerOnTimesMap
.
set
(
r
.
device_id
,
[]);
}
powerOnTimesMap
.
get
(
r
.
device_id
)
!
.
push
(
dt
);
}
// ⑤ 内存比对:开机时间 > 故障时间 → 标记已解决
// ⑤ 逐条故障比对:查找该故障发生时间之后最早的开机记录
// 关键修复:不再拿"全量最早开机时间"与故障时间对比,
// 而是在该设备故障后出现的开机记录中找第一条,确保 isAfter 判定正确
const
toUpdate
:
{
id
:
number
;
resolved_time
:
Date
}[]
=
[];
for
(
const
fault
of
activeFaults
as
any
[])
{
const
powerOnTime
=
powerOn
Map
.
get
(
fault
.
device_id
);
if
(
!
powerOnTime
)
continue
;
const
powerOnTime
s
=
powerOnTimes
Map
.
get
(
fault
.
device_id
);
if
(
!
powerOnTime
s
||
powerOnTimes
.
length
===
0
)
continue
;
const
occurredTime
=
fault
.
occurred_time
?
moment
(
fault
.
occurred_time
)
:
null
;
const
occurredTime
=
fault
.
occurred_time
?
new
Date
(
fault
.
occurred_time
)
:
null
;
if
(
!
occurredTime
)
continue
;
if
(
moment
(
powerOnTime
).
isAfter
(
occurredTime
))
{
toUpdate
.
push
({
id
:
fault
.
id
,
resolved_time
:
powerOnTime
});
// 数据已按 device_time ASC 排序,找第一条在故障时间之后的记录
const
resolvedTime
=
powerOnTimes
.
find
(
dt
=>
dt
>
occurredTime
);
if
(
resolvedTime
)
{
toUpdate
.
push
({
id
:
fault
.
id
,
resolved_time
:
resolvedTime
});
}
}
...
...
src/biz/feiyiClient.ts
View file @
a29676d4
...
...
@@ -102,7 +102,7 @@ export async function getFeiYiToken(): Promise<string> {
}
catch
(
err
)
{
throw
new
BizError
(
ERRORENUM
.
网络错误
,
`获取 token 网络失败:
${
err
.
message
}
`
);
}
console
.
log
(
'获取 token 响应:'
,
result
);
console
.
log
(
'获取 token 响应:'
,
result
?.
code
);
// 检查响应结构:文档中返回 { code, data: { access_token } }
if
(
!
result
||
result
.
code
!==
'00000'
)
{
...
...
@@ -143,7 +143,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
let
result
:
any
;
try
{
result
=
await
post
(
url
,
body
,
headers
);
console
.
log
(
`请求
${
path
}
成功,响应:`
,
result
);
console
.
log
(
`请求
${
path
}
成功,响应:`
,
result
?.
code
);
}
catch
(
err
)
{
throw
new
BizError
(
ERRORENUM
.
网络错误
,
`请求
${
path
}
失败:
${
err
.
message
}
`
);
}
...
...
@@ -155,7 +155,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
// 可选:清除 token 缓存,重新获取
return
feiYiPost
(
path
,
body
,
false
);
}
throw
new
BizError
(
ERRORENUM
.
第三方接口错误
,
`接口
${
path
}
调用失败:
${
result
.
message
||
JSON
.
stringify
(
result
)}
`
);
throw
new
BizError
(
ERRORENUM
.
第三方接口错误
,
`接口
${
path
}
调用失败:
${
result
.
message
}
`
);
// || JSON.stringify(result)
}
return
result
;
}
...
...
@@ -228,7 +228,7 @@ export async function controlIndoorUnit(params: any): Promise<any> {
}
const
body
=
{
...
params
};
const
result
=
await
feiYiPost
(
path
,
body
);
console
.
log
(
'控制内机响应:'
,
result
);
console
.
log
(
'控制内机响应:'
,
result
?.
code
);
let
success
=
result
.
code
===
'00000'
?
true
:
false
;
return
{
success
,
data
:
result
.
message
};
}
...
...
src/biz/mqttClient.ts
View file @
a29676d4
...
...
@@ -36,7 +36,7 @@ export async function processDeviceData(message) {
console
.
error
(
'消息不是合法JSON,跳过:'
,
msgStr
);
return
;
}
console
.
log
(
'收到
数据:'
,
data
);
console
.
log
(
'收到
环境数据:'
,
data
.
length
);
// 提取关键字段
const
devEUI
=
data
.
devEUI
?
data
.
devEUI
.
toUpperCase
()
:
null
;
...
...
@@ -89,6 +89,12 @@ export async function processDeviceData(message) {
console
.
log
(
`数据入库成功: 设备
${
devEUI
}
`
);
// 晚上 23:00 ~ 次日 05:00 不执行空调自控(休眠时段)
const
currentHour
=
new
Date
().
getHours
();
if
(
currentHour
>=
23
||
currentHour
<
5
)
{
console
.
log
(
`当前时间
${
currentHour
}
:00,处于休眠时段(23:00-05:00),跳过空调自控`
);
return
;
}
// 根据regionKey查询区域表,若包含文创,则禁用空调控制
if
(
deviceRow
.
data
&&
deviceRow
.
data
.
region_key
)
{
const
regionRow
=
await
selectOneDataByParam
(
...
...
@@ -96,13 +102,13 @@ export async function processDeviceData(message) {
[
"name"
]
);
// 调用空调控制(非文创区域才允许自动控温)
if
(
regionRow
?.
data
&&
!
regionRow
.
data
.
name
?.
includes
(
"文创"
)
&&
!
regionRow
.
data
.
name
?.
includes
(
"原料厅"
)
&&
!
regionRow
.
data
.
name
?.
includes
(
"社会责任厅"
)
&&
!
regionRow
.
data
.
name
?.
includes
(
"新包装"
)
)
{
await
controlAcByEnvironment
(
devEUI
,
data
);
}
//
if (regionRow?.data && !regionRow.data.name?.includes("文创")
//
&& !regionRow.data.name?.includes("原料厅")
//
&& !regionRow.data.name?.includes("社会责任厅")
//
&& !regionRow.data.name?.includes("新包装")
//
) {
await
controlAcByEnvironment
(
devEUI
,
data
);
//
}
}
}
catch
(
error
)
{
...
...
@@ -146,7 +152,7 @@ export async function customerDeviceData(message) {
console
.
error
(
'消息不是合法JSON,跳过:'
,
msgStr
);
return
;
}
console
.
log
(
'收到客流数据:'
,
data
);
console
.
log
(
'收到客流数据:'
,
data
.
length
);
// 提取设备信息
const
deviceInfo
=
data
.
device_info
;
...
...
@@ -223,14 +229,14 @@ export async function customerDeviceData(message) {
updated_at
:
now
,
}
);
console
.
log
(
`客流设备已创建:
${
deviceSn
}
, 线路
${
item
.
triggerKey
}
`
);
//
console.log(`客流设备已创建: ${deviceSn}, 线路 ${item.triggerKey}`);
}
}
// 遍历每条线路的客流数据,逐条入库
for
(
const
item
of
triggerItems
)
{
const
inCount
=
item
.
total
?.
in
??
0
;
const
outCount
=
item
.
total
?.
out
??
0
;
//
const inCount = item.total?.in ?? 0;
//
const outCount = item.total?.out ?? 0;
await
addData
(
TABLENAME
.
设备数据表
,
...
...
@@ -243,7 +249,7 @@ export async function customerDeviceData(message) {
}
);
console
.
log
(
`客流数据入库成功: 设备
${
deviceSn
}
,
${
item
.
triggerName
||
item
.
triggerUuid
}
, 进
${
inCount
}
出
${
outCount
}
`
);
//
console.log(`客流数据入库成功: 设备 ${deviceSn}, ${item.triggerName || item.triggerUuid}, 进 ${inCount} 出 ${outCount}`);
}
console
.
log
(
`客流数据处理完成: 设备
${
deviceSn
}
, 共入库
${
triggerItems
.
length
}
条数据`
);
...
...
src/biz/running.ts
View file @
a29676d4
...
...
@@ -50,7 +50,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
}
let
regionInfo
:
any
=
await
selectDataListByParam
(
TABLENAME
.
区域表
,
regionParam
,
[
"id"
]);
let
regionKeys
=
regionInfo
.
data
.
map
((
r
:
any
)
=>
r
.
id
);
console
.
log
(
"筛选区域Key:"
,
regionKeys
);
console
.
log
(
"筛选区域Key:"
,
regionKeys
.
length
);
// 1.1 获取所选区域三相电表及电能监测设备的用电数据(用于能耗管理)
let
powerParams
:
any
=
{
device_type
:
{
"%in%"
:
[
"三相电表"
,
"电能监测"
]
}};
...
...
@@ -591,7 +591,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let
d
=
new
Date
(
rec
.
device_time
);
let
hourKey
=
`
${
d
.
getHours
()}
时`
;
let
data
=
rec
.
data
;
console
.
log
(
data
);
//
console.log(data);
if
(
!
hourlyData
.
has
(
hourKey
))
{
let
totalNum
=
1
;
let
onlineNum
=
data
.
power
===
"on"
?
1
:
0
;
...
...
@@ -603,7 +603,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
hourlyData
.
set
(
hourKey
,
{
online
:
onlineNum
,
total
:
totalNum
+
1
});
}
}
console
.
log
(
"空调在线数据(过去24小时):"
,
hourlyData
);
console
.
log
(
"空调在线数据(过去24小时):"
,
hourlyData
.
size
);
// 2.2.3 填充24小时空调在线数据,没有在线数据的小时段则设为零,并计算每小时的空调在线率
for
(
let
i
=
0
;
i
<
24
;
i
++
)
{
let
key
=
`
${
i
}
时`
;
...
...
@@ -906,9 +906,9 @@ function getSeason(): 'summer' | 'winter' {
function
calcAcTempSet
(
temperature
:
number
,
season
:
'summer'
|
'winter'
):
{
tempSet
:
number
;
fanSpeed
:
number
;
onOff
:
number
}
{
if
(
season
===
'summer'
)
{
// 夏季制冷:温度越高,设定越低
if
(
temperature
>
2
6
)
return
{
tempSet
:
22
,
fanSpeed
:
1
,
onOff
:
1
};
// 未达需求:高风制冷
if
(
temperature
>=
22
)
return
{
tempSet
:
24
,
fanSpeed
:
4
,
onOff
:
Math
.
round
(
temperature
)
===
24
?
0
:
1
};
// 舒适区:四舍五入24
℃待机
return
{
tempSet
:
2
6
,
fanSpeed
:
4
,
onOff
:
1
};
// 已达需求:低风维持
if
(
temperature
>
2
3
)
return
{
tempSet
:
19
,
fanSpeed
:
1
,
onOff
:
1
};
// 未达需求:高风制冷
if
(
temperature
>=
19
)
return
{
tempSet
:
21
,
fanSpeed
:
4
,
onOff
:
Math
.
round
(
temperature
)
===
21
?
0
:
1
};
// 舒适区:四舍五入21
℃待机
return
{
tempSet
:
2
3
,
fanSpeed
:
4
,
onOff
:
1
};
// 已达需求:低风维持
}
else
{
// 冬季制热:温度越低,设定越高
if
(
temperature
<
18
)
return
{
tempSet
:
21
,
fanSpeed
:
1
,
onOff
:
1
};
// 未达需求:高风制热
...
...
src/config/mysqlTableConfig.ts
View file @
a29676d4
...
...
@@ -165,7 +165,7 @@ export const TablesConfig = [
},
device_state
:
{
type
:
DataTypes
.
TINYINT
,
allowNull
:
fals
e
,
allowNull
:
tru
e
,
defaultValue
:
0
,
comment
:
'设备状态:0=正常 1=硬件故障 2=通信故障 3=离线'
},
...
...
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