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
4f9fb2b3
Commit
4f9fb2b3
authored
May 27, 2026
by
PC-20251223ZVQQ\Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
对接飞奕API获取token
parent
f8e817ba
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
751 additions
and
1586 deletions
+751
-1586
device.ts
src/biz/device.ts
+4
-818
feiyiClient.ts
src/biz/feiyiClient.ts
+46
-19
region.ts
src/biz/region.ts
+1
-685
running.ts
src/biz/running.ts
+665
-0
device.ts
src/routers/device.ts
+31
-59
feiyiClient.ts
src/routers/feiyiClient.ts
+2
-3
users.ts
src/routers/users.ts
+2
-2
No files found.
src/biz/device.ts
View file @
4f9fb2b3
import
{
selectOneDataByParam
,
selectDataListByParam
,
selectPaginatedDataWithOrder
}
from
"../data/findData"
;
import
{
selectOneDataByParam
}
from
"../data/findData"
;
import
{
TABLENAME
}
from
"../config/dbEnum"
;
import
{
TABLENAME
}
from
"../config/dbEnum"
;
import
{
getMySqlMs
}
from
"../tools/systemTools"
;
import
{
getMySqlMs
}
from
"../tools/systemTools"
;
import
{
BizError
}
from
"../util/bizError"
;
import
{
BizError
}
from
"../util/bizError"
;
...
@@ -83,17 +83,17 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?:
...
@@ -83,17 +83,17 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?:
return
{
success
:
true
};
return
{
success
:
true
};
}
}
/**
/**
* 处理设备商推送的故障数据
* 处理设备商推送的故障数据
* @param deviceId 设备唯一标识(必填)
* @param deviceId 设备唯一标识(必填)
* @param faultType 故障类型(必填),如“离线”“数据异常”“硬件故障”
* @param faultType 故障类型(必填),如“离线”“数据异常”“硬件故障”
* @param faultLevel 故障等级
* @param faultDescription 故障详细描述(可选)
* @param faultDescription 故障详细描述(可选)
* @param occurredTime 故障发生时间(可选),格式:YYYY-MM-DD HH:MM:SS,不传则使用服务器时间
* @param occurredTime 故障发生时间(可选),格式:YYYY-MM-DD HH:MM:SS,不传则使用服务器时间
* @returns {Promise<{faultId: number}>} 返回新插入故障记录的ID
* @returns {Promise<{faultId: number}>} 返回新插入故障记录的ID
* @throws 如果设备未注册,则抛出错误
* @throws 如果设备未注册,则抛出错误
*/
*/
export
async
function
handleDeviceFaultPush
(
deviceId
:
string
,
faultType
:
string
,
faultDescription
?:
string
,
occurredTime
?:
string
)
{
export
async
function
handleDeviceFaultPush
(
deviceId
:
string
,
faultType
:
string
,
fault
Level
:
string
,
fault
Description
?:
string
,
occurredTime
?:
string
)
{
if
(
!
deviceId
||
!
faultType
)
{
if
(
!
deviceId
||
!
faultType
)
{
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"deviceId 和 faultType 不能为空"
);
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"deviceId 和 faultType 不能为空"
);
}
}
...
@@ -115,6 +115,7 @@ export async function handleDeviceFaultPush( deviceId: string, faultType: string
...
@@ -115,6 +115,7 @@ export async function handleDeviceFaultPush( deviceId: string, faultType: string
fault_type
:
faultType
,
fault_type
:
faultType
,
fault_description
:
faultDescription
||
null
,
fault_description
:
faultDescription
||
null
,
occurred_time
:
faultTime
,
occurred_time
:
faultTime
,
level
:
faultLevel
||
'1'
,
// 默认一级故障
status
:
0
,
// 0=未处理
status
:
0
,
// 0=未处理
resolved_time
:
null
,
resolved_time
:
null
,
created_at
:
nowStr
,
created_at
:
nowStr
,
...
@@ -124,832 +125,17 @@ export async function handleDeviceFaultPush( deviceId: string, faultType: string
...
@@ -124,832 +125,17 @@ export async function handleDeviceFaultPush( deviceId: string, faultType: string
return
{
faultId
:
insertResult
.
insertId
,
success
:
true
};
return
{
faultId
:
insertResult
.
insertId
,
success
:
true
};
}
}
/**
* 运行分析 todu(待测试)
* @returns 大屏所需的所有指标数据
*/
export
async
function
getRunAnalysis
()
{
// 1. 获取所有三相电表及电能监测设备的用电数据(用于能耗管理)
let
energyDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_type
:
{
"%in%"
:
[
"三相电表"
,
"电能监测"
]
}
// 使用 %in% 操作符
},
[
"device_id"
]);
let
energyDeviceIds
=
energyDevices
.
data
.
map
((
d
:
any
)
=>
d
.
device_id
);
// 2. 获取今日、昨日、本月、上月用电量
let
todayStart
=
new
Date
();
todayStart
.
setHours
(
0
,
0
,
0
,
0
);
let
todayEnd
=
new
Date
();
let
yesterdayStart
=
new
Date
(
Date
.
now
()
-
86400000
);
yesterdayStart
.
setHours
(
0
,
0
,
0
,
0
);
let
yesterdayEnd
=
new
Date
(
Date
.
now
()
-
86400000
);
yesterdayEnd
.
setHours
(
23
,
59
,
59
,
999
);
let
currentMonthStart
=
new
Date
();
currentMonthStart
.
setDate
(
1
);
currentMonthStart
.
setHours
(
0
,
0
,
0
,
0
);
let
lastMonthStart
=
new
Date
();
lastMonthStart
.
setMonth
(
lastMonthStart
.
getMonth
()
-
1
);
lastMonthStart
.
setDate
(
1
);
lastMonthStart
.
setHours
(
0
,
0
,
0
,
0
);
let
lastMonthEnd
=
new
Date
(
currentMonthStart
.
getTime
()
-
1
);
// 辅助函数:查询某个时间段内的总用电量(单位 kwh)
async
function
getTotalEnergy
(
deviceIds
:
string
[],
startTime
:
Date
,
endTime
:
Date
):
Promise
<
number
>
{
if
(
deviceIds
.
length
===
0
)
return
0
;
let
records
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
{
"%in%"
:
deviceIds
},
received_time
:
{
"%gte%"
:
startTime
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
),
"%lte%"
:
endTime
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
)
},
"%orderAsc%"
:
"received_time"
},
[
"device_id"
,
"data"
,
"received_time"
]);
let
total
=
0
;
let
deviceEnergyMap
=
new
Map
();
for
(
let
rec
of
records
.
data
)
{
let
data
=
rec
.
data
;
let
energy
=
data
.
energy
??
data
.
total_energy
;
if
(
energy
===
undefined
)
continue
;
if
(
!
deviceEnergyMap
.
has
(
rec
.
device_id
))
{
deviceEnergyMap
.
set
(
rec
.
device_id
,
{
min
:
energy
,
max
:
energy
});
}
else
{
let
cur
=
deviceEnergyMap
.
get
(
rec
.
device_id
);
if
(
energy
<
cur
.
min
)
cur
.
min
=
energy
;
if
(
energy
>
cur
.
max
)
cur
.
max
=
energy
;
}
}
for
(
let
{
min
,
max
}
of
deviceEnergyMap
.
values
())
{
total
+=
(
max
-
min
);
}
return
total
;
}
let
todayEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
todayStart
,
todayEnd
);
let
yesterdayEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
yesterdayStart
,
yesterdayEnd
);
let
currentMonthEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
currentMonthStart
,
new
Date
());
let
previousMonthEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
lastMonthStart
,
lastMonthEnd
);
let
dailyYearOnYear
=
yesterdayEnergy
===
0
?
'0%'
:
`
${(((
todayEnergy
-
yesterdayEnergy
)
/
yesterdayEnergy
)
*
100
).
toFixed
(
2
)}
%`
;
let
monthlyYearOnYear
=
previousMonthEnergy
===
0
?
'0%'
:
`
${(((
currentMonthEnergy
-
previousMonthEnergy
)
/
previousMonthEnergy
)
*
100
).
toFixed
(
2
)}
%`
;
// 3. 用电趋势:过去24小时(按小时)、过去7天、过去30天、过去一年(按月)
let
now
=
new
Date
();
let
last24hStart
=
new
Date
(
now
.
getTime
()
-
24
*
3600000
);
let
hourlyEnergy
=
await
getEnergyTrend
(
energyDeviceIds
,
last24hStart
,
now
,
'hour'
);
let
last7DaysStart
=
new
Date
(
now
.
getTime
()
-
7
*
86400000
);
let
dailyEnergy7
=
await
getEnergyTrend
(
energyDeviceIds
,
last7DaysStart
,
now
,
'day'
);
let
last30DaysStart
=
new
Date
(
now
.
getTime
()
-
30
*
86400000
);
let
dailyEnergy30
=
await
getEnergyTrend
(
energyDeviceIds
,
last30DaysStart
,
now
,
'day'
);
let
lastYearStart
=
new
Date
(
now
.
getFullYear
()
-
1
,
now
.
getMonth
(),
1
);
let
monthlyEnergyYear
=
await
getEnergyTrend
(
energyDeviceIds
,
lastYearStart
,
now
,
'month'
);
// 4. 设备表格:按设备类型统计数量和耗电情况
let
allDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{},
[
"device_type"
,
"device_id"
]);
let
typeCountMap
=
new
Map
();
for
(
let
dev
of
allDevices
.
data
)
{
let
type
=
dev
.
device_type
;
typeCountMap
.
set
(
type
,
(
typeCountMap
.
get
(
type
)
||
0
)
+
1
);
}
let
deviceTable
=
[];
for
(
let
[
type
,
count
]
of
typeCountMap
.
entries
())
{
// 耗电比例示例(实际可关联用电数据)
let
powerRatio
=
'30%'
;
if
(
type
===
'空调'
)
powerRatio
=
'97%'
;
else
if
(
type
===
'灯光'
)
powerRatio
=
'45%'
;
deviceTable
.
push
({
deviceName
:
type
,
quantity
:
count
,
powerConsumptionRatio
:
powerRatio
});
}
// 5. 汇总数据(简化)
let
totalDevices
=
allDevices
.
data
.
length
;
let
runningDevices
=
totalDevices
;
// 或根据在线数量计算,在线即运行
// 查询未解决的故障设备数量(去重,一个设备可能有多个故障,取 status != 2 的)
let
faultRecords
=
await
selectDataListByParam
(
TABLENAME
.
设备故障表
,
{
status
:
{
"%ne%"
:
2
}
},
// 未解决的状态不是2(已解决)
[
"device_id"
]
);
let
faultDeviceIds
=
new
Set
(
faultRecords
.
data
.
map
((
r
:
any
)
=>
r
.
device_id
));
let
faultDevices
=
faultDeviceIds
.
size
;
let
normalRate
=
totalDevices
===
0
?
'100%'
:
(((
totalDevices
-
faultDevices
)
/
totalDevices
)
*
100
).
toFixed
(
2
)
+
'%'
;
// 6. 设备监测情况:在线/离线(根据最后数据时间是否超过30分钟)
let
nowTime
=
new
Date
();
let
deviceLatestMap
=
new
Map
();
let
deviceIds
=
allDevices
.
data
.
map
(
d
=>
d
.
device_id
);
for
(
let
devId
of
deviceIds
)
{
let
rec
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
devId
,
"%orderDesc%"
:
"received_time"
,
"%limit%"
:
1
},
[
"received_time"
]);
if
(
rec
.
data
.
length
)
{
let
lastTime
=
new
Date
(
rec
.
data
[
0
].
received_time
);
let
diffMinutes
=
(
nowTime
.
getTime
()
-
lastTime
.
getTime
())
/
60000
;
deviceLatestMap
.
set
(
devId
,
{
lastTime
,
isOnline
:
diffMinutes
<=
30
});
}
else
{
deviceLatestMap
.
set
(
devId
,
{
lastTime
:
null
,
isOnline
:
false
});
}
}
let
onlineCount
=
Array
.
from
(
deviceLatestMap
.
values
()).
filter
(
v
=>
v
.
isOnline
).
length
;
let
offlineCount
=
totalDevices
-
onlineCount
;
let
faultCount
=
faultRecords
.
data
.
length
;
// 所有未解决的故障单数量(含同一设备多个故障)
// 7. 监测设备趋势:过去24小时每小时在线数量
let
hourlyOnlineTrend
=
[];
for
(
let
i
=
0
;
i
<
24
;
i
++
)
{
let
hourStart
=
new
Date
(
nowTime
.
getTime
()
-
(
23
-
i
)
*
3600000
);
let
hourEnd
=
new
Date
(
hourStart
.
getTime
()
+
3600000
);
let
onlineInHour
=
0
;
for
(
let
{
lastTime
}
of
deviceLatestMap
.
values
())
{
if
(
lastTime
&&
lastTime
>=
hourStart
&&
lastTime
<=
hourEnd
)
onlineInHour
++
;
}
hourlyOnlineTrend
.
push
({
time
:
`
${
i
}
时`
,
value
:
onlineInHour
.
toString
()
});
}
// 8. 预警处理状态和预警工单(模拟数据,待接入真实告警表)
let
alertStatus
=
{
totalAlerts
:
48
,
resolved
:
20
,
responded
:
24
,
unresolved
:
4
};
let
alertWorkOrders
=
[
{
riskContent
:
"空调压缩机异常"
,
alertTime
:
"26/02/01"
,
status
:
"已解决"
},
{
riskContent
:
"温度传感器离线"
,
alertTime
:
"26/02/01"
,
status
:
"已响应"
},
{
riskContent
:
"湿度超标"
,
alertTime
:
"26/02/02"
,
status
:
"未处理"
}
];
// 9. 空气质量相关数据
let
airQualityDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_type
:
"空气质量传感"
},
[
"device_id"
]);
let
airDeviceIds
=
airQualityDevices
.
data
.
map
(
d
=>
d
.
device_id
);
let
qualityIndex
=
300
;
let
co2Trend
=
[];
let
currentCo2
=
'21ppm'
;
let
currentTemperature
=
'21℃'
;
let
temperatureTrend
=
[];
let
currentHumidity
=
'47%'
;
let
humidityTrend
=
[];
let
currentPm25
=
'21μg/m³'
;
let
pm25Trend
=
[];
let
co2TrendDetail
=
[];
if
(
airDeviceIds
.
length
)
{
// 最新数据
let
latestAir
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
{
"%in%"
:
airDeviceIds
},
"%orderDesc%"
:
"received_time"
,
"%limit%"
:
1
},
[
"data"
]);
if
(
latestAir
.
data
.
length
)
{
let
airData
=
latestAir
.
data
[
0
].
data
;
currentTemperature
=
`
${
airData
.
temperature
??
21
}
℃
`;
currentHumidity = `
$
{
airData
.
humidity
??
47
}
%
`;
currentPm25 = `
$
{
airData
.
pm25
??
21
}
μ
g
/
m
³
`;
currentCo2 = `
$
{
airData
.
co2
??
400
}
ppm
`;
qualityIndex = 500 - (airData.pm25 ?? 0);
}
// 过去24小时趋势
let last24h = new Date(nowTime.getTime() - 24*3600000);
let airHistory = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": airDeviceIds },
received_time: { "%gte%": last24h.toISOString().slice(0,19).replace('T',' ') }
}, ["data", "received_time"]);
let hourlyData = new Map();
for (let rec of airHistory.data) {
let d = new Date(rec.received_time);
let hourKey = `
$
{
d
.
getHours
()}
时
`;
let data = rec.data;
if (!hourlyData.has(hourKey)) {
hourlyData.set(hourKey, { temp: data.temperature, hum: data.humidity, pm: data.pm25, co2: data.co2 });
}
}
for (let i = 0; i < 24; i++) {
let key = `
$
{
i
}
时
`;
let val = hourlyData.get(key);
temperatureTrend.push({ time: `
$
{
i
}:
00
`, value: val?.temp?.toString() ?? '21' });
humidityTrend.push({ time: `
$
{
i
}:
00
`, value: val?.hum?.toString() ?? '47' });
pm25Trend.push({ time: `
$
{
i
}:
00
`, value: val?.pm?.toString() ?? '21' });
let co2Val = val?.co2 ?? 400;
co2TrendDetail.push({ time: `
$
{
i
}:
00
`, value: (co2Val / 100).toFixed(1) });
co2Trend.push({ time: `
$
{
i
}
时
`, value: co2Val.toString() });
}
}
// 10. 机房环境监控和功率监控
let roomEnvironment = [
{ key: "温度", value: currentTemperature },
{ key: "湿度", value: currentHumidity },
{ key: "配置", value: "烟感" },
{ key: "常开", value: "水浸" }
];
let roomPowerTrend = hourlyOnlineTrend.slice(0, 24).map(item => ({ time: item.time.replace('时',':00'), value: item.value }));
// 最终组装返回数据
return {
energyManagement: {
todayElectricity: `
$
{
todayEnergy
.
toFixed
(
0
)}
kwh
`,
yesterdayElectricity: `
$
{
yesterdayEnergy
.
toFixed
(
0
)}
kwh
`,
dailyYearOnYear,
currentMonthElectricity: `
$
{
currentMonthEnergy
.
toFixed
(
0
)}
kwh
`,
previousMonthElectricity: `
$
{
previousMonthEnergy
.
toFixed
(
0
)}
kwh
`,
monthlyYearOnYear
},
electricityTrend: {
last24Hours: hourlyEnergy,
last7Days: dailyEnergy7,
last30Days: dailyEnergy30,
lastYear: monthlyEnergyYear
},
deviceTable,
summaryData: {
runningDevices,
normalRate,
faultDevices
},
deviceMonitoring: {
totalMonitored: totalDevices,
offlineCount,
onlineCount,
faultCount
},
monitoringDeviceTrend: hourlyOnlineTrend,
alertStatus,
alertWorkOrders,
qualityIndex,
co2Trend,
currentTemperature,
temperatureTrend,
currentHumidity,
humidityTrend,
currentPm25,
pm25Trend,
currentCo2,
co2TrendDetail,
roomEnvironment,
roomPowerTrend
};
}
// 辅助函数:获取用电趋势(按间隔分组)
async function getEnergyTrend(deviceIds: string[], startTime: Date, endTime: Date, granularity: 'hour' | 'day' | 'month') {
if (deviceIds.length === 0) return [];
let records = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": deviceIds },
received_time: { "%gte%": startTime.toISOString().slice(0,19).replace('T',' '), "%lte%": endTime.toISOString().slice(0,19).replace('T',' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
let intervalMap = new Map(); // key: 时间字符串, value: { deviceMin, deviceMax }
for (let rec of records.data) {
let data = rec.data;
let energy = data.energy ?? data.total_energy;
if (energy === undefined) continue;
let intervalKey: string;
let d = new Date(rec.received_time);
if (granularity === 'hour') {
intervalKey = `
$
{
d
.
getFullYear
()}
-
$
{
d
.
getMonth
()
+
1
}
-
$
{
d
.
getDate
()}
$
{
d
.
getHours
()}
时
`;
} else if (granularity === 'day') {
intervalKey = `
$
{
d
.
getFullYear
()}
/${d.getMonth
()
+1}/
$
{
d
.
getDate
()}
`;
} else {
intervalKey = `
$
{
d
.
getFullYear
()}
-
$
{
d
.
getMonth
()
+
1
}
`;
}
if (!intervalMap.has(intervalKey)) {
intervalMap.set(intervalKey, { deviceMin: new Map(), deviceMax: new Map() });
}
let entry = intervalMap.get(intervalKey);
if (!entry.deviceMin.has(rec.device_id)) {
entry.deviceMin.set(rec.device_id, energy);
entry.deviceMax.set(rec.device_id, energy);
} else {
let curMin = entry.deviceMin.get(rec.device_id);
let curMax = entry.deviceMax.get(rec.device_id);
if (energy < curMin) entry.deviceMin.set(rec.device_id, energy);
if (energy > curMax) entry.deviceMax.set(rec.device_id, energy);
}
}
let result = [];
for (let [key, { deviceMin, deviceMax }] of intervalMap.entries()) {
let total = 0;
for (let [deviceId, minVal] of deviceMin.entries()) {
let maxVal = deviceMax.get(deviceId);
total += (maxVal - minVal);
}
result.push({ key, value: total.toFixed(0) });
}
result.sort((a,b) => a.key.localeCompare(b.key));
return result;
}
/**
/**
* 设备类型中文到英文Key的映射
* 设备类型中文到英文Key的映射
*/
*/
let
deviceTypeKeyMap
:
{
[
key
:
string
]:
string
}
=
{
let
deviceTypeKeyMap
:
{
[
key
:
string
]:
string
}
=
{
"灯光": "lighting",
"窗帘": "curtain",
"推窗器": "windowOpener",
"空调"
:
"hvac"
,
"空调"
:
"hvac"
,
"新风"
:
"freshAir"
,
"新风"
:
"freshAir"
,
"音乐": "music",
"人体传感"
:
"occupancySensor"
,
"人体传感"
:
"occupancySensor"
,
"人体传感器"
:
"occupancySensor"
,
"人体传感器"
:
"occupancySensor"
,
"空气质量传感"
:
"airSensor"
,
"空气质量传感"
:
"airSensor"
,
"燃气表": "gasMeter",
"水表": "waterMeter",
"三相电表"
:
"electricityMeter"
,
"三相电表"
:
"electricityMeter"
,
"电表"
:
"electricityMeter"
,
"电表"
:
"electricityMeter"
,
"电能监测"
:
"electricityMeter"
"电能监测"
:
"electricityMeter"
};
};
// 辅助函数:将字符串或 Date 转换为 Date 对象
function toDate(dateInput: string | Date): Date {
if (dateInput instanceof Date) return dateInput;
return new Date(dateInput);
}
// 辅助函数:将 Date 对象转换为数据库存储的字符串格式 YYYY-MM-DD HH:MM:SS
function toDateTimeStr(date: Date): string {
return date.toISOString().slice(0, 19).replace('T', ' ');
}
/**
* 获取运行分析区域弹窗
*/
export async function getRunAnalysisPop(regionKey: number) {
let devices = await selectDataListByParam(
TABLENAME.设备表,
{ region_key: regionKey },
["device_id", "device_type", "device_name"]
);
let deviceList = devices.data;
let deviceIds = deviceList.map((d: any) => d.device_id);
if (deviceIds.length === 0) {
return {
deviceOnlineStatus: {},
deviceStatusMonitoring: { totalDevices: "0", online: "0", offline: "0", fault: "0" },
deviceStatusTrend: [],
energyConsumptionMonitoring: []
};
}
// 获取每个设备的最新数据(用于判定业务在线状态)
let deviceLatestMap = new Map<string, { isOnline: boolean; latestData: any }>();
for (let devId of deviceIds) {
let rec = await selectDataListByParam(
TABLENAME.设备数据表,
{ device_id: devId, "%orderDesc%": "received_time", "%limit%": 1 },
["data"]
);
let isOnline = false;
let latestData = null;
if (rec.data.length) {
latestData = rec.data[0].data // 已解析的对象
let devType = deviceList.find((d: any) => d.device_id === devId)?.device_type || "";
isOnline = isDeviceOnlineByStatus(devType, latestData);
}
deviceLatestMap.set(devId, { isOnline, latestData });
}
// 设备在线状态(按类型聚合)
let deviceOnlineStatus: { [key: string]: boolean } = {};
for (let dev of deviceList) {
let cnType = dev.device_type;
let enKey = deviceTypeKeyMap[cnType];
if (enKey) {
let isOnline = deviceLatestMap.get(dev.device_id)?.isOnline ?? false;
if (deviceOnlineStatus[enKey] === undefined) {
deviceOnlineStatus[enKey] = isOnline;
} else {
deviceOnlineStatus[enKey] = deviceOnlineStatus[enKey] || isOnline;
}
}
}
let totalDevices = deviceIds.length;
let onlineCount = Array.from(deviceLatestMap.values()).filter(v => v.isOnline).length;
let offlineCount = totalDevices - onlineCount;
// 故障设备数量
let faultRecords = await selectDataListByParam(
TABLENAME.设备故障表,
{ device_id: { "%in%": deviceIds }, status: { "%ne%": 2 } },
["device_id"]
);
let faultDeviceIds = new Set(faultRecords.data.map((r: any) => r.device_id));
let faultCount = faultDeviceIds.size;
let nowStr = getNowStr();
let hourlyStatusTrend = await getDeviceStatusTrend(deviceList, nowStr);
let hourlyEnergyTrend = await getEnergyTrendByRegion(deviceIds, nowStr);
return {
deviceOnlineStatus,
deviceStatusMonitoring: {
totalDevices: totalDevices.toString(),
online: onlineCount.toString(),
offline: offlineCount.toString(),
fault: faultCount.toString()
},
deviceStatusTrend: hourlyStatusTrend,
energyConsumptionMonitoring: hourlyEnergyTrend
};
}
// 辅助函数:将 Date 转换为本地时间字符串 YYYY-MM-DD HH:MM:SS
function toLocalDateTimeStr(date: Date): string {
let year = date.getFullYear();
let month = String(date.getMonth() + 1).padStart(2, '0');
let day = String(date.getDate()).padStart(2, '0');
let hours = String(date.getHours()).padStart(2, '0');
let minutes = String(date.getMinutes()).padStart(2, '0');
let seconds = String(date.getSeconds()).padStart(2, '0');
return `
$
{
year
}
-
$
{
month
}
-
$
{
day
}
$
{
hours
}:
$
{
minutes
}:
$
{
seconds
}
`;
}
// 获取当前本地时间字符串
function getNowStr(): string {
return toLocalDateTimeStr(new Date());
}
/**
* 获取过去24小时内每小时的设备在线统计(基于业务状态)
* @param deviceList 设备列表
* @param nowStr 当前时间字符串(本地时间)
* @returns 数组,从最早到最晚,每个元素包含 key(整点小时)、online、offline
*/
async function getDeviceStatusTrend(
deviceList: any[],
nowStr: string
) {
let deviceIds = deviceList.map((d: any) => d.device_id);
let totalDevices = deviceIds.length;
let now = new Date(nowStr);
// 计算起始时间:24小时前,并取整到小时(向下取整)
let startDate = new Date(now.getTime() - 24 * 3600000);
startDate.setMinutes(0, 0, 0);
// 结束时间:当前小时整点(不含当前小时区间,因为未结束)
let endDate = new Date(now);
endDate.setMinutes(0, 0, 0);
// 生成小时区间列表(从 startDate 开始,每小时递增,直到 < endDate)
let hourRanges: { start: Date; end: Date; key: string }[] = [];
let current = new Date(startDate);
while (current < endDate) {
let next = new Date(current.getTime() + 3600000);
hourRanges.push({
start: current,
end: next,
key: `
$
{
current
.
getHours
()}:
00
`
});
current = next;
}
// 查询所有设备在时间范围内的数据
let startStr = toLocalDateTimeStr(startDate);
let endStr = toLocalDateTimeStr(now); // 使用当前时间作为结束,但实际查询时用 < endStr
let records = await selectDataListByParam(
TABLENAME.设备数据表,
{
device_id: { "%in%": deviceIds },
received_time: { "%gte%": startStr, "%lte%": endStr },
"%orderAsc%": "received_time"
},
["device_id", "data", "received_time"]
);
// 按设备分组
let deviceDataMap = new Map<string, Array<{ timeStr: string; data: any }>>();
for (let rec of records.data) {
if (!deviceDataMap.has(rec.device_id)) {
deviceDataMap.set(rec.device_id, []);
}
deviceDataMap.get(rec.device_id)!.push({
timeStr: rec.received_time,
data: rec.data
});
}
// 对每个小时区间统计在线设备数
let result = [];
for (let range of hourRanges) {
let startStrRange = toLocalDateTimeStr(range.start);
let endStrRange = toLocalDateTimeStr(range.end);
let onlineCount = 0;
for (let dev of deviceList) {
let devId = dev.device_id;
let devType = dev.device_type;
let dataPoints = deviceDataMap.get(devId) || [];
// 查找该小时内最后一条数据
let lastInRange: { timeStr: string; data: any } | null = null;
for (let point of dataPoints) {
if (point.timeStr >= startStrRange && point.timeStr < endStrRange) {
lastInRange = point;
}
}
if (lastInRange && isDeviceOnlineByStatus(devType, lastInRange.data)) {
onlineCount++;
}
}
result.push({
key: range.key,
online: onlineCount,
offline: totalDevices - onlineCount,
fault: 0
});
}
return result;
}
/**
* 获取过去24小时每小时的能耗趋势
* @param deviceIds 设备ID列表
* @param nowStr 当前时间字符串
*/
async function getEnergyTrendByRegion(deviceIds: string[], nowStr: string) {
let nowTime = toDate(nowStr).getTime();
let startTime = new Date(nowTime - 24 * 3600000);
let startStr = toDateTimeStr(startTime);
let records = await selectDataListByParam(
TABLENAME.设备数据表,
{
device_id: { "%in%": deviceIds },
received_time: { "%gte%": startStr },
"%orderAsc%": "received_time"
},
["device_id", "data", "received_time"]
);
// 按设备分组提取能耗值
let deviceEnergySeries = new Map<string, Array<{ timestamp: number; energy: number }>>();
for (let rec of records.data) {
let dataObj = rec.data;
let energy = dataObj.energy ?? dataObj.total_energy;
if (energy === undefined) continue;
let ts = toDate(rec.received_time).getTime();
if (!deviceEnergySeries.has(rec.device_id)) {
deviceEnergySeries.set(rec.device_id, []);
}
deviceEnergySeries.get(rec.device_id)!.push({ timestamp: ts, energy });
}
let hourlyEnergy = new Array(24).fill(0);
for (let i = 0; i < 24; i++) {
let hourStart = new Date(nowTime - (23 - i) * 3600000);
let hourEnd = new Date(hourStart.getTime() + 3600000);
let startTs = hourStart.getTime();
let endTs = hourEnd.getTime();
for (let series of deviceEnergySeries.values()) {
let firstInHour: { timestamp: number; energy: number } | null = null;
let lastInHour: { timestamp: number; energy: number } | null = null;
for (let point of series) {
if (point.timestamp >= startTs && point.timestamp < endTs) {
if (!firstInHour || point.timestamp < firstInHour.timestamp) firstInHour = point;
if (!lastInHour || point.timestamp > lastInHour.timestamp) lastInHour = point;
}
}
if (firstInHour && lastInHour && lastInHour.energy > firstInHour.energy) {
hourlyEnergy[i] += (lastInHour.energy - firstInHour.energy);
}
}
}
let result = [];
for (let i = 0; i < 24; i++) {
let hourPoint = new Date(nowTime - (23 - i) * 3600000);
let hourKey = `
$
{
hourPoint
.
getHours
()}:
00
`;
result.push({ key: hourKey, energy: Number(hourlyEnergy[i].toFixed(2)) });
}
return result;
}
function isDeviceOnlineByStatus(deviceType: string, latestData: any): boolean {
if (!latestData) return false;
switch (deviceType) {
case "灯光":
case "空调":
case "新风":
case "音乐":
case "水景":
case "雾森":
case "灌溉":
case "驱蚊":
case "增压泵":
case "智慧庭院-水景": // 兼容旧数据
case "智慧庭院-雾森": // 兼容旧数据
case "智慧庭院-灌溉": // 兼容旧数据
case "智慧庭院-驱蚊": // 兼容旧数据
case "智慧庭院-增压泵": // 兼容旧数据
return latestData.power === "on" || latestData.power === true;
case "窗帘":
case "推窗器":
return latestData.action !== undefined && latestData.action !== null;
case "人体传感":
case "人体传感器":
return true;
default:
// 空气质量传感、土壤传感器、气象设备、电能监测等默认只要有数据即视为激活
return true;
}
}
/**
* 获取智能监控弹窗数据
* @param regionKey 区域编号(region.id)
* @returns 设备在线状态、空气质量监测、设备监测详情
*/
export async function getSmartMonitorPop(regionKey: number) {
// 1. 获取该区域下所有设备
let devices = await selectDataListByParam(
TABLENAME.设备表,
{ region_key: regionKey },
["device_id", "device_type", "device_name"]
);
let deviceList = devices.data;
if (deviceList.length === 0) {
return {
deviceOnlineStatus: {},
airQualityMonitoring: {
airQualityLevel: "无数据",
temperature: "--",
humidity: "--",
pm25: "--",
co2: "--",
tvoc: "--"
},
deviceMonitoring: {}
};
}
// 2. 获取每个设备的最新数据(按设备ID分组取最新一条)
let deviceIds = deviceList.map(d => d.device_id);
let latestDataMap = new Map<string, any>(); // key: device_id, value: { data, received_time }
for (let devId of deviceIds) {
let result = await selectDataListByParam(
TABLENAME.设备数据表,
{ device_id: devId, "%orderDesc%": "received_time", "%limit%": 1 },
["data", "received_time"]
);
if (result.data.length > 0) {
latestDataMap.set(devId, {
data: JSON.parse(result.data[0].data),
received_time: result.data[0].received_time
});
}
}
let nowTime = new Date();
let deviceOnlineStatus: { [key: string]: boolean } = {};
let deviceMonitoring: { [key: string]: any } = {};
// 辅助:判断在线状态(30分钟内有数据视为在线)
function isOnline(receivedTime: string | undefined): boolean {
if (!receivedTime) return false;
let lastTime = new Date(receivedTime);
let diffMinutes = (nowTime.getTime() - lastTime.getTime()) / 60000;
return diffMinutes <= 30;
}
// 遍历设备列表,按类型聚合
for (let dev of deviceList) {
let cnType = dev.device_type;
let enKey = deviceTypeKeyMap[cnType];
if (!enKey) continue; // 跳过未映射的设备类型
let latest = latestDataMap.get(dev.device_id);
let online = isOnline(latest?.received_time);
// 设备在线状态:同类型只要有一个在线即为 true(按业务需求可调整)
if (deviceOnlineStatus[enKey] === undefined) {
deviceOnlineStatus[enKey] = online;
} else {
deviceOnlineStatus[enKey] = deviceOnlineStatus[enKey] || online;
}
// 设备监测详情:每种类型只保留最新一个设备的数据(若同类型多设备,可改为取最后上报的设备)
if (!deviceMonitoring[enKey] && latest) {
let data = latest.data;
switch (cnType) {
case "灯光":
deviceMonitoring[enKey] = {
power: data.power === true || data.power === "on",
brightness: data.brightness ?? 0,
colorTemperature: data.colorTemperature ?? 3000
};
break;
case "窗帘":
case "推窗器":
let position = data.position ?? 0;
deviceMonitoring[enKey] = {
position: position,
action: position >= 50 ? "open" : "close"
};
break;
case "空调":
deviceMonitoring[enKey] = {
power: data.power === true || data.power === "on",
targetTemperature: data.targetTemperature ?? 22.0,
roomTemperature: data.roomTemperature ?? 21.0,
mode: data.mode ?? "auto",
fanSpeed: data.fanSpeed ?? "auto"
};
break;
case "新风":
deviceMonitoring[enKey] = {
power: data.power === true || data.power === "on",
fanSpeed: data.fanSpeed ?? "auto"
};
break;
case "音乐":
deviceMonitoring[enKey] = {
power: data.power === true || data.power === "on",
volume: data.volume ?? 0
};
break;
case "人体传感":
case "人体传感器":
deviceMonitoring[enKey] = {
occupied: data.occupied === true
};
break;
// 空气质量传感、电表等不在 deviceMonitoring 中展示,跳过
default:
break;
}
}
}
// 3. 空气质量监测数据(从空气质量传感设备获取)
let airQualityMonitoring = {
airQualityLevel: "无数据",
temperature: "--",
humidity: "--",
pm25: "--",
co2: "--",
tvoc: "--"
};
let airDevices = deviceList.filter(d => d.device_type === "空气质量传感");
for (let airDev of airDevices) {
let latestAir = latestDataMap.get(airDev.device_id);
if (latestAir) {
let data = latestAir.data;
let temperature = data.temperature !== undefined ? `
$
{
data
.
temperature
}
℃`
:
undefined
;
let
humidity
=
data
.
humidity
!==
undefined
?
`
${
data
.
humidity
}
%`
:
undefined
;
let
pm25
=
data
.
pm25
!==
undefined
?
`
${
data
.
pm25
}
μg/m³`
:
undefined
;
let
co2
=
data
.
co2
!==
undefined
?
`
${
data
.
co2
}
ppm`
:
undefined
;
let
tvoc
=
data
.
tvoc
!==
undefined
?
`
${
data
.
tvoc
}
`
:
"0"
;
// 空气质量等级:根据 PM2.5 简单划分(也可根据 AQI)
let
level
=
"优"
;
if
(
pm25
)
{
let
val
=
data
.
pm25
;
if
(
val
<=
35
)
level
=
"优"
;
else
if
(
val
<=
75
)
level
=
"良"
;
else
if
(
val
<=
115
)
level
=
"轻度"
;
else
if
(
val
<=
150
)
level
=
"中度"
;
else
level
=
"重度"
;
}
airQualityMonitoring
=
{
airQualityLevel
:
level
,
temperature
:
temperature
??
"--"
,
humidity
:
humidity
??
"--"
,
pm25
:
pm25
??
"--"
,
co2
:
co2
??
"--"
,
tvoc
:
tvoc
};
break
;
// 取第一个空气质量设备的数据
}
}
return
{
deviceOnlineStatus
,
airQualityMonitoring
,
deviceMonitoring
};
}
/**
* 获取所有区域列表(供前端下拉选择)
* @returns 区域列表,按 sort_order 排序
*/
export
async
function
getRegionList
()
{
let
result
=
await
selectDataListByParam
(
TABLENAME
.
区域表
,
{
"%orderAsc%"
:
"sort_order"
},
[
"id"
,
"name"
,
"sort_order"
]
);
return
{
list
:
result
.
data
.
map
((
r
:
any
)
=>
({
regionKey
:
r
.
id
,
regionName
:
r
.
name
,
sortOrder
:
r
.
sort_order
,
})),
};
}
src/biz/feiyiClient.ts
View file @
4f9fb2b3
...
@@ -8,6 +8,14 @@ import { post } from '../util/request';
...
@@ -8,6 +8,14 @@ import { post } from '../util/request';
import
{
BizError
}
from
'../util/bizError'
;
import
{
BizError
}
from
'../util/bizError'
;
import
{
ERRORENUM
}
from
'../config/errorEnum'
;
import
{
ERRORENUM
}
from
'../config/errorEnum'
;
// 全局变量缓存 token 和过期时间
let
feiYiToken
=
""
;
let
expireTime
=
0
;
let
getTokenTime
=
0
;
let
isFetchingToken
=
false
;
// 避免并发获取 token 的标志位
let
refreshTokenPromise
:
Promise
<
string
>
|
null
=
null
;
// 用于避免并发刷新 token 的 Promise
/**
/**
* 飞奕云平台配置(建议从环境变量或配置文件中读取)
* 飞奕云平台配置(建议从环境变量或配置文件中读取)
*/
*/
...
@@ -21,8 +29,8 @@ const FEIYI_CONFIG = {
...
@@ -21,8 +29,8 @@ const FEIYI_CONFIG = {
* 生成随机字符串 nonce
* 生成随机字符串 nonce
* @param length 长度,默认 16
* @param length 长度,默认 16
*/
*/
function
generateNonce
(
length
:
number
=
16
):
string
{
function
generateNonce
():
string
{
return
crypto
.
randomBytes
(
length
).
toString
(
'hex'
);
return
crypto
.
randomBytes
(
16
).
toString
(
'hex'
);
}
}
/**
/**
...
@@ -36,7 +44,7 @@ function generateNonce(length: number = 16): string {
...
@@ -36,7 +44,7 @@ function generateNonce(length: number = 16): string {
* @param params 请求参数对象
* @param params 请求参数对象
* @param secretKey 密钥
* @param secretKey 密钥
*/
*/
function
generateSign
(
params
:
Record
<
string
,
any
>
,
secretKey
:
string
):
string
{
function
generateSign
(
params
:
Record
<
string
,
any
>
,
timestamp
:
string
,
nonce
:
string
,
secretKey
:
string
,
charset
:
string
=
'UTF-8'
):
string
{
// 1. 过滤掉值为 null 或 undefined 的参数(空字符串保留)
// 1. 过滤掉值为 null 或 undefined 的参数(空字符串保留)
const
filteredParams
:
Record
<
string
,
any
>
=
{};
const
filteredParams
:
Record
<
string
,
any
>
=
{};
for
(
const
key
in
params
)
{
for
(
const
key
in
params
)
{
...
@@ -48,11 +56,11 @@ function generateSign(params: Record<string, any>, secretKey: string): string {
...
@@ -48,11 +56,11 @@ function generateSign(params: Record<string, any>, secretKey: string): string {
const
sortedKeys
=
Object
.
keys
(
filteredParams
).
sort
();
const
sortedKeys
=
Object
.
keys
(
filteredParams
).
sort
();
// 3. 拼接成 key1=value1&key2=value2...
// 3. 拼接成 key1=value1&key2=value2...
const
paramStr
=
sortedKeys
.
map
(
key
=>
`
${
key
}
=
${
filteredParams
[
key
]}
`
).
join
(
'&'
);
const
paramStr
=
sortedKeys
.
map
(
key
=>
`
${
key
}
=
${
filteredParams
[
key
]}
`
).
join
(
'&'
);
// 4. 拼接
SecretKey
// 4. 拼接
timestamp、nonce和
SecretKey
const
stringSignTemp
=
paramStr
+
secretKey
;
const
stringSignTemp
=
paramStr
+
`×tamp=
${
timestamp
}
&nonce=
${
nonce
}
&SecretKey=
${
secretKey
}
`
;
// 5. MD5 并转大写
console
.
log
(
'MD5加密请求参数:'
,
stringSignTemp
);
const
md5
=
crypto
.
createHash
(
'md5'
).
update
(
stringSignTemp
).
digest
(
'hex'
);
// 6. MD5 并转大写
return
md5
.
toUpperCase
();
return
crypto
.
createHash
(
'md5'
).
update
(
stringSignTemp
).
digest
(
'hex'
)
.
toUpperCase
();
}
}
/**
/**
...
@@ -61,21 +69,29 @@ function generateSign(params: Record<string, any>, secretKey: string): string {
...
@@ -61,21 +69,29 @@ function generateSign(params: Record<string, any>, secretKey: string): string {
* @throws 如果请求失败或响应码非 00000,则抛出 BizError
* @throws 如果请求失败或响应码非 00000,则抛出 BizError
*/
*/
export
async
function
getFeiYiToken
():
Promise
<
string
>
{
export
async
function
getFeiYiToken
():
Promise
<
string
>
{
const
timestamp
=
Date
.
now
().
toString
();
// 毫秒时间戳
// 校验token是否有效,有效则直接返回
const
nonce
=
generateNonce
();
const
now
=
Date
.
now
();
if
(
feiYiToken
&&
expireTime
>
now
)
{
console
.
log
(
`使用缓存 token,剩余有效时间:
${(
expireTime
-
now
)
/
1000
}
秒`
);
return
feiYiToken
;
}
// 根据文档,获取 token 的请求参数
const
params
=
{
const
params
=
{
accessKey
:
''
,
// 文档要求传空串
AccessKey
:
''
,
// 文档要求传空串
clientId
:
FEIYI_CONFIG
.
clientId
,
clientId
:
FEIYI_CONFIG
.
clientId
nonce
:
nonce
,
timestamp
:
timestamp
,
};
};
// 生成签名
const
sign
=
generateSign
(
params
,
FEIYI_CONFIG
.
secretKey
);
const
timestamp
=
Date
.
now
().
toString
();
// 毫秒时间戳
const
nonce
=
generateNonce
();
const
sign
=
generateSign
(
params
,
timestamp
,
nonce
,
FEIYI_CONFIG
.
secretKey
);
console
.
log
(
'MD5加密签名:'
,
sign
);
const
requestBody
=
{
const
requestBody
=
{
...
params
,
...
params
,
sign
:
sign
,
timestamp
:
timestamp
,
nonce
:
nonce
,
sign
:
sign
};
};
const
url
=
`
${
FEIYI_CONFIG
.
baseUrl
}
/open/oauth2/openapi/token`
;
const
url
=
`
${
FEIYI_CONFIG
.
baseUrl
}
/open/oauth2/openapi/token`
;
...
@@ -88,6 +104,7 @@ export async function getFeiYiToken(): Promise<string> {
...
@@ -88,6 +104,7 @@ export async function getFeiYiToken(): Promise<string> {
}
catch
(
err
)
{
}
catch
(
err
)
{
throw
new
BizError
(
ERRORENUM
.
网络错误
,
`获取 token 网络失败:
${
err
.
message
}
`
);
throw
new
BizError
(
ERRORENUM
.
网络错误
,
`获取 token 网络失败:
${
err
.
message
}
`
);
}
}
console
.
log
(
'获取 token 响应:'
,
result
);
// 检查响应结构:文档中返回 { code, data: { access_token } }
// 检查响应结构:文档中返回 { code, data: { access_token } }
if
(
!
result
||
result
.
code
!==
'00000'
)
{
if
(
!
result
||
result
.
code
!==
'00000'
)
{
...
@@ -97,19 +114,28 @@ export async function getFeiYiToken(): Promise<string> {
...
@@ -97,19 +114,28 @@ export async function getFeiYiToken(): Promise<string> {
const accessToken = result.data?.access_token;
const accessToken = result.data?.access_token;
if (!accessToken) {
if (!accessToken) {
throw new BizError(ERRORENUM.第三方接口错误, '响应中未包含 access_token');
throw new BizError(ERRORENUM.第三方接口错误, '响应中未包含 access_token');
} else {
// 更新全局 token 和过期时间
feiYiToken = accessToken;
getTokenTime = now;
// 默认 2分钟 过期,如果响应中包含 expires_in 字段,则使用该值(单位毫秒)
expireTime = now + (result.data.expires_in ? result.data.expires_in : 2 * 60 * 1000);
console.log(`
获取新
token
:
$
{
accessToken
}
, 过期时间:
${
new
Date
(
expireTime
).
toISOString
()}
`
);
}
}
return
accessToken
;
return
accessToken
;
}
}
/**
/**
* 带 Token 的 POST 请求封装
* 带 Token 的 POST 请求封装
* 自动获取/刷新 token
(简单实现,生产环境建议做 token 缓存和过期刷新)
* 自动获取/刷新 token
* @param path 接口路径(相对路径)
* @param path 接口路径(相对路径)
* @param body 请求体
* @param body 请求体
* @param retry 是否重试(用于 token 过期重试)
* @param retry 是否重试(用于 token 过期重试)
*/
*/
async
function
feiYiPost
(
path
:
string
,
body
:
any
,
retry
:
boolean
=
true
):
Promise
<
any
>
{
async
function
feiYiPost
(
path
:
string
,
body
:
any
,
retry
:
boolean
=
true
):
Promise
<
any
>
{
const token = await getFeiYiToken(); // 每次都获取最新 token(简单起见,可缓存)
// 获取 token
const
token
=
await
getFeiYiToken
();
const
url
=
`
${
FEIYI_CONFIG
.
baseUrl
}${
path
}
`
;
const
url
=
`
${
FEIYI_CONFIG
.
baseUrl
}${
path
}
`
;
const
headers
=
{
const
headers
=
{
'Authorization'
:
`Bearer
${
token
}
`
,
'Authorization'
:
`Bearer
${
token
}
`
,
...
@@ -119,6 +145,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
...
@@ -119,6 +145,7 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
let
result
:
any
;
let
result
:
any
;
try
{
try
{
result
=
await
post
(
url
,
body
,
headers
);
result
=
await
post
(
url
,
body
,
headers
);
console
.
log
(
`请求
${
path
}
成功,响应:`
,
result
);
}
catch
(
err
)
{
}
catch
(
err
)
{
throw
new
BizError
(
ERRORENUM
.
网络错误
,
`请求
${
path
}
失败:
${
err
.
message
}
`
);
throw
new
BizError
(
ERRORENUM
.
网络错误
,
`请求
${
path
}
失败:
${
err
.
message
}
`
);
}
}
...
...
src/biz/region.ts
View file @
4f9fb2b3
import
{
select
OneDataByParam
,
selectDataListByParam
,
selectPaginatedDataWithOrder
}
from
"../data/findData"
;
import
{
select
DataListByParam
}
from
"../data/findData"
;
import
{
TABLENAME
}
from
"../config/dbEnum"
;
import
{
TABLENAME
}
from
"../config/dbEnum"
;
import
{
getMySqlMs
}
from
"../tools/systemTools"
;
import
{
BizError
}
from
"../util/bizError"
;
import
{
ERRORENUM
}
from
"../config/errorEnum"
;
import
{
addData
}
from
"../data/addData"
;
import
{
updateManyData
}
from
"../data/updateData"
;
import
{
region
}
from
"tencentcloud-sdk-nodejs"
;
/**
* 注册或更新设备信息
* @param deviceId 设备唯一标识(必填)
* @param regionKey 区域编号(必填),对应 region 表的 id
* @param deviceType 设备类型(必填),如“灯光”“空调”
* @param deviceName 设备名称(可选),如“主照明灯”
* @param controlParams 设备支持的参数定义(可选),JSON对象
* @returns {Promise<{success: boolean}>}
* @throws 如果 regionKey 对应的区域不存在,则抛出错误
*/
export
async
function
registerOrUpdateDevice
(
deviceId
:
string
,
regionKey
:
number
,
deviceType
:
string
,
deviceName
?:
string
|
null
,
controlParams
?:
any
)
{
// 校验区域是否存在
let
region
=
await
selectOneDataByParam
(
TABLENAME
.
区域表
,
{
id
:
regionKey
});
if
(
!
region
.
data
)
{
throw
new
BizError
(
ERRORENUM
.
参数错误
,
`区域编号
${
regionKey
}
不存在`
);
}
let
exist
=
await
selectOneDataByParam
(
TABLENAME
.
设备表
,
{
device_id
:
deviceId
});
let
now
=
new
Date
().
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
);
if
(
Object
.
keys
(
exist
.
data
).
length
)
{
await
updateManyData
(
TABLENAME
.
设备表
,
{
device_id
:
deviceId
},
{
region_key
:
regionKey
,
device_type
:
deviceType
,
device_name
:
deviceName
,
control_params
:
controlParams
||
null
,
updated_at
:
now
,
});
}
else
{
await
addData
(
TABLENAME
.
设备表
,
{
device_id
:
deviceId
,
region_key
:
regionKey
,
device_type
:
deviceType
,
device_name
:
deviceName
,
control_params
:
controlParams
||
null
,
created_at
:
now
,
updated_at
:
now
,
});
}
return
{
success
:
true
};
}
/**
* 处理设备商推送的数据
* @param deviceId 设备唯一标识(必填)
* @param data 设备上报的JSON数据(必填),例如:{ "power": "on", "brightness": 80 }
* @param deviceTime 设备本地时间(可选),格式:YYYY-MM-DD HH:MM:SS
* @returns {Promise<{recordId: number}>} 返回新插入记录的ID
* @throws 如果设备未在 device 表中注册,则抛出错误(需先调用注册接口)
*/
export
async
function
handleDevicePush
(
deviceId
:
string
,
data
:
any
,
deviceTime
?:
string
)
{
if
(
!
deviceId
||
!
data
)
{
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"deviceId和data不能为空"
);
}
// 检查设备是否存在,不存在则抛出错误(要求先注册)
let
device
=
await
selectOneDataByParam
(
TABLENAME
.
设备表
,
{
device_id
:
deviceId
});
if
(
!
device
.
data
)
{
throw
new
BizError
(
ERRORENUM
.
未找到数据
,
`设备
${
deviceId
}
未注册,请先调用注册接口`
);
}
let
nowMs
=
getMySqlMs
();
let
receivedTime
=
new
Date
(
nowMs
).
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
);
await
addData
(
TABLENAME
.
设备数据表
,
{
device_id
:
deviceId
,
data
:
data
,
received_time
:
receivedTime
,
device_time
:
deviceTime
?
new
Date
(
deviceTime
).
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
)
:
null
,
created_at
:
receivedTime
,
});
return
{
success
:
true
};
}
/**
* 处理设备商推送的故障数据
* @param deviceId 设备唯一标识(必填)
* @param faultType 故障类型(必填),如“离线”“数据异常”“硬件故障”
* @param faultLevel 故障等级
* @param faultDescription 故障详细描述(可选)
* @param occurredTime 故障发生时间(可选),格式:YYYY-MM-DD HH:MM:SS,不传则使用服务器时间
* @returns {Promise<{faultId: number}>} 返回新插入故障记录的ID
* @throws 如果设备未注册,则抛出错误
*/
export
async
function
handleDeviceFaultPush
(
deviceId
:
string
,
faultType
:
string
,
faultLevel
:
string
,
faultDescription
?:
string
,
occurredTime
?:
string
)
{
if
(
!
deviceId
||
!
faultType
)
{
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"deviceId 和 faultType 不能为空"
);
}
// 检查设备是否存在
let
device
=
await
selectOneDataByParam
(
TABLENAME
.
设备表
,
{
device_id
:
deviceId
});
if
(
!
device
.
data
)
{
throw
new
BizError
(
ERRORENUM
.
未找到数据
,
`设备
${
deviceId
}
未注册,请先调用注册接口`
);
}
let
nowMs
=
getMySqlMs
();
let
nowStr
=
new
Date
(
nowMs
).
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
);
let
faultTime
=
occurredTime
?
new
Date
(
occurredTime
).
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
)
:
nowStr
;
let
insertResult
:
any
=
await
addData
(
TABLENAME
.
设备故障表
,
{
device_id
:
deviceId
,
fault_type
:
faultType
,
fault_description
:
faultDescription
||
null
,
occurred_time
:
faultTime
,
level
:
faultLevel
||
'1'
,
// 默认一级故障
status
:
0
,
// 0=未处理
resolved_time
:
null
,
created_at
:
nowStr
,
updated_at
:
nowStr
,
});
return
{
faultId
:
insertResult
.
insertId
,
success
:
true
};
}
/**
* 智能管控-运行分析
* @param regionGroup 区域组(可选),如“A馆”,如果提供则返回该类型下所有区域的分析数据
* @param regionType 区域类型(可选),如“1F”,如果提供则返回该区域的分析数据
* @returns 大屏所需的所有指标数据
*/
export
async
function
getRunAnalysis
(
regionGroup
:
string
,
regionType
:
string
)
{
// 根据区域类型和名称筛选设备ID列表(如果提供了区域信息)
let
regionParam
:
any
=
{
groups
:
regionGroup
};
if
(
regionType
)
{
regionParam
.
type
=
regionType
;
}
let
regionInfo
:
any
=
await
selectDataListByParam
(
TABLENAME
.
区域表
,
regionParam
,
[
"id"
]);
let
regionKeys
=
regionInfo
.
data
.
map
((
r
:
any
)
=>
r
.
id
);
console
.
log
(
"筛选区域Key:"
,
regionKeys
);
// 1.1 获取所选区域三相电表及电能监测设备的用电数据(用于能耗管理)
let
powerParams
:
any
=
{
device_type
:
{
"%in%"
:
[
"三相电表"
,
"电能监测"
]
}};
if
(
regionKeys
.
length
>
0
)
{
powerParams
.
region_key
=
{
"%in%"
:
regionKeys
};
}
let
energyDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
powerParams
,
[
"device_id"
]);
let
energyDeviceIds
=
energyDevices
.
data
.
map
((
d
:
any
)
=>
d
.
device_id
);
// 1.2 获取今日、昨日、本月、上月用电量
let
todayStart
=
new
Date
();
todayStart
.
setHours
(
0
,
0
,
0
,
0
);
let
todayEnd
=
new
Date
();
let
yesterdayStart
=
new
Date
(
Date
.
now
()
-
86400000
);
yesterdayStart
.
setHours
(
0
,
0
,
0
,
0
);
let
yesterdayEnd
=
new
Date
(
Date
.
now
()
-
86400000
);
yesterdayEnd
.
setHours
(
23
,
59
,
59
,
999
);
let
currentMonthStart
=
new
Date
();
currentMonthStart
.
setDate
(
1
);
currentMonthStart
.
setHours
(
0
,
0
,
0
,
0
);
let
lastMonthStart
=
new
Date
();
lastMonthStart
.
setMonth
(
lastMonthStart
.
getMonth
()
-
1
);
lastMonthStart
.
setDate
(
1
);
lastMonthStart
.
setHours
(
0
,
0
,
0
,
0
);
let
lastMonthEnd
=
new
Date
(
currentMonthStart
.
getTime
()
-
1
);
// 辅助函数:查询某个时间段内的总用电量(单位 kwh)
async
function
getTotalEnergy
(
deviceIds
:
string
[],
startTime
:
Date
,
endTime
:
Date
):
Promise
<
number
>
{
if
(
deviceIds
.
length
===
0
)
return
0
;
let
records
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
{
"%in%"
:
deviceIds
},
received_time
:
{
"%gte%"
:
startTime
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
),
"%lte%"
:
endTime
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
)
},
"%orderAsc%"
:
"received_time"
},
[
"device_id"
,
"data"
,
"received_time"
]);
let
total
=
0
;
let
deviceEnergyMap
=
new
Map
();
for
(
let
rec
of
records
.
data
)
{
let
data
=
rec
.
data
;
let
energy
=
data
.
energy
??
data
.
total_energy
;
if
(
energy
===
undefined
)
continue
;
if
(
!
deviceEnergyMap
.
has
(
rec
.
device_id
))
{
deviceEnergyMap
.
set
(
rec
.
device_id
,
{
min
:
energy
,
max
:
energy
});
}
else
{
let
cur
=
deviceEnergyMap
.
get
(
rec
.
device_id
);
if
(
energy
<
cur
.
min
)
cur
.
min
=
energy
;
if
(
energy
>
cur
.
max
)
cur
.
max
=
energy
;
}
}
for
(
let
{
min
,
max
}
of
deviceEnergyMap
.
values
())
{
total
+=
(
max
-
min
);
}
return
total
;
}
let
todayEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
todayStart
,
todayEnd
);
let
yesterdayEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
yesterdayStart
,
yesterdayEnd
);
let
currentMonthEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
currentMonthStart
,
new
Date
());
let
previousMonthEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
lastMonthStart
,
lastMonthEnd
);
let
dailyYearOnYear
=
yesterdayEnergy
===
0
?
'0%'
:
`
${(((
todayEnergy
-
yesterdayEnergy
)
/
yesterdayEnergy
)
*
100
).
toFixed
(
2
)}
%`
;
let
monthlyYearOnYear
=
previousMonthEnergy
===
0
?
'0%'
:
`
${(((
currentMonthEnergy
-
previousMonthEnergy
)
/
previousMonthEnergy
)
*
100
).
toFixed
(
2
)}
%`
;
// 1.3 用电趋势:过去24小时(按小时)、过去7天、过去30天、过去一年(按月)
let
nowTime
=
new
Date
();
let
last24hStart
=
new
Date
(
nowTime
.
getTime
()
-
24
*
3600000
);
let
hourlyEnergy
=
await
getEnergyTrend
(
energyDeviceIds
,
last24hStart
,
nowTime
,
'hour'
);
let
last7DaysStart
=
new
Date
(
nowTime
.
getTime
()
-
7
*
86400000
);
let
dailyEnergy7
=
await
getEnergyTrend
(
energyDeviceIds
,
last7DaysStart
,
nowTime
,
'day'
);
let
last30DaysStart
=
new
Date
(
nowTime
.
getTime
()
-
30
*
86400000
);
let
dailyEnergy30
=
await
getEnergyTrend
(
energyDeviceIds
,
last30DaysStart
,
nowTime
,
'day'
);
let
lastYearStart
=
new
Date
(
nowTime
.
getFullYear
()
-
1
,
nowTime
.
getMonth
(),
1
);
let
monthlyEnergyYear
=
await
getEnergyTrend
(
energyDeviceIds
,
lastYearStart
,
nowTime
,
'month'
);
// 2.1 获取所选区域空调设备运行状态数据(如空调数、在线率、故障率等)
let
acParams
:
any
=
{
device_type
:
"空调"
};
if
(
regionKeys
.
length
>
0
)
{
acParams
.
region_key
=
{
"%in%"
:
regionKeys
};
}
let
acDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
acParams
,
[
"device_id"
]);
let
acDeviceIds
=
acDevices
.
data
.
map
((
d
:
any
)
=>
d
.
device_id
);
let
acCount
=
acDeviceIds
.
length
;
let
acOfflineCount
=
0
;
let
acOnlineCount
=
acCount
-
acOfflineCount
;
let
acFaultRecords
=
await
selectDataListByParam
(
TABLENAME
.
设备故障表
,
{
status
:
{
"%ne%"
:
2
},
device_id
:
{
"%in%"
:
acDeviceIds
}
},
[
"device_id"
]
);
let
acFaultDeviceIds
=
new
Set
(
acFaultRecords
.
data
.
map
((
r
:
any
)
=>
r
.
device_id
));
let
acFaultDevices
=
acFaultDeviceIds
.
size
;
let
acNormalRate
=
acCount
===
0
?
'100%'
:
(((
acCount
-
acFaultDevices
)
/
acCount
)
*
100
).
toFixed
(
2
)
+
'%'
;
let
acFaultCount
=
acFaultRecords
.
data
.
length
;
//所有未解决的故障单数量(含同一设备多个故障)
// 2.2 获取今日空调在线趋势分析数据
let
acOnlineTrend
=
[];
if
(
acDeviceIds
.
length
)
{
// 2.2.1 获取过去24小时设备数据
let
last24h
=
new
Date
(
nowTime
.
getTime
()
-
24
*
3600000
);
let
acHistory
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
{
"%in%"
:
acDeviceIds
},
received_time
:
{
"%gte%"
:
last24h
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
)
}
},
[
"device_id"
,
"data"
,
"received_time"
]);
// 2.2.2 按小时统计在线设备数和总设备数
let
hourlyData
=
new
Map
();
for
(
let
rec
of
acHistory
.
data
)
{
let
d
=
new
Date
(
rec
.
received_time
);
let
hourKey
=
`
${
d
.
getHours
()}
时`
;
let
data
=
rec
.
data
;
console
.
log
(
data
);
if
(
!
hourlyData
.
has
(
hourKey
))
{
let
totalNum
=
1
;
let
onlineNum
=
data
.
power
===
"on"
?
1
:
0
;
hourlyData
.
set
(
hourKey
,
{
online
:
onlineNum
,
total
:
totalNum
});
}
else
{
let
totalNum
=
hourlyData
.
get
(
hourKey
).
total
;
let
onlineNum
=
hourlyData
.
get
(
hourKey
).
online
;
if
(
data
.
power
===
"on"
)
onlineNum
+=
1
;
hourlyData
.
set
(
hourKey
,
{
online
:
onlineNum
,
total
:
totalNum
+
1
});
}
}
console
.
log
(
"空调在线数据(过去24小时):"
,
hourlyData
);
// 2.2.3 填充24小时空调在线数据,没有在线数据的小时段则设为零,并计算每小时的空调在线率
for
(
let
i
=
0
;
i
<
24
;
i
++
)
{
let
key
=
`
${
i
}
时`
;
let
val
=
hourlyData
.
get
(
key
);
acOnlineTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
?
((
val
.
online
/
val
.
total
)
*
100
).
toFixed
(
2
)
:
'0'
});
}
// 2.3. 设备监测情况:在线/离线(根据最后数据时间是否超过30分钟)
let
deviceLatestMap
=
new
Map
();
for
(
let
devId
of
acDeviceIds
)
{
let
rec
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
devId
,
"%orderDesc%"
:
"received_time"
,
"%limit%"
:
1
},
[
"received_time"
]);
if
(
rec
.
data
.
length
)
{
let
lastTime
=
new
Date
(
rec
.
data
[
0
].
received_time
);
let
diffMinutes
=
(
nowTime
.
getTime
()
-
lastTime
.
getTime
())
/
60000
;
deviceLatestMap
.
set
(
devId
,
{
lastTime
,
isOnline
:
diffMinutes
<=
30
});
}
else
{
deviceLatestMap
.
set
(
devId
,
{
lastTime
:
null
,
isOnline
:
false
});
}
}
acOnlineCount
=
Array
.
from
(
deviceLatestMap
.
values
()).
filter
(
v
=>
v
.
isOnline
).
length
;
acOfflineCount
=
acCount
-
acOnlineCount
;
}
// 3.1 新风运行监控
let
fanParams
:
any
=
{
device_type
:
"新风"
};
if
(
regionKeys
.
length
>
0
)
{
fanParams
.
region_key
=
{
"%in%"
:
regionKeys
};
}
let
fanDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
fanParams
,
[
"device_id"
]);
let
fanDeviceIds
=
fanDevices
.
data
.
map
((
d
:
any
)
=>
d
.
device_id
);
let
fanCount
=
fanDeviceIds
.
length
;
let
fanOfflineCount
=
0
;
let
fanOnlineCount
=
fanCount
-
fanOfflineCount
;
let
fanFaultRecords
=
await
selectDataListByParam
(
TABLENAME
.
设备故障表
,
{
status
:
{
"%ne%"
:
2
},
device_id
:
{
"%in%"
:
fanDeviceIds
}
},
[
"device_id"
]
);
let
fanFaultDeviceIds
=
new
Set
(
fanFaultRecords
.
data
.
map
((
r
:
any
)
=>
r
.
device_id
));
let
fanFaultDevices
=
fanFaultDeviceIds
.
size
;
let
fanNormalRate
=
fanCount
===
0
?
'100%'
:
(((
fanCount
-
fanFaultDevices
)
/
fanCount
)
*
100
).
toFixed
(
2
)
+
'%'
;
let
fanFaultCount
=
fanFaultRecords
.
data
.
length
;
//所有未解决的故障单数量(含同一设备多个故障)
// 3.2 新风风量运行监测趋势分析数据(如果有风量数据)
let
fanOnlineTrend
=
[];
if
(
fanDeviceIds
.
length
)
{
let
fanDeviceDatas
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
{
"%in%"
:
fanDeviceIds
},
received_time
:
{
"%gte%"
:
last24hStart
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
),
"%lte%"
:
nowTime
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
)
},
"%orderAsc%"
:
"received_time"
},
[
"device_id"
,
"data"
,
"received_time"
]);
// 3.2.1 按小时统计新风送风量(换气次数=送风量/房间体积)
let
hourlyData
=
new
Map
();
// {key: 00:00, ly: 0, ry: 0 }
for
(
let
rec
of
fanDeviceDatas
.
data
)
{
let
d
=
new
Date
(
rec
.
received_time
);
let
hourKey
=
`
${
d
.
getHours
()}
时`
;
let
data
=
rec
.
data
;
if
(
!
hourlyData
.
has
(
hourKey
))
{
hourlyData
.
set
(
hourKey
,
{
airSupply
:
data
.
airSupply
});
}
}
// 3.2.2 填充24小时新风监控数据,没有在线数据的小时段则设为零,并计算每小时的新风送风量
for
(
let
i
=
0
;
i
<
24
;
i
++
)
{
let
key
=
`
${
i
}
时`
;
let
val
=
hourlyData
.
get
(
key
);
fanOnlineTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
?
val
.
airSupply
:
'0'
});
}
}
// 4.1 环境监测九合一
let
airParams
:
any
=
{
device_type
:
"环境监测"
};
if
(
regionKeys
.
length
>
0
)
{
airParams
.
region_key
=
{
"%in%"
:
regionKeys
};
}
let
airQualityDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
airParams
,
[
"device_id"
]);
let
airDeviceIds
=
airQualityDevices
.
data
.
map
(
d
=>
d
.
device_id
);
let
qualityIndex
=
300
;
let
co2Trend
=
[];
let
currentCo2
=
'21ppm'
;
let
currentTemperature
=
'21℃'
;
let
temperatureTrend
=
[];
let
currentHumidity
=
'47%'
;
let
humidityTrend
=
[];
let
currentPm25
=
'21μg/m³'
;
let
pm25Trend
=
[];
let
co2TrendDetail
=
[];
if
(
airDeviceIds
.
length
)
{
// 最新数据
let
latestAir
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
{
"%in%"
:
airDeviceIds
},
"%orderDesc%"
:
"received_time"
,
"%limit%"
:
1
},
[
"data"
]);
if
(
latestAir
.
data
.
length
)
{
let
airData
=
latestAir
.
data
[
0
].
data
;
currentTemperature
=
`
${
airData
.
temperature
??
21
}
℃
`;
currentHumidity = `
$
{
airData
.
humidity
??
47
}
%
`;
currentPm25 = `
$
{
airData
.
pm25
??
21
}
μ
g
/
m
³
`;
currentCo2 = `
$
{
airData
.
co2
??
400
}
ppm
`;
qualityIndex = 500 - (airData.pm25 ?? 0);
}
// 过去24小时趋势
let last24h = new Date(nowTime.getTime() - 24*3600000);
let airHistory = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": airDeviceIds },
received_time: { "%gte%": last24h.toISOString().slice(0,19).replace('T',' ') }
}, ["data", "received_time"]);
let hourlyData = new Map();
for (let rec of airHistory.data) {
let d = new Date(rec.received_time);
let hourKey = `
$
{
d
.
getHours
()}
时
`;
let data = rec.data;
if (!hourlyData.has(hourKey)) {
hourlyData.set(hourKey, { temp: data.temperature, hum: data.humidity, pm: data.pm25, co2: data.co2 });
}
}
for (let i = 0; i < 24; i++) {
let key = `
$
{
i
}
时
`;
let val = hourlyData.get(key);
temperatureTrend.push({ time: `
$
{
i
}:
00
`, value: val?.temp?.toString() ?? '21' });
humidityTrend.push({ time: `
$
{
i
}:
00
`, value: val?.hum?.toString() ?? '47' });
pm25Trend.push({ time: `
$
{
i
}:
00
`, value: val?.pm?.toString() ?? '21' });
let co2Val = val?.co2 ?? 400;
co2TrendDetail.push({ time: `
$
{
i
}:
00
`, value: (co2Val / 100).toFixed(1) });
co2Trend.push({ time: `
$
{
i
}
时
`, value: co2Val.toString() });
}
}
// 5.1 预警监控和预警工单处理进度分析
// 6.1 人流监控及客流量趋势分析
let regionInfos: any = [];
if (regionKeys.length === 0) {
// 6.1.1 若没有选择区域,则查询区域表获取所有区域类型
regionInfos = await selectDataListByParam(TABLENAME.区域表, {}, ["id", "name", "type"]);
} else {
// 6.1.2 若选择了区域,则查询选择的区域
regionInfos = await selectDataListByParam(TABLENAME.区域表, { id: { "%in%": regionKeys } }, ["id", "name", "type"]);
}
// 6.2 循环区域查询客流传感器设备,获取过去24小时每个区域的客流量
let totalRegionCustomerNum = 0; // 所有区域的总客流量
let regionMaxCustomerMap = new Map(); // {regionName: customerMaxNum}
let regionCustomerMap = new Map(); // {regionName: customerNum}
let regionCustomerTable = []; // [{regionName: A馆, customerNum: 5000, saturation: '80%'}]
for (let regionInfo of regionInfos.data) {
let customerDevices = await selectDataListByParam(TABLENAME.设备表, {
device_type: "客流传感器", region_key: regionInfo.id
}, ["device_type", "device_id"]);
let customerDeviceIds = customerDevices.data.map((d: any) => d.device_id);
// 获取今日的客流量数据
if (customerDeviceIds) {
let customerDeviceDatas = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": customerDeviceIds },
received_time: { "%gte%": todayStart.toISOString().slice(0,19).replace('T',' '), "%lte%": todayEnd.toISOString().slice(0,19).replace('T',' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
// 统计区域类型的客流量
for (let rec of customerDeviceDatas.data) {
let data = rec.data;
if (data.presence === '进') {
totalRegionCustomerNum += 1;
// 区分楼栋与楼层
let regionName = regionKeys.length === 0 ? regionInfo.type : regionInfo.name;
if (!regionCustomerMap.has(regionName)) {
regionCustomerMap.set(regionName, 1);
// 如果是按区域类型统计,则最大承载量取该类型下所有区域的总和;如果是按区域名称统计,则取该区域的最大承载量
let maxCapacity = regionKeys.length === 0 ? regionMaxCustomerMap.get(regionName) : regionInfo.max_capacity;
regionMaxCustomerMap.set(regionName, maxCapacity); // 区域最大承载量
} else {
let totalNum = regionCustomerMap.get(regionName);
regionCustomerMap.set(regionName, totalNum + 1);
}
}
}
}
}
// 计算每个区域的饱和度(客流量/区域最大承载量),并转换成表格
for (let [regionName, customerNum] of regionCustomerMap.entries()) {
let regionMaxCustomer = regionMaxCustomerMap.get(regionName) || 10000; // 默认最大承载量为10000
let saturation = `
$
{((
customerNum
/
regionMaxCustomer
)
*
100
).
toFixed
(
2
)}
%
`;
regionCustomerTable.push({ regionName, customerNum, saturation });
}
// 6.3 客流量趋势分析:根据查询到的客流传感器数据,统计所选区域过去24小时客流量变化趋势
let customerTrend = [];
let customerParams: any = { device_type: "客流传感器" };
if (regionKeys.length > 0) {
customerParams.region_key = { "%in%": regionKeys };
}
let customerDevices = await selectDataListByParam(TABLENAME.设备表, customerParams, ["device_type", "device_id"]);
if (customerDevices.data.length > 0) {
let customerDeviceIds = customerDevices.data.map((d: any) => d.device_id);
// 获取过去24小时客流量数据
let customerDeviceDatas = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": customerDeviceIds },
received_time: { "%gte%": last24hStart.toISOString().slice(0,19).replace('T',' '), "%lte%": nowTime.toISOString().slice(0,19).replace('T',' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
let hourlyCustomerMap = new Map();
for (let rec of customerDeviceDatas.data) {
let d = new Date(rec.received_time);
let hourKey = `
$
{
d
.
getHours
()}
时
`;
let data = rec.data;
let customerNum = data.presence === '进' ? 1 : 0;
if (!hourlyCustomerMap.has(hourKey)) {
hourlyCustomerMap.set(hourKey, customerNum);
} else {
let totalNum = hourlyCustomerMap.get(hourKey);
hourlyCustomerMap.set(hourKey, totalNum + customerNum);
}
}
// 填充过去24小时的客流量趋势数据
for (let i = 0; i < 24; i++) {
let key = `
$
{
i
}
时
`;
let val = hourlyCustomerMap.get(key) ?? 0;
customerTrend.push({ time: `
$
{
i
}:
00
`, value: val });
}
}
// 7.1 获取区域列表接口,供前端下拉选择区域类型和区域名称
let regionList: any = await getRegionList(regionGroup);
// 最终组装返回数据
return {
// 区域列表数据
regionList: regionList.data,
// 运行监控总数据(一年)
mainRunningManagement: {
totalCustomerNumber: 10000,
totalDeviceOnlineRate: '99.8%',
totalElectricity: `
360
kwh
`,
},
// 能耗监控数据
energyManagement: {
todayElectricity: `
$
{
todayEnergy
.
toFixed
(
0
)}
kwh
`,
yesterdayElectricity: `
$
{
yesterdayEnergy
.
toFixed
(
0
)}
kwh
`,
dailyYearOnYear,
currentMonthElectricity: `
$
{
currentMonthEnergy
.
toFixed
(
0
)}
kwh
`,
previousMonthElectricity: `
$
{
previousMonthEnergy
.
toFixed
(
0
)}
kwh
`,
monthlyYearOnYear
},
// 能耗监控趋势图数据
electricityTrend: {
last24Hours: hourlyEnergy,
last7Days: dailyEnergy7,
last30Days: dailyEnergy30,
lastYear: monthlyEnergyYear
},
// 空调设备在线趋势图数据
acDeviceOnlineTrend: {
acCount: acDeviceIds.length,
acOnlineCount: acOnlineCount,
acOfflineCount: acOfflineCount,
acFaultCount: acFaultCount,
acOnlineTrend: acOnlineTrend
},
// 新风设备在线趋势图数据
fanDeviceOnlineTrend: {
fanCount: fanDeviceIds.length,
fanOnlineCount: fanOnlineCount,
fanOfflineCount: fanOfflineCount,
fanFaultCount: fanFaultCount,
fanOnlineTrend: fanOnlineTrend
},
// 环境监测趋势图数据
environmentalTrend: {
currentTemperature: currentTemperature,
currentHumidity: currentHumidity,
currentPm25: currentPm25,
currentCo2: currentCo2,
temperatureTrend: temperatureTrend,
humidityTrend: humidityTrend,
pm25Trend: pm25Trend,
co2Trend: co2Trend,
qualityIndex: qualityIndex
},
// 人流监控实时数据和趋势图数据
customerOnlineTrend: {
passengerFlowTable: regionCustomerTable,
customerTrend: customerTrend
}
}
}
/**
* 智能管控-空调设备运行弹框
* @param regionKey
* @param deviceId
*/
export async function getAcRunningPop(regionKey: number, deviceId: string) {
// 获取设备信息
let devices = await selectDataListByParam(
TABLENAME.设备表,
{ region_key: regionKey, device_id: deviceId },
["device_id", "device_type", "device_name", "control_params"]
);
if (!devices.data.length) {
throw new BizError(ERRORENUM.未找到数据, `
设备
$
{
deviceId
}
在区域
$
{
regionKey
}
中未找到
`);
}
// 获取设备最新数据
let deviceData = await selectDataListByParam(
TABLENAME.设备数据表,
{ device_id: deviceId, "%orderDesc%": "received_time", "%limit%": 1 },
["data", "received_time"]
);
return {
deviceInfo: devices.data[0].control_params,
latestData: deviceData.data.length ? deviceData.data[0].data : null
}
}
/**
* 设备运行弹框 - 空调启停
* @param deviceId
*/
export async function controlAcRunning(deviceId?: string) {
}
// 辅助函数:获取用电趋势(按间隔分组)
async function getEnergyTrend(deviceIds: string[], startTime: Date, endTime: Date, granularity: 'hour' | 'day' | 'month') {
if (deviceIds.length === 0) return [];
let records = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": deviceIds },
received_time: { "%gte%": startTime.toISOString().slice(0,19).replace('T',' '), "%lte%": endTime.toISOString().slice(0,19).replace('T',' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
let intervalMap = new Map(); // key: 时间字符串, value: { deviceMin, deviceMax }
for (let rec of records.data) {
let data = rec.data;
let energy = data.energy ?? data.total_energy;
if (energy === undefined) continue;
let intervalKey: string;
let d = new Date(rec.received_time);
if (granularity === 'hour') {
intervalKey = `
$
{
d
.
getFullYear
()}
-
$
{
d
.
getMonth
()
+
1
}
-
$
{
d
.
getDate
()}
$
{
d
.
getHours
()}
时
`;
} else if (granularity === 'day') {
intervalKey = `
$
{
d
.
getFullYear
()}
/${d.getMonth
()
+1}/
$
{
d
.
getDate
()}
`;
} else {
intervalKey = `
$
{
d
.
getFullYear
()}
-
$
{
d
.
getMonth
()
+
1
}
`;
}
if (!intervalMap.has(intervalKey)) {
intervalMap.set(intervalKey, { deviceMin: new Map(), deviceMax: new Map() });
}
let entry = intervalMap.get(intervalKey);
if (!entry.deviceMin.has(rec.device_id)) {
entry.deviceMin.set(rec.device_id, energy);
entry.deviceMax.set(rec.device_id, energy);
} else {
let curMin = entry.deviceMin.get(rec.device_id);
let curMax = entry.deviceMax.get(rec.device_id);
if (energy < curMin) entry.deviceMin.set(rec.device_id, energy);
if (energy > curMax) entry.deviceMax.set(rec.device_id, energy);
}
}
let result = [];
for (let [key, { deviceMin, deviceMax }] of intervalMap.entries()) {
let total = 0;
for (let [deviceId, minVal] of deviceMin.entries()) {
let maxVal = deviceMax.get(deviceId);
total += (maxVal - minVal);
}
result.push({ key, value: total.toFixed(0) });
}
result.sort((a,b) => a.key.localeCompare(b.key));
return result;
}
// 基于馆内环境温度、湿度变化等,联动空调启停
async function controlAcByEnvironment(deviceId: string, data: any) {
// 获取区域内环境监测设备数据
let envDevices = await selectDataListByParam(TABLENAME.设备表, {
device_id: deviceId,
device_type: "环境监测"
}, ["id", "device_name", "region_key"]);
// 若是环境设备,则解析环境设备数据,判断是否需要联动空调设备
if (envDevices.data.length) {
// 通过设备所属区域查询当前区域中的空调设备,获取空调设备标准参数中预设的温度、湿度阈值
let regionKey = envDevices.data[0].region_key;
let acDevices = await selectDataListByParam(TABLENAME.设备表, {
region_key: regionKey,
device_type: "空调"
}, ["id", "device_name", "control_params"]);
acDevices.data.forEach((ac: any) => {
let controlParams = ac.control_params;
let maxTemp = controlParams.maxTemp;
let minTemp = controlParams.minTemp;
if (data.temperature && (data.temperature > maxTemp || data.temperature < minTemp)){
// 调用空调控制接口启停空调设备
console.log(`
联动空调
$
{
ac
.
device_name
}
(
$
{
ac
.
id
}
)启停,当前温度:
$
{
data
.
temperature
}
,阈值范围:
$
{
minTemp
}
-
$
{
maxTemp
}
`);
// 单个 TODO: 接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
}
});
// 多个 TODO: 接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
return { success: true, message: "成功" };
}
}
/**
* 设备类型中文到英文Key的映射
*/
let deviceTypeKeyMap: { [key: string]: string } = {
// "灯光": "lighting",
// "窗帘": "curtain",
// "推窗器": "windowOpener",
"空调": "hvac",
"新风": "freshAir",
// "音乐": "music",
"人体传感": "occupancySensor",
"人体传感器": "occupancySensor",
"空气质量传感": "airSensor",
// "燃气表": "gasMeter",
// "水表": "waterMeter",
"三相电表": "electricityMeter",
"电表": "electricityMeter",
"电能监测": "electricityMeter"
};
/**
/**
* 获取所有区域列表(供前端下拉选择)
* 获取所有区域列表(供前端下拉选择)
...
...
src/biz/running.ts
0 → 100644
View file @
4f9fb2b3
import
{
TABLENAME
}
from
"../config/dbEnum"
;
import
{
ERRORENUM
}
from
"../config/errorEnum"
;
import
{
selectDataListByParam
}
from
"../data/findData"
;
import
{
BizError
}
from
"../util/bizError"
;
import
{
getRegionList
}
from
"./region"
;
/**
* 智能管控-运行分析
* @param regionGroup 区域组(可选),如“A馆”,如果提供则返回该类型下所有区域的分析数据
* @param regionType 区域类型(可选),如“1F”,如果提供则返回该区域的分析数据
* @returns 大屏所需的所有指标数据
*/
export
async
function
getRunAnalysis
(
regionGroup
:
string
,
regionType
:
string
)
{
// 最终响应结果数据
let
resultAny
:
any
=
{
};
// 根据区域类型和名称筛选设备ID列表(如果提供了区域信息)
let
regionParam
:
any
=
{
groups
:
regionGroup
};
if
(
regionType
)
{
regionParam
.
type
=
regionType
;
}
let
regionInfo
:
any
=
await
selectDataListByParam
(
TABLENAME
.
区域表
,
regionParam
,
[
"id"
]);
let
regionKeys
=
regionInfo
.
data
.
map
((
r
:
any
)
=>
r
.
id
);
console
.
log
(
"筛选区域Key:"
,
regionKeys
);
// 1.1 获取所选区域三相电表及电能监测设备的用电数据(用于能耗管理)
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
);
// 1.2 获取今日、昨日、本月、上月用电量
let
todayStart
=
new
Date
();
todayStart
.
setHours
(
0
,
0
,
0
,
0
);
let
todayEnd
=
new
Date
();
let
yesterdayStart
=
new
Date
(
Date
.
now
()
-
86400000
);
yesterdayStart
.
setHours
(
0
,
0
,
0
,
0
);
let
yesterdayEnd
=
new
Date
(
Date
.
now
()
-
86400000
);
yesterdayEnd
.
setHours
(
23
,
59
,
59
,
999
);
let
currentMonthStart
=
new
Date
();
currentMonthStart
.
setDate
(
1
);
currentMonthStart
.
setHours
(
0
,
0
,
0
,
0
);
let
lastMonthStart
=
new
Date
();
lastMonthStart
.
setMonth
(
lastMonthStart
.
getMonth
()
-
1
);
lastMonthStart
.
setDate
(
1
);
lastMonthStart
.
setHours
(
0
,
0
,
0
,
0
);
let
lastMonthEnd
=
new
Date
(
currentMonthStart
.
getTime
()
-
1
);
// 辅助函数:查询某个时间段内的总用电量(单位 kwh)
async
function
getTotalEnergy
(
deviceIds
:
string
[],
startTime
:
Date
,
endTime
:
Date
):
Promise
<
number
>
{
if
(
deviceIds
.
length
===
0
)
return
0
;
let
records
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
{
"%in%"
:
deviceIds
},
received_time
:
{
"%gte%"
:
startTime
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
),
"%lte%"
:
endTime
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
)
},
"%orderAsc%"
:
"received_time"
},
[
"device_id"
,
"data"
,
"received_time"
]);
let
total
=
0
;
let
deviceEnergyMap
=
new
Map
();
for
(
let
rec
of
records
.
data
)
{
let
data
=
rec
.
data
;
let
energy
=
data
.
energy
??
data
.
total_energy
;
if
(
energy
===
undefined
)
continue
;
if
(
!
deviceEnergyMap
.
has
(
rec
.
device_id
))
{
deviceEnergyMap
.
set
(
rec
.
device_id
,
{
min
:
energy
,
max
:
energy
});
}
else
{
let
cur
=
deviceEnergyMap
.
get
(
rec
.
device_id
);
if
(
energy
<
cur
.
min
)
cur
.
min
=
energy
;
if
(
energy
>
cur
.
max
)
cur
.
max
=
energy
;
}
}
for
(
let
{
min
,
max
}
of
deviceEnergyMap
.
values
())
{
total
+=
(
max
-
min
);
}
return
total
;
}
let
todayEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
todayStart
,
todayEnd
);
let
yesterdayEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
yesterdayStart
,
yesterdayEnd
);
let
currentMonthEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
currentMonthStart
,
new
Date
());
let
previousMonthEnergy
=
await
getTotalEnergy
(
energyDeviceIds
,
lastMonthStart
,
lastMonthEnd
);
let
dailyYearOnYear
=
yesterdayEnergy
===
0
?
'0%'
:
`
${(((
todayEnergy
-
yesterdayEnergy
)
/
yesterdayEnergy
)
*
100
).
toFixed
(
2
)}
%`
;
let
monthlyYearOnYear
=
previousMonthEnergy
===
0
?
'0%'
:
`
${(((
currentMonthEnergy
-
previousMonthEnergy
)
/
previousMonthEnergy
)
*
100
).
toFixed
(
2
)}
%`
;
// 1.3 用电趋势:过去24小时(按小时)、过去7天、过去30天、过去一年(按月)
let
nowTime
=
new
Date
();
let
last24hStart
=
new
Date
(
nowTime
.
getTime
()
-
24
*
3600000
);
let
hourlyEnergy
=
await
getEnergyTrend
(
energyDeviceIds
,
last24hStart
,
nowTime
,
'hour'
);
let
last7DaysStart
=
new
Date
(
nowTime
.
getTime
()
-
7
*
86400000
);
let
dailyEnergy7
=
await
getEnergyTrend
(
energyDeviceIds
,
last7DaysStart
,
nowTime
,
'day'
);
let
last30DaysStart
=
new
Date
(
nowTime
.
getTime
()
-
30
*
86400000
);
let
dailyEnergy30
=
await
getEnergyTrend
(
energyDeviceIds
,
last30DaysStart
,
nowTime
,
'day'
);
let
lastYearStart
=
new
Date
(
nowTime
.
getFullYear
()
-
1
,
nowTime
.
getMonth
(),
1
);
let
monthlyEnergyYear
=
await
getEnergyTrend
(
energyDeviceIds
,
lastYearStart
,
nowTime
,
'month'
);
// 1.4. 能耗监控数据和能耗监控趋势图数据
resultAny
.
energyManagement
=
{
todayElectricity
:
`
${
todayEnergy
.
toFixed
(
0
)}
kwh`
,
yesterdayElectricity
:
`
${
yesterdayEnergy
.
toFixed
(
0
)}
kwh`
,
dailyYearOnYear
,
currentMonthElectricity
:
`
${
currentMonthEnergy
.
toFixed
(
0
)}
kwh`
,
previousMonthElectricity
:
`
${
previousMonthEnergy
.
toFixed
(
0
)}
kwh`
,
monthlyYearOnYear
};
resultAny
.
electricityTrend
=
{
last24Hours
:
hourlyEnergy
,
last7Days
:
dailyEnergy7
,
last30Days
:
dailyEnergy30
,
lastYear
:
monthlyEnergyYear
};
// 4.1 环境监测九合一
let
airParams
:
any
=
{
device_type
:
"环境监测"
};
if
(
regionGroup
)
{
airParams
.
region_key
=
{
"%in%"
:
regionKeys
};
}
let
airQualityDevices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
airParams
,
[
"device_id"
]);
let
airDeviceIds
=
airQualityDevices
.
data
.
map
(
d
=>
d
.
device_id
);
let
qualityIndex
=
300
;
let
co2Trend
=
[];
let
currentCo2
=
'21ppm'
;
let
currentTemperature
=
'21℃'
;
let
temperatureTrend
=
[];
let
currentHumidity
=
'47%'
;
let
humidityTrend
=
[];
let
currentPm25
=
'21μg/m³'
;
let
pm25Trend
=
[];
let
co2TrendDetail
=
[];
if
(
airDeviceIds
.
length
)
{
// 最新数据
let
latestAir
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
{
"%in%"
:
airDeviceIds
},
"%orderDesc%"
:
"received_time"
,
"%limit%"
:
1
},
[
"data"
]);
if
(
latestAir
.
data
.
length
)
{
let
airData
=
latestAir
.
data
[
0
].
data
;
currentTemperature
=
`
${
airData
.
temperature
??
21
}
℃
`;
currentHumidity = `
$
{
airData
.
humidity
??
47
}
%
`;
currentPm25 = `
$
{
airData
.
pm25
??
21
}
μ
g
/
m
³
`;
currentCo2 = `
$
{
airData
.
co2
??
400
}
ppm
`;
qualityIndex = 500 - (airData.pm25 ?? 0);
}
// 过去24小时趋势
let last24h = new Date(nowTime.getTime() - 24*3600000);
let airHistory = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": airDeviceIds },
received_time: { "%gte%": last24h.toISOString().slice(0,19).replace('T',' ') }
}, ["data", "received_time"]);
let hourlyData = new Map();
for (let rec of airHistory.data) {
let d = new Date(rec.received_time);
let hourKey = `
$
{
d
.
getHours
()}
时
`;
let data = rec.data;
if (!hourlyData.has(hourKey)) {
hourlyData.set(hourKey, { temp: data.temperature, hum: data.humidity, pm: data.pm25, co2: data.co2 });
}
}
for (let i = 0; i < 24; i++) {
let key = `
$
{
i
}
时
`;
let val = hourlyData.get(key);
temperatureTrend.push({ time: `
$
{
i
}:
00
`, value: val?.temp?.toString() ?? '21' });
humidityTrend.push({ time: `
$
{
i
}:
00
`, value: val?.hum?.toString() ?? '47' });
pm25Trend.push({ time: `
$
{
i
}:
00
`, value: val?.pm?.toString() ?? '21' });
let co2Val = val?.co2 ?? 400;
co2TrendDetail.push({ time: `
$
{
i
}:
00
`, value: (co2Val / 100).toFixed(1) });
co2Trend.push({ time: `
$
{
i
}
时
`, value: co2Val.toString() });
}
}
// 4.2. 环境监测趋势图数据
resultAny.environmentalTrend = {
currentTemperature: currentTemperature,
currentHumidity: currentHumidity,
currentPm25: currentPm25,
currentCo2: currentCo2,
temperatureTrend: temperatureTrend,
humidityTrend: humidityTrend,
pm25Trend: pm25Trend,
co2Trend: co2Trend,
qualityIndex: qualityIndex
};
// 5.1 预警监控和预警工单处理进度分析
let alertStatus = {
totalAlerts: 48,
resolved: 20,
responded: 24,
unresolved: 4
};
let alertWorkOrders = [
{ riskContent: "空调压缩机异常", alertTime: "26/02/01", status: "已解决" },
{ riskContent: "温度传感器离线", alertTime: "26/02/01", status: "已响应" },
{ riskContent: "湿度超标", alertTime: "26/02/02", status: "未处理" }
];
let alertWorkLevel = {
"一级预警": 10,
"二级预警": 20,
"三级预警": 18
};
let alertWorkType = {
"设备故障": { count: 25, proportion: '52%' },
"环境异常": { count: 15, proportion: '30%' },
"安全事件": { count: 8, proportion: '18%' }
};
// 5.2. 预警监控数据
resultAny.alertManagement = {
alertWorkLevel: alertWorkLevel,
alertWorkType: alertWorkType,
alertWorkOrders: alertWorkOrders
};
// 6.1 人流监控及客流量趋势分析
let regionInfos: any = [];
if (regionKeys.length === 0) {
// 6.1.1 若没有选择区域,则查询区域表获取所有区域类型
regionInfos = await selectDataListByParam(TABLENAME.区域表, {}, ["id", "name", "type"]);
} else {
// 6.1.2 若选择了区域,则查询选择的区域
regionInfos = await selectDataListByParam(TABLENAME.区域表, { id: { "%in%": regionKeys } }, ["id", "name", "type"]);
}
// 6.2 循环区域查询客流传感器设备,获取过去24小时每个区域的客流量
let totalRegionCustomerNum = 0; // 所有区域的总客流量
let regionMaxCustomerMap = new Map(); // {regionName: customerMaxNum}
let regionCustomerMap = new Map(); // {regionName: customerNum}
let regionCustomerTable = []; // [{regionName: A馆, customerNum: 5000, saturation: '80%'}]
for (let regionInfo of regionInfos.data) {
let customerDevices = await selectDataListByParam(TABLENAME.设备表, {
device_type: "客流传感器", region_key: regionInfo.id
}, ["device_type", "device_id"]);
let customerDeviceIds = customerDevices.data.map((d: any) => d.device_id);
// 获取今日的客流量数据
if (customerDeviceIds) {
let customerDeviceDatas = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": customerDeviceIds },
received_time: { "%gte%": todayStart.toISOString().slice(0,19).replace('T',' '), "%lte%": todayEnd.toISOString().slice(0,19).replace('T',' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
// 统计区域类型的客流量
for (let rec of customerDeviceDatas.data) {
let data = rec.data;
if (data.presence === '进') {
totalRegionCustomerNum += 1;
// 区分楼栋与楼层
let regionName = regionKeys.length === 0 ? regionInfo.type : regionInfo.name;
if (!regionCustomerMap.has(regionName)) {
regionCustomerMap.set(regionName, 1);
// 如果是按区域类型统计,则最大承载量取该类型下所有区域的总和;如果是按区域名称统计,则取该区域的最大承载量
let maxCapacity = regionKeys.length === 0 ? regionMaxCustomerMap.get(regionName) : regionInfo.max_capacity;
regionMaxCustomerMap.set(regionName, maxCapacity); // 区域最大承载量
} else {
let totalNum = regionCustomerMap.get(regionName);
regionCustomerMap.set(regionName, totalNum + 1);
}
}
}
}
}
// 计算每个区域的饱和度(客流量/区域最大承载量),并转换成表格
for (let [regionName, customerNum] of regionCustomerMap.entries()) {
let regionMaxCustomer = regionMaxCustomerMap.get(regionName) || 10000; // 默认最大承载量为10000
let saturation = `
$
{((
customerNum
/
regionMaxCustomer
)
*
100
).
toFixed
(
2
)}
%
`;
regionCustomerTable.push({ regionName, customerNum, saturation });
}
// 6.3 客流量趋势分析:根据查询到的客流传感器数据,统计所选区域过去24小时客流量变化趋势
let customerTrend = [];
let customerParams: any = { device_type: "客流传感器" };
if (regionGroup) {
customerParams.region_key = { "%in%": regionKeys };
}
let customerDevices = await selectDataListByParam(TABLENAME.设备表, customerParams, ["device_type", "device_id"]);
if (customerDevices.data.length > 0) {
let customerDeviceIds = customerDevices.data.map((d: any) => d.device_id);
// 获取过去24小时客流量数据
let customerDeviceDatas = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": customerDeviceIds },
received_time: { "%gte%": last24hStart.toISOString().slice(0,19).replace('T',' '), "%lte%": nowTime.toISOString().slice(0,19).replace('T',' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
let hourlyCustomerMap = new Map();
for (let rec of customerDeviceDatas.data) {
let d = new Date(rec.received_time);
let hourKey = `
$
{
d
.
getHours
()}
时
`;
let data = rec.data;
let customerNum = data.presence === '进' ? 1 : 0;
if (!hourlyCustomerMap.has(hourKey)) {
hourlyCustomerMap.set(hourKey, customerNum);
} else {
let totalNum = hourlyCustomerMap.get(hourKey);
hourlyCustomerMap.set(hourKey, totalNum + customerNum);
}
}
// 填充过去24小时的客流量趋势数据
for (let i = 0; i < 24; i++) {
let key = `
$
{
i
}
时
`;
let val = hourlyCustomerMap.get(key) ?? 0;
customerTrend.push({ time: `
$
{
i
}:
00
`, value: val });
}
}
// 6.4. 人流监控实时数据和趋势图数据
resultAny.customerOnlineTrend = {
passengerFlowTable: regionCustomerTable,
customerTrend: customerTrend
};
// 三级页面不需要执行此逻辑
if (!regionType) {
// 2.1 获取所选区域空调设备运行状态数据(如空调数、在线率、故障率等)
let acParams: any = { device_type: "空调" };
if (regionGroup) {
acParams.region_key = { "%in%": regionKeys };
}
let acDevices = await selectDataListByParam(TABLENAME.设备表, acParams, ["device_id"]);
let acDeviceIds = acDevices.data.map((d: any) => d.device_id);
let acCount = acDeviceIds.length;
let acOfflineCount = 0;
let acOnlineCount = acCount - acOfflineCount;
let acFaultRecords = await selectDataListByParam(
TABLENAME.设备故障表,
{ status: { "%ne%": 2 }, device_id: { "%in%": acDeviceIds } },
["device_id"]
);
let acFaultDeviceIds = new Set(acFaultRecords.data.map((r: any) => r.device_id));
let acFaultDevices = acFaultDeviceIds.size;
let acNormalRate = acCount === 0 ? '100%' : (((acCount - acFaultDevices) / acCount) * 100).toFixed(2) + '%';
let acFaultCount = acFaultRecords.data.length; //所有未解决的故障单数量(含同一设备多个故障)
// 2.2 获取今日空调在线趋势分析数据
let acOnlineTrend = [];
if (acDeviceIds.length) {
// 2.2.1 获取过去24小时设备数据
let last24h = new Date(nowTime.getTime() - 24 * 3600000);
let acHistory = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": acDeviceIds },
received_time: { "%gte%": last24h.toISOString().slice(0, 19).replace('T', ' ') }
}, ["device_id", "data", "received_time"]);
// 2.2.2 按小时统计在线设备数和总设备数
let hourlyData = new Map();
for (let rec of acHistory.data) {
let d = new Date(rec.received_time);
let hourKey = `
$
{
d
.
getHours
()}
时
`;
let data = rec.data;
console.log(data);
if (!hourlyData.has(hourKey)) {
let totalNum = 1;
let onlineNum = data.power === "on" ? 1 : 0;
hourlyData.set(hourKey, { online: onlineNum, total: totalNum });
} else {
let totalNum = hourlyData.get(hourKey).total;
let onlineNum = hourlyData.get(hourKey).online;
if (data.power === "on") onlineNum += 1;
hourlyData.set(hourKey, { online: onlineNum, total: totalNum + 1 });
}
}
console.log("空调在线数据(过去24小时):", hourlyData);
// 2.2.3 填充24小时空调在线数据,没有在线数据的小时段则设为零,并计算每小时的空调在线率
for (let i = 0; i < 24; i++) {
let key = `
$
{
i
}
时
`;
let val = hourlyData.get(key);
acOnlineTrend.push({ time: `
$
{
i
}:
00
`, value: val ? ((val.online / val.total) * 100).toFixed(2) : '0' });
}
// 2.3 设备监测情况:在线/离线(根据最后数据时间是否超过30分钟)
let deviceLatestMap = new Map();
for (let devId of acDeviceIds) {
let rec = await selectDataListByParam(TABLENAME.设备数据表,
{ device_id: devId, "%orderDesc%": "received_time", "%limit%": 1 },
["received_time"]);
if (rec.data.length) {
let lastTime = new Date(rec.data[0].received_time);
let diffMinutes = (nowTime.getTime() - lastTime.getTime()) / 60000;
deviceLatestMap.set(devId, { lastTime, isOnline: diffMinutes <= 30 });
} else {
deviceLatestMap.set(devId, { lastTime: null, isOnline: false });
}
}
acOnlineCount = Array.from(deviceLatestMap.values()).filter(v => v.isOnline).length;
acOfflineCount = acCount - acOnlineCount;
}
// 2.4. 空调设备在线趋势图数据
resultAny.acDeviceOnlineTrend = {
acCount: acDeviceIds.length,
acOnlineCount: acOnlineCount,
acOfflineCount: acOfflineCount,
acFaultCount: acFaultCount,
acOnlineTrend: acOnlineTrend
};
// 3.1 新风运行监控
let fanParams: any = { device_type: "新风" };
if (regionGroup) {
fanParams.region_key = { "%in%": regionKeys };
}
let fanDevices = await selectDataListByParam(TABLENAME.设备表, fanParams, ["device_id"]);
let fanDeviceIds = fanDevices.data.map((d: any) => d.device_id);
let fanCount = fanDeviceIds.length;
let fanOfflineCount = 0;
let fanOnlineCount = fanCount - fanOfflineCount;
let fanFaultRecords = await selectDataListByParam(
TABLENAME.设备故障表,
{ status: { "%ne%": 2 }, device_id: { "%in%": fanDeviceIds } },
["device_id"]
);
let fanFaultDeviceIds = new Set(fanFaultRecords.data.map((r: any) => r.device_id));
let fanFaultDevices = fanFaultDeviceIds.size;
let fanNormalRate = fanCount === 0 ? '100%' : (((fanCount - fanFaultDevices) / fanCount) * 100).toFixed(2) + '%';
let fanFaultCount = fanFaultRecords.data.length; //所有未解决的故障单数量(含同一设备多个故障)
// 3.2 新风风量运行监测趋势分析数据(如果有风量数据)
let fanOnlineTrend = [];
if (fanDeviceIds.length) {
let fanDeviceDatas = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": fanDeviceIds },
received_time: { "%gte%": last24hStart.toISOString().slice(0, 19).replace('T', ' '), "%lte%": nowTime.toISOString().slice(0, 19).replace('T', ' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
// 3.2.1 按小时统计新风送风量(换气次数=送风量/房间体积)
let hourlyData = new Map(); // {key: 00:00, ly: 0, ry: 0 }
for (let rec of fanDeviceDatas.data) {
let d = new Date(rec.received_time);
let hourKey = `
$
{
d
.
getHours
()}
时
`;
let data = rec.data;
if (!hourlyData.has(hourKey)) {
hourlyData.set(hourKey, { airSupply: data.airSupply });
}
}
// 3.2.2 填充24小时新风监控数据,没有在线数据的小时段则设为零,并计算每小时的新风送风量
for (let i = 0; i < 24; i++) {
let key = `
$
{
i
}
时
`;
let val = hourlyData.get(key);
fanOnlineTrend.push({ time: `
$
{
i
}:
00
`, value: val ? val.airSupply : '0' });
}
}
// 3.3. 新风设备在线趋势图数据
resultAny.fanDeviceOnlineTrend = {
fanCount: fanDeviceIds.length,
fanOnlineCount: fanOnlineCount,
fanOfflineCount: fanOfflineCount,
fanFaultCount: fanFaultCount,
fanOnlineTrend: fanOnlineTrend
};
// 7.1 获取区域列表接口,供前端下拉选择区域类型和区域名称
let regionMap: any = await getRegionList(regionGroup);
// 7.2. 区域列表数据
resultAny.regionData = regionMap;
// 运行监控总数据(一天)
resultAny.mainRunningManagement = {
totalCustomerNumber: 10000,
totalDeviceOnlineRate: '99.8%',
totalElectricity: `
360
kwh
`,
};
} else {
// 设备监控总数据
let devices = await selectDataListByParam(TABLENAME.设备表, {
region_key: { "%in%": regionKeys }
}, ["device_id"]);
let deviceIds = devices.data.map((d: any) => d.device_id);
let deviceCount = deviceIds.length;
let deviceOfflineCount = 0;
let deviceOnlineCount = deviceCount - deviceOfflineCount;
let deviceFaultRecords = await selectDataListByParam(
TABLENAME.设备故障表,
{ status: { "%ne%": 2 }, device_id: { "%in%": deviceIds } },
["device_id"]
);
let deviceFaultDeviceIds = new Set(deviceFaultRecords.data.map((r: any) => r.device_id));
let deviceFaultDevices = deviceFaultDeviceIds.size;
let deviceNormalRate = deviceCount === 0 ? '100%' : (((deviceCount - deviceFaultDevices) / deviceCount) * 100).toFixed(2) + '%';
let deviceFaultCount = deviceFaultRecords.data.length; //所有未解决的故障单数量(含同一设备多个故障)
resultAny.mainRunningManagement = {
deviceCount: deviceIds.length,
deviceOnlineCount: deviceOnlineCount,
deviceOfflineCount: deviceOfflineCount,
deviceFaultCount: deviceFaultCount,
};
}
// 最终组装返回数据
// let resultAny: any = {
// // 区域列表数据
// regionData: regionMap,
// // 运行监控总数据(一天)
// mainRunningManagement: {
// totalCustomerNumber: 10000,
// totalDeviceOnlineRate: '99.8%',
// totalElectricity: `
360
kwh
`,
// },
// // 能耗监控数据
// energyManagement: {
// todayElectricity: `
$
{
todayEnergy
.
toFixed
(
0
)}
kwh
`,
// yesterdayElectricity: `
$
{
yesterdayEnergy
.
toFixed
(
0
)}
kwh
`,
// dailyYearOnYear,
// currentMonthElectricity: `
$
{
currentMonthEnergy
.
toFixed
(
0
)}
kwh
`,
// previousMonthElectricity: `
$
{
previousMonthEnergy
.
toFixed
(
0
)}
kwh
`,
// monthlyYearOnYear
// },
// // 能耗监控趋势图数据
// electricityTrend: {
// last24Hours: hourlyEnergy,
// last7Days: dailyEnergy7,
// last30Days: dailyEnergy30,
// lastYear: monthlyEnergyYear
// },
// // 空调设备在线趋势图数据
// acDeviceOnlineTrend: {
// acCount: acDeviceIds.length,
// acOnlineCount: acOnlineCount,
// acOfflineCount: acOfflineCount,
// acFaultCount: acFaultCount,
// acOnlineTrend: acOnlineTrend
// },
// // 新风设备在线趋势图数据
// fanDeviceOnlineTrend: {
// fanCount: fanDeviceIds.length,
// fanOnlineCount: fanOnlineCount,
// fanOfflineCount: fanOfflineCount,
// fanFaultCount: fanFaultCount,
// fanOnlineTrend: fanOnlineTrend
// },
// // 环境监测趋势图数据
// environmentalTrend: {
// currentTemperature: currentTemperature,
// currentHumidity: currentHumidity,
// currentPm25: currentPm25,
// currentCo2: currentCo2,
// temperatureTrend: temperatureTrend,
// humidityTrend: humidityTrend,
// pm25Trend: pm25Trend,
// co2Trend: co2Trend,
// qualityIndex: qualityIndex
// },
// // 人流监控实时数据和趋势图数据
// customerOnlineTrend: {
// passengerFlowTable: regionCustomerTable,
// customerTrend: customerTrend
// },
// // 预警监控数据
// alertManagement: {
// alertWorkLevel: alertWorkLevel,
// alertWorkType: alertWorkType,
// alertWorkOrders: alertWorkOrders
// }
// };
return resultAny;
}
/**
* 智能管控-空调设备运行弹框
* @param regionKey
* @param deviceId
*/
export async function getAcRunningPop(regionKey: number, deviceId: string) {
// 获取设备信息
let devices = await selectDataListByParam(
TABLENAME.设备表,
{ region_key: regionKey, device_id: deviceId },
["device_id", "device_type", "device_name", "control_params"]
);
if (!devices.data.length) {
throw new BizError(ERRORENUM.未找到数据, `
设备
$
{
deviceId
}
在区域
$
{
regionKey
}
中未找到
`);
}
// 获取设备最新数据
let deviceData = await selectDataListByParam(
TABLENAME.设备数据表,
{ device_id: deviceId, "%orderDesc%": "received_time", "%limit%": 1 },
["data", "received_time"]
);
return {
deviceInfo: devices.data[0].control_params,
latestData: deviceData.data.length ? deviceData.data[0].data : null
}
}
/**
* 设备运行弹框 - 空调启停
* @param deviceId
*/
export async function controlAcRunning(deviceId?: string) {
}
// 辅助函数:获取用电趋势(按间隔分组)
async function getEnergyTrend(deviceIds: string[], startTime: Date, endTime: Date, granularity: 'hour' | 'day' | 'month') {
if (deviceIds.length === 0) return [];
let records = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": deviceIds },
received_time: { "%gte%": startTime.toISOString().slice(0,19).replace('T',' '), "%lte%": endTime.toISOString().slice(0,19).replace('T',' ') },
"%orderAsc%": "received_time"
}, ["device_id", "data", "received_time"]);
let intervalMap = new Map(); // key: 时间字符串, value: { deviceMin, deviceMax }
for (let rec of records.data) {
let data = rec.data;
let energy = data.energy ?? data.total_energy;
if (energy === undefined) continue;
let intervalKey: string;
let d = new Date(rec.received_time);
if (granularity === 'hour') {
intervalKey = `
$
{
d
.
getFullYear
()}
-
$
{
d
.
getMonth
()
+
1
}
-
$
{
d
.
getDate
()}
$
{
d
.
getHours
()}
时
`;
} else if (granularity === 'day') {
intervalKey = `
$
{
d
.
getFullYear
()}
/${d.getMonth
()
+1}/
$
{
d
.
getDate
()}
`;
} else {
intervalKey = `
$
{
d
.
getFullYear
()}
-
$
{
d
.
getMonth
()
+
1
}
`;
}
if (!intervalMap.has(intervalKey)) {
intervalMap.set(intervalKey, { deviceMin: new Map(), deviceMax: new Map() });
}
let entry = intervalMap.get(intervalKey);
if (!entry.deviceMin.has(rec.device_id)) {
entry.deviceMin.set(rec.device_id, energy);
entry.deviceMax.set(rec.device_id, energy);
} else {
let curMin = entry.deviceMin.get(rec.device_id);
let curMax = entry.deviceMax.get(rec.device_id);
if (energy < curMin) entry.deviceMin.set(rec.device_id, energy);
if (energy > curMax) entry.deviceMax.set(rec.device_id, energy);
}
}
let result = [];
for (let [key, { deviceMin, deviceMax }] of intervalMap.entries()) {
let total = 0;
for (let [deviceId, minVal] of deviceMin.entries()) {
let maxVal = deviceMax.get(deviceId);
total += (maxVal - minVal);
}
result.push({ key, value: total.toFixed(0) });
}
result.sort((a,b) => a.key.localeCompare(b.key));
return result;
}
// 基于馆内环境温度、湿度变化等,联动空调启停
async function controlAcByEnvironment(deviceId: string, data: any) {
// 获取区域内环境监测设备数据
let envDevices = await selectDataListByParam(TABLENAME.设备表, {
device_id: deviceId,
device_type: "环境监测"
}, ["id", "device_name", "region_key"]);
// 若是环境设备,则解析环境设备数据,判断是否需要联动空调设备
if (envDevices.data.length) {
// 通过设备所属区域查询当前区域中的空调设备,获取空调设备标准参数中预设的温度、湿度阈值
let regionKey = envDevices.data[0].region_key;
let acDevices = await selectDataListByParam(TABLENAME.设备表, {
region_key: regionKey,
device_type: "空调"
}, ["id", "device_name", "control_params"]);
acDevices.data.forEach((ac: any) => {
let controlParams = ac.control_params;
let maxTemp = controlParams.maxTemp;
let minTemp = controlParams.minTemp;
if (data.temperature && (data.temperature > maxTemp || data.temperature < minTemp)){
// 调用空调控制接口启停空调设备
console.log(`
联动空调
$
{
ac
.
device_name
}
(
$
{
ac
.
id
}
)启停,当前温度:
$
{
data
.
temperature
}
,阈值范围:
$
{
minTemp
}
-
$
{
maxTemp
}
`);
// 单个 TODO: 接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
}
});
// 多个 TODO: 接入空调控制API,传入设备ID和控制指令(如开/关、温度设定等)
return { success: true, message: "成功" };
}
}
\ No newline at end of file
src/routers/device.ts
View file @
4f9fb2b3
...
@@ -5,39 +5,36 @@
...
@@ -5,39 +5,36 @@
import
asyncHandler
from
'express-async-handler'
;
import
asyncHandler
from
'express-async-handler'
;
import
*
as
deviceBiz
from
'../biz/device'
;
import
*
as
deviceBiz
from
'../biz/device'
;
import
*
as
regionBiz
from
'../biz/region'
;
import
*
as
regionBiz
from
'../biz/region'
;
import
*
as
runningBiz
from
'../biz/running'
;
import
{
eccReqParamater
}
from
'../util/verificationParam'
;
import
{
eccReqParamater
}
from
'../util/verificationParam'
;
import
{
checkUser
}
from
'../middleware/user'
;
import
{
checkUser
}
from
'../middleware/user'
;
export
function
setRouter
(
httpServer
)
{
export
function
setRouter
(
httpServer
)
{
/** 注册/更新设备信息 */
/** 注册/更新设备信息 */
httpServer
.
post
(
'/api/
zc
/device/register'
,
asyncHandler
(
registerDevice
));
httpServer
.
post
(
'/api/
qdm
/device/register'
,
asyncHandler
(
registerDevice
));
httpServer
.
post
(
'/api/zc/region/regionList'
,
asyncHandler
(
getRegionList
));
/** 设备商推送实时数据 */
/** 设备商推送实时数据 */
httpServer
.
post
(
'/api/
zc
/device/push'
,
asyncHandler
(
deviceDataPush
));
httpServer
.
post
(
'/api/
qdm
/device/push'
,
asyncHandler
(
deviceDataPush
));
/** 设备商推送故障数据 */
/** 设备商推送故障数据 */
httpServer
.
post
(
'/api/
zc
/device/fault/push'
,
asyncHandler
(
deviceFaultPush
));
httpServer
.
post
(
'/api/
qdm
/device/fault/push'
,
asyncHandler
(
deviceFaultPush
));
/**
运行分析
*/
/**
获取区域列表
*/
httpServer
.
post
(
'/api/
zc/run/analysis'
,
asyncHandler
(
getRunAnalysis
));
httpServer
.
post
(
'/api/
qdm/region/regionList'
,
asyncHandler
(
getRegionList
));
/** 运行分析 */
httpServer
.
post
(
'/api/qdm/run/analysis'
,
asyncHandler
(
getRunAnalysis
));
/** 运行分析 - 楼栋 */
/** 运行分析 - 楼栋 */
httpServer
.
post
(
'/api/
zc
/run/analysis/building'
,
asyncHandler
(
getRunAnalysisByBuilding
));
httpServer
.
post
(
'/api/
qdm
/run/analysis/building'
,
asyncHandler
(
getRunAnalysisByBuilding
));
/** 运行分析 - 楼层 */
/** 运行分析 - 楼层 */
httpServer
.
post
(
'/api/
zc
/run/analysis/Floor'
,
asyncHandler
(
getRunAnalysisByFloor
));
httpServer
.
post
(
'/api/
qdm
/run/analysis/Floor'
,
asyncHandler
(
getRunAnalysisByFloor
));
/** 空调运行弹框 */
/** 空调运行弹框 */
httpServer
.
post
(
'/api/
zc
/run/monitor/acPop'
,
asyncHandler
(
getAcRunningPop
));
httpServer
.
post
(
'/api/
qdm
/run/monitor/acPop'
,
asyncHandler
(
getAcRunningPop
));
/** 空调开关 */
/** 空调开关 */
httpServer
.
post
(
'/api/
zc
/run/monitor/acRun'
,
asyncHandler
(
controlAcRunning
));
httpServer
.
post
(
'/api/
qdm
/run/monitor/acRun'
,
asyncHandler
(
controlAcRunning
));
/**运行分析-弹窗 */
httpServer
.
post
(
'/api/zc/run/analysis/pop'
,
asyncHandler
(
getRunAnalysisPop
));
/** 智能监控弹窗 */
httpServer
.
post
(
'/api/zc/smart/monitor/pop'
,
asyncHandler
(
getSmartMonitorPop
));
}
}
/**
/**
* 注册或更新设备信息
* 注册或更新设备信息
*/
*/
...
@@ -50,20 +47,6 @@ export function setRouter(httpServer) {
...
@@ -50,20 +47,6 @@ export function setRouter(httpServer) {
}
}
/**
/**
* 获取区域列表接口,供前端下拉选择区域类型和区域名称
* @param req
* @param res
*/
async
function
getRegionList
(
req
,
res
)
{
let
reqConf
=
{
regionGroup
:
'String'
};
const
NotMustHaveKeys
=
[];
let
{
regionGroup
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
regionBiz
.
getRegionList
(
regionGroup
);
res
.
success
(
result
);
}
/**
* 设备商推送数据
* 设备商推送数据
*/
*/
async
function
deviceDataPush
(
req
,
res
)
{
async
function
deviceDataPush
(
req
,
res
)
{
...
@@ -75,7 +58,6 @@ async function deviceDataPush(req, res) {
...
@@ -75,7 +58,6 @@ async function deviceDataPush(req, res) {
res
.
success
(
result
);
res
.
success
(
result
);
}
}
/**
/**
* 设备商推送故障数据
* 设备商推送故障数据
*/
*/
...
@@ -84,17 +66,29 @@ async function deviceFaultPush(req, res) {
...
@@ -84,17 +66,29 @@ async function deviceFaultPush(req, res) {
const
NotMustHaveKeys
=
[
"faultDescription"
];
const
NotMustHaveKeys
=
[
"faultDescription"
];
let
{
deviceId
,
faultType
,
faultLevel
,
faultDescription
,
occurredTime
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
let
{
deviceId
,
faultType
,
faultLevel
,
faultDescription
,
occurredTime
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
region
Biz
.
handleDeviceFaultPush
(
deviceId
,
faultType
,
faultLevel
,
faultDescription
,
occurredTime
);
const
result
=
await
device
Biz
.
handleDeviceFaultPush
(
deviceId
,
faultType
,
faultLevel
,
faultDescription
,
occurredTime
);
res
.
success
(
result
);
res
.
success
(
result
);
}
}
/**
* 获取区域列表接口,供前端下拉选择区域类型和区域名称
* @param req
* @param res
*/
async
function
getRegionList
(
req
,
res
)
{
let
reqConf
=
{
regionGroup
:
'String'
};
const
NotMustHaveKeys
=
[];
let
{
regionGroup
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
regionBiz
.
getRegionList
(
regionGroup
);
res
.
success
(
result
);
}
/**
/**
* 运行分析
* 运行分析
*/
*/
async
function
getRunAnalysis
(
req
,
res
)
{
async
function
getRunAnalysis
(
req
,
res
)
{
const
{
}
=
req
.
body
;
const
{
}
=
req
.
body
;
const
result
=
await
r
egion
Biz
.
getRunAnalysis
(
""
,
""
);
const
result
=
await
r
unning
Biz
.
getRunAnalysis
(
""
,
""
);
res
.
success
(
result
);
res
.
success
(
result
);
}
}
...
@@ -105,7 +99,7 @@ async function deviceFaultPush(req, res) {
...
@@ -105,7 +99,7 @@ async function deviceFaultPush(req, res) {
let
reqConf
=
{
regionGroup
:
'String'
};
let
reqConf
=
{
regionGroup
:
'String'
};
const
NotMustHaveKeys
=
[];
const
NotMustHaveKeys
=
[];
let
{
regionGroup
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
let
{
regionGroup
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
r
egionBiz
.
getRunAnalysis
(
""
,
regionGroup
);
const
result
=
await
r
unningBiz
.
getRunAnalysis
(
regionGroup
,
""
);
res
.
success
(
result
);
res
.
success
(
result
);
}
}
...
@@ -116,7 +110,7 @@ async function deviceFaultPush(req, res) {
...
@@ -116,7 +110,7 @@ async function deviceFaultPush(req, res) {
let
reqConf
=
{
regionGroup
:
'String'
,
regionType
:
'String'
};
let
reqConf
=
{
regionGroup
:
'String'
,
regionType
:
'String'
};
const
NotMustHaveKeys
=
[];
const
NotMustHaveKeys
=
[];
let
{
regionGroup
,
regionType
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
let
{
regionGroup
,
regionType
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
r
egionBiz
.
getRunAnalysis
(
regionType
,
regionGroup
);
const
result
=
await
r
unningBiz
.
getRunAnalysis
(
regionGroup
,
regionType
);
res
.
success
(
result
);
res
.
success
(
result
);
}
}
...
@@ -127,7 +121,7 @@ async function deviceFaultPush(req, res) {
...
@@ -127,7 +121,7 @@ async function deviceFaultPush(req, res) {
let
reqConf
=
{
regionKey
:
'Number'
,
deviceId
:
'String'
};
let
reqConf
=
{
regionKey
:
'Number'
,
deviceId
:
'String'
};
const
NotMustHaveKeys
=
[];
const
NotMustHaveKeys
=
[];
let
{
regionKey
,
deviceId
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
let
{
regionKey
,
deviceId
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
r
egion
Biz
.
getAcRunningPop
(
regionKey
,
deviceId
);
const
result
=
await
r
unning
Biz
.
getAcRunningPop
(
regionKey
,
deviceId
);
res
.
success
(
result
);
res
.
success
(
result
);
}
}
...
@@ -138,33 +132,11 @@ async function controlAcRunning(req, res) {
...
@@ -138,33 +132,11 @@ async function controlAcRunning(req, res) {
let
reqConf
=
{
deviceId
:
'String'
};
let
reqConf
=
{
deviceId
:
'String'
};
const
NotMustHaveKeys
=
[];
const
NotMustHaveKeys
=
[];
let
{
deviceId
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
let
{
deviceId
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
regionBiz
.
controlAcRunning
(
deviceId
);
const
result
=
await
runningBiz
.
controlAcRunning
(
deviceId
);
res
.
success
(
result
);
}
/**
* 运行分析-弹窗
*/
async
function
getRunAnalysisPop
(
req
,
res
)
{
let
reqConf
=
{
regionKey
:
'Number'
};
const
NotMustHaveKeys
=
[];
let
{
regionKey
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
deviceBiz
.
getRunAnalysisPop
(
regionKey
);
res
.
success
(
result
);
res
.
success
(
result
);
}
}
/**
* 智能监控弹窗
*/
async
function
getSmartMonitorPop
(
req
,
res
)
{
let
reqConf
=
{
regionKey
:
'Number'
};
const
NotMustHaveKeys
=
[];
let
{
regionKey
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
const
result
=
await
deviceBiz
.
getSmartMonitorPop
(
regionKey
);
res
.
success
(
result
);
}
...
...
src/routers/feiyiClient.ts
View file @
4f9fb2b3
/**
/**
*
*
访问飞翼客户端API接口
*/
*/
import
asyncHandler
from
'express-async-handler'
;
import
asyncHandler
from
'express-async-handler'
;
import
*
as
feiyiClientBiz
from
'../biz/feiyiClient'
;
import
*
as
feiyiClientBiz
from
'../biz/feiyiClient'
;
import
{
eccReqParamater
}
from
'../util/verificationParam'
;
import
{
eccReqParamater
}
from
'../util/verificationParam'
;
export
function
setRouter
(
httpServer
)
{
export
function
setRouter
(
httpServer
)
{
/** 注册/更新设备信息 */
/** 注册/更新设备信息 */
httpServer
.
post
(
'/api/
zc
/feiyi/gateway/list'
,
asyncHandler
(
getFeiyiGatewayList
));
httpServer
.
post
(
'/api/
qdm
/feiyi/gateway/list'
,
asyncHandler
(
getFeiyiGatewayList
));
}
}
...
...
src/routers/users.ts
View file @
4f9fb2b3
...
@@ -7,10 +7,10 @@ import { eccReqParamater } from '../util/verificationParam';
...
@@ -7,10 +7,10 @@ import { eccReqParamater } from '../util/verificationParam';
export
function
setRouter
(
httpServer
)
{
export
function
setRouter
(
httpServer
)
{
/** 用户注册 */
/** 用户注册 */
httpServer
.
post
(
'/api/
zc
/user/register'
,
asyncHandler
(
registerUser
));
httpServer
.
post
(
'/api/
qdm
/user/register'
,
asyncHandler
(
registerUser
));
/** 用户登录 */
/** 用户登录 */
httpServer
.
post
(
'/api/
zc
/user/login'
,
asyncHandler
(
login
));
httpServer
.
post
(
'/api/
qdm
/user/login'
,
asyncHandler
(
login
));
/**
/**
* 注册信息
* 注册信息
...
...
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