联动修改

parent 027f2027
......@@ -3,6 +3,7 @@
/node_modules
/test
/public
/scripts
/logs
/video
/files
......
......@@ -24,4 +24,7 @@
<username>admin</username>
<password>admin123</password>
</mqttsrv>
<controller>
<is_use>true</is_use>
</controller>
</config>
......@@ -7,6 +7,8 @@ import * as crypto from 'crypto';
import { post, get } from '../util/request';
import { BizError } from '../util/bizError';
import { ERRORENUM } from '../config/errorEnum';
import { addData } from '../data/addData';
import { TABLENAME } from '../config/dbEnum';
// 全局变量缓存 token 和过期时间
let feiYiToken = "";
......@@ -124,6 +126,20 @@ export async function getFeiYiToken(): Promise<string> {
}
/**
* 接口路径 -> 中文描述映射
*/
const PATH_DESC_MAP: Record<string, string> = {
'/openApi/gw/page': '查询网关设备列表',
'/openApi/indoorUnit/list': '查询内机列表',
'/openApi/indoorUnit/control': '控制内机开关',
'/openApi/building/tree': '查询建筑物树结构',
'/openApi/electricMeter/getAll': '查询电表列表',
'/openApi/electricMeterState/list': '查询抄表记录',
'/openApi/indoorUnit/event/switch': '查询内机开关机记录',
'/openApi/indoorUnit/alarm/errorHis': '查询内机故障记录',
};
/**
* 带 Token 的 POST 请求封装
* 自动获取/刷新 token
* @param path 接口路径(相对路径)
......@@ -131,32 +147,96 @@ export async function getFeiYiToken(): Promise<string> {
* @param retry 是否重试(用于 token 过期重试)
*/
async function feiYiPost(path: string, body: any, retry: boolean = true): Promise<any> {
const url = `${FEIYI_CONFIG.baseUrl}${path}`;
const requestTime = new Date();
// 获取 token
const token = await getFeiYiToken();
const url = `${FEIYI_CONFIG.baseUrl}${path}`;
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
};
let result: any;
let responseStatus: string;
let responseTime: Date;
let dbStatus: number;
try {
result = await post(url, body, headers);
responseTime = new Date();
console.log(`请求 ${path} 成功,响应:`, result?.code);
if (result.code === '00000') {
dbStatus = 1;
responseStatus = '正常';
} else {
dbStatus = 0;
responseStatus = '请求报错';
}
} catch (err) {
responseTime = new Date();
dbStatus = 0;
// 区分连接超时和其他网络错误
if (err.code === 'ETIMEDOUT' || err.code === 'ECONNABORTED' || err.message?.includes('timeout')) {
responseStatus = '连接超时';
} else if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
responseStatus = '连接失败';
} else {
responseStatus = '请求报错';
}
// 记录日志(网络异常,无响应数据)
addData(TABLENAME.数据接入日志表, {
partner_name: '飞奕',
request_url: url,
request_desc: PATH_DESC_MAP[path] || '',
request_params: body,
response_data: null,
status: dbStatus,
response_status: responseStatus,
request_time: requestTime,
response_time: responseTime,
}).catch(e => console.error('写入数据接入日志失败:', e));
throw new BizError(ERRORENUM.网络错误, `请求 ${path} 失败: ${err.message}`);
}
// 如果 token 过期(示例错误码可能为 token 过期,文档未明确,假设 code 为 401 或其他)
// 这里简单判断如果 code 不为 '00000' 且包含 token 相关错误,则重试一次
// 如果 token 过期,重试一次
if (result.code !== '00000') {
if (retry && (result.code === '401' || result.message?.includes('token'))) {
// 可选:清除 token 缓存,重新获取
return feiYiPost(path, body, false);
}
throw new BizError(ERRORENUM.第三方接口错误, `接口 ${path} 调用失败: ${result.message}`); // || JSON.stringify(result)
// 非 token 错误,记录日志后抛异常
addData(TABLENAME.数据接入日志表, {
partner_name: '飞奕',
request_url: url,
request_desc: PATH_DESC_MAP[path] || '',
request_params: body,
response_data: result,
status: dbStatus,
response_status: responseStatus,
request_time: requestTime,
response_time: responseTime,
}).catch(e => console.error('写入数据接入日志失败:', e));
throw new BizError(ERRORENUM.第三方接口错误, `接口 ${path} 调用失败: ${result.message}`);
}
// 成功也记录日志
addData(TABLENAME.数据接入日志表, {
partner_name: '飞奕',
request_url: url,
request_desc: PATH_DESC_MAP[path] || '',
request_params: body,
response_data: result,
status: dbStatus,
response_status: responseStatus,
request_time: requestTime,
response_time: responseTime,
}).catch(e => console.error('写入数据接入日志失败:', e));
return result;
}
......
......@@ -2,6 +2,7 @@ import { TABLENAME } from "../config/dbEnum";
import { addData } from "../data/addData";
import { selectOneDataByParam } from "../data/findData";
import { controlAcByEnvironment } from "./running";
import { systemConfig } from "../config/serverConfig";
/**
* 处理接收到的环境设备数据,并存入数据库
......@@ -26,14 +27,32 @@ import { controlAcByEnvironment } from "./running";
* }
*/
export async function processDeviceData(message) {
const requestTime = new Date();
const msgStr = message.toString();
const topic = systemConfig.mqttsrv.topic_env;
const writeLog = (status: number, responseStatus: string, responseData?: any) => {
addData(TABLENAME.数据接入日志表, {
partner_name: '星纵物联',
request_url: topic,
request_desc: '环境监测数据推送',
request_params: msgStr,
response_data: responseData ?? null,
status,
response_status: responseStatus,
request_time: requestTime,
response_time: new Date(),
}).catch(e => console.error('写入数据接入日志失败:', e));
};
try {
// 解析JSON消息
const msgStr = message.toString();
let data: any;
try {
data = JSON.parse(msgStr);
} catch (e) {
console.error('消息不是合法JSON,跳过:', msgStr);
writeLog(0, '消息格式错误');
return;
}
console.log('收到环境数据:', data.length);
......@@ -42,6 +61,7 @@ export async function processDeviceData(message) {
const devEUI = data.devEUI ? data.devEUI.toUpperCase() : null;
if (!devEUI) {
console.warn('消息缺少devEUI字段,跳过');
writeLog(0, '缺少devEUI');
return;
}
const gatewayTime = data.gatewayTime ? new Date(data.gatewayTime) : new Date();
......@@ -59,6 +79,7 @@ export async function processDeviceData(message) {
);
if (!deviceRow && !deviceRow.data) {
console.warn(`设备不存在,跳过入库: ${devEUI}`);
writeLog(0, '设备不存在');
return;
}
......@@ -88,6 +109,7 @@ export async function processDeviceData(message) {
);
console.log(`数据入库成功: 设备 ${devEUI}`);
writeLog(1, '正常');
// 晚上 23:00 ~ 次日 05:00 不执行空调自控(休眠时段)
const currentHour = new Date().getHours();
......@@ -114,6 +136,7 @@ export async function processDeviceData(message) {
} catch (error) {
console.error('数据处理失败:', error);
writeLog(0, '数据处理异常');
}
}
......@@ -143,14 +166,32 @@ interface CustomerTriggerItem {
* // region_trigger_data: { region_count_data: [ { region: 1, region_name: 'Region1', region_uuid: '...', total: { current_total: 9 } } ] }
*/
export async function customerDeviceData(message) {
const requestTime = new Date();
const msgStr = message.toString();
const topic = systemConfig.mqttsrv.topic_crowd;
const writeLog = (status: number, responseStatus: string, responseData?: any) => {
addData(TABLENAME.数据接入日志表, {
partner_name: '星纵物联',
request_url: topic,
request_desc: '客流数据推送',
request_params: msgStr,
response_data: responseData ?? null,
status,
response_status: responseStatus,
request_time: requestTime,
response_time: new Date(),
}).catch(e => console.error('写入数据接入日志失败:', e));
};
try {
// 解析JSON消息
const msgStr = message.toString();
let data: any;
try {
data = JSON.parse(msgStr);
} catch (e) {
console.error('消息不是合法JSON,跳过:', msgStr);
writeLog(0, '消息格式错误');
return;
}
console.log('收到客流数据:', msgStr);
......@@ -159,11 +200,13 @@ export async function customerDeviceData(message) {
const deviceInfo = data.device_info;
if (!deviceInfo) {
console.warn('消息缺少device_info字段,跳过');
writeLog(0, '缺少device_info');
return;
}
const deviceSn = deviceInfo.device_sn ? deviceInfo.device_sn.toUpperCase() : null; // 设备序列号,转大写作为设备标识
if (!deviceSn) {
console.warn('消息缺少device_info.device_sn字段,跳过');
writeLog(0, '缺少device_sn');
return;
}
const deviceMac = deviceInfo.device_mac;
......@@ -206,6 +249,7 @@ export async function customerDeviceData(message) {
if (triggerItems.length === 0) {
console.warn('消息缺少客流线路数据,跳过');
writeLog(0, '缺少客流线路数据');
return;
}
......@@ -254,9 +298,11 @@ export async function customerDeviceData(message) {
}
console.log(`客流数据处理完成: 设备 ${deviceSn}, 共入库 ${triggerItems.length} 条数据`);
writeLog(1, '正常');
} catch (error) {
console.error('客流数据处理失败:', error);
writeLog(0, '数据处理异常');
}
}
......
......@@ -219,7 +219,13 @@ function calcMeterDeviceDaily(records: any[]): number {
* @param weekOffset 0=最近七天(默认), -1=再往前七天, ...
*/
export async function getWeeklyReportData(weekOffset: number = 0): Promise<any> {
const { start: monday, end: sunday, timer } = calcWeekRange(weekOffset);
// ==== 固定查询 7月29日 ~ 8月4日 ====
// const { start: monday, end: sunday, timer } = calcWeekRange(weekOffset);
const start = new Date(2026, 6, 29, 0, 0, 0, 0); // 7月29日 00:00
const end = new Date(2026, 7, 4, 23, 59, 59, 999); // 8月4日 23:59
const monday = start;
const sunday = end;
const timer = `${formatChineseDate(monday)}${formatChineseDate(sunday)}`;
console.log(`[周报告JSON] 查询数据: ${timer}`);
// ===== 第一步:并行查询区域 + 各馆设备分组 =====
......@@ -394,12 +400,15 @@ const ENV_INDICATOR_KEYS = [
*/
export async function getEnvIndicatorsWeeklyAvg(): Promise<any> {
// 1. 计算七天时间范围
const now = new Date();
const end = new Date(now);
end.setHours(23, 59, 59, 999);
const start = new Date(now);
start.setDate(now.getDate() - 6);
start.setHours(0, 0, 0, 0);
// ==== 固定查询 7月29日 ~ 8月4日 ====
// const now = new Date();
// const end = new Date(now);
// end.setHours(23, 59, 59, 999);
// const start = new Date(now);
// start.setDate(now.getDate() - 6);
// start.setHours(0, 0, 0, 0);
const start = new Date(2026, 6, 29, 0, 0, 0, 0); // 7月29日 00:00
const end = new Date(2026, 7, 4, 23, 59, 59, 999); // 8月4日 23:59
const from = formatLocalTime(start);
const to = formatLocalTime(end);
......@@ -486,12 +495,15 @@ const LEVEL_MAP: { [key: number]: string } = { 1: "一级", 2: "二级", 3: "三
* 每组展示:故障代码、风险内容、报警时间、处理状态、报警设备
*/
export async function getFaultInfoLast7Days(): Promise<any> {
const now = new Date();
const end = new Date(now);
end.setHours(23, 59, 59, 999);
const start = new Date(now);
start.setDate(now.getDate() - 6);
start.setHours(0, 0, 0, 0);
// ==== 固定查询 7月29日 ~ 8月4日 ====
// const now = new Date();
// const end = new Date(now);
// end.setHours(23, 59, 59, 999);
// const start = new Date(now);
// start.setDate(now.getDate() - 6);
// start.setHours(0, 0, 0, 0);
const start = new Date(2026, 6, 29, 0, 0, 0, 0); // 7月29日 00:00
const end = new Date(2026, 7, 4, 23, 59, 59, 999); // 8月4日 23:59
const from = formatLocalTime(start);
const to = formatLocalTime(end);
......
......@@ -10,7 +10,8 @@ export enum TABLENAME {
用户信息表 = 'user_info',
设备日志表 = 'device_logs',
日程表 = 'schedule',
日程设备关联表 = 'schedule_device'
日程设备关联表 = 'schedule_device',
数据接入日志表 = 'data_access_log'
};
/**
......
import { response } from "express";
const { Sequelize, DataTypes } = require('sequelize');
export const TablesConfig = [
......@@ -169,6 +171,12 @@ export const TablesConfig = [
defaultValue: 0,
comment: '设备状态:0=正常 1=硬件故障 2=通信故障 3=离线'
},
linkage_start: {
type: DataTypes.STRING(10),
allowNull: true,
defaultValue: 'on',
comment: '联动启用:on=开 off=关'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
......@@ -417,6 +425,80 @@ export const TablesConfig = [
{ type: "hasMany", target: "schedule_device", foreignKey: "schedule_id" }
]
},
// 数据接入日志表
{
tableNameCn: '数据接入日志表',
tableName: 'data_access_log',
schema: {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
comment: '自增主键'
},
partner_name: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '对接方名称,如“飞奕”'
},
request_url: {
type: DataTypes.STRING(500),
allowNull: false,
comment: '请求的第三方接口地址'
},
request_desc: {
type: DataTypes.STRING(200),
allowNull: true,
comment: '请求描述,如"查询设备列表""控制空调开关"'
},
request_params: {
type: DataTypes.JSON,
allowNull: true,
comment: '请求参数'
},
response_data: {
type: DataTypes.JSON,
allowNull: true,
comment: '第三方返回的数据'
},
status: {
type: DataTypes.TINYINT,
allowNull: false,
defaultValue: 1,
comment: '请求状态,0失败1成功'
},
request_time: {
type: DataTypes.DATE,
allowNull: true,
comment: '请求时间'
},
response_time: {
type: DataTypes.DATE,
allowNull: true,
comment: '响应时间'
},
response_status: {
type: DataTypes.STRING(100),
allowNull: true,
comment: '响应状态描述,如"连接超时""请求报错""正常"等'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '创建时间'
}
},
association: [],
indexes: [
{ fields: ['partner_name'] },
{ fields: ['status'] },
{ fields: ['request_time'] },
{ fields: ['partner_name', 'status'] },
{ fields: ['partner_name', 'request_time'] },
]
},
// 日程设备关联表
{
tableNameCn: '日程设备关联表',
......
......@@ -19,13 +19,13 @@ async function lanuch() {
/**创建http服务 */
httpServer.createServer(systemConfig.port);
/**启动MQTT订阅服务 */
// await mqttSrv.startMqttClient();
await mqttSrv.startMqttClient();
/**启动定时任务 */
// startSchedule();
startSchedule();
/**启动预警工单定时任务(10分钟) */
// startAlertSchedule();
startAlertSchedule();
/**启动故障状态更新定时任务(5分钟) */
// startFaultStatusSchedule();
startFaultStatusSchedule();
/**启动日程执行器 */
// await initScheduleTasks();
......
......@@ -25,7 +25,7 @@ export async function initMysqlModel() {
/**第一步:初始化所有表 */
for (let i = 0; i < TablesConfig.length; i++) {
let { tableName, schema } = TablesConfig[i];
let { tableName, schema, indexes } = TablesConfig[i];
if (!tableName) {
console.warn(`⚠️ 第 ${i} 个表配置缺少 tableName,跳过`);
......@@ -39,10 +39,13 @@ export async function initMysqlModel() {
console.log(`🔄 正在初始化表: ${tableName}`);
let schemaConf = {
let schemaConf: any = {
freezeTableName: true,
timestamps: false
};
if (indexes && Array.isArray(indexes) && indexes.length) {
schemaConf.indexes = indexes;
}
try {
let model = mysqlDB.define(tableName, schema, schemaConf);
......
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