From b27355fe55abf32a32c744d68831643a0e6091e6 Mon Sep 17 00:00:00 2001
From: wangrong <wangrong@shsening.com>
Date: 星期一, 03 八月 2026 19:23:04 +0800
Subject: [PATCH] 更新页面标题和内容,优化萤石云接口处理逻辑,增加摄像头告警事件保存功能

---
 public/index.html                       |    4 
 src/modules/push/service/open.ts        |  153 +++++++++++++++----------
 src/modules/push/controller/app/open.ts |  160 ++++++++++++++++++++++++--
 src/config/config.default.ts            |   40 ------
 4 files changed, 242 insertions(+), 115 deletions(-)

diff --git a/public/index.html b/public/index.html
index 10b2ba8..99141b6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,14 +5,14 @@
 	<meta charset="UTF-8" />
 	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
 	<meta http-equiv="X-UA-Compatible" content="ie=edge" />
-	<title>COOL-ADMIN 涓�涓緢閰风殑鍚庡彴鏉冮檺绠$悊绯荤粺</title>
+	<title>鍚庡彴鏉冮檺绠$悊绯荤粺</title>
 	<meta name="keywords" content="cool-admin锛屽悗鍙扮鐞嗙郴缁燂紝vue锛宔lement-ui锛宯odejs" />
 	<meta name="description" content="element-ui銆乵idway.js銆乵ysql銆乺edis銆乶ode.js銆佸墠鍚庣鍒嗙銆佹潈闄愮鐞嗐�佸揩閫熷紑鍙戯紝 COOL-AMIND 涓�涓緢閰风殑鍚庡彴鏉冮檺绠$悊绯荤粺" />
 	<link rel="stylesheet" href="css/welcome.css">
     <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
 
 <body>
-<div class="reveal">HELLO COOL-ADMIN AI蹇�熷紑鍙戞鏋�</div>
+<div class="reveal">鏃犲簾鏍″洯杩愯惀绠$悊骞冲彴</div>
 
 <!-- 娣诲姞搴曢儴璇存槑 -->
 <div class="footer-bar">
diff --git a/src/config/config.default.ts b/src/config/config.default.ts
index 1a5876a..03a7323 100644
--- a/src/config/config.default.ts
+++ b/src/config/config.default.ts
@@ -5,20 +5,15 @@
 import { pCachePath, pUploadPath } from '../comm/path';
 import { availablePort } from '../comm/port';
 
-// redis缂撳瓨
-//import { redisStore } from 'cache-manager-ioredis-yet';
-
 export default {
-  // 纭繚姣忎釜椤圭洰鍞竴锛岄」鐩娆″惎鍔ㄤ細鑷姩鐢熸垚
   keys: 'f65bcafd-b72c-4f01-a451-333155097148',
   koa: {
     port: availablePort(8001),
+    rawBody: true,    
   },
-  // 寮�鍚紓姝ヤ笂涓嬫枃绠$悊
   asyncContextManager: {
     enable: true,
   },
-  // 闈欐�佹枃浠堕厤缃�
   staticFile: {
     buffer: true,
     dirs: {
@@ -32,12 +27,11 @@
       },
     },
   },
-  // 鏂囦欢涓婁紶
   upload: {
     fileSize: '200mb',
     whitelist: null,
+    ignore: ['/open/push/yingshiyun', "/open/push/cameraEvent"],    
   },
-  // 缂撳瓨 鍙垏鎹㈡垚鍏朵粬缂撳瓨濡傦細redis http://www.midwayjs.org/docs/extensions/caching
   cacheManager: {
     clients: {
       default: {
@@ -49,45 +43,19 @@
       },
     },
   },
-  /*
-  cacheManager: {
-     clients: {
-       default: {
-         store: redisStore,
-         options: {
-           port: 6379,
-           host: '127.0.0.1',
-           password: '',
-           ttl: 0,
-           db: 0,
-         },
-       },
-     },
-  },
-  */
   cool: {
-    // 宸茬粡鎻掍欢鍖栵紝鏈湴鏂囦欢涓婁紶鏌ョ湅 plugin/config.ts锛屽叾浠栦簯瀛樺偍鏌ョ湅瀵瑰簲鎻掍欢鐨勪娇鐢�
     file: {},
-    // 鏄惁寮�鍚绉熸埛
     tenant: {
-      // 鏄惁寮�鍚绉熸埛
       enable: true,
-      // 闇�瑕佽繃婊ゅ绉熸埛鐨剈rl, 鏀寔閫氶厤绗︼紝 濡�/admin/**/* 琛ㄧずadmin妯″潡涓嬬殑鎵�鏈夋帴鍙i兘杩涜澶氱鎴疯繃婊�
       urls: [],
     },
-    // 鍥介檯鍖栭厤缃�
     i18n: {
-      // 鏄惁寮�鍚�
       enable: true,
-      // 璇█
       languages: ['zh-cn', 'zh-tw', 'en'],
     },
-    // crud閰嶇疆
     crud: {
-      // 鎻掑叆妯″紡锛宻ave涓嶄細鏍¢獙瀛楁(鍏佽浼犲叆涓嶅瓨鍦ㄧ殑瀛楁)锛宨nsert浼氭牎楠屽瓧娈�
       upsert: 'save',
-      // 杞垹闄�
       softDelete: true,
-    },
+    },  
   } as CoolConfig,
