From 612c91f9ff872a3689a434dd09de7ba6e751f24a Mon Sep 17 00:00:00 2001 From: D8D Developer Date: Fri, 27 Jun 2025 02:01:03 +0000 Subject: [PATCH] u --- src/server/utils/http.ts | 39 ------ src/server/utils/ip_monitor.ts | 154 ---------------------- src/server/utils/logger.ts | 1 - src/server/utils/modbus_rtu.ts | 149 ---------------------- src/server/utils/monitor-utils.ts | 108 ---------------- src/server/utils/sms.com.ts | 204 ------------------------------ 6 files changed, 655 deletions(-) delete mode 100644 src/server/utils/http.ts delete mode 100644 src/server/utils/ip_monitor.ts delete mode 100644 src/server/utils/modbus_rtu.ts delete mode 100644 src/server/utils/monitor-utils.ts delete mode 100644 src/server/utils/sms.com.ts diff --git a/src/server/utils/http.ts b/src/server/utils/http.ts deleted file mode 100644 index d90b63a..0000000 --- a/src/server/utils/http.ts +++ /dev/null @@ -1,39 +0,0 @@ -export async function fetchWithRetry( - url: string, - options: { - method: string - headers: Record - body: string - timeout: number - maxRetries: number - } -): Promise { - let lastError: unknown = null - - for (let attempt = 0; attempt <= options.maxRetries; attempt++) { - try { - const controller = new AbortController() - const timeoutId = setTimeout( - () => controller.abort(), - options.timeout - ) - - const response = await fetch(url, { - ...options, - signal: controller.signal - }) - - clearTimeout(timeoutId) - return response - - } catch (error) { - lastError = error instanceof Error ? error : new Error(String(error)) - if (attempt < options.maxRetries) { - await new Promise(resolve => - setTimeout(resolve, 1000 * (attempt + 1))) - } - } - } - - throw lastError instanceof Error ? lastError : new Error(String(lastError || '请求失败')) -} \ No newline at end of file diff --git a/src/server/utils/ip_monitor.ts b/src/server/utils/ip_monitor.ts deleted file mode 100644 index 14c438d..0000000 --- a/src/server/utils/ip_monitor.ts +++ /dev/null @@ -1,154 +0,0 @@ -import debug from "debug" -import net from 'node:net'; - -const log = { - app: debug('ip_monitor') -}; - -// IP 监控配置接口 -interface IPMonitorConfig { - timeout?: number; // 超时时间(毫秒) - retries?: number; // 重试次数 - interval?: number; // 监控间隔(毫秒) -} - -// IP 监控结果接口 -export interface IPMonitorResult { - success: boolean; // 是否成功 - responseTime?: number; // 响应时间(毫秒) - packetLoss?: number; // 丢包率(百分比) - error?: string; // 错误信息 -} - -// 默认配置 -const DEFAULT_CONFIG: IPMonitorConfig = { - timeout: 1000, - retries: 3, - interval: 60000 -}; - -// IP 监控类 -export class IPMonitor { - private config: IPMonitorConfig; - private monitoring: Map = new Map(); - - constructor(config: IPMonitorConfig = {}) { - this.config = { ...DEFAULT_CONFIG, ...config }; - } - - // 开始监控指定 IP - async startMonitor(ip: string, callback: (result: IPMonitorResult) => void) { - if (this.monitoring.has(ip)) { - log.app(`IP ${ip} 已经在监控中`); - return; - } - - const monitor = async () => { - try { - const result = await this.ping(ip); - callback(result); - } catch (error) { - log.app(`监控 IP ${ip} 时发生错误:`, error); - callback({ - success: false, - error: error instanceof Error ? error.message : '未知错误' - }); - } - }; - - // 立即执行一次 - await monitor(); - - // 设置定时监控 - const interval = setInterval(monitor, this.config.interval); - this.monitoring.set(ip, interval); - log.app(`开始监控 IP: ${ip}`); - } - - // 停止监控指定 IP - stopMonitor(ip: string) { - const interval = this.monitoring.get(ip); - if (interval) { - clearInterval(interval); - this.monitoring.delete(ip); - log.app(`停止监控 IP: ${ip}`); - } - } - - // 停止所有监控 - stopAll() { - for (const [ip, interval] of this.monitoring) { - clearInterval(interval); - this.monitoring.delete(ip); - } - log.app('已停止所有 IP 监控'); - } - - // 执行 ping 操作 - private async ping(ip: string): Promise { - const results: number[] = []; - let successCount = 0; - let totalTime = 0; - - for (let i = 0; i < this.config.retries!; i++) { - try { - const startTime = Date.now(); - const result = await this.tcpPing(ip); - const endTime = Date.now(); - - if (result) { - const responseTime = endTime - startTime; - results.push(responseTime); - totalTime += responseTime; - successCount++; - } - } catch (error) { - log.app(`Ping 尝试 ${i + 1} 失败:`, error); - } - } - - // 计算平均响应时间和丢包率 - const avgResponseTime = successCount > 0 ? totalTime / successCount : undefined; - const packetLoss = ((this.config.retries! - successCount) / this.config.retries!) * 100; - - return { - success: successCount > 0, - responseTime: avgResponseTime, - packetLoss: packetLoss - }; - } - - // TCP ping 实现 - private async tcpPing(ip: string): Promise { - return new Promise((resolve, reject) => { - const socket = new net.Socket(); - let timeout = false; - - // 设置超时 - const timer = setTimeout(() => { - timeout = true; - socket.destroy(); - reject(new Error('连接超时')); - }, this.config.timeout); - - socket.on('connect', () => { - clearTimeout(timer); - socket.destroy(); - resolve(true); - }); - - socket.on('error', (error) => { - clearTimeout(timer); - if (!timeout) { - reject(error); - } - }); - - // 尝试连接 - socket.connect(80, ip); - }); - } -} - -// 导出单例实例 -export const ipMonitor = new IPMonitor(); \ No newline at end of file diff --git a/src/server/utils/logger.ts b/src/server/utils/logger.ts index fb75678..f7253cb 100644 --- a/src/server/utils/logger.ts +++ b/src/server/utils/logger.ts @@ -5,5 +5,4 @@ export const logger = { api: debug('backend:api'), db: debug('backend:db'), middleware: debug('backend:middleware'), - k8s: debug('backend:k8s') }; \ No newline at end of file diff --git a/src/server/utils/modbus_rtu.ts b/src/server/utils/modbus_rtu.ts deleted file mode 100644 index 55b5db5..0000000 --- a/src/server/utils/modbus_rtu.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Socket } from "node:net"; -import { Buffer } from "node:buffer"; - -interface ModbusRTUTestOptions { - ip: string; - port: number; - frameHexStrings?: string[]; -} - -interface ModbusRTUTestResult { - connected: boolean; - message: string; - response?: string; - error?: string; -} - -/** - * Modbus RTU设备连接 - * @param options 连接参数 {ip, port, frameHexStrings} - * @returns 连接结果 - */ -export async function modbusRTUConnection( - options: ModbusRTUTestOptions -): Promise { - try { - const { ip, port, frameHexStrings } = options; - - if (!ip || !port) { - throw new Error("缺少IP或端口参数"); - } - - const socket = new Socket(); - const timeout = 2000; - // 默认测试帧: 设备地址2, 功能码4(读取输入寄存器), 起始地址0, 读取2个寄存器 - const defaultTestFrame = [0x02, 0x04, 0x00, 0x00, 0x00, 0x02, 0x71, 0xF8]; - let testFrame; - - if (frameHexStrings) { - // 16进制字符串数组格式处理 - if (!Array.isArray(frameHexStrings)) { - throw new Error("测试帧格式错误: 必须是16进制字符串数组"); - } - try { - testFrame = Buffer.from(frameHexStrings.map(s => parseInt(s, 16))); - } catch (e) { - throw new Error("测试帧格式错误: 16进制字符串转换失败"); - } - } else { - testFrame = Buffer.from(defaultTestFrame); - } - - return await new Promise((resolve) => { - socket.setTimeout(timeout); - - socket.on('connect', () => { - socket.write(testFrame); - }); - - socket.on('data', (data) => { - const hexResponse = data.toString('hex'); - socket.destroy(); - resolve({ - connected: true, - message: "连接成功", - response: hexResponse - }); - }); - - socket.on('timeout', () => { - socket.destroy(); - resolve({ - connected: false, - message: "连接超时" - }); - }); - - socket.on('error', (error) => { - socket.destroy(); - resolve({ - connected: false, - message: `连接失败: ${error.message}` - }); - }); - - socket.connect(Number(port), ip); - }); - - } catch (error: unknown) { - console.error("Modbus RTU测试连接错误:", error); - const message = error instanceof Error ? error.message : "未知错误"; - return { - connected: false, - message: `测试连接失败: ${message}` - }; - } -} - -/** - * 读取Modbus RTU寄存器数据 - * @param address 设备地址 - * @param register 寄存器地址 - * @param length 读取长度 - * @returns 寄存器数据数组 - */ -export async function readModbusRTU( - address: string, - register: number, - length: number -): Promise { - try { - const [ip, portStr] = address.split(':'); - const port = parseInt(portStr); - - if (!ip || !port) { - throw new Error("设备地址格式错误,应为IP:PORT"); - } - - // 构建读取输入寄存器命令帧 - const frame = [ - 0x02, // 设备地址 - 0x04, // 功能码(读取输入寄存器) - (register >> 8) & 0xFF, // 寄存器地址高字节 - register & 0xFF, // 寄存器地址低字节 - (length >> 8) & 0xFF, // 长度高字节 - length & 0xFF, // 长度低字节 - 0x71, 0xF8 // CRC校验(示例值) - ]; - - const result = await modbusRTUConnection({ - ip, - port, - frameHexStrings: frame.map(b => b.toString(16)) - }); - - if (result.connected && result.response) { - // 解析返回数据(示例) - const bytes = Buffer.from(result.response, 'hex'); - const data = []; - for (let i = 3; i < bytes.length - 2; i++) { - data.push(bytes[i]); - } - return data; - } - return null; - } catch (error) { - console.error("读取Modbus寄存器失败:", error); - return null; - } -} \ No newline at end of file diff --git a/src/server/utils/monitor-utils.ts b/src/server/utils/monitor-utils.ts deleted file mode 100644 index ec4ee1b..0000000 --- a/src/server/utils/monitor-utils.ts +++ /dev/null @@ -1,108 +0,0 @@ -import debug from 'debug'; -import { MetricType } from '@/share/monitorTypes'; - -const log = debug('app:monitor-utils'); - -/** - * 解析温湿度传感器十六进制数据 - * @param hexData 十六进制数据字符串,格式如"[02 04 04 01 02 02 65 A9 F3]" - * @returns { temperature: number, humidity: number } 解析后的温湿度对象 - * @throws 如果数据格式无效会抛出错误 - */ -export function parseTemperatureHumidity(hexData: string): { - temperature: { value: number; unit: string }; - humidity: { value: number; unit: string }; - error?: string; -} { - try { - // 验证数据格式 - if (!/^\[\s*(?:[0-9A-Fa-f]{2}\s*)+\]$/.test(hexData)) { - return { - temperature: { value: 0, unit: '°C' }, - humidity: { value: 0, unit: '%' }, - error: '数据格式错误: 必须以方括号开头和结尾' - }; - } - - // 提取十六进制字节数组 - const bytes = hexData - .replace(/[\[\]\s]/g, '') - .match(/.{1,2}/g) - ?.map(byte => parseInt(byte, 16)) || []; - - if (bytes.length < 7) { - return { - temperature: { value: 0, unit: '°C' }, - humidity: { value: 0, unit: '%' }, - error: '数据长度不足: 至少需要7个十六进制字节' - }; - } - - // 解析温度值 (01位) - const tempInt = bytes[3]; // 整数部分 - const tempFrac = bytes[4]; // 小数部分 - const temperature = parseFloat(`${tempInt}.${tempFrac}`); - - // 解析湿度值 (02位) - const humidityInt = bytes[5]; // 整数部分 - const humidityFrac = bytes[6]; // 小数部分 - const humidity = parseFloat(`${humidityInt}.${humidityFrac}`); - - return { - temperature: { value: temperature, unit: '°C' }, - humidity: { value: humidity, unit: '%' } - }; - } catch (error) { - return { - temperature: { value: 0, unit: '°C' }, - humidity: { value: 0, unit: '%' }, - error: '数据解析失败: ' + (error instanceof Error ? error.message : String(error)) - }; - } -} - -/** - * 从MODBUS设备读取传感器数据 - */ -export async function readModbusSensorData( - address: string, - metricType: string -): Promise<{ value: number; unit: string } | null> { - try { - // 根据传感器类型设置不同的寄存器地址 - let registerAddress = 0; - switch (metricType) { - case MetricType.TEMPERATURE: - registerAddress = 0x1000; // 温度寄存器地址 - break; - case MetricType.HUMIDITY: - registerAddress = 0x1002; // 湿度寄存器地址 - break; - case 'smoke': - registerAddress = 0x1004; // 烟感寄存器地址 - break; - case 'water': - registerAddress = 0x1006; // 水浸寄存器地址 - break; - } - - const { readModbusRTU } = await import('@/server/utils/modbus_rtu'); - const result = await readModbusRTU(address, registerAddress, 2); - - // 解析返回的数据 - if (result && result.length >= 2) { - const value = (result[0] << 8) | result[1]; // 组合高低字节 - return { - value: metricType === 'smoke' || metricType === 'water' - ? value > 0 ? 1 : 0 // 烟感/水浸为开关量 - : value / 10, // 温湿度为模拟量,除以10得到实际值 - unit: metricType === MetricType.TEMPERATURE ? '°C' : - metricType === MetricType.HUMIDITY ? '%' : '' - }; - } - return null; - } catch (error) { - log(`读取MODBUS传感器数据失败: ${error}`); - return null; - } -} \ No newline at end of file diff --git a/src/server/utils/sms.com.ts b/src/server/utils/sms.com.ts deleted file mode 100644 index 5034de3..0000000 --- a/src/server/utils/sms.com.ts +++ /dev/null @@ -1,204 +0,0 @@ -import type { Context } from 'hono' -import type { - DeviceStatus, - SmsItem, - SmsConfig, - SmsApiRequest, - SmsApiResponse, - SmsError, - SmsMetrics -} from '../types/smsTypes.js' -import { getSystemSettings } from './systemSettings.js' -import { createHash } from 'node:crypto' -import { fetchWithRetry } from './http.js' -import { logger } from './logger.js' - -// 加密密钥(应从环境变量获取) -const ENCRYPT_KEY = process.env.SMS_ENCRYPT_KEY || 'default-encrypt-key' - -// 性能指标 -const smsMetrics: SmsMetrics = { - requestCount: 0, - successCount: 0, - failureCount: 0, - averageLatency: 0 -} - -// 加密函数 -function encryptPassword(password: string): string { - return createHash('sha256') - .update(password + ENCRYPT_KEY) - .digest('hex') -} - -// 生成Basic认证头 -function generateAuthHeader(username: string, password: string): string { - const text = `${username}:${password}` - const bytes = new TextEncoder().encode(text) - const token = btoa(String.fromCharCode(...bytes)) - return `Basic ${token}` -} - -// 获取短信配置 -async function getSmsConfig(): Promise { - const settings = await getSystemSettings() - return { - apiUrl: settings.apiUrl, - username: settings.username, - encryptedPassword: settings.encryptedPassword, - timeout: settings.timeout ?? 5000, - maxRetries: settings.maxRetries ?? 3 - } -} - -// 模拟数据存储 -const mockDeviceStatus: DeviceStatus = { - signalStrength: 85, - carrier: '中国移动', - mode: '短信' -} - -const mockSmsList: SmsItem[] = [] - -export const SmsController = { - async login(ctx: Context) { - const { username, password } = await ctx.req.json() - - if (username === 'vsmsd' && password === 'Vsmsd123') { - return ctx.json({ - success: true, - token: 'dummy-token-for-demo' - }) - } - - return ctx.json({ success: false }, 401) - }, - - async getDeviceStatus(ctx: Context) { - return ctx.json({ - data: { - status: mockDeviceStatus, - list: mockSmsList - } - }) - }, - - async sendSms(ctx: Context) { - const { phone, content } = await ctx.req.json() - if (!phone || !content) { - return ctx.json({ - success: false, - message: '手机号和短信内容不能为空' - }, 400) - } - - const taskId = `task-${Date.now()}` - const newSms: SmsItem = { - id: Date.now().toString(), - phone, - content, - taskId, - status: 'pending', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() - } - - try { - const config = await getSmsConfig() - const startTime = Date.now() - - const response = await fetchWithRetry(config.apiUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': generateAuthHeader(config.username, config.encryptedPassword) - }, - body: JSON.stringify({ phone, content }), - timeout: config.timeout ?? 5000, - maxRetries: config.maxRetries ?? 3 - }) - - const data: SmsApiResponse = await response.json() - - if (data.success) { - newSms.status = 'success' - smsMetrics.successCount++ - logger.info(`短信发送成功: ${taskId}`, { phone, taskId }) - } else { - newSms.status = 'failed' - smsMetrics.failureCount++ - logger.error(`短信发送失败: ${data.code} - ${data.message}`, { - phone, - taskId, - error: data - }) - } - - const latency = Date.now() - startTime - smsMetrics.requestCount++ - smsMetrics.averageLatency = - (smsMetrics.averageLatency * (smsMetrics.requestCount - 1) + latency) / - smsMetrics.requestCount - smsMetrics.lastRequestTime = new Date().toISOString() - - } catch (error) { - // 真实接口失败时使用模拟发送作为fallback - newSms.status = 'success' // 模拟成功 - if (error instanceof Error) { - logger.warn(`使用模拟短信发送: ${error.message}`, { - phone, - taskId, - error: error.stack - }) - } else { - logger.warn(`使用模拟短信发送: ${String(error)}`, { - phone, - taskId - }) - } - } - - mockSmsList.unshift(newSms) - - return ctx.json({ - success: true, - data: newSms - }) - }, - - async getSmsResult(ctx: Context) { - const id = ctx.req.param('id') - const sms = mockSmsList.find(item => item.id === id) - - if (!sms) { - return ctx.json({ - success: false, - message: '未找到该短信记录' - }, 404) - } - - // 模拟短信发送结果详情 - return ctx.json({ - success: true, - data: { - ...sms, - results: [ - { - serial: '001', - carrier: '中国移动', - time: new Date().toISOString(), - status: sms.status === 'pending' ? '处理中' : - sms.status === 'success' ? '成功' : '失败' - } - ] - } - }) - }, - - async getMetrics(ctx: Context) { - return ctx.json({ - success: true, - data: smsMetrics - }) - } -} \ No newline at end of file