完善保险项目和养殖端小程序

This commit is contained in:
xuqiuyun
2025-09-26 18:45:42 +08:00
parent 00dfa83fd1
commit ec3f472641
58 changed files with 4866 additions and 2233 deletions

View File

@@ -29,7 +29,6 @@
"redis": "^4.6.12",
"sequelize": "^6.35.2",
"sharp": "^0.33.2",
"socket.io": "^4.7.4",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0",
"winston": "^3.11.0",
@@ -1821,11 +1820,6 @@
"@sinonjs/commons": "^3.0.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1867,14 +1861,6 @@
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/cors": {
"version": "2.8.19",
"resolved": "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz",
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
@@ -2472,14 +2458,6 @@
"integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==",
"optional": true
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.4",
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz",
@@ -3502,62 +3480,6 @@
"node": ">= 0.8"
}
},
"node_modules/engine.io": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/engine.io/-/engine.io-6.6.4.tgz",
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
"dependencies": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/engine.io/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz",
@@ -8529,107 +8451,6 @@
"node": ">=8"
}
},
"node_modules/socket.io": {
"version": "4.8.1",
"resolved": "https://registry.npmmirror.com/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmmirror.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
}
},
"node_modules/socket.io-adapter/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-adapter/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-parser/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/socket.io/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
@@ -9715,26 +9536,6 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
@@ -11088,11 +10889,6 @@
"@sinonjs/commons": "^3.0.0"
}
},
"@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
},
"@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -11134,14 +10930,6 @@
"@babel/types": "^7.28.2"
}
},
"@types/cors": {
"version": "2.8.19",
"resolved": "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz",
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
"requires": {
"@types/node": "*"
}
},
"@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
@@ -11615,11 +11403,6 @@
"integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==",
"optional": true
},
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
},
"baseline-browser-mapping": {
"version": "2.8.4",
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz",
@@ -12367,47 +12150,6 @@
"resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
},
"engine.io": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/engine.io/-/engine.io-6.6.4.tgz",
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
"requires": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"dependencies": {
"cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
},
"debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"requires": {
"ms": "^2.1.3"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
"engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="
},
"error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz",
@@ -15990,83 +15732,6 @@
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
"socket.io": {
"version": "4.8.1",
"resolved": "https://registry.npmmirror.com/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"requires": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"dependencies": {
"debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"requires": {
"ms": "^2.1.3"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
"socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmmirror.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"requires": {
"debug": "~4.3.4",
"ws": "~8.17.1"
},
"dependencies": {
"debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"requires": {
"ms": "^2.1.3"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
"socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"dependencies": {
"debug": {
"version": "4.3.7",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"requires": {
"ms": "^2.1.3"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
}
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
@@ -16858,12 +16523,6 @@
"signal-exit": "^3.0.7"
}
},
"ws": {
"version": "8.17.1",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"requires": {}
},
"xlsx": {
"version": "0.18.5",
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",

View File

@@ -59,7 +59,6 @@
"redis": "^4.6.12",
"sequelize": "^6.35.2",
"sharp": "^0.33.2",
"socket.io": "^4.7.4",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0",
"winston": "^3.11.0",

View File

@@ -6,7 +6,6 @@ const multer = require('multer');
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./config/swagger');
const { sequelize } = require('./config/database-simple');
const webSocketManager = require('./utils/websocket');
const logger = require('./utils/logger');
const {
apiRateLimiter,
@@ -264,24 +263,23 @@ app.use((err, req, res, next) => {
});
// 初始化WebSocket
webSocketManager.init(server);
// webSocketManager.init(server);
// 初始化实时数据推送服务
const realtimeService = require('./services/realtimeService');
// const realtimeService = require('./services/realtimeService');
// 启动服务器
server.listen(PORT, '0.0.0.0', () => {
console.log(`服务器运行在端口 ${PORT}`);
console.log(`服务器监听所有网络接口 (0.0.0.0:${PORT})`);
console.log(`API 文档地址: http://localhost:${PORT}/api-docs`);
console.log(`WebSocket 服务已启动`);
// 启动实时数据推送服务
realtimeService.start();
console.log(`实时数据推送服务已启动`);
// realtimeService.start();
// console.log(`实时数据推送服务已启动`);
logger.info(`宁夏智慧养殖监管平台服务器启动成功,端口: ${PORT}`);
});
// 导出app和webSocketManager供其他模块使用
module.exports = { app, webSocketManager };
// 导出app供其他模块使用
module.exports = { app };

View File

@@ -1,364 +0,0 @@
/**
* 实时数据推送服务
* @file realtimeService.js
* @description 定期检查数据变化并通过WebSocket推送给客户端
*/
const cron = require('node-cron');
const { Device, Alert, Animal, Farm } = require('../models');
const { sequelize } = require('../config/database-simple');
const webSocketManager = require('../utils/websocket');
const notificationService = require('./notificationService');
const logger = require('../utils/logger');
const { Op } = require('sequelize');
class RealtimeService {
constructor() {
this.isRunning = false;
this.lastUpdateTimes = {
devices: null,
alerts: null,
animals: null,
stats: null
};
this.updateInterval = 30; // 30秒更新间隔符合文档要求
}
/**
* 启动实时数据推送服务
*/
start() {
if (this.isRunning) {
logger.warn('实时数据推送服务已在运行中');
return;
}
this.isRunning = true;
// 设备状态监控 - 每30秒检查一次
cron.schedule(`*/${this.updateInterval} * * * * *`, () => {
this.checkDeviceUpdates();
});
// 预警监控 - 每10秒检查一次预警更紧急
cron.schedule('*/10 * * * * *', () => {
this.checkAlertUpdates();
});
// 动物健康状态监控 - 每60秒检查一次
cron.schedule('*/60 * * * * *', () => {
this.checkAnimalUpdates();
});
// 系统统计数据更新 - 每2分钟更新一次
cron.schedule('*/120 * * * * *', () => {
this.updateSystemStats();
});
logger.info('实时数据推送服务已启动');
console.log(`实时数据推送服务已启动,更新间隔: ${this.updateInterval}`);
}
/**
* 停止实时数据推送服务
*/
stop() {
this.isRunning = false;
logger.info('实时数据推送服务已停止');
}
/**
* 检查设备状态更新
*/
async checkDeviceUpdates() {
try {
const lastCheck = this.lastUpdateTimes.devices || new Date(Date.now() - 60000); // 默认检查最近1分钟
const updatedDevices = await Device.findAll({
where: {
updated_at: {
[Op.gt]: lastCheck
}
},
include: [{
model: Farm,
as: 'farm',
attributes: ['id', 'name']
}],
order: [['updated_at', 'DESC']]
});
if (updatedDevices.length > 0) {
logger.info(`检测到 ${updatedDevices.length} 个设备状态更新`);
// 为每个更新的设备推送数据
for (const device of updatedDevices) {
webSocketManager.broadcastDeviceUpdate({
id: device.id,
name: device.name,
type: device.type,
status: device.status,
farm_id: device.farm_id,
farm_name: device.farm?.name,
last_maintenance: device.last_maintenance,
metrics: device.metrics,
updated_at: device.updated_at
});
}
this.lastUpdateTimes.devices = new Date();
}
} catch (error) {
logger.error('检查设备更新失败:', error);
}
}
/**
* 检查预警更新
*/
async checkAlertUpdates() {
try {
const lastCheck = this.lastUpdateTimes.alerts || new Date(Date.now() - 30000); // 默认检查最近30秒
const newAlerts = await Alert.findAll({
where: {
created_at: {
[Op.gt]: lastCheck
}
},
include: [
{
model: Farm,
as: 'farm',
attributes: ['id', 'name', 'contact', 'phone']
},
{
model: Device,
as: 'device',
attributes: ['id', 'name', 'type']
}
],
order: [['created_at', 'DESC']]
});
if (newAlerts.length > 0) {
logger.info(`检测到 ${newAlerts.length} 个新预警`);
// 推送新预警
for (const alert of newAlerts) {
webSocketManager.broadcastAlert({
id: alert.id,
type: alert.type,
level: alert.level,
message: alert.message,
status: alert.status,
farm_id: alert.farm_id,
farm_name: alert.farm?.name,
device_id: alert.device_id,
device_name: alert.device?.name,
created_at: alert.created_at
});
// 如果是高级或紧急预警,立即发送通知
if (alert.level === 'high' || alert.level === 'critical') {
await this.sendUrgentNotification(alert);
}
}
this.lastUpdateTimes.alerts = new Date();
}
} catch (error) {
logger.error('检查预警更新失败:', error);
}
}
/**
* 检查动物健康状态更新
*/
async checkAnimalUpdates() {
try {
const lastCheck = this.lastUpdateTimes.animals || new Date(Date.now() - 120000); // 默认检查最近2分钟
const updatedAnimals = await Animal.findAll({
where: {
updated_at: {
[Op.gt]: lastCheck
}
},
include: [{
model: Farm,
as: 'farm',
attributes: ['id', 'name']
}],
order: [['updated_at', 'DESC']]
});
if (updatedAnimals.length > 0) {
logger.info(`检测到 ${updatedAnimals.length} 个动物健康状态更新`);
for (const animal of updatedAnimals) {
webSocketManager.broadcastAnimalUpdate({
id: animal.id,
type: animal.type,
count: animal.count,
health_status: animal.health_status,
farm_id: animal.farm_id,
farm_name: animal.farm?.name,
last_inspection: animal.last_inspection,
updated_at: animal.updated_at
});
}
this.lastUpdateTimes.animals = new Date();
}
} catch (error) {
logger.error('检查动物更新失败:', error);
}
}
/**
* 更新系统统计数据
*/
async updateSystemStats() {
try {
const stats = await this.getSystemStats();
webSocketManager.broadcastStatsUpdate(stats);
this.lastUpdateTimes.stats = new Date();
logger.info('系统统计数据已推送');
} catch (error) {
logger.error('更新系统统计失败:', error);
}
}
/**
* 获取系统统计数据
* @returns {Promise<Object>} 统计数据
*/
async getSystemStats() {
try {
const [farmCount, deviceCount, animalCount, alertCount] = await Promise.all([
Farm.count(),
Device.count(),
Animal.sum('count'),
Alert.count({ where: { status: 'active' } })
]);
const deviceStatusStats = await Device.findAll({
attributes: [
'status',
[sequelize.fn('COUNT', sequelize.col('status')), 'count']
],
group: ['status']
});
const alertLevelStats = await Alert.findAll({
where: { status: 'active' },
attributes: [
'level',
[sequelize.fn('COUNT', sequelize.col('level')), 'count']
],
group: ['level']
});
return {
farmCount: farmCount || 0,
deviceCount: deviceCount || 0,
animalCount: animalCount || 0,
alertCount: alertCount || 0,
deviceStatus: deviceStatusStats.reduce((acc, item) => {
acc[item.status] = parseInt(item.dataValues.count);
return acc;
}, {}),
alertLevels: alertLevelStats.reduce((acc, item) => {
acc[item.level] = parseInt(item.dataValues.count);
return acc;
}, {}),
timestamp: new Date()
};
} catch (error) {
logger.error('获取系统统计数据失败:', error);
return {
error: '获取统计数据失败',
timestamp: new Date()
};
}
}
/**
* 发送紧急预警通知
* @param {Object} alert 预警对象
*/
async sendUrgentNotification(alert) {
try {
logger.warn(`紧急预警: ${alert.message} (级别: ${alert.level})`);
// 发送实时WebSocket通知给管理员
webSocketManager.broadcastAlert({
id: alert.id,
type: alert.type,
level: alert.level,
message: alert.message,
farm_id: alert.farm_id,
farm_name: alert.farm?.name,
created_at: alert.created_at
});
// 发送邮件/短信通知
const isUrgent = alert.level === 'critical' || alert.level === 'high';
await notificationService.sendAlertNotification(alert, [], {
urgent: isUrgent,
includeSMS: alert.level === 'critical', // 仅紧急预警发送短信
maxResponseTime: 300000 // 5分钟响应时间
});
logger.info(`预警通知已发送: 预警ID ${alert.id}, 紧急程度: ${isUrgent}`);
} catch (error) {
logger.error('发送紧急预警通知失败:', error);
}
}
/**
* 模拟设备数据变化(用于演示)
*/
async simulateDeviceChange(deviceId) {
try {
const device = await Device.findByPk(deviceId);
if (!device) return;
// 随机改变设备状态
const statuses = ['online', 'offline', 'maintenance'];
const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
await device.update({
status: randomStatus,
metrics: {
temperature: Math.random() * 10 + 20, // 20-30度
humidity: Math.random() * 20 + 50, // 50-70%
lastUpdate: new Date()
}
});
logger.info(`模拟设备 ${deviceId} 状态变化为: ${randomStatus}`);
} catch (error) {
logger.error('模拟设备变化失败:', error);
}
}
/**
* 获取服务状态
* @returns {Object} 服务状态
*/
getStatus() {
return {
isRunning: this.isRunning,
lastUpdateTimes: this.lastUpdateTimes,
updateInterval: this.updateInterval,
connectedClients: webSocketManager.getConnectionStats()
};
}
}
// 创建单例实例
const realtimeService = new RealtimeService();
module.exports = realtimeService;

View File

@@ -1,339 +0,0 @@
/**
* WebSocket实时通信系统
* @file websocket.js
* @description 实现实时数据推送,替代轮询机制
*/
const socketIO = require('socket.io');
const jwt = require('jsonwebtoken');
const { User, Role } = require('../models');
const logger = require('./logger');
class WebSocketManager {
constructor() {
this.io = null;
this.connectedClients = new Map(); // 存储连接的客户端信息
this.rooms = {
admins: 'admin_room',
users: 'user_room',
farms: 'farm_', // farm_1, farm_2 等
devices: 'device_', // device_1, device_2 等
};
}
/**
* 初始化WebSocket服务器
* @param {Object} server HTTP服务器实例
*/
init(server) {
this.io = socketIO(server, {
cors: {
origin: ["http://localhost:5300", "http://localhost:3000"],
methods: ["GET", "POST"],
credentials: true
},
pingTimeout: 60000,
pingInterval: 25000
});
// 中间件:认证
this.io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token || socket.handshake.headers.authorization?.split(' ')[1];
if (!token) {
return next(new Error('未提供认证令牌'));
}
// 验证JWT令牌
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key');
// 获取用户信息和角色
const user = await User.findByPk(decoded.id, {
include: [{
model: Role,
as: 'role',
attributes: ['name']
}]
});
if (!user) {
return next(new Error('用户不存在'));
}
// 将用户信息附加到socket
socket.user = {
id: user.id,
username: user.username,
email: user.email,
roles: user.role ? [user.role.name] : []
};
next();
} catch (error) {
logger.error('WebSocket认证失败:', error);
next(new Error('认证失败'));
}
});
// 连接事件处理
this.io.on('connection', (socket) => {
this.handleConnection(socket);
});
logger.info('WebSocket服务器初始化完成');
return this.io;
}
/**
* 处理客户端连接
* @param {Object} socket Socket实例
*/
handleConnection(socket) {
const user = socket.user;
// 存储客户端信息
this.connectedClients.set(socket.id, {
userId: user.id,
username: user.username,
roles: user.roles,
connectedAt: new Date()
});
// 加入相应的房间
this.joinRooms(socket, user);
logger.info(`用户 ${user.username} 已连接 WebSocket连接ID: ${socket.id}`);
// 发送连接成功消息
socket.emit('connected', {
message: '实时连接已建立',
timestamp: new Date(),
user: {
id: user.id,
username: user.username
}
});
// 处理客户端事件
this.setupSocketEvents(socket);
// 断开连接处理
socket.on('disconnect', () => {
this.handleDisconnection(socket);
});
}
/**
* 让用户加入相应的房间
* @param {Object} socket Socket实例
* @param {Object} user 用户信息
*/
joinRooms(socket, user) {
// 所有用户加入用户房间
socket.join(this.rooms.users);
// 管理员加入管理员房间
if (user.roles && user.roles.includes('admin')) {
socket.join(this.rooms.admins);
}
// 可以根据业务需求加入特定的农场或设备房间
// 这里暂时加入全局房间,后续可以根据用户权限细化
}
/**
* 设置Socket事件监听
* @param {Object} socket Socket实例
*/
setupSocketEvents(socket) {
// 订阅特定农场的数据
socket.on('subscribe_farm', (farmId) => {
socket.join(`${this.rooms.farms}${farmId}`);
logger.info(`用户 ${socket.user.username} 订阅农场 ${farmId} 的实时数据`);
});
// 取消订阅农场数据
socket.on('unsubscribe_farm', (farmId) => {
socket.leave(`${this.rooms.farms}${farmId}`);
logger.info(`用户 ${socket.user.username} 取消订阅农场 ${farmId} 的实时数据`);
});
// 订阅特定设备的数据
socket.on('subscribe_device', (deviceId) => {
socket.join(`${this.rooms.devices}${deviceId}`);
logger.info(`用户 ${socket.user.username} 订阅设备 ${deviceId} 的实时数据`);
});
// 心跳检测
socket.on('ping', () => {
socket.emit('pong', { timestamp: new Date() });
});
}
/**
* 处理客户端断开连接
* @param {Object} socket Socket实例
*/
handleDisconnection(socket) {
const clientInfo = this.connectedClients.get(socket.id);
if (clientInfo) {
logger.info(`用户 ${clientInfo.username} 断开 WebSocket 连接连接ID: ${socket.id}`);
this.connectedClients.delete(socket.id);
}
}
/**
* 广播设备状态更新
* @param {Object} deviceData 设备数据
*/
broadcastDeviceUpdate(deviceData) {
if (!this.io) return;
// 向所有用户广播设备更新
this.io.to(this.rooms.users).emit('device_update', {
type: 'device_status',
data: deviceData,
timestamp: new Date()
});
// 向特定设备的订阅者发送更新
this.io.to(`${this.rooms.devices}${deviceData.id}`).emit('device_detail_update', {
type: 'device_detail',
data: deviceData,
timestamp: new Date()
});
logger.info(`设备状态更新已广播: 设备ID ${deviceData.id}`);
}
/**
* 广播预警信息
* @param {Object} alertData 预警数据
*/
broadcastAlert(alertData) {
if (!this.io) return;
// 向管理员发送紧急预警
if (alertData.level === 'critical' || alertData.level === 'high') {
this.io.to(this.rooms.admins).emit('urgent_alert', {
type: 'urgent_alert',
data: alertData,
timestamp: new Date()
});
}
// 向所有用户广播预警
this.io.to(this.rooms.users).emit('alert_update', {
type: 'new_alert',
data: alertData,
timestamp: new Date()
});
// 向特定农场的订阅者发送预警
if (alertData.farm_id) {
this.io.to(`${this.rooms.farms}${alertData.farm_id}`).emit('farm_alert', {
type: 'farm_alert',
data: alertData,
timestamp: new Date()
});
}
logger.info(`预警信息已广播: 预警ID ${alertData.id}, 级别: ${alertData.level}`);
}
/**
* 广播动物健康数据更新
* @param {Object} animalData 动物数据
*/
broadcastAnimalUpdate(animalData) {
if (!this.io) return;
this.io.to(this.rooms.users).emit('animal_update', {
type: 'animal_health',
data: animalData,
timestamp: new Date()
});
// 向特定农场的订阅者发送动物更新
if (animalData.farm_id) {
this.io.to(`${this.rooms.farms}${animalData.farm_id}`).emit('farm_animal_update', {
type: 'farm_animal',
data: animalData,
timestamp: new Date()
});
}
logger.info(`动物健康数据更新已广播: 动物ID ${animalData.id}`);
}
/**
* 广播系统统计数据更新
* @param {Object} statsData 统计数据
*/
broadcastStatsUpdate(statsData) {
if (!this.io) return;
this.io.to(this.rooms.users).emit('stats_update', {
type: 'system_stats',
data: statsData,
timestamp: new Date()
});
logger.info('系统统计数据更新已广播');
}
/**
* 发送性能监控数据
* @param {Object} performanceData 性能数据
*/
broadcastPerformanceUpdate(performanceData) {
if (!this.io) return;
// 只向管理员发送性能数据
this.io.to(this.rooms.admins).emit('performance_update', {
type: 'system_performance',
data: performanceData,
timestamp: new Date()
});
logger.info('性能监控数据已发送给管理员');
}
/**
* 获取连接统计信息
* @returns {Object} 连接统计
*/
getConnectionStats() {
return {
totalConnections: this.connectedClients.size,
connectedUsers: Array.from(this.connectedClients.values()).map(client => ({
userId: client.userId,
username: client.username,
connectedAt: client.connectedAt
}))
};
}
/**
* 向特定用户发送消息
* @param {number} userId 用户ID
* @param {string} event 事件名称
* @param {Object} data 数据
*/
sendToUser(userId, event, data) {
if (!this.io) return;
for (const [socketId, clientInfo] of this.connectedClients.entries()) {
if (clientInfo.userId === userId) {
this.io.to(socketId).emit(event, data);
logger.info(`消息已发送给用户 ${clientInfo.username}: ${event}`);
}
}
}
}
// 创建单例实例
const webSocketManager = new WebSocketManager();
module.exports = webSocketManager;