-} as MidwayConfig;
+} as MidwayConfig;
\ No newline at end of file
diff --git a/src/modules/push/controller/app/open.ts b/src/modules/push/controller/app/open.ts
index 3d70d84..67199aa 100644
--- a/src/modules/push/controller/app/open.ts
+++ b/src/modules/push/controller/app/open.ts
@@ -1,11 +1,10 @@
-import { Inject, Post, Body, Headers } from '@midwayjs/core';
+import { Inject, Post, Get, Body, Headers, Fields, Files } from '@midwayjs/core';
 import { CoolController, BaseController } from '@cool-midway/core';
 import { PushOpenService } from '../../service/open';
 import { WeightUploadDto } from '../../dto/weight.dto';
 import * as crypto from 'crypto';
 import { BaseSysParamService } from '../../../base/service/sys/param';
-
-
+import { Context } from '@midwayjs/koa'; //
 
 /**
  * 寮�鏀炬棤闇�楠岃瘉鐨勪笂鎶ユ暟鎹帴鍙�
@@ -14,6 +13,9 @@
   prefix: '/open/push',
 })
 export class AppPushOpenController extends BaseController {
+  @Inject()
+  ctx: Context;
+
   @Inject()
   pushOpenService: PushOpenService;
 
@@ -88,16 +90,146 @@
     }
   }
 
-
   @Post('/yingshiyun', { summary: '钀ょ煶浜慦ebhook涓婃姤鎺ュ彛' })
-  async saveYingshiyunMessage(
-    @Body() body: any
-  ) {
-
-    const result = await this.pushOpenService.saveYingshiyunMessage(body);
-    //return this.ok(result);
-
-    return { "messageId": result }
-
+  async saveYingshiyunMessage() {
+    const req = this.ctx.req;
+    const rawBody = await new Promise<string>((resolve, reject) => {
+      let data = '';
+      const timer = setTimeout(() => reject(new Error('璇诲彇娴佽秴鏃�')), 5000);
+      req.on('data', chunk => data += chunk);
+      req.on('end', () => {
+        clearTimeout(timer);
+        resolve(data);
+      });
+      req.on('error', err => {
+        clearTimeout(timer);
+        reject(err);
+      });
+    }).catch(err => {
+      console.error('璇诲彇stream寮傚父锛�', err);
+      this.ctx.status = 200;
+      this.ctx.body = { messageId: '' };
+      return '';
+    });
+    if (!rawBody) {
+      this.ctx.status = 200;
+      this.ctx.body = { messageId: '' };
+      return;
+    }
+    console.log('銆愯悿鐭充簯鍘熷鎶ユ枃銆�:', rawBody.slice(0, 500));
+    let msg: any;
+    try {
+      msg = JSON.parse(rawBody);
+    } catch (e) {
+      console.error('JSON瑙f瀽澶辫触:', e);
+      this.ctx.status = 200;
+      this.ctx.body = { messageId: '' };
+      return;
+    }
+    const messageId = msg?.header?.messageId;
+    const msgType = msg?.header?.type; // "ys.onoffline" / "ys.alarm" / ...
+    console.log('娑堟伅绫诲瀷:', msgType, '| messageId:', messageId);
+    if (msgType === 'ys.alarm') {
+      console.log('鍛婅绫诲瀷:', msg.body?.alarmType, '| 鎻忚堪:', msg.body?.describe);
+    } else if (msgType === 'ys.onoffline') {
+      console.log('璁惧涓婁笅绾�:', msg.body?.msgType, '| 璁惧:', msg.body?.deviceName);
+    }
+    try {
+      await this.pushOpenService.saveYingshiyunMessage(msg);
+    } catch (e) {
+      console.error('鏁版嵁澶勭悊澶辫触:', e);
+      // 鍗充娇瀛樺簱澶辫触锛屼篃瑕佸洖 200 + messageId锛岄伩鍏嶈悿鐭抽噸璇�
+    }
+    this.ctx.status = 200;
+    this.ctx.set('Content-Type', 'application/json');
+    this.ctx.body = { messageId: messageId || '' };
   }
-}
+
+
+  /**
+   * 鎽勫儚鏈轰富鍔ㄦ帹閫佸憡璀︿簨浠讹紙绉诲姩渚︽祴VMD銆佷汉浣撲睛娴嬶紝XML multipart鏍煎紡锛�
+   * 鍦板潃锛歅OST /open/push/cameraEvent
+   */
+  @Post('/cameraEvent', { summary: '鎽勫儚澶翠富鍔ㄤ笂鎶ュ憡璀︿簨浠�' })
+  async receiveCameraEvent() {
+    const req = this.ctx.req;
+    let rawBody = '';
+
+    try {
+      rawBody = await new Promise<string>((resolve, reject) => {
+        let data = '';
+        const timer = setTimeout(() => reject(new Error('璇诲彇娴佽秴鏃�')), 5000);
+        req.on('data', (chunk) => (data += chunk));
+        req.on('end', () => {
+          clearTimeout(timer);
+          resolve(data);
+        });
+        req.on('error', (err) => {
+          clearTimeout(timer);
+          reject(err);
+        });
+      });
+    } catch (err) {
+      console.error('銆愭憚鍍忓ご浜嬩欢銆戣鍙杝tream寮傚父锛�', err);
+      this.ctx.status = 200;
+      this.ctx.body = { code: -1, msg: 'read error' };
+      return;
+    }
+    if (!rawBody) {
+      this.ctx.status = 200;
+      this.ctx.body = { code: -1, msg: 'empty body' };
+      return;
+    }
+
+    // 鎴彇瀹屾暣XML鏂囨湰
+    const xmlReg = /<\?xml[\s\S]*?<\/EventNotificationAlert>/;
+    const xmlMatch = rawBody.match(xmlReg);
+    if (!xmlMatch) {
+      console.warn('銆愭憚鍍忓ご浜嬩欢銆戞湭鍖归厤鍒板憡璀ML鍐呭');
+      this.ctx.status = 200;
+      this.ctx.body = { code: -1 };
+      return;
+    }
+    const xmlStr = xmlMatch[0];
+
+    // 閫氱敤XML鑺傜偣鎻愬彇锛堟敮鎸佹爣绛惧墠鍚庢崲琛岀┖鏍硷級
+    const getVal = (xml: string, tag: string) => {
+      const reg = new RegExp(`<${tag}>\\s*([\\s\\S]*?)\\s*<\\/${tag}>`);
+      const res = xml.match(reg);
+      return res ? res[1].trim() : '';
+    };
+
+    // 缁勮鍛婅瀹炰綋
+    const cameraEvent = {
+      ipAddress: getVal(xmlStr, 'ipAddress'),
+      macAddress: getVal(xmlStr, 'macAddress'),
+      portNo: getVal(xmlStr, 'portNo'),
+      channelID: getVal(xmlStr, 'channelID'),
+      channelName: getVal(xmlStr, 'channelName'),
+      dateTime: getVal(xmlStr, 'dateTime'),
+      eventType: getVal(xmlStr, 'eventType'), // VMD=绉诲姩渚︽祴
+      eventState: getVal(xmlStr, 'eventState'),
+      eventDescription: getVal(xmlStr, 'eventDescription'),
+      targetType: getVal(xmlStr, 'targetType'), // human 浜轰綋
+      targetID: getVal(xmlStr, 'targetID'),
+      // 鐩爣鍧愭爣妗�
+      rectX: getVal(xmlStr, 'X'),
+      rectY: getVal(xmlStr, 'Y'),
+      rectWidth: getVal(xmlStr, 'width'),
+      rectHeight: getVal(xmlStr, 'height'),
+    };
+
+    try {
+      // 璋冪敤service澶勭悊鎽勫儚澶村憡璀︿簨浠讹紝寤鸿鏂板缓service鏂规硶
+      const result = await this.pushOpenService.saveCameraAlarmEvent(cameraEvent);
+
+      this.ctx.status = 200;
+      this.ctx.set('Content-Type', 'application/json');
+      this.ctx.body = { success: true, messageId: result };
+    } catch (serviceErr) {
+      console.error('銆愭憚鍍忓ご浜嬩欢銆戜笟鍔″鐞嗗け璐ワ細', serviceErr);
+      this.ctx.status = 200;
+      this.ctx.body = { code: -1, msg: 'handle fail' };
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/modules/push/service/open.ts b/src/modules/push/service/open.ts
index f0882da..b6421f5 100644
--- a/src/modules/push/service/open.ts
+++ b/src/modules/push/service/open.ts
@@ -115,78 +115,98 @@
     const dataStr = await this.midwayCache.get(`push:yingshiyun:queue:${messageId}`) as string | null;
     if (!dataStr) return;
     const body = JSON.parse(dataStr);
-
     const type = body?.header?.type;
-
+    console.log(`---------------------------------${type}`);
     try {
       switch (type) {
-
         // 璁惧涓婁笅绾�
         case 'ys.onoffline': {
-          await this.pushDeviceOnoffEntity.save({
-            deviceId:body.header.deviceId,
-            channelNo: body.header.channelNo,
+          const onoffData: any = {
+            deviceId: body.header.deviceId,
+            channelNo: Number(body.header.channelNo) || 0,
             messageId: body.header.messageId,
-            messageTime: new Date(body.header.messageTime),
+            messageTime: new Date(Number(body.header.messageTime)),
             msgType: body.body.msgType,
-            devType: body.body.devType,
-            deviceName: body.body.deviceName,
-            subSerial: body.body.subSerial,
-            dasId: body.body.dasId,
-            natIp: body.body.natIp,
-            occurTime: body.body.occurTime,
-            regTime: body.body.regTime
-          });
+            devType: body.body.devType || '',
+            deviceName: body.body.deviceName || '',
+            subSerial: body.body.subSerial || '',
+            dasId: body.body.dasId || '',
+            natIp: body.body.natIp || '',
+            occurTime: body.body.occurTime ? new Date(body.body.occurTime.replace(' ', 'T')) : null,
+            regTime: body.body.regTime ? new Date(body.body.regTime.replace(' ', 'T')) : null,
+          };
+          await this.pushDeviceOnoffEntity.save(onoffData);
           break;
         }
 
         // 钀ょ煶鍛婅
         case 'ys.alarm': {
-          // 鍥剧墖涓嬭浇骞朵笂浼犲埌鏈湴/瀵硅薄瀛樺偍  pictureList: Array<{ url: string; id: string }>;
-          const localUrls: Record<string, string>[] = [];
-          if (body.body.pictureList && Array.isArray(body.body.pictureList)) {
-            const filePlugin = await this.pluginService.getInstance('upload');
-            for (const item of body.body.pictureList) {
-              try {
-                const result = await filePlugin.downAndUpload(item.url);
-                localUrls.push({url: result, id: item.id});
-              } catch (err) {
-                console.error(`鍥剧墖涓嬭浇澶辫触: ${item.url}`, err);
-              }
-            }
-          }
-          await this.pushDevicealarmEntity.save({
+          const alarmData: any = {
             devSerial: body.body.devSerial,
-            channelNo: body.body.channelNo || String(body.body.channel),
             alarmId: body.body.alarmId,
-            alarmTime: body.body.alarmTime,
+            alarmTime: body.body.alarmTime ? new Date(body.body.alarmTime.replace('T', ' ')) : new Date(),
             alarmType: body.body.alarmType,
             channelName: body.body.channelName,
-            channelType: body.body.channelType ? Number(body.body.channelType) : 1,
-            checksum: body.body.checksum,
-            crypt: body.body.crypt ? Number(body.body.crypt) : 0,
-            customInfo: body.body.customInfo,
-            customType: body.body.customType,
-            describe: body.body.describe,
-            location: body.body.location,
-            relationId: body.body.relationId,
-            status: body.body.status ? Number(body.body.status) : 1,
-            pictureList: localUrls || []
-          });
+            channelType: Number(body.body.channelType) || 1,
+            customInfo: body.body.customInfo || '',
+            customType: body.body.customType || '',
+            describe: body.body.describe || '',
+            location: body.body.location || '',
+            relationId: body.body.relationId || '',
+            status: Number(body.body.status) || 1,
+            channelNo: String(body.body.channel ?? body.body.channelNo ?? ''),
+          };
+          if (body.body.checksum) alarmData.checksum = body.body.checksum;
+          if (body.body.crypt !== undefined) alarmData.crypt = Number(body.body.crypt);
+          // 鍥剧墖澶勭悊锛堟湁鍒欏鐞嗭紝鏃犲垯璺宠繃锛�
+          const localUrls: Record<string, string>[] = [];
+          if (body.body.pictureList?.length) {
+            try {
+              const filePlugin = await this.pluginService.getInstance('upload');
+              for (const item of body.body.pictureList) {
+                try {
+                  const result = await filePlugin.downAndUpload(item.url);
+                  localUrls.push({ url: result, id: item.id });
+                } catch (err) {
+                  console.error(`鍥剧墖涓嬭浇澶辫触: ${item.url}`, err);
+                }
+              }
+            } catch (err) {
+              console.error('filePlugin init failed:', err);
+            }
+          }
+          alarmData.pictureList = localUrls;
+          await this.pushDevicealarmEntity.save(alarmData);
           break;
         }
-
-        // 娴峰悍 ISAPI
-        //case 'ys.open.isapi': {
-          //processCameraGpsAsync(body)
-          //await this.pushIsapiRepo.save({
-          //  messageId,
-          //  raw: JSON.stringify(body),
-          //  createTime: new Date(),
-          //});
-          //break;
-        //}
-
+        //娴峰悍 ISAPI
+        case 'ys.open.isapi': {
+          console.log(`---------start ys.isapi-------------------------------`);
+          console.log(body);
+          //TODO: 瑙f瀽娴峰悍 ISAPI 娑堟伅锛屽叆搴�
+// 娑堟伅绫诲瀷: ys.open.isapi | messageId: 6a69a482d05a3d0d7cdb5c83  鎵撳嵃鍐呭濡備笅锛�
+// ---------------------------------ys.open.isapi
+// ---------start ys.isapi-------------------------------
+// {
+//   body: {
+//     payload: '{"ipAddress": "10.83.239.158", "protocol": "HTTP", "dateTime": "2026-07-29T14:58:07+08:00", "activePostCount": 1, "eventType": "deviceStatus", "deviceID": "RkYmx96HhzkQHnK+te7tNB89AAHahWc=", "eventState": "active", "eventDescription": "device Status", "DeviceStatus": {"sleepStatus": "sleep", "batteryList": [{"id": 1, "voltage": 3595, "current": 137, "batteryPercentage": 33, "temperature": 36, "remainingBattery": 16.829, "state": "charing", "protocolType": "ADC", "lowTemperatureHeatingStatus": "stop", "protocolVersion": "1.0"}], "solarPanelStatus": {"voltage": 4.919, "current": 918, "power": 4.509, "powerGeneration": 3.790}, "powerConsumptionStatus": {"voltage": 0.000, "current": 0, "power": 0.000, "powerConsumption": 0.220}, "dialStatusList": [{"dialSignalStrength": 5, "band": "B39", "PCI": 63}]}}'
+//   },
+//   header: {
+//     channelNo: 0,
+//     deviceId: 'GN3665733',
+//     messageId: '6a69a482d05a3d0d7cdb5c83',
+//     messageTime: 1785308290000,
+//     type: 'ys.open.isapi'
+//   }
+// }          
+          // processCameraGpsAsync(body)
+          // await this.pushIsapiRepo.save({
+          // messageId,
+          // raw: JSON.stringify(body),
+          // createTime: new Date(),
+          // });
+          break;
+        }
         default:
           console.warn('Unknown yingshi webhook type:', type);
       }
@@ -197,11 +217,9 @@
         errorMessage: err,
       });
     }
