运行分析展示数据问题修复、日程管理定时开关机任务创建和环境质量弹窗接口

parent e110cac7
......@@ -418,7 +418,8 @@ export async function getEnvironmentDevices() {
}
const regionResult = await selectDataListByParam(TABLENAME.区域表, {
id: { "%in%": regionKeys }
id: { "%in%": regionKeys },
"%orderAsc%": "sort_order",
}, ["id", "name", "type", "groups"]);
const regions = regionResult.data || [];
......@@ -569,6 +570,7 @@ export async function getEnvironmentData(regionGroup: string, regionType: string
if (deviceRegionKeys.length > 0) {
const regionResult = await selectDataListByParam(TABLENAME.区域表, {
id: { "%in%": deviceRegionKeys },
"%orderAsc%": "sort_order",
}, ["id", "name", "type", "groups"]);
(regionResult.data || []).forEach((r: any) => {
regionNameMap[r.id] = r.name || '';
......@@ -622,4 +624,131 @@ export async function getEnvironmentData(regionGroup: string, regionType: string
};
return { indicators, records, areaInfo };
}
/**
* CO2 线性超标描述(仅一般/差级别调用)
* 偏高:1000~1500 超标:1500~2500 严重超标:>2500
*/
function getCo2ExceedLabel(co2: number): string | null {
if (co2 > 2500) return '二氧化碳严重超标';
if (co2 > 1500) return '二氧化碳超标';
if (co2 >= 1000) return '二氧化碳偏高';
return null;
}
/**
* PM2.5 线性超标描述(仅一般/差级别调用)
* 偏高:50~75 超标:75~95 严重超标:>95
*/
function getPm25ExceedLabel(pm25: number): string | null {
if (pm25 > 95) return 'PM2.5严重超标';
if (pm25 > 75) return 'PM2.5超标';
if (pm25 > 50) return 'PM2.5偏高';
return null;
}
/**
* 运行分析 - 环境质量弹窗
* 按区域查询环境监测设备最新数据,返回超标区域(一般/差)。
* 质量等级与 todayEnvironmental 一致,优秀/良好不返回。
*/
export async function getEnvQualityPop(regionGroup?: string, regionType?: string) {
// 1. 查询区域
const regionWhere: any = {};
if (regionGroup) regionWhere.groups = regionGroup;
if (regionType) regionWhere.type = regionType;
const regionResult = await selectDataListByParam(TABLENAME.区域表, { ...regionWhere, "%orderAsc%": "sort_order" }, ["id", "name", "type", "groups"]);
const regions = regionResult.data || [];
if (regions.length === 0) return { total: 0, dataList: [] };
const regionMap: { [id: number]: any } = {};
regions.forEach((r: any) => { regionMap[r.id] = r; });
const regionKeys = regions.map((r: any) => r.id);
// 2. 查询环境监测设备
const deviceResult = await selectDataListByParam(TABLENAME.设备表, {
region_key: { "%in%": regionKeys },
device_type: "环境监测"
}, ["device_id", "region_key"]);
const devices = deviceResult.data || [];
// 3. 按 region_key 分组
const regionDevices: { [rk: number]: string[] } = {};
devices.forEach((d: any) => {
if (!regionDevices[d.region_key]) regionDevices[d.region_key] = [];
regionDevices[d.region_key].push(d.device_id);
});
const dataList: any[] = [];
const todayStart = getTodayStart();
// 4. 并行处理有设备的区域
await Promise.all(Object.keys(regionDevices).map(async (rkStr) => {
const rk = Number(rkStr);
const deviceIds = regionDevices[rk];
const region = regionMap[rk];
if (!region || deviceIds.length === 0) return;
// 仅查询今天的数据,按 device_time 降序,每个设备最多取一条
const dataResult = await selectDataListByParam(TABLENAME.设备数据表, {
device_id: { "%in%": deviceIds },
device_time: { "%gte%": todayStart },
"%orderDesc%": "device_time",
"%limit%": deviceIds.length
}, ["device_id", "data"]);
const records = dataResult.data || [];
if (records.length === 0) return;
// 每个设备取最新一条,算均值
const deviceLatest: { [devId: string]: any } = {};
records.forEach((r: any) => {
if (!deviceLatest[r.device_id]) deviceLatest[r.device_id] = r.data;
});
const dataArr = Object.values(deviceLatest);
const co2Avg = Math.round(dataArr.reduce((s, d: any) => s + (d.co2 ?? 0), 0) / dataArr.length);
const pm25Avg = Math.round(dataArr.reduce((s, d: any) => s + (d.pm2_5 ?? 0), 0) / dataArr.length);
// 质量等级判定(与 todayEnvironmental 一致)
let quality: string;
if (co2Avg < 800 && pm25Avg <= 35) {
quality = '优秀';
} else if (co2Avg < 1000 && pm25Avg <= 45) {
quality = '优秀';
} else if (co2Avg <= 1000 && pm25Avg <= 50) {
quality = '良好';
} else if (co2Avg > 1000 && co2Avg < 1500 && pm25Avg > 50 && pm25Avg < 75) {
quality = '一般';
} else {
quality = '差';
}
// 仅返回一般/差
if (quality === '优秀' || quality === '良好') return;
// 构建超标内容
const exceedItems: string[] = [];
const co2Label = getCo2ExceedLabel(co2Avg);
const pm25Label = getPm25ExceedLabel(pm25Avg);
if (co2Label) exceedItems.push(co2Label);
if (pm25Label) exceedItems.push(pm25Label);
const qualityContent = exceedItems.length > 0
? `环境质量${quality}${exceedItems.join('、')}`
: `环境质量${quality}`;
dataList.push({
indexs: 0,
regionName: [region.groups, region.type, region.name].filter(Boolean).join('_'),
co2: co2Avg,
pm25: pm25Avg,
qualityContent
});
}));
// 5. 排序并分配序号
dataList.sort((a, b) => a.regionName.localeCompare(b.regionName, 'zh'));
dataList.forEach((item, i) => { item.indexs = i + 1; });
return { total: dataList.length, dataList };
}
\ No newline at end of file
import { selectDataListByParam } from "../data/findData";
import { TABLENAME } from "../config/dbEnum";
import { updateManyData } from "../data/updateData";
/** 楼栋排序基础值(间隔100,便于中间插入) */
const BUILDING_BASE: Record<string, number> = {
'A馆': 100,
'B馆': 200,
'C馆': 300,
};
/** 楼层排序偏移值 */
const FLOOR_OFFSET: Record<string, number> = {
'一层': 1,
'二层': 2,
'三层': 3,
'四层': 4,
};
/**
* 根据楼栋和楼层计算 sort_order 值
* 排序规则:A馆 > B馆 > C馆 > 其它;一层 > 二层 > 三层 > 四层 > 其它
*/
export function computeSortOrder(groups: string, type: string): number {
const buildingBase = BUILDING_BASE[groups] ?? 900;
const floorOffset = FLOOR_OFFSET[type] ?? 99;
return buildingBase + floorOffset;
}
/**
* 刷新所有区域的 sort_order(系统启动时执行)
* 遍历所有区域,根据 groups + type 重新计算并更新 sort_order
*/
export async function refreshRegionSortOrder(): Promise<void> {
const regionList = await selectDataListByParam(
TABLENAME.区域表,
{},
["id", "groups", "type", "sort_order"],
);
const regions = regionList.data || [];
let updatedCount = 0;
for (const r of regions) {
const newVal = computeSortOrder(r.groups, r.type);
if (r.sort_order !== newVal) {
await updateManyData(TABLENAME.区域表, { id: r.id }, { sort_order: newVal });
updatedCount++;
}
}
console.log(`sort_order 刷新完成,共更新 ${updatedCount} 条区域记录`);
}
/**
* 获取楼栋区域列表(供前端下拉选择)
......
......@@ -24,7 +24,7 @@ function formatLocalTime(date: Date): string {
/** 并行查询三馆区域,按 groups 分组 */
async function queryHallRegions(): Promise<Map<string, number[]>> {
const results = await Promise.all(
HALL_NAMES.map(g => selectDataListByParam(TABLENAME.区域表, { groups: g }, ["id"]))
HALL_NAMES.map(g => selectDataListByParam(TABLENAME.区域表, { groups: g, "%orderAsc%": "sort_order" }, ["id"]))
);
const hallRegionMap = new Map<string, number[]>();
for (let i = 0; i < HALL_NAMES.length; i++) {
......@@ -112,7 +112,7 @@ async function queryHallDeviceGroup(regionIds: number[]): Promise<HallDeviceGrou
// 并行查询该馆设备和区域名称
const [allDevRes, regionRes] = await Promise.all([
selectDataListByParam(TABLENAME.设备表, { region_key: { "%in%": regionIds } }, ["device_id", "device_name", "device_type", "region_key"]),
selectDataListByParam(TABLENAME.区域表, { id: { "%in%": regionIds } }, ["id", "name"]),
selectDataListByParam(TABLENAME.区域表, { id: { "%in%": regionIds }, "%orderAsc%": "sort_order" }, ["id", "name"]),
]);
const nameMap = new Map<number, string>();
......
......@@ -43,7 +43,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let resultAny: any = { };
// 根据区域类型和名称筛选设备ID列表(如果提供了区域信息)
let regionParam: any = { groups: regionGroup };
let regionParam: any = { groups: regionGroup, "%orderAsc%": "sort_order" };
if (regionType) {
// regionParam.type = regionTypeKeyMap[regionType];
regionParam.type = regionType;
......@@ -381,7 +381,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
}
if (regionKeySet.size > 0) {
const regionsRes = await selectDataListByParam(TABLENAME.区域表,
{ id: { "%in%": [...regionKeySet] } },
{ id: { "%in%": [...regionKeySet] }, "%orderAsc%": "sort_order" },
["id", "name"]);
const regionNameMap = new Map<string, string>();
for (const r of regionsRes.data) {
......@@ -422,10 +422,10 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
let regionInfos: any = [];
if (regionKeys.length === 0) {
// 6.1.1 若没有选择区域,则查询区域表获取所有区域类型
regionInfos = await selectDataListByParam(TABLENAME.区域表, {}, ["id", "room_id", "name", "type", "groups"]);
regionInfos = await selectDataListByParam(TABLENAME.区域表, { "%orderAsc%": "sort_order" }, ["id", "room_id", "name", "type", "groups"]);
} else {
// 6.1.2 若选择了区域,则查询选择的区域
regionInfos = await selectDataListByParam(TABLENAME.区域表, { id: { "%in%": regionKeys } }, ["id", "room_id", "name", "type", "groups"]);
regionInfos = await selectDataListByParam(TABLENAME.区域表, { id: { "%in%": regionKeys }, "%orderAsc%": "sort_order" }, ["id", "room_id", "name", "type", "groups"]);
}
// 6.2 循环区域查询客流传感器设备,获取过去24小时每个区域的客流量
......@@ -472,6 +472,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
if (!regionCustomerMap.has(regionName)) {
regionCustomerMap.set(regionName, netVisitor);
} else {
totalNum = regionCustomerMap.get(regionName);
regionCustomerMap.set(regionName, totalNum + netVisitor);
}
}
......@@ -487,7 +488,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
// 计算每个区域的饱和度(客流量/区域最大承载量),并转换成表格
for (let [regionName, customerNum] of regionCustomerMap.entries()) {
let regionMaxCustomer = regionMaxCustomerMap.get(regionName) || 1000; // 默认最大承载量为1000
let saturation = `${((customerNum / regionMaxCustomer) * 100).toFixed(2)}%`;
let saturation = `${((customerNum / regionMaxCustomer) * 100)>100?100:((customerNum / regionMaxCustomer) * 100).toFixed(2)}%`;
regionCustomerTable.push({ regionName, customerNum, saturation });
todayCustomer += customerNum;
}
......@@ -684,7 +685,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
// 运行监控总数据(一天)
resultAny.mainRunningManagement = {
totalCustomerNumber: totalRegionCustomerNum,
totalDeviceOnlineRate: (acOnlineCount/acCount*100).toFixed(1),
totalDeviceOnlineRate: (acOnlineCount/acCount*100)>=100?'100':(acOnlineCount/acCount*100).toFixed(1),
// totalElectricity: last24hRunningEnergy.toFixed(0).slice(0, 5),
totalElectricity: last24hRunningEnergy.toFixed(0),
};
......
......@@ -102,6 +102,7 @@ export async function getScheduleList(params: {
if (regionKeySet.size > 0) {
const regionRes = await selectDataListByParam(TABLENAME.区域表, {
id: { "%in%": [...regionKeySet] },
"%orderAsc%": "sort_order",
}, ["id", "name"]);
for (const r of (regionRes.data || [])) {
regionNameMap[r.id] = r.name;
......
/**
* 日程执行器
* 项目启动时加载有效期内的日程,通过关联设备表获取执行设备,
* 按 schedule_time 每天定时触发,调用飞奕 controlIndoorUnit API 控制空调设备。
*
* 特性:
* - 启动时加载所有有效日程并设置 setTimeout
* - 执行失败时自动重试(最多 3 次,间隔 30s/60s/120s)
* - 执行后自动安排下一次(第二天同一时间)
* - 每天凌晨 00:05 自动重载,处理新增/过期日程
* - 增删改日程后可通过 reloadScheduleTasks() 立即生效
* - 设备为空则跳过执行(不安排定时器)
*/
import { selectDataListByParam } from "../data/findData";
import { addData } from "../data/addData";
import { TABLENAME } from "../config/dbEnum";
import { controlIndoorUnit } from "./feiyiClient";
// ======================= 常量 =======================
/** 重试配置 */
const RETRY_CONFIG = {
maxRetries: 3,
delays: [30_000, 60_000, 120_000], // 30s, 60s, 120s
};
/**
* 空调参数中文到 API 数字的映射
* 与 running.ts 中的 acDeviceKeyMap 保持一致
*/
const AC_KEY_MAP: Record<string, number> = {
// 模式
"制冷": 1,
"制热": 2,
"送风": 3,
"除湿": 4,
// 开关
"on": 1,
"off": 0,
// 风速
"超强": 1,
"高风": 1,
"强": 2,
"中风": 2,
"弱": 4,
"低风": 4,
// 锁定
"解除": 1,
"生效": 0,
};
// ======================= 数据结构 =======================
/** 活跃定时器记录 */
interface ActiveTimer {
scheduleId: number;
timer: NodeJS.Timeout;
title: string;
}
/** 所有活跃的定时器 Map<scheduleId, ActiveTimer> */
const activeTimers: Map<number, ActiveTimer> = new Map();
/** 每日重载定时器 */
let dailyReloadTimer: NodeJS.Timeout | null = null;
// ======================= 工具函数 =======================
/** 格式化为本地时间字符串 YYYY-MM-DD HH:mm:ss */
function formatLocalTime(d: Date): string {
const pad = (n: number) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}
/**
* 根据 schedule_time(HH:mm:ss)计算下一次执行时间
* 如果今天的时间已过,安排到明天
*/
function calcNextExecutionTime(timeStr: string): Date {
const now = new Date();
const parts = timeStr.split(":").map(Number);
const next = new Date(now);
next.setHours(parts[0] || 0, parts[1] || 0, parts[2] || 0, 0);
if (next.getTime() <= now.getTime()) {
next.setDate(next.getDate() + 1);
}
return next;
}
/** 判断日程是否在有效期内 */
function isInValidity(begin: string | null | undefined, end: string | null | undefined): boolean {
const now = new Date();
if (begin && new Date(begin) > now) return false;
if (end && new Date(end) < now) return false;
return true;
}
/** 判断日程是否已过期(end_validity 已过,不应再排下次) */
function isExpired(end: string | null | undefined): boolean {
if (!end) return false;
return new Date(end) < new Date();
}
// ======================= 核心逻辑 =======================
/**
* 构建 controlIndoorUnit 的请求参数
*/
function buildControlParams(
deviceIds: string[],
acControl: string,
todoItem: Record<string, string> | null | undefined
): Record<string, any> {
const params: Record<string, any> = {
indoorUnitAddressFull: deviceIds,
onOff: AC_KEY_MAP[acControl] ?? 1,
openApiAction: "control",
};
// 关机只发送 onOff=0,不传模式/温度/风速
if (acControl === "off") {
return params;
}
if (todoItem) {
if (todoItem["温度"] != null) {
params.tempSet = Number(todoItem["温度"]);
}
if (todoItem["模式"] && AC_KEY_MAP[todoItem["模式"]] != null) {
params.workMode = AC_KEY_MAP[todoItem["模式"]];
}
if (todoItem["风速"] && AC_KEY_MAP[todoItem["风速"]] != null) {
params.fanSpeed = AC_KEY_MAP[todoItem["风速"]];
}
}
return params;
}
/**
* 执行单次设备控制 + 写入设备日志
* @returns 执行结果 { success, message }
*/
async function executeControl(
scheduleId: number,
title: string,
deviceIds: string[],
acControl: string,
todoItem: Record<string, string> | null | undefined
): Promise<{ success: boolean; message: string }> {
console.log(`[日程执行器] 执行日程 #${scheduleId} "${title}", 设备: ${deviceIds.join(",")}, 操作: ${acControl}`);
// 设备为空直接跳过
if (deviceIds.length === 0) {
console.log(`[日程执行器] 日程 #${scheduleId} 没有关联设备,跳过执行`);
return { success: true, message: "无关联设备,跳过" };
}
const params = buildControlParams(deviceIds, acControl, todoItem);
const now = new Date();
// 调用第三方 API
let result: { success: boolean; message: string };
try {
const res = await controlIndoorUnit(params);
result = { success: res.success, message: res.data ?? "" };
} catch (err: any) {
result = { success: false, message: err.message || "控制异常" };
}
// 批量写入设备日志
const logs = deviceIds.map((deviceId) => ({
device_id: deviceId,
device_type: "空调",
operation_content: {
option: `日程定时执行: ${title}`,
scheduleId,
time: formatLocalTime(now),
acControl,
params: { ...params, indoorUnitAddressFull: undefined },
success: result.success,
message: result.message,
},
status: result.success ? 1 : 0,
created_at: now,
updated_at: now,
}));
try {
await addData(TABLENAME.设备日志表, logs);
} catch (err) {
console.error(`[日程执行器] 写入设备日志失败:`, err);
}
if (result.success) {
console.log(`[日程执行器] 日程 #${scheduleId} "${title}" 执行成功`);
} else {
console.error(`[日程执行器] 日程 #${scheduleId} "${title}" 执行失败: ${result.message}`);
}
return result;
}
/**
* 带重试的执行逻辑
* 最多重试 RETRY_CONFIG.maxRetries 次,间隔递增
*/
async function executeWithRetry(
scheduleId: number,
title: string,
deviceIds: string[],
acControl: string,
todoItem: Record<string, string> | null | undefined
): Promise<void> {
let lastResult: { success: boolean; message: string } | null = null;
for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
if (attempt > 0) {
const delay = RETRY_CONFIG.delays[attempt - 1] ?? 120_000;
console.log(`[日程执行器] 日程 #${scheduleId}${attempt} 次重试,等待 ${delay / 1000}s...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
const result = await executeControl(scheduleId, title, deviceIds, acControl, todoItem);
lastResult = result;
if (result.success) {
return; // 成功,结束
}
console.error(`[日程执行器] 日程 #${scheduleId}${attempt} 次执行失败: ${result.message}`);
}
console.error(
`[日程执行器] 日程 #${scheduleId} "${title}" 重试 ${RETRY_CONFIG.maxRetries} 次后仍失败: ${lastResult?.message}`
);
}
/**
* 为单个日程安排下一次执行
* 执行完成后自动排下一次(第二天同一时间)
*/
function scheduleNextRun(schedule: any, deviceIds: string[]): void {
const { id, title, schedule_time, ac_control, todo_item, end_validity } = schedule;
// 已过期不再调度
if (isExpired(end_validity)) {
console.log(`[日程执行器] 日程 #${id} "${title}" 已过有效期,不再调度`);
return;
}
const nextTime = calcNextExecutionTime(schedule_time);
const delayMs = nextTime.getTime() - Date.now();
console.log(
`[日程执行器] 日程 #${id} "${title}" 下次执行: ${formatLocalTime(nextTime)} (${Math.round(delayMs / 1000)}s 后)`
);
const timer = setTimeout(async () => {
// 执行前再次检查有效期(可能在等待期间越过 end_validity)
if (isExpired(end_validity)) {
console.log(`[日程执行器] 日程 #${id} "${title}" 在等待期间过期,取消执行`);
activeTimers.delete(id);
return;
}
// 带重试执行
await executeWithRetry(id, title, deviceIds, ac_control, todo_item);
// 安排下一次
activeTimers.delete(id);
scheduleNextRun(schedule, deviceIds);
}, delayMs);
activeTimers.set(id, { scheduleId: id, timer, title });
}
// ======================= 公开接口 =======================
/** 清除所有活跃定时器 */
function clearAllTimers(): void {
console.log(`[日程执行器] 清除 ${activeTimers.size} 个活跃定时器`);
for (const [, entry] of activeTimers) {
clearTimeout(entry.timer);
}
activeTimers.clear();
}
/**
* 从数据库加载所有有效期内的日程,安排定时执行
*/
async function loadAndScheduleAll(): Promise<void> {
console.log("[日程执行器] 开始加载日程...");
try {
const scheduleRes = await selectDataListByParam(
TABLENAME.日程表,
{},
["id", "title", "ac_control", "todo_item", "schedule_time", "begin_validity", "end_validity"]
);
const schedules: any[] = scheduleRes.data || [];
let scheduledCount = 0;
for (const schedule of schedules) {
const { id, title, schedule_time, begin_validity, end_validity } = schedule;
// 没有 schedule_time 的跳过
if (!schedule_time) {
console.log(`[日程执行器] 日程 #${id} "${title}" 未设置 schedule_time,跳过`);
continue;
}
// 不在有效期内跳过
if (!isInValidity(begin_validity, end_validity)) {
console.log(`[日程执行器] 日程 #${id} "${title}" 不在有效期内,跳过`);
continue;
}
// 查询关联设备
const sdRes = await selectDataListByParam(
TABLENAME.日程设备关联表,
{ schedule_id: id },
["device_id"]
);
const sdRows: any[] = sdRes.data || [];
const deviceIds = sdRows
.map((r: any) => r.device_id)
.filter((did: string) => did && did.trim() !== "");
// 没有有效设备则跳过
if (deviceIds.length === 0) {
console.log(`[日程执行器] 日程 #${id} "${title}" 没有有效设备,跳过`);
continue;
}
// 安排执行
scheduleNextRun(schedule, deviceIds);
scheduledCount++;
}
console.log(`[日程执行器] 加载完成,共安排 ${scheduledCount} 个日程`);
} catch (err) {
console.error("[日程执行器] 加载日程失败:", err);
}
}
/**
* 安排每日凌晨 00:05 重载
* 用于自动处理新增、过期日程
*/
function scheduleDailyReload(): void {
if (dailyReloadTimer) {
clearTimeout(dailyReloadTimer);
}
const now = new Date();
const reloadTime = new Date(now);
reloadTime.setDate(reloadTime.getDate() + 1);
reloadTime.setHours(0, 5, 0, 0);
const delayMs = reloadTime.getTime() - now.getTime();
console.log(`[日程执行器] 每日重载将在 ${formatLocalTime(reloadTime)} 执行 (${Math.round(delayMs / 1000)}s 后)`);
dailyReloadTimer = setTimeout(async () => {
console.log("[日程执行器] 开始每日重载...");
clearAllTimers();
await loadAndScheduleAll();
scheduleDailyReload();
}, delayMs);
}
/**
* 初始化日程任务(项目启动时调用)
*/
export async function initScheduleTasks(): Promise<void> {
console.log("[日程执行器] ========== 初始化日程任务 ==========");
clearAllTimers();
await loadAndScheduleAll();
scheduleDailyReload();
console.log("[日程执行器] ========== 初始化完成 ==========");
}
/**
* 重载日程任务(增/删/改日程后调用,立即生效)
*/
export async function reloadScheduleTasks(): Promise<void> {
console.log("[日程执行器] ========== 重载日程任务 ==========");
clearAllTimers();
if (dailyReloadTimer) {
clearTimeout(dailyReloadTimer);
dailyReloadTimer = null;
}
await loadAndScheduleAll();
scheduleDailyReload();
console.log("[日程执行器] ========== 重载完成 ==========");
}
/**
* 获取当前活跃的定时任务信息(调试用)
*/
export function getActiveScheduleTasks(): Array<{ scheduleId: number; title: string }> {
const result: Array<{ scheduleId: number; title: string }> = [];
for (const [, entry] of activeTimers) {
result.push({ scheduleId: entry.scheduleId, title: entry.title });
}
return result;
}
......@@ -4,6 +4,8 @@ import * as mqttSrv from "./service/mqttInit";
import { initMysqlModel } from "./model/sqlModelBind";
import { httpServer } from "./net/http_server";
import { startSchedule, startAlertSchedule, startFaultStatusSchedule } from "./config/schedule";
import { initScheduleTasks } from "./biz/scheduleExecutor";
import { refreshRegionSortOrder } from "./biz/region";
async function lanuch() {
......@@ -12,6 +14,8 @@ async function lanuch() {
/**初始化sql */
await mysqlDB.initMysqlDB();
await initMysqlModel();
/**刷新区域排序 */
await refreshRegionSortOrder();
/**创建http服务 */
httpServer.createServer(systemConfig.port);
/**启动MQTT订阅服务 */
......@@ -22,6 +26,8 @@ async function lanuch() {
startAlertSchedule();
/**启动故障状态更新定时任务(5分钟) */
startFaultStatusSchedule();
/**启动日程执行器 */
await initScheduleTasks();
console.log('This indicates that the server is started successfully.');
......
......@@ -6,6 +6,7 @@ import asyncHandler from "express-async-handler";
import * as reportBiz from "../biz/report";
import * as regionBiz from "../biz/region";
import * as scheduleBiz from "../biz/schedule";
import { reloadScheduleTasks } from "../biz/scheduleExecutor";
import { eccReqParamater } from "../util/verificationParam";
export function setRouter(httpServer) {
......@@ -134,6 +135,8 @@ async function addSchedule(req, res) {
regionKeys: regionKeys ?? []
});
res.success(data);
// 新增日程后重载执行器
reloadScheduleTasks().catch((err) => console.error("[日程重载] 新增后重载失败:", err));
}
/**
......@@ -164,6 +167,8 @@ async function editSchedule(req, res) {
regionKeys: regionKeys ?? []
});
res.success(data);
// 编辑日程后重载执行器
reloadScheduleTasks().catch((err) => console.error("[日程重载] 编辑后重载失败:", err));
}
/**
......@@ -179,4 +184,6 @@ async function deleteSchedule(req, res) {
}
const data = await scheduleBiz.deleteSchedule(Number(scheduleId));
res.success(data);
// 删除日程后重载执行器
reloadScheduleTasks().catch((err) => console.error("[日程重载] 删除后重载失败:", err));
}
......@@ -32,15 +32,17 @@ export function setRouter(httpServer) {
httpServer.post('/api/qdm/run/monitor/acPop', asyncHandler(getAcRunningPop));
/** 空调开关 */
httpServer.post('/api/qdm/run/monitor/acRun', asyncHandler(controlAcRunning));
/** 运行分析 - 环境质量弹窗 */
httpServer.post('/api/qdm/run/analysis/envPop', asyncHandler(getRunAnalysisEnvPop));
/** 启动数据集成 */
httpServer.post('/api/qdm/run/start/schedule', asyncHandler(startSchedule));
/** 停止数据集成 */
httpServer.post('/api/qdm/run/stop/schedule', asyncHandler(stopSchedule));
/** 获取环境监测设备 */
/** IAQ-获取环境监测设备 */
httpServer.post('/api/qdm/device/envDevice', asyncHandler(getEnvironmentDevices));
/** 获取环境监测数据 */
/** IAQ-获取环境监测数据 */
httpServer.post('/api/qdm/device/environment', asyncHandler(getEnvironmentData));
}
......@@ -184,6 +186,17 @@ async function getEnvironmentData(req, res) {
res.success(result);
}
/**
* 运行分析 - 环境质量弹窗
*/
async function getRunAnalysisEnvPop(req, res) {
let reqConf = { regionGroup: 'String', regionType: 'String' };
const NotMustHaveKeys = ["regionGroup", "regionType"];
let { regionGroup, regionType } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await deviceBiz.getEnvQualityPop(regionGroup, regionType);
res.success(result);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment