接口开发

parent 92e486c8
...@@ -16,4 +16,14 @@ ...@@ -16,4 +16,14 @@
<mysqlPwd>root</mysqlPwd> <mysqlPwd>root</mysqlPwd>
<dataBase>dst_technology_platform</dataBase> --> <dataBase>dst_technology_platform</dataBase> -->
</mysqldb> </mysqldb>
<feiyi>
<feiyiAppKey>0</feiyiAppKey>
<feiyiSecretKey>0</feiyiSecretKey>
<feiyiBaseUrl>https://m.achelp.cn/open</feiyiBaseUrl>
</feiyi>
<diqin>
<diqinAppKey>178428005410049</diqinAppKey>
<diqinSecretKey>pGAII3TfpdrJ4zjrPncERwtt</diqinSecretKey>
<diqinBaseUrl>https://airiccc.com</diqinBaseUrl>
</diqin>
</config> </config>
...@@ -4,27 +4,27 @@ ...@@ -4,27 +4,27 @@
*/ */
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { post } from '../util/request'; import { get, post } from '../util/request';
import { BizError } from '../util/bizError'; import { BizError } from '../util/bizError';
import { ERRORENUM } from '../config/errorEnum'; import { ERRORENUM } from '../config/errorEnum';
import { FEIYI } from '../config/clientEnum'; import { FEIYI } from '../config/clientEnum';
import { systemConfig } from '../config/serverConfig';
// 全局变量缓存 token 和过期时间 // 全局变量缓存 token 和过期时间
let feiYiToken = ""; let feiYiToken = "";
let expireTime = 0; let expireTime = 0;
let getTokenTime = 0;
let isFetchingToken = false; // 避免并发获取 token 的标志位
let refreshTokenPromise: Promise<string> | null = null; // 用于避免并发刷新 token 的 Promise
/** /**
* 飞奕云平台配置(建议从环境变量或配置文件中读取 * 获取飞奕云平台配置(从 serverConfig.xml 读取,兜底使用环境变量或默认值
*/ */
const FEIYI_CONFIG = { function getFeiYiConfig() {
baseUrl: 'https://m.achelp.cn/open', const config = systemConfig.feiyi;
clientId: process.env.FEIYI_CLIENT_ID || '419636996764598272', // 我方提供的 clientId return {
secretKey: process.env.FEIYI_SECRET_KEY || '02fc75446b4544b3a02444a2b5f9be2b', // 我方提供的 appSecret baseUrl: (config && config.baseUrl) || process.env.FEIYI_BASE_URL || 'https://m.achelp.cn/open',
}; clientId: (config && config.appKey) || process.env.FEIYI_CLIENT_ID || '419636996764598272',
secretKey: (config && config.secretKey) || process.env.FEIYI_SECRET_KEY || '02fc75446b4544b3a02444a2b5f9be2b',
};
}
/** /**
* 生成随机字符串 nonce * 生成随机字符串 nonce
...@@ -70,7 +70,8 @@ function generateSign(params: Record<string, any>, timestamp: string, nonce: str ...@@ -70,7 +70,8 @@ function generateSign(params: Record<string, any>, timestamp: string, nonce: str
* @throws 如果请求失败或响应码非 00000,则抛出 BizError * @throws 如果请求失败或响应码非 00000,则抛出 BizError
*/ */
export async function getFeiYiToken(): Promise<string> { export async function getFeiYiToken(): Promise<string> {
const url = `${FEIYI_CONFIG.baseUrl}${FEIYI.获取Token}`; const feiyiConfig = getFeiYiConfig();
const url = `${feiyiConfig.baseUrl}${FEIYI.获取Token}`;
// 校验token是否有效,有效则直接返回 // 校验token是否有效,有效则直接返回
const now = Date.now(); const now = Date.now();
...@@ -82,12 +83,12 @@ export async function getFeiYiToken(): Promise<string> { ...@@ -82,12 +83,12 @@ export async function getFeiYiToken(): Promise<string> {
// 根据文档,获取 token 的请求参数 // 根据文档,获取 token 的请求参数
const params = { const params = {
AccessKey: '', // 文档要求传空串 AccessKey: '', // 文档要求传空串
clientId: FEIYI_CONFIG.clientId clientId: feiyiConfig.clientId
}; };
// 生成签名 // 生成签名
const timestamp = Date.now().toString(); // 毫秒时间戳 const timestamp = Date.now().toString(); // 毫秒时间戳
const nonce = generateNonce(); const nonce = generateNonce();
const sign = generateSign(params, timestamp, nonce, FEIYI_CONFIG.secretKey); const sign = generateSign(params, timestamp, nonce, feiyiConfig.secretKey);
console.log('MD5加密签名:', sign); console.log('MD5加密签名:', sign);
const requestBody = { const requestBody = {
...@@ -116,7 +117,6 @@ export async function getFeiYiToken(): Promise<string> { ...@@ -116,7 +117,6 @@ export async function getFeiYiToken(): Promise<string> {
} else { } else {
// 更新全局 token 和过期时间 // 更新全局 token 和过期时间
feiYiToken = accessToken; feiYiToken = accessToken;
getTokenTime = now;
// 默认 2分钟 过期,如果响应中包含 expires_in 字段,则使用该值(单位毫秒) // 默认 2分钟 过期,如果响应中包含 expires_in 字段,则使用该值(单位毫秒)
expireTime = now + (result.data.expires_in ? result.data.expires_in : 2 * 60 * 1000); expireTime = now + (result.data.expires_in ? result.data.expires_in : 2 * 60 * 1000);
console.log(`获取新 token: ${accessToken}, 过期时间: ${new Date(expireTime).toISOString()}`); console.log(`获取新 token: ${accessToken}, 过期时间: ${new Date(expireTime).toISOString()}`);
...@@ -135,7 +135,8 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis ...@@ -135,7 +135,8 @@ async function feiYiPost(path: string, body: any, retry: boolean = true): Promis
// 获取 token // 获取 token
const token = await getFeiYiToken(); const token = await getFeiYiToken();
const url = `${FEIYI_CONFIG.baseUrl}${path}`; const feiyiConfig = getFeiYiConfig();
const url = `${feiyiConfig.baseUrl}${path}`;
const headers = { const headers = {
'Authorization': `Bearer ${token}`, 'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...@@ -380,3 +381,79 @@ export async function getIndoorUnitStateList(params: any): Promise<any> { ...@@ -380,3 +381,79 @@ export async function getIndoorUnitStateList(params: any): Promise<any> {
const result = await feiYiPost(path, body); const result = await feiYiPost(path, body);
return result.data; return result.data;
} }
/**
* 空调内机故障记录分页查询
* @param params { page, limit, roomIds?, indoorUnitAddressFull?, alarmType?, occurrenceTime? }
* @returns data 部分,包含 page, limit, total, rows
*/
export async function getIndoorUnitAlarmErrorHis(params: any): Promise<any> {
const path = '/openApi/indoorUnit/alarm/errorHis';
const body = { ...params };
const result = await feiYiPost(path, body);
return result.data;
}
/**
* 故障码查询故障详情
* 根据品牌编码和错误编码查询故障描述和解决方案
* 接口文档:空调故障码对接服务与解析流程.pdf
* @param brandCode 品牌编码,如 "1"=日立
* @param errorCode 错误编码,如 "LOST"
* @returns { errorInfo: string, scheme: string | null } 故障信息描述、解决方案
*/
export async function getFaultCodeInfo(brandCode: string, errorCode: string): Promise<{ errorInfo: string; scheme: string | null }> {
const url = 'https://errorcode.feiyikj.cn/prod-api/feiyi/app/errorCode/info';
const query = { brandCode, errorCode };
let result: any;
try {
result = await get(url, query);
} catch (err) {
console.error(`[故障码查询] 请求失败 brandCode=${brandCode} errorCode=${errorCode}:`, err);
return { errorInfo: '查询失败', scheme: null };
}
if (!result || result.code !== 200) {
console.error(`[故障码查询] 接口返回异常 brandCode=${brandCode} errorCode=${errorCode}:`, result);
return { errorInfo: '查询失败', scheme: null };
}
const data = result.data || [];
if (data.length === 0) {
console.warn(`[故障码查询] 未查到故障码信息 brandCode=${brandCode} errorCode=${errorCode}`);
return { errorInfo: errorCode, scheme: null };
}
return {
errorInfo: data[0].errorInfo || errorCode,
scheme: data[0].scheme || null,
};
}
/**
* 故障类型映射(中文 -> 数字)
*/
let faultTypeMap: { [key: string]: number } = {
"内机故障": 5,
"未绑定房间": 15
};
/** 数字 -> 中文的反向映射 */
let faultTypeReverseMap: Map<number, string> | null = null;
/** 构建数字->中文的反向映射 */
function getFaultTypeReverseMap(): Map<number, string> {
if (faultTypeReverseMap) return faultTypeReverseMap;
faultTypeReverseMap = new Map();
for (const [cn, num] of Object.entries(faultTypeMap)) {
faultTypeReverseMap.set(num, cn);
}
return faultTypeReverseMap;
}
/**
* 根据 alarmType 数字获取中文故障类型名称
* @param alarmType 故障类型数字
* @returns 中文名称,未匹配时返回原数字字符串
*/
export function getFaultTypeName(alarmType: number | string): string {
const num = typeof alarmType === 'string' ? parseInt(alarmType, 10) : alarmType;
return getFaultTypeReverseMap().get(num) || String(alarmType);
}
...@@ -46,6 +46,72 @@ export async function readRegionAxis(gatewayPage: number) { ...@@ -46,6 +46,72 @@ export async function readRegionAxis(gatewayPage: number) {
return result; return result;
} }
/**
* 动态查询:根据 regionName→regionKey 映射,从数据库查询各区域下的设备及最新功率状态
* @returns 格式与 mock 数据一致: { regionName: [{deviceId, deviceType, power}, ...] }
*/
async function buildDeviceMapDynamic(regionMap: { [regionName: string]: string }): Promise<{ [regionName: string]: any[] }> {
const deviceMap: { [regionName: string]: any[] } = {};
// 1. 收集所有 region_key
const regionKeys = Object.values(regionMap) as string[];
if (regionKeys.length === 0) return deviceMap;
// 2. 反向映射:regionKey → regionName
const regionKeyToName: { [key: string]: string } = {};
for (const name in regionMap) {
regionKeyToName[regionMap[name]] = name;
}
// 3. 批量查询这些区域下所有的设备
const deviceResult = await selectDataListByParam(
TABLENAME.设备表,
{ region_key: { '%in%': regionKeys } },
['device_id', 'region_key', 'device_type'],
);
const devices = (deviceResult.data || []) as any[];
// 4. 批量查询这些设备的最新一条 device_data,提取 power 状态
const deviceIds = devices.map((d) => d.device_id).filter(Boolean);
const powerMap = new Map<string, string>();
if (deviceIds.length > 0) {
const dataResult = await selectDataListByParam(
TABLENAME.设备数据表,
{
device_id: { '%in%': deviceIds },
'%orderDesc%': 'device_time',
'%limit%': deviceIds.length,
},
['device_id', 'device_data'],
);
// 按 device_time 降序返回,去重取每个设备的第一条(最新)
const seen = new Set<string>();
(dataResult.data || []).forEach((r: any) => {
if (!seen.has(r.device_id)) {
seen.add(r.device_id);
const parsed = typeof r.device_data === 'string' ? JSON.parse(r.device_data) : r.device_data;
powerMap.set(r.device_id, parsed?.power || 'on');
}
});
}
// 5. 组装 deviceMap(按 regionName 分组,格式对齐 mock 数据)
for (const device of devices) {
const regionName = regionKeyToName[device.region_key];
if (!regionName) continue;
if (!deviceMap[regionName]) {
deviceMap[regionName] = [];
}
deviceMap[regionName].push({
deviceId: device.device_id,
deviceType: device.device_type,
power: powerMap.get(device.device_id) || 'on',
});
}
return deviceMap;
}
async function readSheet(sheet: any[][], gatewayPage: number) { async function readSheet(sheet: any[][], gatewayPage: number) {
// 取出所有区域名称,查询区域表获取区域数据 // 取出所有区域名称,查询区域表获取区域数据
let regionList = await selectDataListByParam(TABLENAME.区域表, {}); let regionList = await selectDataListByParam(TABLENAME.区域表, {});
...@@ -59,40 +125,44 @@ async function readSheet(sheet: any[][], gatewayPage: number) { ...@@ -59,40 +125,44 @@ async function readSheet(sheet: any[][], gatewayPage: number) {
// "前台": "0001", // "前台": "0001",
// "卫生间": "3000" // "卫生间": "3000"
// } // }
let regionMap = regionList.data.map((bean)=>{ let regionMap:any = {};
regionList.data.map((bean)=>{
regionMap[bean.regionName] = bean.regionKey regionMap[bean.regionName] = bean.regionKey
}); });
// 根据区域信息查询所有环境设备,通过环境设备查询最新环境指标 // 根据区域信息查询所有环境设备,通过环境设备查询最新环境指标
let deviceMap = { // ====== MOCK 数据(注释保留作为格式参考) ======
"会议室": [ // let deviceMap = {
{ "deviceId": "AC1", "deviceType": "空调", "power": "off" }, // "会议室": [
{ "deviceId": "Z1", "deviceType": "照明", "power": "on" } // { "deviceId": "AC1", "deviceType": "空调", "power": "off" },
], // { "deviceId": "Z1", "deviceType": "照明", "power": "on" }
"公共办公区": [ // ],
{ "deviceId": "AC2", "deviceType": "空调", "power": "on" }, // "公共办公区": [
{ "deviceId": "Z12", "deviceType": "照明", "power": "on" }, // { "deviceId": "AC2", "deviceType": "空调", "power": "on" },
{ "deviceId": "F1", "deviceType": "排风", "power": "on" }, // { "deviceId": "Z12", "deviceType": "照明", "power": "on" },
{ "deviceId": "E1", "deviceType": "IEQ传感器", "power": "on" }, // { "deviceId": "F1", "deviceType": "排风", "power": "on" },
{ "deviceId": "Y1", "deviceType": "烟感器", "power": "on" } // { "deviceId": "E1", "deviceType": "IEQ传感器", "power": "on" },
], // { "deviceId": "Y1", "deviceType": "烟感器", "power": "on" }
"办公室": [ // ],
{ "deviceId": "AC3", "deviceType": "空调", "power": "on" }, // "办公室": [
{ "deviceId": "Z2", "deviceType": "照明", "power": "on" }, // { "deviceId": "AC3", "deviceType": "空调", "power": "on" },
{ "deviceId": "F2", "deviceType": "排风", "power": "on" } // { "deviceId": "Z2", "deviceType": "照明", "power": "on" },
], // { "deviceId": "F2", "deviceType": "排风", "power": "on" }
"仓库": [{ "deviceId": "AC11", "deviceType": "空调", "power": "off" }], // ],
"CEO办公室": [{ "deviceId": "AC12", "deviceType": "空调", "power": "off" }], // "仓库": [{ "deviceId": "AC11", "deviceType": "空调", "power": "off" }],
"传感器生产间": [{ "deviceId": "AC13", "deviceType": "空调", "power": "off" }], // "CEO办公室": [{ "deviceId": "AC12", "deviceType": "空调", "power": "off" }],
"卫生间": [ // "传感器生产间": [{ "deviceId": "AC13", "deviceType": "空调", "power": "off" }],
{ "deviceId": "S1", "deviceType": "摄像头", "power": "on" }, // "卫生间": [
{ "deviceId": "E100", "deviceType": "IEQ传感器", "power": "on" }, // { "deviceId": "S1", "deviceType": "摄像头", "power": "on" },
{ "deviceId": "X1", "deviceType": "音响", "power": "on" } // { "deviceId": "E100", "deviceType": "IEQ传感器", "power": "on" },
], // { "deviceId": "X1", "deviceType": "音响", "power": "on" }
"前台": [ // ],
{ "deviceId": "S0", "deviceType": "摄像头", "power": "on" }, // "前台": [
{ "deviceId": "T1", "deviceType": "人流监测", "power": "on" } // { "deviceId": "S0", "deviceType": "摄像头", "power": "on" },
] // { "deviceId": "T1", "deviceType": "人流监测", "power": "on" }
} // ]
// }
// ====== 动态查询:从数据库获取真实设备及最新状态 ======
const deviceMap = await buildDeviceMapDynamic(regionMap);
// 循环区域,根据各区域环境指标计算各区域的环境质量得出优良中差四个值 // 循环区域,根据各区域环境指标计算各区域的环境质量得出优良中差四个值
let sheetData = await planaryArrayBecomeOfBlockData(sheet); let sheetData = await planaryArrayBecomeOfBlockData(sheet);
let result = []; let result = [];
......
...@@ -15,6 +15,9 @@ export enum DIQIN { ...@@ -15,6 +15,9 @@ export enum DIQIN {
获取Token = '/v2/tokens/access_token', 获取Token = '/v2/tokens/access_token',
获取区域信息 = '/v2/locations', 获取区域信息 = '/v2/locations',
获取区域明细信息 = '/v2/locations/', // {location_id} 获取区域明细信息 = '/v2/locations/', // {location_id}
获取环境设备信息 = '/v2/devices/', // {device_id} 获取监测点明细 = '/v2/stations/', // {station_id}
获取环境设备数据信息 = '/v2/data_sources/' // {data_source_id} 获取设备列表 = '/v2/data_sources',
获取设备明细 = '/v2/data_sources/', // {data_source_id}
获取历史数据 = '/v2/readings',
获取图表数据 = '/v2/graphs',
} }
\ No newline at end of file
...@@ -3,9 +3,13 @@ ...@@ -3,9 +3,13 @@
* 表名 * 表名
*/ */
export enum TABLENAME { export enum TABLENAME {
用户信息表 = 'user_info',
设备表 = 'device', 设备表 = 'device',
设备数据表 = 'device_data', 设备数据表 = 'device_data',
区域表 = 'region', 区域表 = 'region',
区域设备关联表 = 'region_device_rel',
日程表 = 'schedule',
设备操作日志表 = 'device_logs',
设备故障表 = 'device_fault' 设备故障表 = 'device_fault'
}; };
......
...@@ -85,6 +85,12 @@ export const TablesConfig = [ ...@@ -85,6 +85,12 @@ export const TablesConfig = [
defaultValue: null, defaultValue: null,
comment: '父级区域key,作为parent_id补充' comment: '父级区域key,作为parent_id补充'
}, },
region_type: {
type: Sequelize.STRING(50),
allowNull: true,
defaultValue: null,
comment: '区域类型:办公室/机房/车间'
},
region_param: { region_param: {
type: DataTypes.JSON, type: DataTypes.JSON,
allowNull: true, allowNull: true,
...@@ -154,9 +160,15 @@ export const TablesConfig = [ ...@@ -154,9 +160,15 @@ export const TablesConfig = [
comment: '区域编码,关联区域表' comment: '区域编码,关联区域表'
}, },
device_type: { device_type: {
type: Sequelize.ENUM('空调内机', '新风机', '空气质量监测', '烟雾感应', '音响', '人流感应', '电表监测'), type: Sequelize.STRING(50),
allowNull: false, allowNull: false,
comment: '设备类型' comment: '设备类型:IEQ传感器, 烟感器, 摄像头, 空调, 音响, 照明, 等离子, 人流监测, 电表, 排风'
},
device_state: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
comment: '设备状态:0=离线, 1=在线, 2=故障'
}, },
device_name: { device_name: {
type: Sequelize.STRING(255), type: Sequelize.STRING(255),
...@@ -214,8 +226,8 @@ export const TablesConfig = [ ...@@ -214,8 +226,8 @@ export const TablesConfig = [
comment: '设备标识' comment: '设备标识'
}, },
relation_type: { relation_type: {
type: Sequelize.ENUM('region_to_device', 'device_to_region'), type: Sequelize.STRING(50),
allowNull: false, allowNull: true,
defaultValue: 'region_to_device', defaultValue: 'region_to_device',
comment: '关联类型:region_to_device区域一对多设备,device_to_region设备一对多区域' comment: '关联类型:region_to_device区域一对多设备,device_to_region设备一对多区域'
}, },
...@@ -460,7 +472,7 @@ export const TablesConfig = [ ...@@ -460,7 +472,7 @@ export const TablesConfig = [
comment: '原始故障id' comment: '原始故障id'
}, },
fault_code: { fault_code: {
type: DataTypes.STRING(50), type: Sequelize.STRING(50),
allowNull: true, allowNull: true,
comment: '故障代码' comment: '故障代码'
}, },
...@@ -470,9 +482,9 @@ export const TablesConfig = [ ...@@ -470,9 +482,9 @@ export const TablesConfig = [
comment: '故障类型' comment: '故障类型'
}, },
fault_level: { fault_level: {
type: Sequelize.ENUM('一级', '二级', '三级'), type: Sequelize.STRING(50),
allowNull: true, allowNull: true,
comment: '故障等级' comment: '故障等级:一级, 二级, 三级'
}, },
fault_info: { fault_info: {
type: DataTypes.JSON, type: DataTypes.JSON,
...@@ -485,10 +497,10 @@ export const TablesConfig = [ ...@@ -485,10 +497,10 @@ export const TablesConfig = [
comment: '故障时间' comment: '故障时间'
}, },
fault_status: { fault_status: {
type: Sequelize.ENUM('未处理', '处理中', '已处理'), type: Sequelize.STRING(50),
allowNull: false, allowNull: false,
defaultValue: '未处理', defaultValue: '未处理',
comment: '故障状态' comment: '故障状态:未处理, 处理中, 已处理'
}, },
handle_time: { handle_time: {
type: DataTypes.DATE, type: DataTypes.DATE,
......
/**
* 定时任务调度器
* - 每分钟执行一次数据同步任务
* - 每 6 分钟执行一次预警工单同步任务
* - 内置锁机制,防止任务重叠执行
* - 任务锁死时(超时未释放),会主动强制释放锁,确保后续任务能正常执行
*/
import { region, device, deviceData, alertWorkData, processFaultStatus, diqinSyncAll } from "../biz/dataIntegration";
// ==================== 通用任务锁 ====================
interface TaskLockState {
locked: boolean;
lockTime: number;
lockKey: string;
}
/**
* 任务锁类:防止定时任务重叠执行,支持超时自动释放
*/
class TaskLock {
private state: TaskLockState = { locked: false, lockTime: 0, lockKey: '' };
private timeoutMs: number;
constructor(timeoutMs: number) {
this.timeoutMs = timeoutMs;
}
/** 尝试获取锁,成功返回 true */
tryAcquire(): boolean {
if (this.state.locked) {
const elapsed = Date.now() - this.state.lockTime;
if (elapsed > this.timeoutMs) {
console.warn(`[定时任务] 检测到锁死,强制释放锁。已锁定 ${elapsed}ms,锁标识: ${this.state.lockKey}`);
this.release();
return this.tryAcquire();
}
console.log(`[定时任务] 上一次任务尚未完成,跳过本次执行。已锁定 ${elapsed}ms`);
return false;
}
this.state.locked = true;
this.state.lockTime = Date.now();
this.state.lockKey = `${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
return true;
}
/** 释放锁 */
release(): void {
this.state.locked = false;
this.state.lockTime = 0;
this.state.lockKey = '';
}
/** 获取锁状态(调试用) */
getStatus(): { locked: boolean; lockDuration: number; lockKey: string } {
return {
locked: this.state.locked,
lockDuration: this.state.locked ? Date.now() - this.state.lockTime : 0,
lockKey: this.state.lockKey,
};
}
}
// ==================== 锁实例 & 定时器配置 ====================
const TASK_INTERVAL_MS = 60 * 1000; // 1分钟
const ALERT_TASK_INTERVAL_MS = 6 * 60 * 1000; // 6分钟
const FAULT_STATUS_TASK_INTERVAL_MS = 5 * 60 * 1000; // 5分钟
const dataSyncLock = new TaskLock(55 * 1000); // 55秒超时
const alertLock = new TaskLock(5 * 60 * 1000); // 5分钟超时
const faultStatusLock = new TaskLock(4 * 60 * 1000); // 4分钟超时
// 定时器句柄
let timerHandle: NodeJS.Timeout | null = null;
let alertTimerHandle: NodeJS.Timeout | null = null;
let faultStatusTimerHandle: NodeJS.Timeout | null = null;
// ==================== 通用定时任务启动/停止 ====================
/**
* 创建受锁保护的定时任务
* @param name 任务名称(日志用)
* @param intervalMs 执行间隔(毫秒)
* @param lock 任务锁实例
* @param taskFn 任务函数
* @returns 启动和停止函数
*/
function createScheduledTask(
name: string,
intervalMs: number,
lock: TaskLock,
taskFn: () => Promise<void>,
): { start: () => void; stop: () => void } {
let handle: NodeJS.Timeout | null = null;
async function run(): Promise<void> {
if (!lock.tryAcquire()) return;
try {
console.log(`[${name}] 开始执行 - ${new Date().toLocaleString()}`);
await taskFn();
console.log(`[${name}] 执行完成 - ${new Date().toLocaleString()}`);
} catch (err) {
console.error(`[${name}] 执行异常:`, err);
} finally {
lock.release();
}
}
return {
start() {
if (handle) {
console.warn(`[${name}] 定时器已在运行中,无需重复启动`);
return;
}
console.log(`[${name}] 定时器已启动,间隔 ${intervalMs / 1000} 秒`);
handle = setInterval(run, intervalMs);
},
stop() {
if (handle) {
clearInterval(handle);
handle = null;
console.log(`[${name}] 定时器已停止`);
}
},
};
}
// 数据同步任务
const dataSyncTask = createScheduledTask('定时任务', TASK_INTERVAL_MS, dataSyncLock, async () => {
await region();
await device();
await deviceData();
await diqinSyncAll();
});
// 预警工单任务
const alertTask = createScheduledTask('预警工单定时任务', ALERT_TASK_INTERVAL_MS, alertLock, async () => {
await alertWorkData();
});
// 故障状态更新任务
const faultStatusTask = createScheduledTask('故障状态更新任务', FAULT_STATUS_TASK_INTERVAL_MS, faultStatusLock, async () => {
await processFaultStatus();
});
// ==================== 公开 API ====================
export function startSchedule(): void { dataSyncTask.start(); }
export function stopSchedule(): void { dataSyncTask.stop(); }
export function startAlertSchedule(): void { alertTask.start(); }
export function stopAlertSchedule(): void { alertTask.stop(); }
export function startFaultStatusSchedule(): void { faultStatusTask.start(); }
export function stopFaultStatusSchedule(): void { faultStatusTask.stop(); }
...@@ -2,6 +2,7 @@ import { initConfig, systemConfig} from "./config/serverConfig"; ...@@ -2,6 +2,7 @@ import { initConfig, systemConfig} from "./config/serverConfig";
import * as mysqlDB from "./db/mysqlInit"; import * as mysqlDB from "./db/mysqlInit";
import { initMysqlModel } from "./model/sqlModelBind"; import { initMysqlModel } from "./model/sqlModelBind";
import { httpServer } from "./net/http_server"; import { httpServer } from "./net/http_server";
import { startSchedule, startAlertSchedule, startFaultStatusSchedule } from "./config/schedule";
async function lanuch() { async function lanuch() {
...@@ -12,6 +13,12 @@ async function lanuch() { ...@@ -12,6 +13,12 @@ async function lanuch() {
await initMysqlModel(); await initMysqlModel();
/**创建http服务 */ /**创建http服务 */
httpServer.createServer(systemConfig.port); httpServer.createServer(systemConfig.port);
/**启动定时任务 */
startSchedule();
/**启动预警工单定时任务(10分钟) */
// startAlertSchedule();
/**启动故障状态更新定时任务(5分钟) */
// startFaultStatusSchedule();
console.log('This indicates that the server is started successfully.'); console.log('This indicates that the server is started successfully.');
......
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