-
     // 涓嶇鎴愬姛涓庡惁锛屽垹闄ょ紦瀛樹腑鐨勪换鍔¢槦鍒�
     await this.midwayCache.del(`push:yingshiyun:queue:${messageId}`);
-
-}
+  }
 
 
 
@@ -209,8 +227,6 @@
    * 寮傛瑙f瀽杞﹁浇鐩戞帶鐨凣PS鏁版嵁锛屽瓨鍌ㄥ苟娣诲姞涓氬姟琛�
    */
   async processCameraGpsAsync(body: any) {
-
-
     try {
       let vehicleNo = '';
       if (body.sn) {
@@ -292,8 +308,19 @@
 
   }
 
+  /**
+   * 淇濆瓨鎽勫儚澶翠富鍔ㄤ笂鎶ュ憡璀︿簨浠�
+   * @param alarmData
+   */
+  async saveCameraAlarmEvent(alarmData: any) {
+    const { eventType, targetType, ipAddress, dateTime } = alarmData;
+    if (eventType === 'VMD') {
+      console.log(`銆愮Щ鍔ㄤ睛娴嬪憡璀︺�戣澶�${ipAddress}锛屾椂闂达細${dateTime},鐩爣绫诲瀷:${targetType}`);
+      // TODO: 瀹屾垚鎽勫儚鏈轰睛娴嬫暟鎹繚瀛�
+    } else {
+      console.log(`銆愭湭鐭ユ憚鍍忓ご浜嬩欢绫诲瀷銆憈ype:${eventType}`);
+    }
+    return Date.now();
+  }
 
-
-
-
-}
+}
\ No newline at end of file

--
Gitblit v1.9.1