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
addb30b5
Commit
addb30b5
authored
Jul 29, 2026
by
PC-20251223ZVQQ\Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
环境监测和运行分析优化
parent
ffe69e79
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
162 additions
and
26 deletions
+162
-26
IAQ.html
public/IAQ.html
+13
-7
serverConfig.xml
serverConfig.xml
+1
-1
device.ts
src/biz/device.ts
+1
-1
feiyiClient.ts
src/biz/feiyiClient.ts
+2
-2
mqttClient.ts
src/biz/mqttClient.ts
+2
-2
running.ts
src/biz/running.ts
+98
-7
scheduleExecutor.ts
src/biz/scheduleExecutor.ts
+1
-0
main.ts
src/main.ts
+5
-5
device.ts
src/routers/device.ts
+15
-1
feiyiClient.ts
src/routers/feiyiClient.ts
+24
-0
No files found.
public/IAQ.html
View file @
addb30b5
...
...
@@ -1084,7 +1084,7 @@
if
(
!
largeChart
||
largeChart
.
isDisposed
())
return
;
const
ind
=
getInd
(
key
);
if
(
!
ind
)
return
;
largeChart
.
setOption
(
createGaugeOption
(
ind
,
true
)
,
true
);
largeChart
.
setOption
(
createGaugeOption
(
ind
,
true
));
}
function
destroySmallCharts
()
{
...
...
@@ -1145,7 +1145,7 @@
smallIndicators
.
value
.
forEach
(
ind
=>
{
const
existing
=
smallChartMap
[
ind
.
key
];
if
(
existing
&&
!
existing
.
isDisposed
())
{
existing
.
setOption
(
createGaugeOption
(
ind
,
false
)
,
true
);
existing
.
setOption
(
createGaugeOption
(
ind
,
false
));
}
else
{
const
dom
=
document
.
getElementById
(
'gauge-'
+
ind
.
key
);
if
(
dom
)
{
...
...
@@ -1169,6 +1169,7 @@
// ======== 定时轮询 ========
const
POLL_INTERVAL
=
30
*
1000
;
// 30 秒刷新一次
let
pollTimer
=
null
;
let
polling
=
false
;
// 并发锁:上一次请求未完成则跳过本次
// 轻量刷新:只 setOption 更新数据,不销毁重建图表
function
refreshCharts
()
{
...
...
@@ -1179,7 +1180,7 @@
smallIndicators
.
value
.
forEach
(
ind
=>
{
const
chart
=
smallChartMap
[
ind
.
key
];
if
(
chart
&&
!
chart
.
isDisposed
())
{
chart
.
setOption
(
createGaugeOption
(
ind
,
false
)
,
true
);
chart
.
setOption
(
createGaugeOption
(
ind
,
false
));
}
});
}
...
...
@@ -1187,10 +1188,15 @@
function
startPolling
()
{
stopPolling
();
// 先清旧定时器,防止累积
pollTimer
=
setInterval
(
async
()
=>
{
if
(
!
isMounted
.
value
)
return
;
// 组件已卸载,跳过
await
fetchEnvironmentData
();
await
nextTick
();
refreshCharts
();
if
(
!
isMounted
.
value
||
polling
)
return
;
// 组件已卸载或上一次未完成,跳过
polling
=
true
;
try
{
await
fetchEnvironmentData
();
await
nextTick
();
refreshCharts
();
}
finally
{
polling
=
false
;
}
},
POLL_INTERVAL
);
}
...
...
serverConfig.xml
View file @
addb30b5
...
...
@@ -10,7 +10,7 @@
<mysqlPwd>
123456
</mysqlPwd>
<dataBase>
beermuseumplatform
</dataBase>
<!-- 服务器mysql配置 -->
<!-- <mysqlHost>12
7.0.0.1
</mysqlHost>
<!-- <mysqlHost>12
3.207.147.179
</mysqlHost>
<mysqlPort>3306</mysqlPort>
<mysqlUser>root</mysqlUser>
<mysqlPwd>qaz123456</mysqlPwd>
...
...
src/biz/device.ts
View file @
addb30b5
...
...
@@ -96,7 +96,7 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?:
// 调用空调控制(非文创区域才允许自动控温)
// if (regionRow?.data && !regionRow.data.name?.includes("文创")) {
// 根据设备上报的数据,联动控制空调设备
let
regionName
=
regionRow
?.
data
&&
!
regionRow
.
data
.
name
?
regionRow
.
data
.
name
:
""
;
let
regionName
=
regionRow
&&
regionRow
.
data
&&
regionRow
.
data
.
name
?
regionRow
.
data
.
name
:
""
;
await
controlAcByEnvironment
(
deviceId
,
data
,
regionName
);
// }
...
...
src/biz/feiyiClient.ts
View file @
addb30b5
...
...
@@ -227,9 +227,9 @@ export async function controlIndoorUnit(params: any): Promise<any> {
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"设备编号不能为空"
);
}
const
body
=
{
...
params
};
const
result
=
await
feiYiPost
(
path
,
body
);
const
result
=
await
feiYiPost
(
path
,
body
);
// {code:"", message:""}
console
.
log
(
'控制内机响应:'
,
result
?.
code
);
let
success
=
result
.
code
===
'00000'
?
true
:
false
;
let
success
=
result
&&
result
.
code
===
'00000'
?
true
:
false
;
return
{
success
,
data
:
result
.
message
};
}
...
...
src/biz/mqttClient.ts
View file @
addb30b5
...
...
@@ -107,7 +107,7 @@ export async function processDeviceData(message) {
// && !regionRow.data.name?.includes("社会责任厅")
// && !regionRow.data.name?.includes("新包装")
// ) {
let
regionName
=
regionRow
?.
data
&&
!
regionRow
.
data
.
name
?
regionRow
.
data
.
name
:
""
;
let
regionName
=
regionRow
&&
regionRow
.
data
&&
regionRow
.
data
.
name
?
regionRow
.
data
.
name
:
""
;
await
controlAcByEnvironment
(
devEUI
,
data
,
regionName
);
// }
}
...
...
@@ -153,7 +153,7 @@ export async function customerDeviceData(message) {
console
.
error
(
'消息不是合法JSON,跳过:'
,
msgStr
);
return
;
}
console
.
log
(
'收到客流数据:'
,
data
.
length
);
console
.
log
(
'收到客流数据:'
,
msgStr
);
// 提取设备信息
const
deviceInfo
=
data
.
device_info
;
...
...
src/biz/running.ts
View file @
addb30b5
...
...
@@ -812,7 +812,7 @@ export async function getAcRunningPop(regionKey: number, deviceId: string) {
// 获取设备信息
let
devices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
region_key
:
regionKey
,
device_id
:
deviceId
}
,
{
device_id
:
deviceId
},
// region_key: regionKey
,
[
"device_id"
,
"device_type"
,
"device_name"
,
"control_params"
]
);
if
(
!
devices
.
data
.
length
)
{
...
...
@@ -824,9 +824,13 @@ export async function getAcRunningPop(regionKey: number, deviceId: string) {
{
device_id
:
deviceId
,
"%orderDesc%"
:
"device_time"
,
"%limit%"
:
1
},
[
"data"
,
"device_time"
]
);
let
detailData
=
deviceData
.
data
.
length
?
deviceData
.
data
[
0
].
data
:
null
;
detailData
.
fanSpeed
=
detailData
&&
detailData
.
fanSpeed
?
acDeviceKeyMap
[
detailData
.
fanSpeed
]
:
2
;
return
{
deviceInfo
:
devices
.
data
[
0
].
control_params
,
latestData
:
de
viceData
.
data
.
length
?
deviceData
.
data
[
0
].
data
:
null
latestData
:
de
tailData
}
}
...
...
@@ -856,8 +860,9 @@ export async function controlAcRunning(params: {}) {
// 2. 接入空调控制API,更新空调设备信息
let
acPrarms
=
{
"indoorUnitAddressFull"
:
[
deviceId
],
"workMode"
:
acDeviceKeyMap
[
mode
],
"onOff"
:
acDeviceKeyMap
[
power
],
"workMode"
:
mode
?
acDeviceKeyMap
[
mode
]
:
1
,
// 关机时传一个默认模式
// "onOff": acDeviceKeyMap[power],
"onOff"
:
1
,
// TODO:临时处理,等待前端调好就改回通过传参处理
"tempSet"
:
Number
(
setTemp
),
"fanSpeed"
:
acDeviceKeyMap
[
fanSpeed
],
"onOffLock"
:
acDeviceKeyMap
[
autoControl
],
...
...
@@ -865,9 +870,36 @@ export async function controlAcRunning(params: {}) {
"tempSetLock"
:
acDeviceKeyMap
[
autoControl
],
"tempSetLo"
:
Number
(
minTemp
),
"tempSetHi"
:
Number
(
maxTemp
),
"openApiAction"
:
autoControl
===
"生效"
?
"control"
:
"lock"
// "openApiAction": autoControl === "生效" ? "control" : "lock"
"openApiAction"
:
"control"
// TODO:临时处理
};
return
await
controlIndoorUnit
(
acPrarms
);
const
result
=
await
controlIndoorUnit
(
acPrarms
);
// 手动控制日志
const
now
=
new
Date
();
const
operationTime
=
new
Date
();
const
acLogs
=
[{
device_id
:
deviceId
,
device_type
:
'空调'
,
operation_content
:
{
option
:
'手动控制'
,
device_id
:
deviceId
,
time
:
operationTime
,
mode
:
mode
,
power
:
power
,
setTemp
:
setTemp
,
fanSpeed
:
fanSpeed
,
autoControl
:
autoControl
,
maxTemp
:
maxTemp
,
minTemp
:
minTemp
,
success
:
result
.
success
,
message
:
result
.
message
||
(
result
.
success
?
'控制成功'
:
'控制失败'
),
},
status
:
result
.
success
?
1
:
0
,
created_at
:
now
,
updated_at
:
now
,
}];
await
addData
(
TABLENAME
.
设备日志表
,
acLogs
);
return
result
;
}
// 辅助函数:获取用电趋势(按间隔分组)
...
...
@@ -910,6 +942,63 @@ async function getEnergyTrend(deviceIds: string[], startTime: Date, endTime: Dat
return
result
;
}
/**
* 获取本月累计用电(按天)
* 查询三相电表/电能监测设备的每日用电量,补全当月 1 日到今天的 x 轴
* @param regionGroup 区域分组
*/
export
async
function
getCurrentMonthDailyEnergy
(
regionGroup
:
string
)
{
// 1. 查区域
let
regionParam
:
any
=
{};
if
(
regionGroup
)
{
regionParam
.
region_group
=
regionGroup
;
}
let
regionInfo
:
any
=
await
selectDataListByParam
(
TABLENAME
.
区域表
,
regionParam
,
[
"id"
]);
let
regionKeys
=
regionInfo
.
data
.
map
((
r
:
any
)
=>
r
.
id
);
// 2. 查三相电表/电能监测设备
let
powerParams
:
any
=
{
device_type
:
{
"%in%"
:
[
"三相电表"
,
"电能监测"
]
}
};
if
(
regionGroup
)
{
powerParams
.
region_key
=
{
"%in%"
:
regionKeys
};
}
let
energyDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
powerParams
,
[
"device_id"
]);
let
energyDeviceIds
=
energyDevices
.
data
.
map
((
d
:
any
)
=>
d
.
device_id
);
if
(
energyDeviceIds
.
length
===
0
)
return
[];
// 3. 时间范围:本月 1 日 ~ 现在
const
now
=
new
Date
();
const
monthStart
=
new
Date
(
now
.
getFullYear
(),
now
.
getMonth
(),
1
,
0
,
0
,
0
,
0
);
// 4. 查设备数据
let
records
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
{
"%in%"
:
energyDeviceIds
},
device_time
:
{
"%gte%"
:
formatLocalTime
(
monthStart
),
"%lte%"
:
formatLocalTime
(
now
)
},
"%orderAsc%"
:
"device_time"
},
[
"device_id"
,
"data"
,
"device_time"
]);
// 5. 按天聚合用电量
const
dayMap
=
new
Map
<
string
,
number
>
();
for
(
let
rec
of
records
.
data
)
{
let
energy
=
rec
.
data
?.
current
;
if
(
energy
===
undefined
)
continue
;
let
d
=
new
Date
(
rec
.
device_time
);
const
pad
=
(
n
:
number
)
=>
String
(
n
).
padStart
(
2
,
'0'
);
const
key
=
`
${
d
.
getFullYear
()}
/
${
pad
(
d
.
getMonth
()
+
1
)}
/
${
pad
(
d
.
getDate
())}
`
;
dayMap
.
set
(
key
,
(
dayMap
.
get
(
key
)
||
0
)
+
energy
);
}
// 6. 补全本月 1 日到今天的每一天(缺失填 0)
const
result
:
{
key
:
string
;
value
:
string
}[]
=
[];
const
todayDate
=
now
.
getDate
();
for
(
let
i
=
1
;
i
<=
todayDate
;
i
++
)
{
const
d
=
new
Date
(
now
.
getFullYear
(),
now
.
getMonth
(),
i
);
const
pad
=
(
n
:
number
)
=>
String
(
n
).
padStart
(
2
,
'0'
);
const
key
=
`
${
d
.
getFullYear
()}
/
${
pad
(
d
.
getMonth
()
+
1
)}
/
${
pad
(
d
.
getDate
())}
`
;
result
.
push
({
key
,
value
:
String
(
dayMap
.
get
(
key
)
||
0
)
});
}
return
result
;
}
// 判断当前季节:5-9月为夏季(制冷),其余为冬季(制热)
function
getSeason
():
'summer'
|
'winter'
{
const
month
=
new
Date
().
getMonth
()
+
1
;
...
...
@@ -961,7 +1050,9 @@ export async function controlAcByEnvironment(deviceId: string, data: any, region
let
regionKey
=
envDevices
.
data
[
0
].
region_key
;
// ===== 新风联动(后执行,通过送风模式覆盖空调温控) =====
if
(
co2
!=
null
&&
!
regionName
?.
includes
(
"原料厅"
)
&&
!
regionName
?.
includes
(
"社会责任厅"
))
{
if
(
co2
!=
null
&&
!
regionName
?.
includes
(
"原料厅"
)
&&
!
regionName
?.
includes
(
"社会责任厅"
)
&&
!
regionName
?.
includes
(
"中途酒吧"
))
{
const
fanParams
=
calcFanParams
(
co2
);
if
(
fanParams
)
{
let
fanDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
...
...
src/biz/scheduleExecutor.ts
View file @
addb30b5
...
...
@@ -116,6 +116,7 @@ function buildControlParams(
indoorUnitAddressFull
:
deviceIds
,
onOff
:
AC_KEY_MAP
[
acControl
]
??
1
,
openApiAction
:
"control"
,
workMode
:
1
// 传一个默认模式
};
// 关机只发送 onOff=0,不传模式/温度/风速
...
...
src/main.ts
View file @
addb30b5
...
...
@@ -19,15 +19,15 @@ async function lanuch() {
/**创建http服务 */
httpServer
.
createServer
(
systemConfig
.
port
);
/**启动MQTT订阅服务 */
await
mqttSrv
.
startMqttClient
();
//
await mqttSrv.startMqttClient();
/**启动定时任务 */
startSchedule
();
//
startSchedule();
/**启动预警工单定时任务(10分钟) */
startAlertSchedule
();
//
startAlertSchedule();
/**启动故障状态更新定时任务(5分钟) */
startFaultStatusSchedule
();
//
startFaultStatusSchedule();
/**启动日程执行器 */
await
initScheduleTasks
();
//
await initScheduleTasks();
console
.
log
(
'This indicates that the server is started successfully.'
);
...
...
src/routers/device.ts
View file @
addb30b5
...
...
@@ -44,6 +44,9 @@ export function setRouter(httpServer) {
httpServer
.
post
(
'/api/qdm/device/envDevice'
,
asyncHandler
(
getEnvironmentDevices
));
/** IAQ-获取环境监测数据 */
httpServer
.
post
(
'/api/qdm/device/environment'
,
asyncHandler
(
getEnvironmentData
));
/** 本月累计用电(按天) */
httpServer
.
post
(
'/api/qdm/run/energy/monthly'
,
asyncHandler
(
getMonthlyEnergy
));
}
/**
...
...
@@ -130,7 +133,7 @@ async function getRegionList(req, res) {
*/
async
function
getAcRunningPop
(
req
,
res
)
{
let
reqConf
=
{
regionKey
:
'Number'
,
deviceId
:
'String'
};
const
NotMustHaveKeys
=
[];
const
NotMustHaveKeys
=
[
'regionKey'
];
let
{
regionKey
,
deviceId
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
runningBiz
.
getAcRunningPop
(
regionKey
,
deviceId
);
res
.
success
(
result
);
...
...
@@ -197,6 +200,17 @@ async function getRunAnalysisEnvPop(req, res) {
res
.
success
(
result
);
}
/**
* 本月累计用电(按天)
*/
async
function
getMonthlyEnergy
(
req
,
res
)
{
let
reqConf
=
{
regionGroup
:
'String'
};
const
NotMustHaveKeys
=
[
"regionGroup"
];
let
{
regionGroup
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
runningBiz
.
getCurrentMonthDailyEnergy
(
regionGroup
);
res
.
success
(
result
);
}
...
...
src/routers/feiyiClient.ts
View file @
addb30b5
...
...
@@ -4,6 +4,8 @@
import
asyncHandler
from
'express-async-handler'
;
import
*
as
feiyiClientBiz
from
'../biz/feiyiClient'
;
import
{
eccReqParamater
}
from
'../util/verificationParam'
;
import
{
addData
}
from
'../data/addData'
;
import
{
TABLENAME
}
from
'../config/dbEnum'
;
export
function
setRouter
(
httpServer
)
{
/** 网关设备列表 */
...
...
@@ -54,6 +56,28 @@ async function getFeiyiIndoorUnitControl(req, res) {
const
NotMustHaveKeys
=
[
"workMode"
,
"openApiAction"
];
let
params
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
feiyiClientBiz
.
controlIndoorUnit
(
params
);
// 直接API控制日志
const
now
=
new
Date
();
const
operationTime
=
new
Date
();
const
logEntries
=
params
.
indoorUnitAddressFull
.
map
((
deviceId
:
string
)
=>
({
device_id
:
deviceId
,
device_type
:
'空调'
,
operation_content
:
{
option
:
'直接API控制'
,
device_id
:
deviceId
,
time
:
operationTime
,
params
:
{
workMode
:
params
.
workMode
,
openApiAction
:
params
.
openApiAction
,
},
success
:
result
.
success
,
message
:
result
.
message
||
(
result
.
success
?
'控制成功'
:
'控制失败'
),
},
status
:
result
.
success
?
1
:
0
,
created_at
:
now
,
updated_at
:
now
,
}));
await
addData
(
TABLENAME
.
设备日志表
,
logEntries
);
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