修改百度地图AK
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
# 2. 应用类型是否为「浏览器端」
|
||||
# 3. Referer白名单配置(开发环境可设置为 *)
|
||||
# 4. API密钥状态是否为「启用」
|
||||
VITE_BAIDU_MAP_API_KEY=SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo
|
||||
VITE_BAIDU_MAP_API_KEY=fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC
|
||||
|
||||
# API服务地址
|
||||
VITE_API_BASE_URL=http://localhost:5350/api
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
### 百度地图配置
|
||||
| 变量名 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `VITE_BAIDU_MAP_API_KEY` | `SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo` | 百度地图API密钥 |
|
||||
| `VITE_BAIDU_MAP_API_KEY` | `fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC` | 百度地图API密钥 |
|
||||
|
||||
### 应用配置
|
||||
| 变量名 | 默认值 | 说明 |
|
||||
|
||||
@@ -9,10 +9,10 @@ export const BAIDU_MAP_CONFIG = {
|
||||
// 从环境变量读取API密钥,如果没有则使用开发测试密钥
|
||||
// 生产环境请设置 VITE_BAIDU_MAP_API_KEY 环境变量
|
||||
// 请访问 http://lbsyun.baidu.com/apiconsole/key 申请有效的API密钥
|
||||
apiKey: import.meta.env.VITE_BAIDU_MAP_API_KEY || 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo',
|
||||
apiKey: import.meta.env.VITE_BAIDU_MAP_API_KEY || 'fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC',
|
||||
|
||||
// 备用API密钥(用于不同环境)
|
||||
fallbackApiKey: import.meta.env.VITE_BAIDU_MAP_FALLBACK_KEY || 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo',
|
||||
fallbackApiKey: import.meta.env.VITE_BAIDU_MAP_FALLBACK_KEY || 'fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC',
|
||||
|
||||
// 是否启用Referer校验(开发环境建议关闭)
|
||||
enableRefererCheck: import.meta.env.VITE_BAIDU_MAP_ENABLE_REFERER !== 'false',
|
||||
|
||||
@@ -14,7 +14,7 @@ const MAX_RETRY = 3;
|
||||
* @param {string} apiKey - 百度地图API密钥
|
||||
* @returns {Promise} 加载完成的Promise
|
||||
*/
|
||||
export const loadBaiduMapAPI = async (apiKey = 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo') => {
|
||||
export const loadBaiduMapAPI = async (apiKey = 'fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC') => {
|
||||
// 如果已经加载过,直接返回
|
||||
if (BMapLoaded && window.BMap) {
|
||||
console.log('百度地图API已加载');
|
||||
@@ -118,7 +118,7 @@ export const loadBaiduMapAPI = async (apiKey = 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo
|
||||
* @param {string} apiKey - 百度地图API密钥
|
||||
* @returns {Promise} 加载完成的Promise
|
||||
*/
|
||||
export const retryLoadBaiduMapAPI = async (apiKey = 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo') => {
|
||||
export const retryLoadBaiduMapAPI = async (apiKey = 'fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC') => {
|
||||
if (retryCount >= MAX_RETRY) {
|
||||
throw new Error('百度地图API加载重试次数已达上限');
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ export class BaiduMapTester {
|
||||
this.isLoading = true;
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://api.map.baidu.com/api?v=3.0&ak=SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo&callback=initBMapTest';
|
||||
script.src = 'https://api.map.baidu.com/api?v=3.0&ak=fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC&callback=initBMapTest';
|
||||
|
||||
window.initBMapTest = () => {
|
||||
this.isLoading = false;
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
# 智能项圈预警检测逻辑说明
|
||||
|
||||
## 概述
|
||||
为智能项圈预警系统添加了自动预警检测逻辑,根据设备数据自动判断预警类型,无需依赖后端预处理的预警数据。
|
||||
|
||||
## 预警检测规则
|
||||
|
||||
### 1. 低电量预警
|
||||
- **条件**: `battery < 20`
|
||||
- **级别**: 高级
|
||||
- **颜色**: 橙色
|
||||
- **说明**: 当设备电量低于20%时触发
|
||||
|
||||
### 2. 离线预警
|
||||
- **条件**: `is_connect === 0`
|
||||
- **级别**: 高级
|
||||
- **颜色**: 红色
|
||||
- **说明**: 当设备连接状态为0时触发
|
||||
|
||||
### 3. 佩戴异常预警
|
||||
- **条件**: `bandge_status === 0`
|
||||
- **级别**: 中级
|
||||
- **颜色**: 蓝色
|
||||
- **说明**: 当设备佩戴状态为0时触发
|
||||
|
||||
### 4. 温度过低预警
|
||||
- **条件**: `temperature < 20`
|
||||
- **级别**: 中级
|
||||
- **颜色**: 蓝色
|
||||
- **说明**: 当设备温度低于20°C时触发
|
||||
|
||||
### 5. 温度过高预警
|
||||
- **条件**: `temperature > 40`
|
||||
- **级别**: 高级
|
||||
- **颜色**: 红色
|
||||
- **说明**: 当设备温度高于40°C时触发
|
||||
|
||||
### 6. 异常运动预警
|
||||
- **条件**: `steps - y_steps === 0`
|
||||
- **级别**: 中级
|
||||
- **颜色**: 紫色
|
||||
- **说明**: 当当日步数与昨日步数相同时触发
|
||||
|
||||
## 实现细节
|
||||
|
||||
### 判断函数
|
||||
```javascript
|
||||
const determineAlertType = (record) => {
|
||||
const alerts = []
|
||||
|
||||
// 检查电量预警
|
||||
if (record.battery !== undefined && record.battery !== null && record.battery < 20) {
|
||||
alerts.push('battery')
|
||||
}
|
||||
|
||||
// 检查脱落预警 (bandge_status为0)
|
||||
if (record.bandge_status !== undefined && record.bandge_status !== null && record.bandge_status === 0) {
|
||||
alerts.push('wear')
|
||||
}
|
||||
|
||||
// 检查离线预警 (is_connect为0)
|
||||
if (record.is_connect !== undefined && record.is_connect !== null && record.is_connect === 0) {
|
||||
alerts.push('offline')
|
||||
}
|
||||
|
||||
// 检查温度预警
|
||||
if (record.temperature !== undefined && record.temperature !== null) {
|
||||
if (record.temperature < 20) {
|
||||
alerts.push('temperature_low')
|
||||
} else if (record.temperature > 40) {
|
||||
alerts.push('temperature_high')
|
||||
}
|
||||
}
|
||||
|
||||
// 检查运动异常预警 (steps - y_steps为0)
|
||||
if (record.steps !== undefined && record.y_steps !== undefined &&
|
||||
record.steps !== null && record.y_steps !== null) {
|
||||
const movementDiff = record.steps - record.y_steps
|
||||
if (movementDiff === 0) {
|
||||
alerts.push('movement')
|
||||
}
|
||||
}
|
||||
|
||||
// 返回第一个预警类型,如果没有预警则返回null
|
||||
return alerts.length > 0 ? alerts[0] : null
|
||||
}
|
||||
```
|
||||
|
||||
### 预警级别分配
|
||||
```javascript
|
||||
let alertLevel = 'low'
|
||||
if (determinedAlertType === 'battery' || determinedAlertType === 'offline' || determinedAlertType === 'temperature_high') {
|
||||
alertLevel = 'high'
|
||||
} else if (determinedAlertType === 'movement' || determinedAlertType === 'wear' || determinedAlertType === 'temperature_low') {
|
||||
alertLevel = 'medium'
|
||||
}
|
||||
```
|
||||
|
||||
### 统计数据计算
|
||||
```javascript
|
||||
const calculateStats = (data) => {
|
||||
const newStats = {
|
||||
lowBattery: 0,
|
||||
offline: 0,
|
||||
highTemperature: 0,
|
||||
abnormalMovement: 0,
|
||||
wearOff: 0
|
||||
}
|
||||
|
||||
data.forEach(item => {
|
||||
const alertType = determineAlertType(item)
|
||||
if (alertType === 'battery') {
|
||||
newStats.lowBattery++
|
||||
} else if (alertType === 'offline') {
|
||||
newStats.offline++
|
||||
} else if (alertType === 'temperature_high') {
|
||||
newStats.highTemperature++
|
||||
} else if (alertType === 'movement') {
|
||||
newStats.abnormalMovement++
|
||||
} else if (alertType === 'wear') {
|
||||
newStats.wearOff++
|
||||
}
|
||||
})
|
||||
|
||||
return newStats
|
||||
}
|
||||
```
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 运行测试脚本
|
||||
```bash
|
||||
cd backend
|
||||
node test-alert-detection-logic.js
|
||||
```
|
||||
|
||||
### 测试用例
|
||||
测试脚本包含以下测试用例:
|
||||
1. 正常设备
|
||||
2. 低电量预警
|
||||
3. 离线预警
|
||||
4. 佩戴异常预警
|
||||
5. 温度过低预警
|
||||
6. 温度过高预警
|
||||
7. 异常运动预警
|
||||
8. 多重预警(低电量+离线)
|
||||
9. 边界值测试(电量20/19,温度20/19/40/41)
|
||||
|
||||
## 前端集成
|
||||
|
||||
### 数据转换
|
||||
在数据转换过程中,系统会:
|
||||
1. 调用 `determineAlertType()` 函数判断预警类型
|
||||
2. 根据预警类型确定预警级别
|
||||
3. 更新统计卡片数据
|
||||
4. 在表格中显示相应的预警标签
|
||||
|
||||
### 筛选功能
|
||||
筛选下拉菜单包含所有预警类型:
|
||||
- 全部预警
|
||||
- 低电量预警
|
||||
- 离线预警
|
||||
- 温度过低预警
|
||||
- 温度过高预警
|
||||
- 异常运动预警
|
||||
- 佩戴异常预警
|
||||
|
||||
## 优势
|
||||
|
||||
1. **实时检测**: 基于最新设备数据实时判断预警
|
||||
2. **灵活配置**: 预警规则可以轻松调整
|
||||
3. **多重预警**: 支持检测多种预警类型
|
||||
4. **优先级处理**: 当存在多重预警时,返回第一个检测到的预警
|
||||
5. **边界值处理**: 精确处理边界值情况
|
||||
6. **前端计算**: 减少后端计算负担,提高响应速度
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据完整性**: 确保设备数据包含所有必要字段
|
||||
2. **空值处理**: 函数会检查字段是否存在且不为null
|
||||
3. **类型转换**: 确保数值字段为数字类型
|
||||
4. **性能考虑**: 大量数据时计算统计可能影响性能
|
||||
5. **规则一致性**: 前后端预警规则应保持一致
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `admin-system/src/views/SmartCollarAlert.vue` - 前端预警页面
|
||||
- `backend/test-alert-detection-logic.js` - 测试脚本
|
||||
- `backend/ALERT_DETECTION_LOGIC.md` - 本文档
|
||||
|
||||
---
|
||||
|
||||
**实现时间**: 2025-01-18
|
||||
**版本**: v1.0.0
|
||||
**状态**: 已实现并测试
|
||||
@@ -1,114 +0,0 @@
|
||||
# API文档问题排查指南
|
||||
|
||||
## 问题描述
|
||||
在访问 `http://localhost:5350/api-docs/` 时,找不到 `/api/smart-alerts/public` 相关的API接口。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 确保服务器正确启动
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm start
|
||||
```
|
||||
|
||||
确保服务器在端口5350上启动。
|
||||
|
||||
### 2. 检查端口配置
|
||||
|
||||
确保 `server.js` 中的端口配置正确:
|
||||
|
||||
```javascript
|
||||
const PORT = process.env.PORT || 5350;
|
||||
```
|
||||
|
||||
### 3. 使用简化的Swagger配置
|
||||
|
||||
我已经创建了一个简化的Swagger配置文件 `swagger-simple.js`,它手动定义了所有API路径。
|
||||
|
||||
### 4. 运行测试脚本
|
||||
|
||||
```bash
|
||||
# 测试API访问
|
||||
node test-api-access.js
|
||||
|
||||
# 启动服务器并测试
|
||||
node start-and-test.js
|
||||
```
|
||||
|
||||
### 5. 手动验证API
|
||||
|
||||
在浏览器中访问以下URL来验证API是否正常工作:
|
||||
|
||||
- 根路径: http://localhost:5350/
|
||||
- API文档: http://localhost:5350/api-docs/
|
||||
- Swagger JSON: http://localhost:5350/api-docs/swagger.json
|
||||
- 智能耳标预警统计: http://localhost:5350/api/smart-alerts/public/eartag/stats
|
||||
- 智能项圈预警统计: http://localhost:5350/api/smart-alerts/public/collar/stats
|
||||
|
||||
### 6. 检查路由注册
|
||||
|
||||
确保在 `server.js` 中正确注册了智能预警路由:
|
||||
|
||||
```javascript
|
||||
// 智能预警相关路由
|
||||
app.use('/api/smart-alerts', require('./routes/smart-alerts'));
|
||||
```
|
||||
|
||||
### 7. 重新启动服务器
|
||||
|
||||
如果修改了配置,请重新启动服务器:
|
||||
|
||||
```bash
|
||||
# 停止当前服务器 (Ctrl+C)
|
||||
# 然后重新启动
|
||||
npm start
|
||||
```
|
||||
|
||||
## 预期结果
|
||||
|
||||
正确配置后,在 `http://localhost:5350/api-docs/` 中应该能看到:
|
||||
|
||||
### 智能耳标预警 API
|
||||
- `GET /smart-alerts/public/eartag/stats` - 获取预警统计
|
||||
- `GET /smart-alerts/public/eartag` - 获取预警列表
|
||||
- `GET /smart-alerts/public/eartag/{id}` - 获取预警详情
|
||||
- `POST /smart-alerts/public/eartag/{id}/handle` - 处理预警
|
||||
- `POST /smart-alerts/public/eartag/batch-handle` - 批量处理预警
|
||||
- `GET /smart-alerts/public/eartag/export` - 导出预警数据
|
||||
|
||||
### 智能项圈预警 API
|
||||
- `GET /smart-alerts/public/collar/stats` - 获取预警统计
|
||||
- `GET /smart-alerts/public/collar` - 获取预警列表
|
||||
- `GET /smart-alerts/public/collar/{id}` - 获取预警详情
|
||||
- `POST /smart-alerts/public/collar/{id}/handle` - 处理预警
|
||||
- `POST /smart-alerts/public/collar/batch-handle` - 批量处理预警
|
||||
- `GET /smart-alerts/public/collar/export` - 导出预警数据
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 如果仍然看不到API路径
|
||||
|
||||
1. **检查控制台错误**:查看服务器启动时的错误信息
|
||||
2. **检查依赖**:确保安装了 `swagger-jsdoc` 和 `swagger-ui-express`
|
||||
3. **检查文件路径**:确保所有文件都在正确的位置
|
||||
4. **清除缓存**:重启浏览器或清除缓存
|
||||
|
||||
### 如果API调用失败
|
||||
|
||||
1. **检查数据库连接**:确保数据库服务正在运行
|
||||
2. **检查模型导入**:确保所有模型都正确导入
|
||||
3. **查看服务器日志**:检查具体的错误信息
|
||||
|
||||
## 联系支持
|
||||
|
||||
如果问题仍然存在,请提供以下信息:
|
||||
|
||||
1. 服务器启动日志
|
||||
2. 浏览器控制台错误
|
||||
3. 具体的错误信息
|
||||
4. 操作系统和Node.js版本
|
||||
|
||||
---
|
||||
|
||||
**注意**: 确保在运行任何测试之前,数据库服务正在运行,并且所有必要的依赖都已安装。
|
||||
@@ -1,394 +0,0 @@
|
||||
# 智能耳标预警 API 接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
智能耳标预警系统提供了完整的API接口,支持预警数据的查询、统计、处理和导出功能。所有接口均为公开接口,无需身份验证。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **基础URL**: `http://localhost:5350/api/smart-alerts/public`
|
||||
- **数据格式**: JSON
|
||||
- **字符编码**: UTF-8
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 获取智能耳标预警统计
|
||||
|
||||
**接口地址**: `GET /eartag/stats`
|
||||
|
||||
**功能描述**: 获取智能耳标预警的统计数据,包括各类预警的数量和设备总数。
|
||||
|
||||
**请求参数**: 无
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"totalDevices": 150,
|
||||
"lowBattery": 12,
|
||||
"offline": 8,
|
||||
"highTemperature": 5,
|
||||
"lowTemperature": 3,
|
||||
"abnormalMovement": 7,
|
||||
"totalAlerts": 35
|
||||
},
|
||||
"message": "获取智能耳标预警统计成功"
|
||||
}
|
||||
```
|
||||
|
||||
**响应字段说明**:
|
||||
- `totalDevices`: 耳标设备总数
|
||||
- `lowBattery`: 低电量预警数量
|
||||
- `offline`: 离线预警数量
|
||||
- `highTemperature`: 高温预警数量
|
||||
- `lowTemperature`: 低温预警数量
|
||||
- `abnormalMovement`: 异常运动预警数量
|
||||
- `totalAlerts`: 预警总数
|
||||
|
||||
### 2. 获取智能耳标预警列表
|
||||
|
||||
**接口地址**: `GET /eartag`
|
||||
|
||||
**功能描述**: 获取智能耳标预警列表,支持分页、搜索和筛选。
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| page | number | 否 | 1 | 页码 |
|
||||
| limit | number | 否 | 10 | 每页数量 |
|
||||
| search | string | 否 | - | 搜索关键词(耳标编号) |
|
||||
| alertType | string | 否 | - | 预警类型筛选 |
|
||||
| alertLevel | string | 否 | - | 预警级别筛选 |
|
||||
| status | string | 否 | - | 设备状态筛选 |
|
||||
| startDate | string | 否 | - | 开始日期 (YYYY-MM-DD) |
|
||||
| endDate | string | 否 | - | 结束日期 (YYYY-MM-DD) |
|
||||
|
||||
**预警类型枚举**:
|
||||
- `battery`: 低电量预警
|
||||
- `offline`: 离线预警
|
||||
- `temperature`: 温度预警
|
||||
- `movement`: 异常运动预警
|
||||
|
||||
**预警级别枚举**:
|
||||
- `high`: 高级
|
||||
- `medium`: 中级
|
||||
- `low`: 低级
|
||||
|
||||
**设备状态枚举**:
|
||||
- `online`: 在线
|
||||
- `offline`: 离线
|
||||
- `alarm`: 报警
|
||||
- `maintenance`: 维护
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "123_offline",
|
||||
"deviceId": 123,
|
||||
"deviceName": "EARTAG001",
|
||||
"eartagNumber": "EARTAG001",
|
||||
"alertType": "offline",
|
||||
"alertLevel": "high",
|
||||
"alertTime": "2024-01-15 10:30:00",
|
||||
"battery": 85,
|
||||
"temperature": 25.5,
|
||||
"dailySteps": 0,
|
||||
"totalSteps": 1500,
|
||||
"yesterdaySteps": 1500,
|
||||
"deviceStatus": "离线",
|
||||
"gpsSignal": "无",
|
||||
"movementStatus": "静止",
|
||||
"description": "设备已离线超过30分钟",
|
||||
"longitude": 116.3974,
|
||||
"latitude": 39.9093
|
||||
}
|
||||
],
|
||||
"total": 35,
|
||||
"stats": {
|
||||
"lowBattery": 12,
|
||||
"offline": 8,
|
||||
"highTemperature": 5,
|
||||
"abnormalMovement": 7
|
||||
},
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"total": 35,
|
||||
"pages": 4
|
||||
},
|
||||
"message": "获取智能耳标预警列表成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 获取单个智能耳标预警详情
|
||||
|
||||
**接口地址**: `GET /eartag/{id}`
|
||||
|
||||
**功能描述**: 获取指定ID的智能耳标预警详细信息。
|
||||
|
||||
**路径参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | string | 是 | 预警ID,格式为 deviceId_alertType |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "123_offline",
|
||||
"deviceId": 123,
|
||||
"deviceName": "EARTAG001",
|
||||
"eartagNumber": "EARTAG001",
|
||||
"alertType": "offline",
|
||||
"alertLevel": "high",
|
||||
"alertTime": "2024-01-15 10:30:00",
|
||||
"battery": 85,
|
||||
"temperature": 25.5,
|
||||
"dailySteps": 0,
|
||||
"totalSteps": 1500,
|
||||
"yesterdaySteps": 1500,
|
||||
"deviceStatus": "离线",
|
||||
"gpsSignal": "无",
|
||||
"movementStatus": "静止",
|
||||
"description": "设备已离线超过30分钟",
|
||||
"longitude": 116.3974,
|
||||
"latitude": 39.9093
|
||||
},
|
||||
"message": "获取智能耳标预警详情成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 处理智能耳标预警
|
||||
|
||||
**接口地址**: `POST /eartag/{id}/handle`
|
||||
|
||||
**功能描述**: 处理指定的智能耳标预警。
|
||||
|
||||
**路径参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| id | string | 是 | 预警ID |
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"action": "acknowledged",
|
||||
"notes": "已联系技术人员处理",
|
||||
"handler": "张三"
|
||||
}
|
||||
```
|
||||
|
||||
**请求字段说明**:
|
||||
- `action`: 处理动作(可选,默认为 "acknowledged")
|
||||
- `notes`: 处理备注(可选)
|
||||
- `handler`: 处理人(可选,默认为 "system")
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"alertId": "123_offline",
|
||||
"action": "acknowledged",
|
||||
"notes": "已联系技术人员处理",
|
||||
"handler": "张三",
|
||||
"processedAt": "2024-01-15T10:35:00.000Z",
|
||||
"status": "processed"
|
||||
},
|
||||
"message": "预警处理成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 批量处理智能耳标预警
|
||||
|
||||
**接口地址**: `POST /eartag/batch-handle`
|
||||
|
||||
**功能描述**: 批量处理多个智能耳标预警。
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"alertIds": ["123_offline", "124_battery", "125_temperature"],
|
||||
"action": "acknowledged",
|
||||
"notes": "批量处理完成",
|
||||
"handler": "李四"
|
||||
}
|
||||
```
|
||||
|
||||
**请求字段说明**:
|
||||
- `alertIds`: 预警ID列表(必填)
|
||||
- `action`: 处理动作(可选,默认为 "acknowledged")
|
||||
- `notes`: 处理备注(可选)
|
||||
- `handler`: 处理人(可选,默认为 "system")
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"processedCount": 3,
|
||||
"results": [
|
||||
{
|
||||
"alertId": "123_offline",
|
||||
"action": "acknowledged",
|
||||
"notes": "批量处理完成",
|
||||
"handler": "李四",
|
||||
"processedAt": "2024-01-15T10:35:00.000Z",
|
||||
"status": "processed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"message": "成功处理 3 个预警"
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 导出智能耳标预警数据
|
||||
|
||||
**接口地址**: `GET /eartag/export`
|
||||
|
||||
**功能描述**: 导出智能耳标预警数据,支持JSON和CSV格式。
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| search | string | 否 | - | 搜索关键词 |
|
||||
| alertType | string | 否 | - | 预警类型筛选 |
|
||||
| alertLevel | string | 否 | - | 预警级别筛选 |
|
||||
| startDate | string | 否 | - | 开始日期 |
|
||||
| endDate | string | 否 | - | 结束日期 |
|
||||
| format | string | 否 | json | 导出格式 (json/csv) |
|
||||
|
||||
**响应示例** (JSON格式):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "123_offline",
|
||||
"deviceId": 123,
|
||||
"deviceName": "EARTAG001",
|
||||
"eartagNumber": "EARTAG001",
|
||||
"alertType": "offline",
|
||||
"alertLevel": "high",
|
||||
"alertTime": "2024-01-15 10:30:00",
|
||||
"battery": 85,
|
||||
"temperature": 25.5,
|
||||
"dailySteps": 0,
|
||||
"totalSteps": 1500,
|
||||
"yesterdaySteps": 1500,
|
||||
"deviceStatus": "离线",
|
||||
"gpsSignal": "无",
|
||||
"movementStatus": "静止",
|
||||
"description": "设备已离线超过30分钟",
|
||||
"longitude": 116.3974,
|
||||
"latitude": 39.9093
|
||||
}
|
||||
],
|
||||
"total": 35,
|
||||
"message": "导出智能耳标预警数据成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
所有接口在发生错误时都会返回统一的错误格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误描述",
|
||||
"error": "详细错误信息"
|
||||
}
|
||||
```
|
||||
|
||||
**常见HTTP状态码**:
|
||||
- `200`: 请求成功
|
||||
- `400`: 请求参数错误
|
||||
- `404`: 资源不存在
|
||||
- `500`: 服务器内部错误
|
||||
|
||||
## 使用示例
|
||||
|
||||
### JavaScript/Node.js 示例
|
||||
|
||||
```javascript
|
||||
// 获取预警统计
|
||||
const stats = await fetch('http://localhost:3000/api/smart-alerts/public/eartag/stats')
|
||||
.then(response => response.json());
|
||||
|
||||
// 获取预警列表
|
||||
const alerts = await fetch('http://localhost:3000/api/smart-alerts/public/eartag?page=1&limit=10&alertType=battery')
|
||||
.then(response => response.json());
|
||||
|
||||
// 处理预警
|
||||
const result = await fetch('http://localhost:3000/api/smart-alerts/public/eartag/123_offline/handle', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'acknowledged',
|
||||
notes: '已处理',
|
||||
handler: '张三'
|
||||
})
|
||||
}).then(response => response.json());
|
||||
```
|
||||
|
||||
### Python 示例
|
||||
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
|
||||
# 获取预警统计
|
||||
stats_response = requests.get('http://localhost:3000/api/smart-alerts/public/eartag/stats')
|
||||
stats = stats_response.json()
|
||||
|
||||
# 获取预警列表
|
||||
alerts_response = requests.get('http://localhost:3000/api/smart-alerts/public/eartag',
|
||||
params={'page': 1, 'limit': 10, 'alertType': 'battery'})
|
||||
alerts = alerts_response.json()
|
||||
|
||||
# 处理预警
|
||||
handle_data = {
|
||||
'action': 'acknowledged',
|
||||
'notes': '已处理',
|
||||
'handler': '张三'
|
||||
}
|
||||
result = requests.post('http://localhost:3000/api/smart-alerts/public/eartag/123_offline/handle',
|
||||
json=handle_data)
|
||||
```
|
||||
|
||||
### cURL 示例
|
||||
|
||||
```bash
|
||||
# 获取预警统计
|
||||
curl -X GET "http://localhost:3000/api/smart-alerts/public/eartag/stats"
|
||||
|
||||
# 获取预警列表
|
||||
curl -X GET "http://localhost:3000/api/smart-alerts/public/eartag?page=1&limit=10&alertType=battery"
|
||||
|
||||
# 处理预警
|
||||
curl -X POST "http://localhost:3000/api/smart-alerts/public/eartag/123_offline/handle" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"action": "acknowledged", "notes": "已处理", "handler": "张三"}'
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有接口均为公开接口,无需身份验证
|
||||
2. 预警ID格式为 `deviceId_alertType`,例如 `123_offline`
|
||||
3. 时间格式统一使用 ISO 8601 标准
|
||||
4. 分页从1开始,不是从0开始
|
||||
5. 搜索功能支持模糊匹配
|
||||
6. 导出功能支持大量数据,建议合理设置筛选条件
|
||||
7. 批量处理功能有数量限制,建议单次处理不超过100个预警
|
||||
|
||||
## 更新日志
|
||||
|
||||
- **v1.0.0** (2024-01-15): 初始版本,包含基础预警查询、统计、处理和导出功能
|
||||
@@ -1,88 +0,0 @@
|
||||
# 数据一致性检查报告
|
||||
|
||||
## 问题描述
|
||||
用户反映项圈编号22012000107在页面中显示的电量为14,但数据库中显示的电量为98。
|
||||
|
||||
## 检查结果
|
||||
|
||||
### 数据库实际数据
|
||||
通过直接查询数据库 `iot_xq_client` 表,项圈22012000107的实际数据为:
|
||||
- **ID**: 3517
|
||||
- **SN**: 22012000107
|
||||
- **电量**: 14
|
||||
- **温度**: 10.1
|
||||
- **状态**: 0 (离线)
|
||||
- **更新时间**: 1668348374 (2022-11-13)
|
||||
|
||||
### API返回数据
|
||||
通过API `/api/smart-alerts/public/collar` 返回的数据:
|
||||
- **项圈编号**: 22012000107
|
||||
- **电量**: 14
|
||||
- **温度**: 10.1
|
||||
- **预警类型**: 低电量预警、离线预警、温度过低预警、异常运动预警、佩戴异常预警
|
||||
|
||||
### 前端显示数据
|
||||
页面中显示的数据:
|
||||
- **项圈编号**: 22012000107
|
||||
- **电量**: 14
|
||||
- **温度**: 10.1
|
||||
- **预警类型**: 低电量预警
|
||||
- **预警级别**: 中级
|
||||
|
||||
## 结论
|
||||
|
||||
**数据是一致的!** 项圈22012000107在数据库、API和前端页面中显示的电量都是14,没有数据不一致的问题。
|
||||
|
||||
### 可能的原因
|
||||
1. **查看的是不同项圈**: 用户可能查看的是其他项圈编号的数据
|
||||
2. **时间差异**: 数据可能在不同时间点发生了变化
|
||||
3. **缓存问题**: 可能存在浏览器缓存或应用缓存问题
|
||||
4. **多个记录**: 可能存在多个相同项圈编号的记录
|
||||
|
||||
### 其他项圈的电量数据
|
||||
从数据库查询结果可以看到,其他项圈的电量数据:
|
||||
- 22012000108: 98%
|
||||
- 15010000006: 98%
|
||||
- 15010000007: 98%
|
||||
- 15010000008: 83%
|
||||
- 15010000015: 98%
|
||||
|
||||
## 建议
|
||||
|
||||
1. **确认项圈编号**: 请确认查看的是正确的项圈编号22012000107
|
||||
2. **清除缓存**: 清除浏览器缓存并刷新页面
|
||||
3. **检查时间**: 确认查看数据的时间点
|
||||
4. **重新查询**: 重新查询数据库确认当前数据
|
||||
|
||||
## 技术细节
|
||||
|
||||
### 数据库表结构
|
||||
- 表名: `iot_xq_client`
|
||||
- 主键: `id`
|
||||
- 项圈编号字段: `sn`
|
||||
- 电量字段: `battery` (varchar类型)
|
||||
- 温度字段: `temperature` (varchar类型)
|
||||
|
||||
### API处理流程
|
||||
1. 从数据库查询设备数据
|
||||
2. 根据数据生成预警信息
|
||||
3. 返回包含原始数据的预警列表
|
||||
4. 前端接收并显示数据
|
||||
|
||||
### 数据转换
|
||||
- 数据库电量: "14" (字符串)
|
||||
- API返回电量: 14 (数字)
|
||||
- 前端显示电量: 14
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `backend/check-specific-collar.js` - 特定项圈检查脚本
|
||||
- `backend/check-database-data.js` - 数据库数据检查脚本
|
||||
- `backend/check-all-tables.js` - 所有表检查脚本
|
||||
- `backend/simple-db-test.js` - 简单数据库测试脚本
|
||||
|
||||
---
|
||||
|
||||
**检查时间**: 2025-01-18
|
||||
**检查结果**: 数据一致,无问题
|
||||
**状态**: 已确认
|
||||
@@ -1,120 +0,0 @@
|
||||
# 智能项圈预警错误修复总结
|
||||
|
||||
## 问题描述
|
||||
在智能项圈预警页面中出现了JavaScript错误:
|
||||
```
|
||||
ReferenceError: determinedAlertType is not defined
|
||||
at SmartCollarAlert.vue:611:32
|
||||
```
|
||||
|
||||
## 问题原因
|
||||
在数据转换的`map`函数中,`determinedAlertType`变量只在`if-else`块内部定义,但在函数外部被引用,导致作用域错误。
|
||||
|
||||
## 解决方案
|
||||
将`determinedAlertType`变量声明移到`if-else`块外部,确保在整个函数作用域内都可以访问。
|
||||
|
||||
### 修复前的问题代码
|
||||
```javascript
|
||||
if (item.alertType) {
|
||||
// 使用API返回的预警类型
|
||||
alertTypeText = alertTypeMap[item.alertType] || item.alertType
|
||||
// determinedAlertType 在这里没有定义
|
||||
} else {
|
||||
// 如果没有预警类型,使用判断函数
|
||||
const determinedAlertType = determineAlertType(item) // 只在else块中定义
|
||||
alertTypeText = determinedAlertType ? getAlertTypeText(determinedAlertType) : '正常'
|
||||
}
|
||||
|
||||
// 在函数外部引用 determinedAlertType - 这里会报错
|
||||
determinedAlertType: determinedAlertType
|
||||
```
|
||||
|
||||
### 修复后的代码
|
||||
```javascript
|
||||
let alertTypeText = '正常'
|
||||
let alertLevel = 'low'
|
||||
let determinedAlertType = null // 在外部声明
|
||||
|
||||
if (item.alertType) {
|
||||
// 使用API返回的预警类型
|
||||
alertTypeText = alertTypeMap[item.alertType] || item.alertType
|
||||
determinedAlertType = item.alertType // 赋值
|
||||
} else {
|
||||
// 如果没有预警类型,使用判断函数
|
||||
determinedAlertType = determineAlertType(item) // 重新赋值
|
||||
alertTypeText = determinedAlertType ? getAlertTypeText(determinedAlertType) : '正常'
|
||||
}
|
||||
|
||||
// 现在可以安全引用 determinedAlertType
|
||||
determinedAlertType: determinedAlertType
|
||||
```
|
||||
|
||||
## 修复效果
|
||||
|
||||
### 修复前
|
||||
- ❌ JavaScript运行时错误
|
||||
- ❌ 页面无法正常加载数据
|
||||
- ❌ 控制台显示ReferenceError
|
||||
|
||||
### 修复后
|
||||
- ✅ 无JavaScript错误
|
||||
- ✅ 页面正常加载和显示数据
|
||||
- ✅ 统计数据正确显示(非零值)
|
||||
- ✅ 预警列表正常显示
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 运行测试脚本
|
||||
```bash
|
||||
node backend/test-error-fix.js
|
||||
```
|
||||
|
||||
### 测试结果
|
||||
```
|
||||
✅ API调用成功
|
||||
数据条数: 3
|
||||
统计数据: {
|
||||
lowBattery: 22,
|
||||
offline: 1779,
|
||||
highTemperature: 1302,
|
||||
abnormalMovement: 1908,
|
||||
wearOff: 50
|
||||
}
|
||||
|
||||
✅ 数据转换测试通过,没有ReferenceError
|
||||
```
|
||||
|
||||
## 当前功能状态
|
||||
|
||||
### 统计数据
|
||||
- 低电量预警: 22个
|
||||
- 离线预警: 1779个
|
||||
- 温度预警: 1302个
|
||||
- 异常运动预警: 1908个
|
||||
- 佩戴异常预警: 50个
|
||||
|
||||
### 数据转换
|
||||
- ✅ 正确使用API返回的预警类型
|
||||
- ✅ 正确映射预警级别
|
||||
- ✅ 保留判断函数作为备用
|
||||
- ✅ 无JavaScript错误
|
||||
|
||||
### 页面功能
|
||||
- ✅ 统计卡片显示
|
||||
- ✅ 预警列表显示
|
||||
- ✅ 搜索和筛选
|
||||
- ✅ 分页功能
|
||||
- ✅ 预警处理
|
||||
- ✅ 数据导出
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `admin-system/src/views/SmartCollarAlert.vue` - 主要修复文件
|
||||
- `backend/test-error-fix.js` - 测试脚本
|
||||
- `backend/ERROR_FIX_SUMMARY.md` - 本文档
|
||||
|
||||
---
|
||||
|
||||
**修复时间**: 2025-01-18
|
||||
**修复版本**: v1.0.1
|
||||
**状态**: 已修复并测试通过
|
||||
@@ -1,139 +0,0 @@
|
||||
# 网络访问问题解决方案
|
||||
|
||||
## 问题描述
|
||||
您能访问 `http://172.28.112.1:5300/` 而别人访问不了,这是因为网络配置和防火墙设置的问题。
|
||||
|
||||
## 已完成的修复
|
||||
|
||||
### 1. ✅ 后端服务器配置修复
|
||||
- 修改了 `server.js` 文件
|
||||
- 服务器现在监听所有网络接口 (`0.0.0.0:5350`)
|
||||
- 不再只监听本地回环地址
|
||||
|
||||
### 2. ✅ 前端服务器配置检查
|
||||
- 前端已正确配置 `host: '0.0.0.0'`
|
||||
- 可以接受来自任何IP地址的连接
|
||||
|
||||
## 解决步骤
|
||||
|
||||
### 步骤1:重启服务器
|
||||
```bash
|
||||
# 停止当前服务器(Ctrl+C)
|
||||
# 重新启动后端服务器
|
||||
cd backend
|
||||
npm start
|
||||
```
|
||||
|
||||
### 步骤2:配置Windows防火墙
|
||||
|
||||
#### 方法A:使用批处理脚本(推荐)
|
||||
1. 右键点击 `configure-firewall.bat`
|
||||
2. 选择"以管理员身份运行"
|
||||
3. 等待配置完成
|
||||
|
||||
#### 方法B:使用PowerShell脚本
|
||||
1. 右键点击 `configure-firewall.ps1`
|
||||
2. 选择"以管理员身份运行"
|
||||
3. 等待配置完成
|
||||
|
||||
#### 方法C:手动配置
|
||||
以管理员身份运行PowerShell,执行以下命令:
|
||||
```powershell
|
||||
# 允许前端端口
|
||||
netsh advfirewall firewall add rule name="Node.js Frontend Port 5300" dir=in action=allow protocol=TCP localport=5300
|
||||
|
||||
# 允许后端端口
|
||||
netsh advfirewall firewall add rule name="Node.js Backend Port 5350" dir=in action=allow protocol=TCP localport=5350
|
||||
```
|
||||
|
||||
### 步骤3:验证配置
|
||||
运行网络诊断脚本:
|
||||
```bash
|
||||
node fix-network-access.js
|
||||
```
|
||||
|
||||
### 步骤4:测试访问
|
||||
让其他用户访问以下地址之一:
|
||||
- `http://172.28.112.1:5300` (前端)
|
||||
- `http://172.28.112.1:5350` (后端)
|
||||
- `http://192.168.0.48:5300` (如果使用以太网)
|
||||
- `http://192.168.0.48:5350` (如果使用以太网)
|
||||
|
||||
## 网络接口说明
|
||||
|
||||
根据诊断结果,您有以下可用的网络接口:
|
||||
- **Clash**: 198.18.0.1 (VPN接口)
|
||||
- **vEthernet (Default Switch)**: 172.28.112.1 (Hyper-V接口)
|
||||
- **以太网**: 192.168.0.48 (主要网络接口)
|
||||
- **VMware Network Adapter VMnet1**: 192.168.134.1
|
||||
- **VMware Network Adapter VMnet8**: 192.168.238.1
|
||||
|
||||
## 常见问题解决
|
||||
|
||||
### 问题1:其他用户仍然无法访问
|
||||
**解决方案**:
|
||||
1. 确认其他用户与您在同一个局域网内
|
||||
2. 使用正确的IP地址(不是localhost)
|
||||
3. 检查路由器是否阻止了设备间通信
|
||||
|
||||
### 问题2:端口被占用
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 查看端口占用情况
|
||||
netstat -ano | findstr :5300
|
||||
netstat -ano | findstr :5350
|
||||
|
||||
# 终止占用端口的进程
|
||||
taskkill /PID [进程ID] /F
|
||||
```
|
||||
|
||||
### 问题3:防火墙配置失败
|
||||
**解决方案**:
|
||||
1. 确保以管理员身份运行脚本
|
||||
2. 检查Windows防火墙是否被禁用
|
||||
3. 手动在Windows安全中心添加规则
|
||||
|
||||
## 验证方法
|
||||
|
||||
### 1. 检查服务器状态
|
||||
```bash
|
||||
# 应该看到类似输出
|
||||
netstat -ano | findstr :5300
|
||||
# TCP 0.0.0.0:5300 0.0.0.0:0 LISTENING [PID]
|
||||
|
||||
netstat -ano | findstr :5350
|
||||
# TCP 0.0.0.0:5350 0.0.0.0:0 LISTENING [PID]
|
||||
```
|
||||
|
||||
### 2. 测试连接
|
||||
```bash
|
||||
# 测试本地连接
|
||||
telnet localhost 5300
|
||||
telnet localhost 5350
|
||||
|
||||
# 测试局域网连接
|
||||
telnet 172.28.112.1 5300
|
||||
telnet 172.28.112.1 5350
|
||||
```
|
||||
|
||||
### 3. 浏览器测试
|
||||
在浏览器中访问:
|
||||
- `http://172.28.112.1:5300` - 应该看到前端页面
|
||||
- `http://172.28.112.1:5350/api-docs` - 应该看到API文档
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **安全性**:开放端口到所有网络接口会降低安全性,仅用于开发环境
|
||||
2. **网络环境**:确保在受信任的局域网环境中使用
|
||||
3. **IP地址变化**:如果IP地址发生变化,需要更新访问地址
|
||||
4. **路由器设置**:某些路由器可能阻止设备间通信,需要检查路由器设置
|
||||
|
||||
## 成功标志
|
||||
|
||||
当配置成功后,您应该看到:
|
||||
- 服务器启动时显示"服务器监听所有网络接口 (0.0.0.0:5350)"
|
||||
- 其他用户可以通过您的IP地址访问服务
|
||||
- 防火墙规则已正确添加
|
||||
- 网络诊断脚本显示端口可以正常监听
|
||||
|
||||
如果按照以上步骤操作后仍有问题,请检查网络环境或联系网络管理员。
|
||||
@@ -1,186 +0,0 @@
|
||||
# ngrok外网访问配置指南
|
||||
|
||||
## 🎯 目标
|
||||
让不在同一局域网的用户能够访问您的开发服务器
|
||||
|
||||
## 📋 完整步骤
|
||||
|
||||
### 步骤1:注册ngrok账号
|
||||
1. 访问 https://ngrok.com/
|
||||
2. 点击 "Sign up" 注册免费账号
|
||||
3. 验证邮箱后登录
|
||||
|
||||
### 步骤2:获取认证令牌
|
||||
1. 登录后访问 https://dashboard.ngrok.com/get-started/your-authtoken
|
||||
2. 复制您的authtoken(类似:`2abc123def456ghi789jkl012mno345pqr678stu901vwx234yz`)
|
||||
|
||||
### 步骤3:配置ngrok认证
|
||||
```bash
|
||||
# 在backend目录下运行
|
||||
.\ngrok.exe authtoken YOUR_AUTH_TOKEN
|
||||
```
|
||||
|
||||
### 步骤4:启动服务穿透
|
||||
|
||||
#### 方法A:使用批处理脚本
|
||||
```bash
|
||||
# 双击运行
|
||||
start-ngrok.bat
|
||||
```
|
||||
|
||||
#### 方法B:使用PowerShell脚本
|
||||
```powershell
|
||||
# 右键以管理员身份运行
|
||||
.\start-ngrok.ps1
|
||||
```
|
||||
|
||||
#### 方法C:手动启动
|
||||
```bash
|
||||
# 启动前端穿透(新开一个终端窗口)
|
||||
.\ngrok.exe http 5300
|
||||
|
||||
# 启动后端穿透(再开一个终端窗口)
|
||||
.\ngrok.exe http 5350
|
||||
```
|
||||
|
||||
### 步骤5:获取访问地址
|
||||
|
||||
ngrok会显示类似这样的信息:
|
||||
```
|
||||
Session Status online
|
||||
Account your-email@example.com
|
||||
Version 3.27.0
|
||||
Region United States (us)
|
||||
Latency 45ms
|
||||
Web Interface http://127.0.0.1:4040
|
||||
Forwarding https://abc123.ngrok.io -> http://localhost:5300
|
||||
```
|
||||
|
||||
### 步骤6:分享访问地址
|
||||
|
||||
- **前端访问地址**:`https://abc123.ngrok.io`
|
||||
- **后端访问地址**:`https://def456.ngrok.io`
|
||||
- **API文档地址**:`https://def456.ngrok.io/api-docs`
|
||||
|
||||
## 🔧 高级配置
|
||||
|
||||
### 自定义子域名(付费功能)
|
||||
```bash
|
||||
# 使用自定义子域名
|
||||
.\ngrok.exe http 5300 --subdomain=myapp-frontend
|
||||
.\ngrok.exe http 5350 --subdomain=myapp-backend
|
||||
```
|
||||
|
||||
### 同时启动多个服务
|
||||
```bash
|
||||
# 使用配置文件
|
||||
.\ngrok.exe start --all --config=ngrok.yml
|
||||
```
|
||||
|
||||
### 配置文件示例 (ngrok.yml)
|
||||
```yaml
|
||||
version: "2"
|
||||
authtoken: YOUR_AUTH_TOKEN
|
||||
tunnels:
|
||||
frontend:
|
||||
proto: http
|
||||
addr: 5300
|
||||
subdomain: myapp-frontend
|
||||
backend:
|
||||
proto: http
|
||||
addr: 5350
|
||||
subdomain: myapp-backend
|
||||
```
|
||||
|
||||
## 📊 免费版限制
|
||||
|
||||
- 每次重启ngrok,URL会变化
|
||||
- 同时只能运行1个隧道
|
||||
- 有连接数限制
|
||||
- 有带宽限制
|
||||
|
||||
## 💰 付费版优势
|
||||
|
||||
- 固定子域名
|
||||
- 多个隧道
|
||||
- 更高带宽
|
||||
- 更多功能
|
||||
|
||||
## 🚨 注意事项
|
||||
|
||||
1. **安全性**:
|
||||
- 外网访问会暴露您的服务
|
||||
- 建议设置访问密码
|
||||
- 不要在生产环境使用
|
||||
|
||||
2. **性能**:
|
||||
- 外网访问比内网慢
|
||||
- 免费版有带宽限制
|
||||
|
||||
3. **稳定性**:
|
||||
- 免费版URL会变化
|
||||
- 付费版更稳定
|
||||
|
||||
## 🛠️ 故障排除
|
||||
|
||||
### 问题1:ngrok启动失败
|
||||
```bash
|
||||
# 检查网络连接
|
||||
ping ngrok.com
|
||||
|
||||
# 重新配置认证
|
||||
.\ngrok.exe authtoken YOUR_AUTH_TOKEN
|
||||
```
|
||||
|
||||
### 问题2:无法访问服务
|
||||
```bash
|
||||
# 检查本地服务是否运行
|
||||
netstat -ano | findstr :5300
|
||||
netstat -ano | findstr :5350
|
||||
|
||||
# 检查防火墙设置
|
||||
netsh advfirewall firewall show rule name="Node.js Frontend Port 5300"
|
||||
```
|
||||
|
||||
### 问题3:URL无法访问
|
||||
- 检查ngrok是否在线
|
||||
- 重新启动ngrok
|
||||
- 检查本地服务状态
|
||||
|
||||
## 📱 移动端访问
|
||||
|
||||
ngrok提供的HTTPS地址可以在移动设备上正常访问:
|
||||
- 手机浏览器访问:`https://abc123.ngrok.io`
|
||||
- 平板电脑访问:`https://abc123.ngrok.io`
|
||||
|
||||
## 🔄 自动重启脚本
|
||||
|
||||
创建自动重启脚本,当ngrok断开时自动重连:
|
||||
|
||||
```bash
|
||||
# auto-restart-ngrok.bat
|
||||
@echo off
|
||||
:start
|
||||
echo 启动ngrok...
|
||||
.\ngrok.exe http 5300
|
||||
echo ngrok断开,3秒后重新启动...
|
||||
timeout /t 3 /nobreak >nul
|
||||
goto start
|
||||
```
|
||||
|
||||
## 📈 监控和日志
|
||||
|
||||
ngrok提供Web界面监控:
|
||||
- 访问:http://127.0.0.1:4040
|
||||
- 查看请求日志
|
||||
- 监控连接状态
|
||||
- 查看流量统计
|
||||
|
||||
## 🎉 完成!
|
||||
|
||||
配置完成后,其他用户就可以通过ngrok提供的HTTPS地址访问您的开发服务器了!
|
||||
|
||||
记住:
|
||||
- 每次重启ngrok,URL会变化
|
||||
- 免费版有使用限制
|
||||
- 建议在开发测试时使用
|
||||
@@ -1,341 +0,0 @@
|
||||
# 智能预警系统 API 完整封装
|
||||
|
||||
## 概述
|
||||
|
||||
本项目为智能预警系统提供了完整的API接口封装,包括智能耳标预警和智能项圈预警两个子系统。所有接口均为公开接口,支持完整的CRUD操作、数据导出和实时监控功能。
|
||||
|
||||
## 系统架构
|
||||
|
||||
```
|
||||
智能预警系统 API
|
||||
├── 智能耳标预警 (Eartag Alerts)
|
||||
│ ├── 预警统计
|
||||
│ ├── 预警列表查询
|
||||
│ ├── 预警详情获取
|
||||
│ ├── 预警处理
|
||||
│ ├── 批量处理
|
||||
│ └── 数据导出
|
||||
└── 智能项圈预警 (Collar Alerts)
|
||||
├── 预警统计
|
||||
├── 预警列表查询
|
||||
├── 预警详情获取
|
||||
├── 预警处理
|
||||
├── 批量处理
|
||||
└── 数据导出
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
|
||||
### ✅ 核心功能
|
||||
- **预警统计**: 实时统计各类预警数量
|
||||
- **预警查询**: 支持分页、搜索、多维度筛选
|
||||
- **预警详情**: 获取单个预警的完整信息
|
||||
- **预警处理**: 单个和批量预警处理
|
||||
- **数据导出**: 支持JSON和CSV格式导出
|
||||
- **实时监控**: 提供预警变化监控功能
|
||||
|
||||
### ✅ 技术特性
|
||||
- **RESTful API**: 遵循REST设计原则
|
||||
- **Swagger文档**: 完整的API文档和在线测试
|
||||
- **错误处理**: 统一的错误响应格式
|
||||
- **参数验证**: 完整的请求参数验证
|
||||
- **性能优化**: 支持分页和筛选优化
|
||||
- **跨平台**: 支持多种编程语言调用
|
||||
|
||||
## API接口总览
|
||||
|
||||
### 智能耳标预警 API
|
||||
|
||||
| 接口 | 方法 | 路径 | 功能 |
|
||||
|------|------|------|------|
|
||||
| 获取预警统计 | GET | `/eartag/stats` | 获取各类预警数量统计 |
|
||||
| 获取预警列表 | GET | `/eartag` | 分页查询预警列表 |
|
||||
| 获取预警详情 | GET | `/eartag/{id}` | 获取单个预警详情 |
|
||||
| 处理预警 | POST | `/eartag/{id}/handle` | 处理单个预警 |
|
||||
| 批量处理预警 | POST | `/eartag/batch-handle` | 批量处理预警 |
|
||||
| 导出预警数据 | GET | `/eartag/export` | 导出预警数据 |
|
||||
|
||||
### 智能项圈预警 API
|
||||
|
||||
| 接口 | 方法 | 路径 | 功能 |
|
||||
|------|------|------|------|
|
||||
| 获取预警统计 | GET | `/collar/stats` | 获取各类预警数量统计 |
|
||||
| 获取预警列表 | GET | `/collar` | 分页查询预警列表 |
|
||||
| 获取预警详情 | GET | `/collar/{id}` | 获取单个预警详情 |
|
||||
| 处理预警 | POST | `/collar/{id}/handle` | 处理单个预警 |
|
||||
| 批量处理预警 | POST | `/collar/batch-handle` | 批量处理预警 |
|
||||
| 导出预警数据 | GET | `/collar/export` | 导出预警数据 |
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 启动服务
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
服务将在 `http://localhost:5350` 启动。
|
||||
|
||||
### 2. 访问API文档
|
||||
|
||||
打开浏览器访问:`http://localhost:5350/api-docs`
|
||||
|
||||
### 3. 测试API接口
|
||||
|
||||
```bash
|
||||
# 测试智能耳标预警API
|
||||
node test-smart-eartag-alert-api.js
|
||||
|
||||
# 测试智能项圈预警API
|
||||
node test-smart-collar-alert-api.js
|
||||
|
||||
# 运行综合测试
|
||||
node test-all-smart-alert-apis.js
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### Node.js 示例
|
||||
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
// 获取智能耳标预警统计
|
||||
const eartagStats = await axios.get('http://localhost:5350/api/smart-alerts/public/eartag/stats');
|
||||
console.log('耳标预警统计:', eartagStats.data);
|
||||
|
||||
// 获取智能项圈预警列表
|
||||
const collarAlerts = await axios.get('http://localhost:5350/api/smart-alerts/public/collar?page=1&limit=10');
|
||||
console.log('项圈预警列表:', collarAlerts.data);
|
||||
|
||||
// 处理预警
|
||||
const handleResult = await axios.post('http://localhost:5350/api/smart-alerts/public/eartag/123_offline/handle', {
|
||||
action: 'acknowledged',
|
||||
notes: '已处理',
|
||||
handler: '张三'
|
||||
});
|
||||
console.log('处理结果:', handleResult.data);
|
||||
```
|
||||
|
||||
### 前端Vue示例
|
||||
|
||||
```javascript
|
||||
import { smartAlertService } from '@/utils/dataService';
|
||||
|
||||
// 获取耳标预警列表
|
||||
const eartagAlerts = await smartAlertService.getEartagAlerts({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
alertType: 'battery'
|
||||
});
|
||||
|
||||
// 获取项圈预警统计
|
||||
const collarStats = await smartAlertService.getCollarAlertStats();
|
||||
|
||||
// 批量处理预警
|
||||
const batchResult = await smartAlertService.batchHandleEartagAlerts({
|
||||
alertIds: ['123_offline', '124_battery'],
|
||||
action: 'acknowledged',
|
||||
notes: '批量处理',
|
||||
handler: '管理员'
|
||||
});
|
||||
```
|
||||
|
||||
### Python 示例
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 获取预警统计
|
||||
response = requests.get('http://localhost:5350/api/smart-alerts/public/eartag/stats')
|
||||
stats = response.json()
|
||||
print('预警统计:', stats['data'])
|
||||
|
||||
# 处理预警
|
||||
handle_data = {
|
||||
'action': 'acknowledged',
|
||||
'notes': '已处理',
|
||||
'handler': '张三'
|
||||
}
|
||||
result = requests.post('http://localhost:5350/api/smart-alerts/public/eartag/123_offline/handle',
|
||||
json=handle_data)
|
||||
print('处理结果:', result.json())
|
||||
```
|
||||
|
||||
## 数据模型
|
||||
|
||||
### 预警数据结构
|
||||
|
||||
#### 智能耳标预警
|
||||
```javascript
|
||||
{
|
||||
"id": "123_offline", // 预警ID
|
||||
"deviceId": 123, // 设备ID
|
||||
"deviceName": "EARTAG001", // 设备名称
|
||||
"eartagNumber": "EARTAG001", // 耳标编号
|
||||
"alertType": "offline", // 预警类型
|
||||
"alertLevel": "high", // 预警级别
|
||||
"alertTime": "2024-01-15 10:30:00", // 预警时间
|
||||
"battery": 85, // 设备电量
|
||||
"temperature": 25.5, // 设备温度
|
||||
"dailySteps": 0, // 当日步数
|
||||
"deviceStatus": "离线", // 设备状态
|
||||
"description": "设备已离线超过30分钟" // 预警描述
|
||||
}
|
||||
```
|
||||
|
||||
#### 智能项圈预警
|
||||
```javascript
|
||||
{
|
||||
"id": "123_offline", // 预警ID
|
||||
"deviceId": 123, // 设备ID
|
||||
"deviceName": "COLLAR001", // 设备名称
|
||||
"collarNumber": "COLLAR001", // 项圈编号
|
||||
"alertType": "offline", // 预警类型
|
||||
"alertLevel": "high", // 预警级别
|
||||
"alertTime": "2024-01-15 10:30:00", // 预警时间
|
||||
"battery": 85, // 设备电量
|
||||
"temperature": 25.5, // 设备温度
|
||||
"dailySteps": 0, // 当日步数
|
||||
"deviceStatus": "离线", // 设备状态
|
||||
"wearStatus": "未佩戴", // 佩戴状态
|
||||
"description": "设备已离线超过30分钟" // 预警描述
|
||||
}
|
||||
```
|
||||
|
||||
### 预警类型
|
||||
|
||||
#### 智能耳标预警类型
|
||||
- `battery`: 低电量预警
|
||||
- `offline`: 离线预警
|
||||
- `temperature`: 温度预警
|
||||
- `movement`: 异常运动预警
|
||||
|
||||
#### 智能项圈预警类型
|
||||
- `battery`: 低电量预警
|
||||
- `offline`: 离线预警
|
||||
- `temperature`: 温度预警
|
||||
- `movement`: 异常运动预警
|
||||
- `wear`: 项圈脱落预警
|
||||
|
||||
### 预警级别
|
||||
- `high`: 高级
|
||||
- `medium`: 中级
|
||||
- `low`: 低级
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 环境变量
|
||||
```bash
|
||||
PORT=5350 # API服务端口
|
||||
NODE_ENV=development # 运行环境
|
||||
```
|
||||
|
||||
### 数据库配置
|
||||
系统使用MySQL数据库,需要配置以下表:
|
||||
- `iot_jbq_client`: 智能耳标设备数据
|
||||
- `iot_xq_client`: 智能项圈设备数据
|
||||
|
||||
## 监控和维护
|
||||
|
||||
### 日志记录
|
||||
- API调用日志
|
||||
- 错误日志
|
||||
- 性能监控日志
|
||||
|
||||
### 监控指标
|
||||
- API响应时间
|
||||
- 错误率
|
||||
- 请求量
|
||||
- 预警处理效率
|
||||
|
||||
### 健康检查
|
||||
```bash
|
||||
# 检查服务状态
|
||||
curl http://localhost:5350/
|
||||
|
||||
# 检查API文档
|
||||
curl http://localhost:5350/api-docs/swagger.json
|
||||
```
|
||||
|
||||
## 扩展开发
|
||||
|
||||
### 添加新的预警类型
|
||||
1. 在控制器中添加新的预警检测逻辑
|
||||
2. 更新API文档中的预警类型枚举
|
||||
3. 更新前端界面的预警类型选项
|
||||
|
||||
### 添加新的处理动作
|
||||
1. 在控制器中添加新的处理逻辑
|
||||
2. 更新数据库模型(如果需要)
|
||||
3. 更新API文档和前端界面
|
||||
|
||||
### 自定义筛选条件
|
||||
1. 在控制器中添加新的筛选逻辑
|
||||
2. 更新API文档中的参数说明
|
||||
3. 更新前端界面的筛选选项
|
||||
|
||||
## 测试
|
||||
|
||||
### 运行测试
|
||||
```bash
|
||||
# 运行所有测试
|
||||
npm test
|
||||
|
||||
# 运行特定测试
|
||||
node test-smart-eartag-alert-api.js
|
||||
node test-smart-collar-alert-api.js
|
||||
node test-all-smart-alert-apis.js
|
||||
```
|
||||
|
||||
### 测试覆盖
|
||||
- ✅ API接口功能测试
|
||||
- ✅ 参数验证测试
|
||||
- ✅ 错误处理测试
|
||||
- ✅ 数据格式验证测试
|
||||
- ✅ 性能测试
|
||||
- ✅ 并发测试
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **API无法访问**
|
||||
- 检查服务是否启动
|
||||
- 检查端口是否正确
|
||||
- 检查防火墙设置
|
||||
|
||||
2. **数据库连接失败**
|
||||
- 检查数据库配置
|
||||
- 检查数据库服务状态
|
||||
- 检查网络连接
|
||||
|
||||
3. **API响应慢**
|
||||
- 检查数据库性能
|
||||
- 检查网络延迟
|
||||
- 优化查询条件
|
||||
|
||||
### 调试模式
|
||||
```bash
|
||||
# 启用调试模式
|
||||
DEBUG=* npm start
|
||||
|
||||
# 查看详细日志
|
||||
NODE_ENV=development npm start
|
||||
```
|
||||
|
||||
## 版本历史
|
||||
|
||||
- **v1.0.0** (2024-01-15): 初始版本,包含完整的智能预警API系统
|
||||
|
||||
## 联系支持
|
||||
|
||||
如有问题或建议,请联系开发团队或查看项目文档。
|
||||
|
||||
---
|
||||
|
||||
**API文档地址**: http://localhost:5350/api-docs
|
||||
**基础API地址**: http://localhost:5350/api/smart-alerts/public
|
||||
**维护者**: 开发团队
|
||||
@@ -1,292 +0,0 @@
|
||||
# 智能耳标预警 API 封装
|
||||
|
||||
## 概述
|
||||
|
||||
本项目为智能耳标预警系统提供了完整的API接口封装,支持预警数据的查询、统计、处理和导出功能。所有接口均为公开接口,其他程序可以轻松集成和调用。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ **预警统计**: 获取各类预警的数量统计
|
||||
- ✅ **预警查询**: 支持分页、搜索、筛选的预警列表查询
|
||||
- ✅ **预警详情**: 获取单个预警的详细信息
|
||||
- ✅ **预警处理**: 单个和批量预警处理功能
|
||||
- ✅ **数据导出**: 支持JSON和CSV格式的数据导出
|
||||
- ✅ **实时监控**: 提供预警变化监控功能
|
||||
- ✅ **错误处理**: 完善的错误处理和响应机制
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
backend/
|
||||
├── controllers/
|
||||
│ └── smartEartagAlertController.js # 智能耳标预警控制器
|
||||
├── routes/
|
||||
│ └── smart-alerts.js # 智能预警路由配置
|
||||
├── examples/
|
||||
│ └── smart-eartag-alert-usage.js # API使用示例
|
||||
├── test-smart-eartag-alert-api.js # API测试脚本
|
||||
├── API_INTEGRATION_GUIDE.md # API接口文档
|
||||
└── README_SMART_EARTAG_ALERT_API.md # 本文件
|
||||
|
||||
admin-system/src/utils/
|
||||
└── dataService.js # 前端数据服务封装
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 启动后端服务
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm start
|
||||
```
|
||||
|
||||
服务将在 `http://localhost:5350` 启动。
|
||||
|
||||
### 2. 测试API接口
|
||||
|
||||
```bash
|
||||
# 运行API测试脚本
|
||||
node test-smart-eartag-alert-api.js
|
||||
```
|
||||
|
||||
### 3. 查看API文档
|
||||
|
||||
详细API文档请参考:[API_INTEGRATION_GUIDE.md](./API_INTEGRATION_GUIDE.md)
|
||||
|
||||
## API接口列表
|
||||
|
||||
| 接口 | 方法 | 路径 | 功能 |
|
||||
|------|------|------|------|
|
||||
| 获取预警统计 | GET | `/eartag/stats` | 获取各类预警数量统计 |
|
||||
| 获取预警列表 | GET | `/eartag` | 分页查询预警列表 |
|
||||
| 获取预警详情 | GET | `/eartag/{id}` | 获取单个预警详情 |
|
||||
| 处理预警 | POST | `/eartag/{id}/handle` | 处理单个预警 |
|
||||
| 批量处理预警 | POST | `/eartag/batch-handle` | 批量处理预警 |
|
||||
| 导出预警数据 | GET | `/eartag/export` | 导出预警数据 |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### Node.js 示例
|
||||
|
||||
```javascript
|
||||
const { SmartEartagAlertClient } = require('./examples/smart-eartag-alert-usage');
|
||||
|
||||
const client = new SmartEartagAlertClient();
|
||||
|
||||
// 获取预警统计
|
||||
const stats = await client.getAlertStats();
|
||||
console.log('预警统计:', stats.data);
|
||||
|
||||
// 获取预警列表
|
||||
const alerts = await client.getAlerts({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
alertType: 'battery'
|
||||
});
|
||||
console.log('预警列表:', alerts.data);
|
||||
|
||||
// 处理预警
|
||||
const result = await client.handleAlert('123_offline', {
|
||||
action: 'acknowledged',
|
||||
notes: '已处理',
|
||||
handler: '张三'
|
||||
});
|
||||
console.log('处理结果:', result.data);
|
||||
```
|
||||
|
||||
### 前端Vue示例
|
||||
|
||||
```javascript
|
||||
import { smartAlertService } from '@/utils/dataService';
|
||||
|
||||
// 获取预警列表
|
||||
const alerts = await smartAlertService.getEartagAlerts({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
alertType: 'battery'
|
||||
});
|
||||
|
||||
// 处理预警
|
||||
const result = await smartAlertService.handleEartagAlert(alertId, {
|
||||
action: 'acknowledged',
|
||||
notes: '已处理',
|
||||
handler: '管理员'
|
||||
});
|
||||
```
|
||||
|
||||
### 监控预警变化
|
||||
|
||||
```javascript
|
||||
const { AlertMonitor } = require('./examples/smart-eartag-alert-usage');
|
||||
|
||||
const monitor = new AlertMonitor(client, {
|
||||
interval: 30000, // 30秒检查一次
|
||||
onNewAlert: (count, stats) => {
|
||||
console.log(`发现 ${count} 个新预警!`);
|
||||
},
|
||||
onAlertChange: (changes, stats) => {
|
||||
console.log('预警统计变化:', changes);
|
||||
}
|
||||
});
|
||||
|
||||
monitor.start(); // 开始监控
|
||||
```
|
||||
|
||||
## 数据模型
|
||||
|
||||
### 预警数据结构
|
||||
|
||||
```javascript
|
||||
{
|
||||
"id": "123_offline", // 预警ID
|
||||
"deviceId": 123, // 设备ID
|
||||
"deviceName": "EARTAG001", // 设备名称
|
||||
"eartagNumber": "EARTAG001", // 耳标编号
|
||||
"alertType": "offline", // 预警类型
|
||||
"alertLevel": "high", // 预警级别
|
||||
"alertTime": "2024-01-15 10:30:00", // 预警时间
|
||||
"battery": 85, // 设备电量
|
||||
"temperature": 25.5, // 设备温度
|
||||
"dailySteps": 0, // 当日步数
|
||||
"totalSteps": 1500, // 总步数
|
||||
"yesterdaySteps": 1500, // 昨日步数
|
||||
"deviceStatus": "离线", // 设备状态
|
||||
"gpsSignal": "无", // GPS信号
|
||||
"movementStatus": "静止", // 运动状态
|
||||
"description": "设备已离线超过30分钟", // 预警描述
|
||||
"longitude": 116.3974, // 经度
|
||||
"latitude": 39.9093 // 纬度
|
||||
}
|
||||
```
|
||||
|
||||
### 预警类型
|
||||
|
||||
- `battery`: 低电量预警
|
||||
- `offline`: 离线预警
|
||||
- `temperature`: 温度预警
|
||||
- `movement`: 异常运动预警
|
||||
|
||||
### 预警级别
|
||||
|
||||
- `high`: 高级
|
||||
- `medium`: 中级
|
||||
- `low`: 低级
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 后端配置
|
||||
|
||||
确保后端服务在端口5350上运行:
|
||||
|
||||
```javascript
|
||||
// server.js
|
||||
const PORT = process.env.PORT || 5350;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`服务器运行在端口 ${PORT}`);
|
||||
});
|
||||
```
|
||||
|
||||
### 前端配置
|
||||
|
||||
确保前端API基础URL配置正确:
|
||||
|
||||
```javascript
|
||||
// api.js
|
||||
const API_BASE_URL = 'http://localhost:5350/api';
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
所有API接口都遵循统一的错误响应格式:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误描述",
|
||||
"error": "详细错误信息"
|
||||
}
|
||||
```
|
||||
|
||||
常见HTTP状态码:
|
||||
- `200`: 请求成功
|
||||
- `400`: 请求参数错误
|
||||
- `404`: 资源不存在
|
||||
- `500`: 服务器内部错误
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **分页查询**: 建议使用分页避免一次性加载大量数据
|
||||
2. **筛选条件**: 使用筛选条件减少不必要的数据传输
|
||||
3. **缓存机制**: 对于统计类数据可以考虑添加缓存
|
||||
4. **批量操作**: 对于大量预警处理建议使用批量接口
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 添加新的预警类型
|
||||
|
||||
1. 在控制器中添加新的预警检测逻辑
|
||||
2. 更新API文档中的预警类型枚举
|
||||
3. 更新前端界面的预警类型选项
|
||||
|
||||
### 添加新的处理动作
|
||||
|
||||
1. 在控制器中添加新的处理逻辑
|
||||
2. 更新数据库模型(如果需要)
|
||||
3. 更新API文档和前端界面
|
||||
|
||||
## 测试
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# 运行完整测试套件
|
||||
node test-smart-eartag-alert-api.js
|
||||
|
||||
# 运行使用示例
|
||||
node examples/smart-eartag-alert-usage.js
|
||||
```
|
||||
|
||||
### 测试覆盖
|
||||
|
||||
- ✅ API接口功能测试
|
||||
- ✅ 参数验证测试
|
||||
- ✅ 错误处理测试
|
||||
- ✅ 数据格式验证测试
|
||||
- ✅ 性能测试
|
||||
|
||||
## 维护说明
|
||||
|
||||
### 日志记录
|
||||
|
||||
所有API调用都会记录详细日志,包括:
|
||||
- 请求参数
|
||||
- 响应数据
|
||||
- 错误信息
|
||||
- 处理时间
|
||||
|
||||
### 监控指标
|
||||
|
||||
建议监控以下指标:
|
||||
- API响应时间
|
||||
- 错误率
|
||||
- 请求量
|
||||
- 预警处理效率
|
||||
|
||||
### 版本更新
|
||||
|
||||
API版本更新时请:
|
||||
1. 更新版本号
|
||||
2. 更新API文档
|
||||
3. 提供迁移指南
|
||||
4. 保持向后兼容性
|
||||
|
||||
## 联系支持
|
||||
|
||||
如有问题或建议,请联系开发团队或查看项目文档。
|
||||
|
||||
---
|
||||
|
||||
**版本**: v1.0.0
|
||||
**更新时间**: 2024-01-15
|
||||
**维护者**: 开发团队
|
||||
272
backend/SERVICE_MANAGER_README.md
Normal file
272
backend/SERVICE_MANAGER_README.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# Node.js 服务管理脚本使用说明
|
||||
|
||||
宁夏智慧养殖监管平台后端服务管理脚本,支持Linux和Windows系统。
|
||||
|
||||
## 文件说明
|
||||
|
||||
- **node_manager.sh** - Linux/CentOS服务器版本(Bash脚本)
|
||||
- **node_manager.ps1** - Windows本地开发版本(PowerShell脚本)
|
||||
|
||||
## 一、Linux版本使用说明
|
||||
|
||||
### 1. 准备工作
|
||||
|
||||
在CentOS服务器上首次使用前,需要设置脚本执行权限:
|
||||
|
||||
```bash
|
||||
chmod +x node_manager.sh
|
||||
```
|
||||
|
||||
### 2. 基本命令
|
||||
|
||||
```bash
|
||||
# 启动服务
|
||||
./node_manager.sh start
|
||||
|
||||
# 停止服务
|
||||
./node_manager.sh stop
|
||||
|
||||
# 重启服务
|
||||
./node_manager.sh restart
|
||||
|
||||
# 查看服务状态
|
||||
./node_manager.sh status
|
||||
|
||||
# 实时查看日志
|
||||
./node_manager.sh logs
|
||||
```
|
||||
|
||||
### 3. 完整部署流程示例
|
||||
|
||||
```bash
|
||||
# 1. 进入项目目录
|
||||
cd /path/to/backend
|
||||
|
||||
# 2. 安装依赖(首次部署)
|
||||
npm install
|
||||
|
||||
# 3. 配置环境变量
|
||||
cp env.example .env
|
||||
vim .env # 编辑配置
|
||||
|
||||
# 4. 启动服务
|
||||
./node_manager.sh start
|
||||
|
||||
# 5. 检查状态
|
||||
./node_manager.sh status
|
||||
|
||||
# 6. 查看日志
|
||||
./node_manager.sh logs
|
||||
```
|
||||
|
||||
### 4. 常见问题
|
||||
|
||||
**Q: 提示 "端口已被占用" 怎么办?**
|
||||
|
||||
A: 使用 `./node_manager.sh stop` 停止服务,或手动查找并杀死进程:
|
||||
|
||||
```bash
|
||||
# 查找占用端口的进程
|
||||
lsof -ti:5350
|
||||
|
||||
# 杀死进程
|
||||
kill -9 $(lsof -ti:5350)
|
||||
```
|
||||
|
||||
**Q: 服务启动失败怎么办?**
|
||||
|
||||
A: 查看日志文件定位问题:
|
||||
|
||||
```bash
|
||||
# 查看完整日志
|
||||
cat logs/nxxmdata-backend.log
|
||||
|
||||
# 实时监控日志
|
||||
tail -f logs/nxxmdata-backend.log
|
||||
```
|
||||
|
||||
## 二、Windows版本使用说明
|
||||
|
||||
### 1. 准备工作
|
||||
|
||||
首次使用前,可能需要设置PowerShell执行策略:
|
||||
|
||||
```powershell
|
||||
# 以管理员身份运行PowerShell
|
||||
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
```
|
||||
|
||||
### 2. 基本命令
|
||||
|
||||
```powershell
|
||||
# 启动服务
|
||||
.\node_manager.ps1 start
|
||||
|
||||
# 停止服务
|
||||
.\node_manager.ps1 stop
|
||||
|
||||
# 重启服务
|
||||
.\node_manager.ps1 restart
|
||||
|
||||
# 查看服务状态
|
||||
.\node_manager.ps1 status
|
||||
|
||||
# 实时查看日志
|
||||
.\node_manager.ps1 logs
|
||||
```
|
||||
|
||||
### 3. 完整部署流程示例
|
||||
|
||||
```powershell
|
||||
# 1. 进入项目目录
|
||||
cd D:\10-24\nxxmdata\backend
|
||||
|
||||
# 2. 安装依赖(首次部署)
|
||||
npm install
|
||||
|
||||
# 3. 配置环境变量
|
||||
Copy-Item env.example .env
|
||||
notepad .env # 编辑配置
|
||||
|
||||
# 4. 启动服务
|
||||
.\node_manager.ps1 start
|
||||
|
||||
# 5. 检查状态
|
||||
.\node_manager.ps1 status
|
||||
|
||||
# 6. 查看日志
|
||||
.\node_manager.ps1 logs
|
||||
```
|
||||
|
||||
### 4. 常见问题
|
||||
|
||||
**Q: 提示 "端口已被占用" 怎么办?**
|
||||
|
||||
A: 使用脚本停止服务,或手动查找并杀死进程:
|
||||
|
||||
```powershell
|
||||
# 查找占用端口的进程
|
||||
Get-NetTCPConnection -LocalPort 5350 | Select-Object -ExpandProperty OwningProcess
|
||||
|
||||
# 杀死进程(替换 <PID> 为实际进程ID)
|
||||
Stop-Process -Id <PID> -Force
|
||||
```
|
||||
|
||||
**Q: PowerShell提示无法执行脚本?**
|
||||
|
||||
A: 设置执行策略:
|
||||
|
||||
```powershell
|
||||
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
|
||||
```
|
||||
|
||||
## 三、配置说明
|
||||
|
||||
### 应用配置
|
||||
|
||||
在脚本中已配置以下默认值:
|
||||
|
||||
| 配置项 | 值 | 说明 |
|
||||
|--------|-----|------|
|
||||
| APP_NAME | nxxmdata-backend | 应用名称 |
|
||||
| ENTRY_FILE | server.js | 入口文件 |
|
||||
| APP_PORT | 5350 | 监听端口 |
|
||||
| NODE_ENV | production | 运行环境 |
|
||||
| LOG_FILE | logs/nxxmdata-backend.log | 日志文件 |
|
||||
|
||||
### 修改配置
|
||||
|
||||
如需修改配置,编辑脚本文件的"配置区域"部分:
|
||||
|
||||
**Linux版本 (node_manager.sh):**
|
||||
```bash
|
||||
# 配置区域
|
||||
APP_NAME="nxxmdata-backend"
|
||||
ENTRY_FILE="server.js"
|
||||
APP_PORT="5350"
|
||||
```
|
||||
|
||||
**Windows版本 (node_manager.ps1):**
|
||||
```powershell
|
||||
# 配置区域
|
||||
$APP_NAME = "nxxmdata-backend"
|
||||
$ENTRY_FILE = "server.js"
|
||||
$APP_PORT = 5350
|
||||
```
|
||||
|
||||
## 四、生产环境建议
|
||||
|
||||
### 使用 PM2(推荐)
|
||||
|
||||
对于生产环境,建议使用PM2进行进程管理:
|
||||
|
||||
```bash
|
||||
# 安装 PM2
|
||||
npm install -g pm2
|
||||
|
||||
# 启动服务
|
||||
pm2 start server.js --name nxxmdata-backend
|
||||
|
||||
# 保存配置
|
||||
pm2 save
|
||||
|
||||
# 设置开机自启
|
||||
pm2 startup
|
||||
|
||||
# 监控服务
|
||||
pm2 monit
|
||||
```
|
||||
|
||||
### 日志管理
|
||||
|
||||
定期清理日志文件,避免占用过多磁盘空间:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
find logs -name "*.log" -mtime +30 -delete
|
||||
|
||||
# Windows PowerShell
|
||||
Get-ChildItem logs -Filter *.log | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} | Remove-Item
|
||||
```
|
||||
|
||||
## 五、脚本功能特点
|
||||
|
||||
### Linux版本特点
|
||||
|
||||
- ✅ 优雅停止进程(SIGTERM → SIGKILL)
|
||||
- ✅ 端口占用检测
|
||||
- ✅ 进程PID管理
|
||||
- ✅ 彩色输出提示
|
||||
- ✅ 详细的状态信息
|
||||
- ✅ 自动创建日志目录
|
||||
- ✅ 实时日志查看
|
||||
|
||||
### Windows版本特点
|
||||
|
||||
- ✅ PowerShell原生支持
|
||||
- ✅ 端口占用检测
|
||||
- ✅ 后台进程管理
|
||||
- ✅ 彩色输出提示
|
||||
- ✅ 详细的状态信息
|
||||
- ✅ 自动创建日志目录
|
||||
- ✅ 实时日志查看
|
||||
|
||||
## 六、技术支持
|
||||
|
||||
如遇到问题,请检查:
|
||||
|
||||
1. Node.js版本是否正确(要求:16.20.2)
|
||||
2. 依赖是否完整安装(node_modules目录)
|
||||
3. 环境变量配置是否正确(.env文件)
|
||||
4. 端口是否被其他程序占用
|
||||
5. 数据库连接是否正常
|
||||
6. 日志文件中的错误信息
|
||||
|
||||
---
|
||||
|
||||
**项目信息:**
|
||||
- 项目名称:宁夏智慧养殖监管平台
|
||||
- 后端端口:5350
|
||||
- 前端端口:5300
|
||||
- API文档:http://localhost:5350/api-docs
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
# 智能项圈预警数据调用修复说明
|
||||
|
||||
## 问题描述
|
||||
智能项圈预警页面 (`admin-system/src/views/SmartCollarAlert.vue`) 存在以下问题:
|
||||
1. 统计数据使用硬编码,没有动态调用数据库
|
||||
2. 部分功能没有正确调用API接口
|
||||
3. 数据格式转换和显示存在问题
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. 添加统计数据API调用
|
||||
- 新增 `fetchStats()` 函数,专门用于获取统计数据
|
||||
- 调用 `smartAlertService.getCollarAlertStats()` API
|
||||
- 动态更新统计卡片数据(低电量、离线、温度、异常运动、佩戴异常)
|
||||
|
||||
### 2. 优化数据获取流程
|
||||
- 修改 `fetchData()` 函数,专注于获取预警列表数据
|
||||
- 在组件挂载时同时调用统计数据和列表数据API
|
||||
- 在搜索、筛选、分页时同步更新统计数据
|
||||
|
||||
### 3. 完善API集成
|
||||
- 更新 `handleAlert()` 函数,调用 `smartAlertService.handleCollarAlert()` API
|
||||
- 更新 `exportData()` 函数,调用 `smartAlertService.exportCollarAlerts()` API
|
||||
- 所有异步函数都使用 `async/await` 模式
|
||||
|
||||
### 4. 数据格式优化
|
||||
- 保持原有的数据格式转换逻辑
|
||||
- 确保API返回的数据能正确显示在界面上
|
||||
- 优化错误处理和用户反馈
|
||||
|
||||
## 修改的文件
|
||||
|
||||
### admin-system/src/views/SmartCollarAlert.vue
|
||||
主要修改:
|
||||
1. 新增 `fetchStats()` 函数
|
||||
2. 修改 `fetchData()` 函数,移除硬编码统计数据
|
||||
3. 更新 `onMounted()` 钩子,同时获取统计和列表数据
|
||||
4. 更新 `handleSearch()`、`handleFilterChange()`、`handleClearSearch()` 函数
|
||||
5. 更新 `handleAlert()` 函数,调用API处理预警
|
||||
6. 更新 `exportData()` 函数,调用API导出数据
|
||||
|
||||
## API端点使用
|
||||
|
||||
### 统计数据
|
||||
- **端点**: `GET /api/smart-alerts/public/collar/stats`
|
||||
- **功能**: 获取智能项圈预警统计数据
|
||||
- **返回**: 各类预警的数量统计
|
||||
|
||||
### 预警列表
|
||||
- **端点**: `GET /api/smart-alerts/public/collar`
|
||||
- **功能**: 获取智能项圈预警列表
|
||||
- **参数**: page, limit, search, alertType, alertLevel, status, startDate, endDate
|
||||
|
||||
### 预警详情
|
||||
- **端点**: `GET /api/smart-alerts/public/collar/{id}`
|
||||
- **功能**: 获取单个预警详情
|
||||
|
||||
### 处理预警
|
||||
- **端点**: `POST /api/smart-alerts/public/collar/{id}/handle`
|
||||
- **功能**: 处理指定的预警
|
||||
|
||||
### 批量处理预警
|
||||
- **端点**: `POST /api/smart-alerts/public/collar/batch-handle`
|
||||
- **功能**: 批量处理多个预警
|
||||
|
||||
### 导出数据
|
||||
- **端点**: `GET /api/smart-alerts/public/collar/export`
|
||||
- **功能**: 导出预警数据
|
||||
- **参数**: format (json/csv), search, alertType, alertLevel, startDate, endDate
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 运行测试脚本
|
||||
```bash
|
||||
cd backend
|
||||
node test-smart-collar-alert-integration.js
|
||||
```
|
||||
|
||||
### 前端页面验证
|
||||
1. 打开智能项圈预警页面
|
||||
2. 检查统计卡片是否显示动态数据
|
||||
3. 测试搜索和筛选功能
|
||||
4. 测试处理预警功能
|
||||
5. 测试导出数据功能
|
||||
|
||||
## 预期效果
|
||||
|
||||
修复后的智能项圈预警页面应该能够:
|
||||
|
||||
1. **动态显示统计数据**
|
||||
- 低电量预警数量
|
||||
- 离线预警数量
|
||||
- 温度预警数量
|
||||
- 异常运动预警数量
|
||||
- 佩戴异常预警数量
|
||||
|
||||
2. **完整的数据交互**
|
||||
- 实时搜索和筛选
|
||||
- 分页浏览
|
||||
- 预警详情查看
|
||||
- 预警处理操作
|
||||
- 数据导出功能
|
||||
|
||||
3. **良好的用户体验**
|
||||
- 加载状态提示
|
||||
- 错误信息反馈
|
||||
- 操作成功确认
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **确保后端服务运行**: 后端服务器必须在端口5350上运行
|
||||
2. **数据库连接**: 确保数据库连接正常,相关表存在
|
||||
3. **API权限**: 确保API端点可以正常访问
|
||||
4. **数据格式**: 确保API返回的数据格式与前端期望一致
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `backend/controllers/smartCollarAlertController.js` - 后端控制器
|
||||
- `backend/routes/smart-alerts.js` - API路由定义
|
||||
- `admin-system/src/utils/dataService.js` - 前端数据服务
|
||||
- `backend/test-smart-collar-alert-integration.js` - 集成测试脚本
|
||||
|
||||
---
|
||||
|
||||
**修复完成时间**: 2025-01-18
|
||||
**修复版本**: v1.0.0
|
||||
**测试状态**: 待验证
|
||||
@@ -1,99 +0,0 @@
|
||||
# ✅ 网络访问问题已解决
|
||||
|
||||
## 修复完成状态
|
||||
|
||||
### 1. ✅ 后端服务器配置
|
||||
- **状态**: 已修复
|
||||
- **配置**: 服务器现在监听 `0.0.0.0:5350`
|
||||
- **验证**: `netstat` 显示 `TCP 0.0.0.0:5350 LISTENING`
|
||||
|
||||
### 2. ✅ 前端服务器配置
|
||||
- **状态**: 已正确配置
|
||||
- **配置**: 服务器监听 `0.0.0.0:5300`
|
||||
- **验证**: `netstat` 显示 `TCP 0.0.0.0:5300 LISTENING`
|
||||
|
||||
### 3. ✅ Windows防火墙配置
|
||||
- **状态**: 已配置
|
||||
- **规则1**: Node.js Frontend Port 5300 (已启用)
|
||||
- **规则2**: Node.js Backend Port 5350 (已启用)
|
||||
- **验证**: 防火墙规则已确认添加
|
||||
|
||||
## 现在其他用户可以访问的地址
|
||||
|
||||
### 主要访问地址
|
||||
- **前端**: `http://172.28.112.1:5300`
|
||||
- **后端**: `http://172.28.112.1:5350`
|
||||
- **API文档**: `http://172.28.112.1:5350/api-docs`
|
||||
|
||||
### 备用访问地址(如果主要地址不可用)
|
||||
- **前端**: `http://192.168.0.48:5300`
|
||||
- **后端**: `http://192.168.0.48:5350`
|
||||
|
||||
## 验证步骤
|
||||
|
||||
### 1. 本地验证
|
||||
在您的浏览器中访问:
|
||||
- `http://172.28.112.1:5300` - 应该看到前端页面
|
||||
- `http://172.28.112.1:5350/api-docs` - 应该看到API文档
|
||||
|
||||
### 2. 外部用户验证
|
||||
让其他用户在他们的浏览器中访问:
|
||||
- `http://172.28.112.1:5300` - 应该看到前端页面
|
||||
- `http://172.28.112.1:5350/api-docs` - 应该看到API文档
|
||||
|
||||
### 3. 网络连接测试
|
||||
其他用户可以运行以下命令测试连接:
|
||||
```cmd
|
||||
# 测试前端端口
|
||||
telnet 172.28.112.1 5300
|
||||
|
||||
# 测试后端端口
|
||||
telnet 172.28.112.1 5350
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 如果其他用户仍然无法访问:
|
||||
|
||||
1. **检查网络环境**
|
||||
- 确保其他用户与您在同一个局域网内
|
||||
- 确认没有使用VPN或代理
|
||||
|
||||
2. **检查IP地址**
|
||||
- 使用 `ipconfig` 确认当前IP地址
|
||||
- 如果IP地址发生变化,更新访问地址
|
||||
|
||||
3. **检查防火墙**
|
||||
- 确认Windows防火墙规则已启用
|
||||
- 检查是否有其他安全软件阻止连接
|
||||
|
||||
4. **检查路由器设置**
|
||||
- 某些路由器可能阻止设备间通信
|
||||
- 检查路由器的访问控制设置
|
||||
|
||||
## 成功标志
|
||||
|
||||
当配置完全成功时,您应该看到:
|
||||
- ✅ 服务器启动时显示"服务器监听所有网络接口"
|
||||
- ✅ 防火墙规则已正确添加
|
||||
- ✅ 其他用户可以通过IP地址访问服务
|
||||
- ✅ 网络诊断脚本显示端口可以正常监听
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **安全性**: 当前配置允许局域网内所有设备访问,仅适用于开发环境
|
||||
2. **IP地址**: 如果网络环境变化,IP地址可能会改变
|
||||
3. **端口占用**: 确保端口5300和5350没有被其他程序占用
|
||||
4. **服务器状态**: 确保服务器持续运行
|
||||
|
||||
## 维护建议
|
||||
|
||||
1. **定期检查**: 定期运行 `node fix-network-access.js` 检查网络状态
|
||||
2. **日志监控**: 查看服务器日志确认连接状态
|
||||
3. **备份配置**: 保存防火墙配置以便快速恢复
|
||||
|
||||
---
|
||||
|
||||
**问题已完全解决!** 🎉
|
||||
|
||||
现在其他用户应该能够正常访问您的开发服务器了。如果还有任何问题,请检查上述故障排除步骤。
|
||||
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* 检查实际数据
|
||||
* @file check-actual-data.js
|
||||
* @description 检查数据库中项圈22012000107的实际数据
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function checkActualData() {
|
||||
console.log('🔍 检查数据库中项圈22012000107的实际数据...\n');
|
||||
|
||||
try {
|
||||
// 1. 测试数据库连接
|
||||
console.log('1. 测试数据库连接...');
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 2. 查询项圈22012000107的数据
|
||||
console.log('\n2. 查询项圈22012000107的数据...');
|
||||
const [results] = await sequelize.query(`
|
||||
SELECT
|
||||
id, sn, deviceId, battery, temperature, state,
|
||||
uptime, longitude, latitude, gps_state, rsrp,
|
||||
bandge_status, is_connect, steps, y_steps
|
||||
FROM iot_xq_client
|
||||
WHERE sn = '22012000107'
|
||||
ORDER BY uptime DESC
|
||||
`);
|
||||
|
||||
console.log(`找到 ${results.length} 条记录`);
|
||||
|
||||
results.forEach((row, index) => {
|
||||
console.log(`\n记录${index + 1}:`);
|
||||
console.log('ID:', row.id);
|
||||
console.log('SN:', row.sn);
|
||||
console.log('设备ID:', row.deviceId);
|
||||
console.log('电量:', row.battery, '(类型:', typeof row.battery, ')');
|
||||
console.log('温度:', row.temperature, '(类型:', typeof row.temperature, ')');
|
||||
console.log('状态:', row.state);
|
||||
console.log('经度:', row.longitude);
|
||||
console.log('纬度:', row.latitude);
|
||||
console.log('GPS状态:', row.gps_state);
|
||||
console.log('RSRP:', row.rsrp);
|
||||
console.log('佩戴状态:', row.bandge_status);
|
||||
console.log('连接状态:', row.is_connect);
|
||||
console.log('步数:', row.steps);
|
||||
console.log('昨日步数:', row.y_steps);
|
||||
console.log('更新时间:', row.uptime);
|
||||
});
|
||||
|
||||
// 3. 查询所有项圈的最新数据
|
||||
console.log('\n3. 查询所有项圈的最新数据...');
|
||||
const [allResults] = await sequelize.query(`
|
||||
SELECT
|
||||
id, sn, deviceId, battery, temperature, state, uptime
|
||||
FROM iot_xq_client
|
||||
ORDER BY uptime DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
|
||||
console.log('所有项圈的最新数据:');
|
||||
allResults.forEach((row, index) => {
|
||||
console.log(`${index + 1}. SN: ${row.sn}, 电量: ${row.battery}, 温度: ${row.temperature}, 状态: ${row.state}`);
|
||||
});
|
||||
|
||||
// 4. 检查数据库配置
|
||||
console.log('\n4. 检查数据库配置...');
|
||||
const config = sequelize.config;
|
||||
console.log('数据库配置:');
|
||||
console.log('主机:', config.host);
|
||||
console.log('端口:', config.port);
|
||||
console.log('数据库名:', config.database);
|
||||
console.log('用户名:', config.username);
|
||||
|
||||
// 5. 检查当前数据库
|
||||
console.log('\n5. 检查当前数据库...');
|
||||
const [currentDb] = await sequelize.query('SELECT DATABASE() as current_db');
|
||||
console.log('当前数据库:', currentDb[0].current_db);
|
||||
|
||||
// 6. 检查是否有其他数据库
|
||||
console.log('\n6. 检查所有数据库...');
|
||||
const [databases] = await sequelize.query('SHOW DATABASES');
|
||||
console.log('所有数据库:');
|
||||
databases.forEach(db => {
|
||||
const dbName = Object.values(db)[0];
|
||||
console.log('-', dbName);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 检查失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
checkActualData().catch(console.error);
|
||||
@@ -1,114 +0,0 @@
|
||||
/**
|
||||
* 检查所有相关表
|
||||
* @file check-all-tables.js
|
||||
* @description 检查所有可能包含项圈数据的表
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function checkAllTables() {
|
||||
console.log('🔍 检查所有相关表...\n');
|
||||
|
||||
try {
|
||||
// 1. 列出所有表
|
||||
console.log('1. 列出所有表...');
|
||||
const [tables] = await sequelize.query("SHOW TABLES");
|
||||
console.log('数据库中的所有表:');
|
||||
tables.forEach((table, index) => {
|
||||
const tableName = Object.values(table)[0];
|
||||
console.log(`${index + 1}. ${tableName}`);
|
||||
});
|
||||
|
||||
// 2. 检查可能包含项圈数据的表
|
||||
const possibleTables = [
|
||||
'iot_xq_client',
|
||||
'iot_collar',
|
||||
'smart_collar',
|
||||
'collar_device',
|
||||
'device_info',
|
||||
'iot_device'
|
||||
];
|
||||
|
||||
console.log('\n2. 检查可能包含项圈数据的表...');
|
||||
|
||||
for (const tableName of possibleTables) {
|
||||
try {
|
||||
console.log(`\n检查表: ${tableName}`);
|
||||
const [rows] = await sequelize.query(`SELECT COUNT(*) as count FROM ${tableName}`);
|
||||
const count = rows[0].count;
|
||||
console.log(`记录数: ${count}`);
|
||||
|
||||
if (count > 0) {
|
||||
// 查看表结构
|
||||
const [columns] = await sequelize.query(`DESCRIBE ${tableName}`);
|
||||
console.log('表结构:');
|
||||
columns.forEach(col => {
|
||||
console.log(` ${col.Field}: ${col.Type}`);
|
||||
});
|
||||
|
||||
// 查找包含22012000107的记录
|
||||
const [searchResults] = await sequelize.query(`
|
||||
SELECT * FROM ${tableName}
|
||||
WHERE sn = '22012000107' OR device_id = '22012000107' OR deviceId = '22012000107'
|
||||
LIMIT 5
|
||||
`);
|
||||
|
||||
if (searchResults.length > 0) {
|
||||
console.log(`找到 ${searchResults.length} 条包含22012000107的记录:`);
|
||||
searchResults.forEach((row, index) => {
|
||||
console.log(`记录${index + 1}:`, row);
|
||||
});
|
||||
} else {
|
||||
console.log('未找到包含22012000107的记录');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`表 ${tableName} 不存在或无法访问: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 检查iot_xq_client表的详细信息
|
||||
console.log('\n3. 检查iot_xq_client表的详细信息...');
|
||||
const [xqClientData] = await sequelize.query(`
|
||||
SELECT * FROM iot_xq_client
|
||||
WHERE sn = '22012000107'
|
||||
ORDER BY uptime DESC
|
||||
`);
|
||||
|
||||
console.log(`iot_xq_client表中项圈22012000107的记录:`);
|
||||
xqClientData.forEach((row, index) => {
|
||||
console.log(`\n记录${index + 1}:`);
|
||||
console.log('ID:', row.id);
|
||||
console.log('SN:', row.sn);
|
||||
console.log('电量:', row.battery);
|
||||
console.log('温度:', row.temperature);
|
||||
console.log('状态:', row.state);
|
||||
console.log('更新时间:', row.uptime);
|
||||
console.log('创建时间:', row.created_at);
|
||||
console.log('更新时间:', row.updated_at);
|
||||
});
|
||||
|
||||
// 4. 检查是否有其他项圈编号
|
||||
console.log('\n4. 检查所有项圈编号...');
|
||||
const [allSnData] = await sequelize.query(`
|
||||
SELECT sn, battery, temperature, state, uptime
|
||||
FROM iot_xq_client
|
||||
ORDER BY uptime DESC
|
||||
LIMIT 20
|
||||
`);
|
||||
|
||||
console.log('所有项圈编号及其电量:');
|
||||
allSnData.forEach((row, index) => {
|
||||
console.log(`${index + 1}. SN: ${row.sn}, 电量: ${row.battery}, 温度: ${row.temperature}, 状态: ${row.state}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 检查失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
checkAllTables().catch(console.error);
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* 检查正确的数据库
|
||||
* @file check-correct-database.js
|
||||
* @description 使用正确的数据库配置检查项圈22012000107的数据
|
||||
*/
|
||||
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
async function checkCorrectDatabase() {
|
||||
console.log('🔍 使用正确的数据库配置检查项圈22012000107的数据...\n');
|
||||
|
||||
try {
|
||||
// 使用正确的数据库配置
|
||||
const connection = await mysql.createConnection({
|
||||
host: '129.211.213.226',
|
||||
port: 9527,
|
||||
user: 'root',
|
||||
password: 'aiotAiot123!',
|
||||
database: 'nxxmdata'
|
||||
});
|
||||
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 查询项圈22012000107的数据
|
||||
console.log('\n查询项圈22012000107的数据...');
|
||||
const [results] = await connection.execute(`
|
||||
SELECT
|
||||
id, sn, deviceId, battery, temperature, state,
|
||||
uptime, longitude, latitude, gps_state, rsrp,
|
||||
bandge_status, is_connect, steps, y_steps
|
||||
FROM iot_xq_client
|
||||
WHERE sn = '22012000107'
|
||||
ORDER BY uptime DESC
|
||||
`);
|
||||
|
||||
console.log(`找到 ${results.length} 条记录`);
|
||||
|
||||
results.forEach((row, index) => {
|
||||
console.log(`\n记录${index + 1}:`);
|
||||
console.log('ID:', row.id);
|
||||
console.log('SN:', row.sn);
|
||||
console.log('设备ID:', row.deviceId);
|
||||
console.log('电量:', row.battery, '(类型:', typeof row.battery, ')');
|
||||
console.log('温度:', row.temperature, '(类型:', typeof row.temperature, ')');
|
||||
console.log('状态:', row.state);
|
||||
console.log('经度:', row.longitude);
|
||||
console.log('纬度:', row.latitude);
|
||||
console.log('GPS状态:', row.gps_state);
|
||||
console.log('RSRP:', row.rsrp);
|
||||
console.log('佩戴状态:', row.bandge_status);
|
||||
console.log('连接状态:', row.is_connect);
|
||||
console.log('步数:', row.steps);
|
||||
console.log('昨日步数:', row.y_steps);
|
||||
console.log('更新时间:', row.uptime);
|
||||
});
|
||||
|
||||
// 查询所有项圈的最新数据
|
||||
console.log('\n查询所有项圈的最新数据...');
|
||||
const [allResults] = await connection.execute(`
|
||||
SELECT
|
||||
id, sn, deviceId, battery, temperature, state, uptime
|
||||
FROM iot_xq_client
|
||||
ORDER BY uptime DESC
|
||||
LIMIT 20
|
||||
`);
|
||||
|
||||
console.log('所有项圈的最新数据:');
|
||||
allResults.forEach((row, index) => {
|
||||
console.log(`${index + 1}. SN: ${row.sn}, 电量: ${row.battery}, 温度: ${row.temperature}, 状态: ${row.state}, 更新时间: ${row.uptime}`);
|
||||
});
|
||||
|
||||
// 检查是否有电量为99的记录
|
||||
console.log('\n检查是否有电量为99的记录...');
|
||||
const [battery99Results] = await connection.execute(`
|
||||
SELECT
|
||||
id, sn, deviceId, battery, temperature, state, uptime
|
||||
FROM iot_xq_client
|
||||
WHERE battery = '99' OR battery = 99
|
||||
ORDER BY uptime DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
|
||||
console.log(`找到 ${battery99Results.length} 条电量为99的记录`);
|
||||
battery99Results.forEach((row, index) => {
|
||||
console.log(`${index + 1}. SN: ${row.sn}, 电量: ${row.battery}, 温度: ${row.temperature}, 状态: ${row.state}`);
|
||||
});
|
||||
|
||||
await connection.end();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 检查失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
checkCorrectDatabase().catch(console.error);
|
||||
@@ -1,114 +0,0 @@
|
||||
/**
|
||||
* 检查数据库原始数据
|
||||
* @file check-database-data.js
|
||||
* @description 检查数据库中项圈22012000107的原始数据
|
||||
*/
|
||||
|
||||
const { IotXqClient } = require('./models');
|
||||
|
||||
async function checkDatabaseData() {
|
||||
console.log('🔍 检查数据库原始数据...\n');
|
||||
|
||||
try {
|
||||
// 1. 查找项圈22012000107的所有记录
|
||||
console.log('1. 查找项圈22012000107的所有记录...');
|
||||
const devices = await IotXqClient.findAll({
|
||||
where: {
|
||||
sn: '22012000107'
|
||||
},
|
||||
order: [['uptime', 'DESC']]
|
||||
});
|
||||
|
||||
console.log(`找到 ${devices.length} 条记录`);
|
||||
|
||||
devices.forEach((device, index) => {
|
||||
console.log(`\n第${index + 1}条记录:`);
|
||||
console.log('ID:', device.id);
|
||||
console.log('SN:', device.sn);
|
||||
console.log('设备ID:', device.deviceId);
|
||||
console.log('电量 (battery):', device.battery);
|
||||
console.log('温度 (temperature):', device.temperature);
|
||||
console.log('步数 (steps):', device.steps);
|
||||
console.log('昨日步数 (y_steps):', device.y_steps);
|
||||
console.log('状态 (state):', device.state);
|
||||
console.log('佩戴状态 (bandge_status):', device.bandge_status);
|
||||
console.log('更新时间 (uptime):', device.uptime);
|
||||
console.log('创建时间 (createdAt):', device.createdAt);
|
||||
console.log('更新时间 (updatedAt):', device.updatedAt);
|
||||
});
|
||||
|
||||
// 2. 查找所有包含22012000107的记录
|
||||
console.log('\n2. 查找所有包含22012000107的记录...');
|
||||
const allDevices = await IotXqClient.findAll({
|
||||
where: {
|
||||
[require('sequelize').Op.or]: [
|
||||
{ sn: '22012000107' },
|
||||
{ deviceId: '22012000107' },
|
||||
{ sn: { [require('sequelize').Op.like]: '%22012000107%' } }
|
||||
]
|
||||
},
|
||||
order: [['uptime', 'DESC']]
|
||||
});
|
||||
|
||||
console.log(`找到 ${allDevices.length} 条相关记录`);
|
||||
|
||||
allDevices.forEach((device, index) => {
|
||||
console.log(`\n相关记录${index + 1}:`);
|
||||
console.log('ID:', device.id);
|
||||
console.log('SN:', device.sn);
|
||||
console.log('设备ID:', device.deviceId);
|
||||
console.log('电量:', device.battery);
|
||||
console.log('温度:', device.temperature);
|
||||
console.log('状态:', device.state);
|
||||
console.log('更新时间:', device.uptime);
|
||||
});
|
||||
|
||||
// 3. 检查最新的记录
|
||||
console.log('\n3. 检查最新的记录...');
|
||||
const latestDevice = await IotXqClient.findOne({
|
||||
where: {
|
||||
sn: '22012000107'
|
||||
},
|
||||
order: [['uptime', 'DESC']]
|
||||
});
|
||||
|
||||
if (latestDevice) {
|
||||
console.log('最新记录:');
|
||||
console.log('电量:', latestDevice.battery);
|
||||
console.log('温度:', latestDevice.temperature);
|
||||
console.log('更新时间:', latestDevice.uptime);
|
||||
} else {
|
||||
console.log('未找到最新记录');
|
||||
}
|
||||
|
||||
// 4. 检查是否有电量为98的记录
|
||||
console.log('\n4. 检查是否有电量为98的记录...');
|
||||
const battery98Devices = await IotXqClient.findAll({
|
||||
where: {
|
||||
battery: 98,
|
||||
sn: '22012000107'
|
||||
},
|
||||
order: [['uptime', 'DESC']]
|
||||
});
|
||||
|
||||
console.log(`找到 ${battery98Devices.length} 条电量为98的记录`);
|
||||
|
||||
battery98Devices.forEach((device, index) => {
|
||||
console.log(`\n电量98记录${index + 1}:`);
|
||||
console.log('ID:', device.id);
|
||||
console.log('SN:', device.sn);
|
||||
console.log('电量:', device.battery);
|
||||
console.log('温度:', device.temperature);
|
||||
console.log('更新时间:', device.uptime);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 检查失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
checkDatabaseData().catch(console.error);
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* 检查其他数据库
|
||||
* @file check-other-databases.js
|
||||
* @description 检查其他数据库中是否有项圈22012000107的数据
|
||||
*/
|
||||
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
async function checkOtherDatabases() {
|
||||
console.log('🔍 检查其他数据库中项圈22012000107的数据...\n');
|
||||
|
||||
const databases = ['nxxmdata', 'nxdata', 'datav', 'aipet_new'];
|
||||
|
||||
for (const dbName of databases) {
|
||||
try {
|
||||
console.log(`\n=== 检查数据库: ${dbName} ===`);
|
||||
|
||||
// 创建连接
|
||||
const connection = await mysql.createConnection({
|
||||
host: '192.168.0.240',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: '', // 根据实际情况填写密码
|
||||
database: dbName
|
||||
});
|
||||
|
||||
// 查询项圈22012000107的数据
|
||||
const [results] = await connection.execute(`
|
||||
SELECT
|
||||
id, sn, deviceId, battery, temperature, state,
|
||||
uptime, longitude, latitude, gps_state, rsrp
|
||||
FROM iot_xq_client
|
||||
WHERE sn = '22012000107'
|
||||
ORDER BY uptime DESC
|
||||
LIMIT 5
|
||||
`);
|
||||
|
||||
console.log(`找到 ${results.length} 条记录`);
|
||||
|
||||
results.forEach((row, index) => {
|
||||
console.log(`\n记录${index + 1}:`);
|
||||
console.log('ID:', row.id);
|
||||
console.log('SN:', row.sn);
|
||||
console.log('设备ID:', row.deviceId);
|
||||
console.log('电量:', row.battery);
|
||||
console.log('温度:', row.temperature);
|
||||
console.log('状态:', row.state);
|
||||
console.log('经度:', row.longitude);
|
||||
console.log('纬度:', row.latitude);
|
||||
console.log('GPS状态:', row.gps_state);
|
||||
console.log('RSRP:', row.rsrp);
|
||||
console.log('更新时间:', row.uptime);
|
||||
});
|
||||
|
||||
await connection.end();
|
||||
|
||||
} catch (error) {
|
||||
console.log(`❌ 数据库 ${dbName} 检查失败:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查当前数据库的最新数据
|
||||
console.log('\n=== 检查当前数据库的最新数据 ===');
|
||||
try {
|
||||
const connection = await mysql.createConnection({
|
||||
host: '192.168.0.240',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: '',
|
||||
database: 'nxxmdata'
|
||||
});
|
||||
|
||||
// 查询所有项圈的最新数据
|
||||
const [allResults] = await connection.execute(`
|
||||
SELECT
|
||||
id, sn, deviceId, battery, temperature, state, uptime
|
||||
FROM iot_xq_client
|
||||
ORDER BY uptime DESC
|
||||
LIMIT 20
|
||||
`);
|
||||
|
||||
console.log('所有项圈的最新数据:');
|
||||
allResults.forEach((row, index) => {
|
||||
console.log(`${index + 1}. SN: ${row.sn}, 电量: ${row.battery}, 温度: ${row.temperature}, 状态: ${row.state}, 更新时间: ${row.uptime}`);
|
||||
});
|
||||
|
||||
await connection.end();
|
||||
|
||||
} catch (error) {
|
||||
console.log('❌ 查询当前数据库失败:', error.message);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
checkOtherDatabases().catch(console.error);
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* 检查服务器配置
|
||||
* @file check-server-config.js
|
||||
* @description 检查服务器使用的数据库配置
|
||||
*/
|
||||
|
||||
const { IotXqClient } = require('./models');
|
||||
|
||||
async function checkServerConfig() {
|
||||
console.log('🔍 检查服务器配置...\n');
|
||||
|
||||
try {
|
||||
// 1. 检查数据库配置
|
||||
console.log('1. 检查数据库配置...');
|
||||
const config = IotXqClient.sequelize.config;
|
||||
console.log('数据库配置:');
|
||||
console.log('主机:', config.host);
|
||||
console.log('端口:', config.port);
|
||||
console.log('数据库名:', config.database);
|
||||
console.log('用户名:', config.username);
|
||||
|
||||
// 2. 测试连接
|
||||
console.log('\n2. 测试数据库连接...');
|
||||
await IotXqClient.sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 3. 查询项圈22012000107的数据
|
||||
console.log('\n3. 查询项圈22012000107的数据...');
|
||||
const devices = await IotXqClient.findAll({
|
||||
where: {
|
||||
sn: '22012000107'
|
||||
},
|
||||
order: [['uptime', 'DESC']]
|
||||
});
|
||||
|
||||
console.log(`找到 ${devices.length} 条记录`);
|
||||
|
||||
devices.forEach((device, index) => {
|
||||
console.log(`\n记录${index + 1}:`);
|
||||
console.log('ID:', device.id);
|
||||
console.log('SN:', device.sn);
|
||||
console.log('电量:', device.battery);
|
||||
console.log('温度:', device.temperature);
|
||||
console.log('状态:', device.state);
|
||||
console.log('更新时间:', device.uptime);
|
||||
});
|
||||
|
||||
// 4. 检查环境变量
|
||||
console.log('\n4. 检查环境变量...');
|
||||
console.log('DB_HOST:', process.env.DB_HOST);
|
||||
console.log('DB_PORT:', process.env.DB_PORT);
|
||||
console.log('DB_PASSWORD:', process.env.DB_PASSWORD);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 检查失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
checkServerConfig().catch(console.error);
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* 检查服务器环境变量
|
||||
* @file check-server-env.js
|
||||
* @description 检查服务器进程的环境变量
|
||||
*/
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
// 启动服务器并检查环境变量
|
||||
const server = spawn('node', ['server.js'], {
|
||||
env: {
|
||||
...process.env,
|
||||
DB_HOST: '129.211.213.226',
|
||||
DB_PORT: '9527',
|
||||
DB_PASSWORD: 'aiotAiot123!'
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
let output = '';
|
||||
|
||||
server.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
console.log('服务器输出:', data.toString());
|
||||
});
|
||||
|
||||
server.stderr.on('data', (data) => {
|
||||
console.error('服务器错误:', data.toString());
|
||||
});
|
||||
|
||||
server.on('close', (code) => {
|
||||
console.log(`服务器进程退出,代码: ${code}`);
|
||||
});
|
||||
|
||||
// 等待服务器启动
|
||||
setTimeout(() => {
|
||||
console.log('\n检查服务器环境变量...');
|
||||
console.log('DB_HOST:', process.env.DB_HOST);
|
||||
console.log('DB_PORT:', process.env.DB_PORT);
|
||||
console.log('DB_PASSWORD:', process.env.DB_PASSWORD);
|
||||
|
||||
// 测试API
|
||||
const axios = require('axios');
|
||||
axios.get('http://localhost:5350/api/smart-alerts/public/collar?search=22012000107&limit=1')
|
||||
.then(response => {
|
||||
console.log('\nAPI测试结果:');
|
||||
if (response.data.success && response.data.data.length > 0) {
|
||||
const collar = response.data.data[0];
|
||||
console.log('项圈编号:', collar.collarNumber);
|
||||
console.log('电量:', collar.battery);
|
||||
console.log('温度:', collar.temperature);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('API测试失败:', error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
server.kill();
|
||||
process.exit(0);
|
||||
});
|
||||
}, 5000);
|
||||
@@ -1,118 +0,0 @@
|
||||
/**
|
||||
* 检查特定项圈数据
|
||||
* @file check-specific-collar.js
|
||||
* @description 检查项圈编号22012000107的数据
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:5350/api/smart-alerts/public';
|
||||
|
||||
async function checkSpecificCollar() {
|
||||
console.log('🔍 检查项圈编号22012000107的数据...\n');
|
||||
|
||||
try {
|
||||
// 1. 搜索特定项圈编号
|
||||
console.log('1. 搜索项圈编号22012000107...');
|
||||
const searchResponse = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: {
|
||||
search: '22012000107',
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
});
|
||||
|
||||
if (searchResponse.data.success) {
|
||||
const data = searchResponse.data.data || [];
|
||||
console.log(`找到 ${data.length} 条相关数据`);
|
||||
|
||||
data.forEach((item, index) => {
|
||||
console.log(`\n第${index + 1}条数据:`);
|
||||
console.log('原始API数据:', {
|
||||
id: item.id,
|
||||
collarNumber: item.collarNumber,
|
||||
battery: item.battery,
|
||||
batteryLevel: item.batteryLevel,
|
||||
temperature: item.temperature,
|
||||
temp: item.temp,
|
||||
alertType: item.alertType,
|
||||
alertLevel: item.alertLevel,
|
||||
alertTime: item.alertTime,
|
||||
dailySteps: item.dailySteps,
|
||||
steps: item.steps
|
||||
});
|
||||
|
||||
// 模拟前端转换逻辑
|
||||
const transformedData = {
|
||||
id: item.id || `${item.deviceId || item.sn}_${item.alertType || 'normal'}`,
|
||||
collarNumber: item.collarNumber || item.sn || item.deviceId || '',
|
||||
battery: item.battery || item.batteryLevel || '',
|
||||
temperature: item.temperature || item.temp || '',
|
||||
dailySteps: item.dailySteps || item.steps || ''
|
||||
};
|
||||
|
||||
console.log('前端转换后:', transformedData);
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log('❌ 搜索失败:', searchResponse.data.message);
|
||||
}
|
||||
|
||||
// 2. 获取所有数据并查找特定项圈
|
||||
console.log('\n2. 获取所有数据查找特定项圈...');
|
||||
const allResponse = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: { page: 1, limit: 100 }
|
||||
});
|
||||
|
||||
if (allResponse.data.success) {
|
||||
const allData = allResponse.data.data || [];
|
||||
const specificCollars = allData.filter(item =>
|
||||
item.collarNumber == 22012000107 ||
|
||||
item.deviceName == 22012000107 ||
|
||||
item.sn == 22012000107
|
||||
);
|
||||
|
||||
console.log(`在所有数据中找到 ${specificCollars.length} 条项圈22012000107的数据`);
|
||||
|
||||
specificCollars.forEach((item, index) => {
|
||||
console.log(`\n项圈22012000107 - 第${index + 1}条:`);
|
||||
console.log('ID:', item.id);
|
||||
console.log('项圈编号:', item.collarNumber);
|
||||
console.log('设备名称:', item.deviceName);
|
||||
console.log('电量字段1 (battery):', item.battery);
|
||||
console.log('电量字段2 (batteryLevel):', item.batteryLevel);
|
||||
console.log('温度字段1 (temperature):', item.temperature);
|
||||
console.log('温度字段2 (temp):', item.temp);
|
||||
console.log('预警类型:', item.alertType);
|
||||
console.log('预警级别:', item.alertLevel);
|
||||
console.log('预警时间:', item.alertTime);
|
||||
console.log('步数字段1 (dailySteps):', item.dailySteps);
|
||||
console.log('步数字段2 (steps):', item.steps);
|
||||
console.log('总步数:', item.totalSteps);
|
||||
console.log('昨日步数:', item.yesterdaySteps);
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log('❌ 获取所有数据失败:', allResponse.data.message);
|
||||
}
|
||||
|
||||
// 3. 检查数据库原始数据
|
||||
console.log('\n3. 检查数据库原始数据...');
|
||||
console.log('请检查数据库中项圈22012000107的原始数据');
|
||||
console.log('可能需要检查以下字段:');
|
||||
console.log('- battery_level 或 battery 字段');
|
||||
console.log('- temperature 或 temp 字段');
|
||||
console.log('- 数据更新时间');
|
||||
console.log('- 是否有多个记录');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 检查失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应状态:', error.response.status);
|
||||
console.error('响应数据:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
checkSpecificCollar().catch(console.error);
|
||||
@@ -1,61 +0,0 @@
|
||||
@echo off
|
||||
echo 正在配置Windows防火墙以允许外部访问...
|
||||
echo.
|
||||
|
||||
REM 检查是否以管理员身份运行
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
echo 检测到管理员权限,继续配置...
|
||||
) else (
|
||||
echo 错误:请以管理员身份运行此脚本
|
||||
echo 右键点击此文件,选择"以管理员身份运行"
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 添加防火墙规则...
|
||||
|
||||
REM 允许前端端口5300
|
||||
netsh advfirewall firewall add rule name="Node.js Frontend Port 5300" dir=in action=allow protocol=TCP localport=5300
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 前端端口5300规则添加成功
|
||||
) else (
|
||||
echo ⚠️ 前端端口5300规则可能已存在
|
||||
)
|
||||
|
||||
REM 允许后端端口5350
|
||||
netsh advfirewall firewall add rule name="Node.js Backend Port 5350" dir=in action=allow protocol=TCP localport=5350
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ 后端端口5350规则添加成功
|
||||
) else (
|
||||
echo ⚠️ 后端端口5350规则可能已存在
|
||||
)
|
||||
|
||||
REM 允许Node.js程序
|
||||
netsh advfirewall firewall add rule name="Node.js Program" dir=in action=allow program="C:\Program Files\nodejs\node.exe" enable=yes
|
||||
if %errorLevel% == 0 (
|
||||
echo ✅ Node.js程序规则添加成功
|
||||
) else (
|
||||
echo ⚠️ Node.js程序规则可能已存在
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 检查已添加的规则...
|
||||
netsh advfirewall firewall show rule name="Node.js Frontend Port 5300"
|
||||
echo.
|
||||
netsh advfirewall firewall show rule name="Node.js Backend Port 5350"
|
||||
echo.
|
||||
|
||||
echo 🎉 防火墙配置完成!
|
||||
echo.
|
||||
echo 现在其他用户可以通过以下地址访问您的服务:
|
||||
echo 前端: http://172.28.112.1:5300
|
||||
echo 后端: http://172.28.112.1:5350
|
||||
echo.
|
||||
echo 请确保:
|
||||
echo 1. 服务器正在运行
|
||||
echo 2. 其他用户与您在同一个局域网内
|
||||
echo 3. 使用正确的IP地址(不是localhost)
|
||||
echo.
|
||||
pause
|
||||
@@ -1,78 +0,0 @@
|
||||
# PowerShell防火墙配置脚本
|
||||
# 解决外部用户无法访问开发服务器的问题
|
||||
|
||||
Write-Host "🔧 正在配置Windows防火墙以允许外部访问..." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# 检查是否以管理员身份运行
|
||||
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
|
||||
Write-Host "❌ 错误:请以管理员身份运行此脚本" -ForegroundColor Red
|
||||
Write-Host "右键点击此文件,选择'以管理员身份运行'" -ForegroundColor Yellow
|
||||
Read-Host "按任意键退出"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✅ 检测到管理员权限,继续配置..." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# 配置防火墙规则
|
||||
Write-Host "添加防火墙规则..." -ForegroundColor Cyan
|
||||
|
||||
# 允许前端端口5300
|
||||
try {
|
||||
New-NetFirewallRule -DisplayName "Node.js Frontend Port 5300" -Direction Inbound -Protocol TCP -LocalPort 5300 -Action Allow -ErrorAction SilentlyContinue
|
||||
Write-Host "✅ 前端端口5300规则添加成功" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "⚠️ 前端端口5300规则可能已存在" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# 允许后端端口5350
|
||||
try {
|
||||
New-NetFirewallRule -DisplayName "Node.js Backend Port 5350" -Direction Inbound -Protocol TCP -LocalPort 5350 -Action Allow -ErrorAction SilentlyContinue
|
||||
Write-Host "✅ 后端端口5350规则添加成功" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "⚠️ 后端端口5350规则可能已存在" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# 允许Node.js程序
|
||||
try {
|
||||
$nodePath = "C:\Program Files\nodejs\node.exe"
|
||||
if (Test-Path $nodePath) {
|
||||
New-NetFirewallRule -DisplayName "Node.js Program" -Direction Inbound -Program $nodePath -Action Allow -ErrorAction SilentlyContinue
|
||||
Write-Host "✅ Node.js程序规则添加成功" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ 未找到Node.js程序路径,跳过程序规则" -ForegroundColor Yellow
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Node.js程序规则可能已存在" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "检查已添加的规则..." -ForegroundColor Cyan
|
||||
|
||||
# 显示规则
|
||||
Get-NetFirewallRule -DisplayName "*Node.js*" | Format-Table DisplayName, Direction, Action, Enabled -AutoSize
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "🎉 防火墙配置完成!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "现在其他用户可以通过以下地址访问您的服务:" -ForegroundColor Yellow
|
||||
Write-Host "前端: http://172.28.112.1:5300" -ForegroundColor White
|
||||
Write-Host "后端: http://172.28.112.1:5350" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "请确保:" -ForegroundColor Yellow
|
||||
Write-Host "1. 服务器正在运行" -ForegroundColor White
|
||||
Write-Host "2. 其他用户与您在同一个局域网内" -ForegroundColor White
|
||||
Write-Host "3. 使用正确的IP地址(不是localhost)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
# 获取所有可用的IP地址
|
||||
Write-Host "可用的访问地址:" -ForegroundColor Cyan
|
||||
$networkAdapters = Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.IPAddress -notlike "127.*" -and $_.IPAddress -notlike "169.254.*" }
|
||||
foreach ($adapter in $networkAdapters) {
|
||||
Write-Host " 前端: http://$($adapter.IPAddress):5300" -ForegroundColor White
|
||||
Write-Host " 后端: http://$($adapter.IPAddress):5350" -ForegroundColor White
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Read-Host "按任意键退出"
|
||||
@@ -1,35 +0,0 @@
|
||||
console.log('开始调试启动过程...');
|
||||
|
||||
try {
|
||||
console.log('1. 加载数据库配置...');
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
console.log('✓ 数据库配置加载成功');
|
||||
|
||||
console.log('2. 加载模型...');
|
||||
const models = require('./models');
|
||||
console.log('✓ 模型加载成功');
|
||||
|
||||
console.log('3. 测试数据库连接...');
|
||||
sequelize.authenticate().then(() => {
|
||||
console.log('✓ 数据库连接成功');
|
||||
console.log('4. 测试用户查询...');
|
||||
return models.User.findByPk(1);
|
||||
}).then(user => {
|
||||
if (user) {
|
||||
console.log('✓ 用户查询成功:', user.username);
|
||||
} else {
|
||||
console.log('⚠ 未找到用户');
|
||||
}
|
||||
console.log('调试完成,所有测试通过');
|
||||
process.exit(0);
|
||||
}).catch(error => {
|
||||
console.error('❌ 错误:', error.message);
|
||||
console.error('堆栈:', error.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 启动失败:', error.message);
|
||||
console.error('堆栈:', error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
@echo off
|
||||
echo ========================================
|
||||
echo ngrok外网访问演示脚本
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
echo 步骤1:检查ngrok是否已配置认证
|
||||
.\ngrok.exe config check
|
||||
echo.
|
||||
|
||||
echo 步骤2:如果没有配置,请先运行以下命令:
|
||||
echo .\ngrok.exe authtoken YOUR_AUTH_TOKEN
|
||||
echo.
|
||||
|
||||
echo 步骤3:启动后端服务穿透
|
||||
echo 正在启动ngrok...
|
||||
echo 请在新窗口中查看访问地址
|
||||
echo.
|
||||
|
||||
start "ngrok-backend" .\ngrok.exe http 5350
|
||||
|
||||
echo.
|
||||
echo 步骤4:等待3秒后启动前端服务穿透
|
||||
timeout /t 3 /nobreak >nul
|
||||
|
||||
start "ngrok-frontend" .\ngrok.exe http 5300
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo ngrok已启动!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 请查看两个新打开的窗口:
|
||||
echo 1. ngrok-backend 窗口:显示后端访问地址
|
||||
echo 2. ngrok-frontend 窗口:显示前端访问地址
|
||||
echo.
|
||||
echo 访问地址格式:
|
||||
echo - 后端:https://xxxxx.ngrok.io
|
||||
echo - 前端:https://yyyyy.ngrok.io
|
||||
echo - API文档:https://xxxxx.ngrok.io/api-docs
|
||||
echo.
|
||||
echo 按任意键退出...
|
||||
pause >nul
|
||||
14
backend/fix-line-endings.sh
Normal file
14
backend/fix-line-endings.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# 修复行尾符问题的脚本
|
||||
|
||||
echo "修复 node_manager.sh 的行尾符..."
|
||||
|
||||
# 方法1: 使用 sed (推荐)
|
||||
sed -i 's/\r$//' node_manager.sh
|
||||
|
||||
# 设置执行权限
|
||||
chmod +x node_manager.sh
|
||||
|
||||
echo "修复完成!"
|
||||
echo "现在可以运行: ./node_manager.sh start"
|
||||
|
||||
289
backend/node_manager.ps1
Normal file
289
backend/node_manager.ps1
Normal file
@@ -0,0 +1,289 @@
|
||||
# Node.js 服务管理脚本 - 宁夏智慧养殖监管平台后端 (Windows版本)
|
||||
# 使用方法: .\node_manager.ps1 [start|stop|restart|status]
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[ValidateSet('start','stop','restart','status','logs')]
|
||||
[string]$Action = 'status'
|
||||
)
|
||||
|
||||
# 配置区域
|
||||
$APP_NAME = "nxxmdata-backend"
|
||||
$ENTRY_FILE = "server.js"
|
||||
$APP_PORT = 5350
|
||||
$LOG_DIR = "logs"
|
||||
$LOG_FILE = Join-Path $LOG_DIR "$APP_NAME.log"
|
||||
$PID_FILE = "pid.$APP_NAME"
|
||||
$NODE_ENV = "production"
|
||||
|
||||
# 创建日志目录
|
||||
if (-not (Test-Path $LOG_DIR)) {
|
||||
New-Item -ItemType Directory -Path $LOG_DIR | Out-Null
|
||||
Write-Host "已创建日志目录: $LOG_DIR" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# 检查 Node.js 是否安装
|
||||
try {
|
||||
$nodeVersion = node --version
|
||||
Write-Host "Node.js 版本: $nodeVersion" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "错误: Node.js 未安装或不在 PATH 中" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 检查入口文件是否存在
|
||||
if (-not (Test-Path $ENTRY_FILE)) {
|
||||
Write-Host "错误: 入口文件 $ENTRY_FILE 不存在" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 停止服务函数
|
||||
function Stop-App {
|
||||
Write-Host "正在停止服务: $APP_NAME" -ForegroundColor Yellow
|
||||
|
||||
# 查找占用端口的进程
|
||||
$processes = Get-NetTCPConnection -LocalPort $APP_PORT -ErrorAction SilentlyContinue |
|
||||
Select-Object -ExpandProperty OwningProcess -Unique
|
||||
|
||||
if ($processes) {
|
||||
foreach ($pid in $processes) {
|
||||
try {
|
||||
$process = Get-Process -Id $pid -ErrorAction SilentlyContinue
|
||||
if ($process -and $process.ProcessName -eq "node") {
|
||||
Write-Host "找到进程 PID: $pid,正在停止..." -ForegroundColor Yellow
|
||||
Stop-Process -Id $pid -Force
|
||||
Write-Host "服务已停止 (PID: $pid)" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host "停止进程失败: $_" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# 清理 PID 文件
|
||||
if (Test-Path $PID_FILE) {
|
||||
Remove-Item $PID_FILE -Force
|
||||
}
|
||||
} else {
|
||||
Write-Host "未找到运行中的服务 (端口 $APP_PORT 未被占用)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# 启动服务函数
|
||||
function Start-App {
|
||||
Write-Host "正在启动服务: $APP_NAME" -ForegroundColor Yellow
|
||||
|
||||
# 检查端口是否被占用
|
||||
$portCheck = Get-NetTCPConnection -LocalPort $APP_PORT -ErrorAction SilentlyContinue
|
||||
if ($portCheck) {
|
||||
$pid = $portCheck | Select-Object -First 1 -ExpandProperty OwningProcess
|
||||
Write-Host "错误: 端口 $APP_PORT 已被占用 (PID: $pid)" -ForegroundColor Red
|
||||
Write-Host "请先停止占用端口的进程或使用 restart 命令" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
|
||||
# 检查 .env 文件是否存在
|
||||
if (-not (Test-Path ".env")) {
|
||||
Write-Host "警告: .env 文件不存在,将使用默认配置" -ForegroundColor Yellow
|
||||
Write-Host "建议从 env.example 复制并配置 .env 文件" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# 检查 node_modules 是否存在
|
||||
if (-not (Test-Path "node_modules")) {
|
||||
Write-Host "错误: node_modules 目录不存在" -ForegroundColor Red
|
||||
Write-Host "请先运行: npm install" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
|
||||
# 启动 Node.js 应用
|
||||
Write-Host "启动命令: node $ENTRY_FILE" -ForegroundColor Cyan
|
||||
|
||||
# 设置环境变量并启动
|
||||
$env:NODE_ENV = $NODE_ENV
|
||||
|
||||
# 使用 Start-Process 启动后台进程
|
||||
$processInfo = Start-Process -FilePath "node" `
|
||||
-ArgumentList $ENTRY_FILE `
|
||||
-RedirectStandardOutput $LOG_FILE `
|
||||
-RedirectStandardError $LOG_FILE `
|
||||
-WindowStyle Hidden `
|
||||
-PassThru
|
||||
|
||||
if ($processInfo) {
|
||||
$PID = $processInfo.Id
|
||||
$PID | Out-File -FilePath $PID_FILE -Encoding ASCII
|
||||
|
||||
Write-Host "等待服务启动..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# 验证启动是否成功
|
||||
if (Get-Process -Id $PID -ErrorAction SilentlyContinue) {
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host "服务启动成功!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host "应用名称: $APP_NAME"
|
||||
Write-Host "进程 ID: $PID"
|
||||
Write-Host "监听端口: $APP_PORT"
|
||||
Write-Host "日志文件: $LOG_FILE"
|
||||
Write-Host "PID 文件: $PID_FILE"
|
||||
Write-Host "环境变量: NODE_ENV=$NODE_ENV"
|
||||
Write-Host "API 文档: http://localhost:${APP_PORT}/api-docs" -ForegroundColor Cyan
|
||||
|
||||
# 检查端口监听状态
|
||||
Start-Sleep -Seconds 2
|
||||
$portCheck = Get-NetTCPConnection -LocalPort $APP_PORT -ErrorAction SilentlyContinue
|
||||
if ($portCheck) {
|
||||
Write-Host "✓ 端口 $APP_PORT 正在监听" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠ 端口 $APP_PORT 尚未监听,请检查日志" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# 显示最近的日志
|
||||
Write-Host ""
|
||||
Write-Host "最近的启动日志:" -ForegroundColor Yellow
|
||||
Write-Host "------------------------"
|
||||
if (Test-Path $LOG_FILE) {
|
||||
Get-Content $LOG_FILE -Tail 20 -ErrorAction SilentlyContinue
|
||||
}
|
||||
} else {
|
||||
Write-Host "========================================" -ForegroundColor Red
|
||||
Write-Host "服务启动失败!" -ForegroundColor Red
|
||||
Write-Host "========================================" -ForegroundColor Red
|
||||
Write-Host "请检查日志文件: $LOG_FILE" -ForegroundColor Yellow
|
||||
|
||||
if (Test-Path $LOG_FILE) {
|
||||
Write-Host ""
|
||||
Write-Host "最近的错误日志:" -ForegroundColor Yellow
|
||||
Write-Host "------------------------"
|
||||
Get-Content $LOG_FILE -Tail 30 -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if (Test-Path $PID_FILE) {
|
||||
Remove-Item $PID_FILE -Force
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
Write-Host "启动进程失败!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# 重启服务函数
|
||||
function Restart-App {
|
||||
Write-Host "正在重启服务: $APP_NAME" -ForegroundColor Yellow
|
||||
Stop-App
|
||||
Start-Sleep -Seconds 2
|
||||
Start-App
|
||||
}
|
||||
|
||||
# 状态检查函数
|
||||
function Get-AppStatus {
|
||||
Write-Host "检查服务状态: $APP_NAME" -ForegroundColor Yellow
|
||||
Write-Host "========================================"
|
||||
|
||||
# 检查端口占用
|
||||
$portCheck = Get-NetTCPConnection -LocalPort $APP_PORT -ErrorAction SilentlyContinue
|
||||
|
||||
if ($portCheck) {
|
||||
$pid = $portCheck | Select-Object -First 1 -ExpandProperty OwningProcess
|
||||
$process = Get-Process -Id $pid -ErrorAction SilentlyContinue
|
||||
|
||||
if ($process -and $process.ProcessName -eq "node") {
|
||||
Write-Host "✓ 服务正在运行" -ForegroundColor Green
|
||||
Write-Host "----------------------------------------"
|
||||
Write-Host "进程 ID: $pid"
|
||||
Write-Host "进程名称: $($process.ProcessName)"
|
||||
Write-Host "应用名称: $APP_NAME"
|
||||
Write-Host "监听端口: $APP_PORT"
|
||||
Write-Host "启动时间: $($process.StartTime)"
|
||||
|
||||
# 内存使用 (转换为 MB)
|
||||
$memoryMB = [math]::Round($process.WorkingSet64 / 1MB, 2)
|
||||
Write-Host "内存使用: $memoryMB MB"
|
||||
|
||||
# CPU时间
|
||||
Write-Host "CPU 时间: $($process.CPU) 秒"
|
||||
|
||||
# 端口状态
|
||||
Write-Host "端口状态: 监听中 ($APP_PORT)" -ForegroundColor Green
|
||||
|
||||
# 日志文件信息
|
||||
if (Test-Path $LOG_FILE) {
|
||||
$logSize = [math]::Round((Get-Item $LOG_FILE).Length / 1KB, 2)
|
||||
Write-Host "日志大小: $logSize KB"
|
||||
Write-Host "日志文件: $LOG_FILE"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "最近的日志 (最后10行):" -ForegroundColor Yellow
|
||||
Write-Host "----------------------------------------"
|
||||
if (Test-Path $LOG_FILE) {
|
||||
Get-Content $LOG_FILE -Tail 10 -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
Write-Host "无法读取日志文件"
|
||||
}
|
||||
} else {
|
||||
Write-Host "⚠ 端口 $APP_PORT 被其他进程占用 (PID: $pid)" -ForegroundColor Yellow
|
||||
Write-Host "进程名称: $($process.ProcessName)"
|
||||
}
|
||||
} else {
|
||||
Write-Host "✗ 服务未运行" -ForegroundColor Red
|
||||
Write-Host "使用以下命令启动: .\node_manager.ps1 start"
|
||||
}
|
||||
|
||||
Write-Host "========================================"
|
||||
}
|
||||
|
||||
# 查看日志函数
|
||||
function View-Logs {
|
||||
if (Test-Path $LOG_FILE) {
|
||||
Write-Host "实时查看日志 (Ctrl+C 退出):" -ForegroundColor Yellow
|
||||
Get-Content $LOG_FILE -Wait -Tail 20
|
||||
} else {
|
||||
Write-Host "日志文件不存在: $LOG_FILE" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# 主逻辑
|
||||
switch ($Action.ToLower()) {
|
||||
'start' {
|
||||
Start-App
|
||||
}
|
||||
'stop' {
|
||||
Stop-App
|
||||
}
|
||||
'restart' {
|
||||
Restart-App
|
||||
}
|
||||
'status' {
|
||||
Get-AppStatus
|
||||
}
|
||||
'logs' {
|
||||
View-Logs
|
||||
}
|
||||
default {
|
||||
Write-Host "========================================"
|
||||
Write-Host "宁夏智慧养殖监管平台 - 服务管理脚本 (Windows)"
|
||||
Write-Host "========================================"
|
||||
Write-Host "使用方法: .\node_manager.ps1 [start|stop|restart|status|logs]"
|
||||
Write-Host ""
|
||||
Write-Host "命令说明:"
|
||||
Write-Host " start - 启动服务"
|
||||
Write-Host " stop - 停止服务"
|
||||
Write-Host " restart - 重启服务"
|
||||
Write-Host " status - 查看服务状态"
|
||||
Write-Host " logs - 实时查看日志"
|
||||
Write-Host ""
|
||||
Write-Host "配置信息:"
|
||||
Write-Host " 应用名称: $APP_NAME"
|
||||
Write-Host " 入口文件: $ENTRY_FILE"
|
||||
Write-Host " 监听端口: $APP_PORT"
|
||||
Write-Host " 日志文件: $LOG_FILE"
|
||||
Write-Host ""
|
||||
Write-Host "示例:"
|
||||
Write-Host " .\node_manager.ps1 start # 启动服务"
|
||||
Write-Host " .\node_manager.ps1 status # 查看状态"
|
||||
Write-Host " .\node_manager.ps1 logs # 查看日志"
|
||||
}
|
||||
}
|
||||
|
||||
328
backend/node_manager.sh
Normal file
328
backend/node_manager.sh
Normal file
@@ -0,0 +1,328 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Node.js 服务管理脚本 - 宁夏智慧养殖监管平台后端
|
||||
# 使用方法: ./node_manager.sh [start|stop|restart|status]
|
||||
|
||||
# 配置区域
|
||||
APP_NAME="nxxmdata-backend"
|
||||
ENTRY_FILE="server.js"
|
||||
APP_PORT="5350"
|
||||
LOG_DIR="logs"
|
||||
LOG_FILE="${LOG_DIR}/${APP_NAME}.log"
|
||||
PID_FILE="pid.${APP_NAME}"
|
||||
NODE_ENV="production"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 创建日志目录
|
||||
if [ ! -d "$LOG_DIR" ]; then
|
||||
mkdir -p "$LOG_DIR"
|
||||
echo "已创建日志目录: $LOG_DIR"
|
||||
fi
|
||||
|
||||
# 检查 Node.js 是否安装
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo -e "${RED}错误: Node.js 未安装或不在 PATH 中${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查入口文件是否存在
|
||||
if [ ! -f "$ENTRY_FILE" ]; then
|
||||
echo -e "${RED}错误: 入口文件 $ENTRY_FILE 不存在${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 显示 Node.js 版本信息
|
||||
NODE_VERSION=$(node --version)
|
||||
echo -e "${GREEN}Node.js 版本: $NODE_VERSION${NC}"
|
||||
|
||||
# 停止服务函数
|
||||
function stopApp() {
|
||||
echo -e "${YELLOW}正在停止服务: $APP_NAME${NC}"
|
||||
|
||||
# 从 PID 文件读取进程 ID
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
echo "找到进程 PID: $PID,正在停止..."
|
||||
kill -TERM $PID
|
||||
|
||||
# 等待进程优雅退出
|
||||
for i in {1..10}; do
|
||||
if ! ps -p $PID > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}服务已优雅停止${NC}"
|
||||
rm -f "$PID_FILE"
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 如果优雅停止失败,强制杀死
|
||||
echo -e "${YELLOW}优雅停止失败,强制终止进程${NC}"
|
||||
kill -9 $PID
|
||||
rm -f "$PID_FILE"
|
||||
else
|
||||
echo "PID 文件存在但进程不存在,清理 PID 文件"
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
else
|
||||
# 如果没有 PID 文件,尝试通过端口查找
|
||||
PID=$(lsof -ti:$APP_PORT 2>/dev/null)
|
||||
if [ -n "$PID" ]; then
|
||||
echo "通过端口 $APP_PORT 找到 PID: $PID,正在停止..."
|
||||
kill -TERM $PID
|
||||
sleep 2
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
kill -9 $PID
|
||||
fi
|
||||
echo -e "${GREEN}服务已停止${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}未找到运行中的服务: $APP_NAME${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 启动服务函数
|
||||
function startApp() {
|
||||
echo -e "${YELLOW}正在启动服务: $APP_NAME${NC}"
|
||||
|
||||
# 检查服务是否已经运行
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
echo -e "${YELLOW}服务已在运行中 (PID: $PID)${NC}"
|
||||
return 0
|
||||
else
|
||||
echo "PID 文件存在但进程不存在,清理后重新启动"
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查端口是否被占用
|
||||
if command -v lsof &> /dev/null; then
|
||||
PORT_CHECK=$(lsof -ti:$APP_PORT 2>/dev/null)
|
||||
if [ -n "$PORT_CHECK" ]; then
|
||||
echo -e "${RED}错误: 端口 $APP_PORT 已被占用 (PID: $PORT_CHECK)${NC}"
|
||||
echo "请先停止占用端口的进程或使用 restart 命令"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查 .env 文件是否存在
|
||||
if [ ! -f ".env" ]; then
|
||||
echo -e "${YELLOW}警告: .env 文件不存在,将使用默认配置${NC}"
|
||||
echo "建议从 env.example 复制并配置 .env 文件"
|
||||
fi
|
||||
|
||||
# 检查 node_modules 是否存在
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo -e "${RED}错误: node_modules 目录不存在${NC}"
|
||||
echo "请先运行: npm install"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 启动 Node.js 应用
|
||||
echo "启动命令: NODE_ENV=$NODE_ENV nohup node $ENTRY_FILE > $LOG_FILE 2>&1 &"
|
||||
NODE_ENV=$NODE_ENV nohup node $ENTRY_FILE > $LOG_FILE 2>&1 &
|
||||
|
||||
# 获取新进程的 PID
|
||||
PID=$!
|
||||
echo $PID > "$PID_FILE"
|
||||
|
||||
# 等待应用启动
|
||||
echo "等待服务启动..."
|
||||
sleep 3
|
||||
|
||||
# 验证启动是否成功
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}服务启动成功!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo "应用名称: $APP_NAME"
|
||||
echo "进程 ID: $PID"
|
||||
echo "监听端口: $APP_PORT"
|
||||
echo "日志文件: $LOG_FILE"
|
||||
echo "PID 文件: $PID_FILE"
|
||||
echo "环境变量: NODE_ENV=$NODE_ENV"
|
||||
echo -e "${GREEN}API 文档: http://localhost:$APP_PORT/api-docs${NC}"
|
||||
echo ""
|
||||
|
||||
# 等待端口监听
|
||||
echo "检查端口监听状态..."
|
||||
sleep 2
|
||||
if command -v lsof &> /dev/null; then
|
||||
if lsof -ti:$APP_PORT > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ 端口 $APP_PORT 正在监听${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ 端口 $APP_PORT 尚未监听,请检查日志${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 显示最近的日志
|
||||
echo ""
|
||||
echo "最近的启动日志:"
|
||||
echo "------------------------"
|
||||
tail -20 "$LOG_FILE"
|
||||
else
|
||||
echo -e "${RED}========================================${NC}"
|
||||
echo -e "${RED}服务启动失败!${NC}"
|
||||
echo -e "${RED}========================================${NC}"
|
||||
echo "请检查日志文件: $LOG_FILE"
|
||||
echo ""
|
||||
echo "最近的错误日志:"
|
||||
echo "------------------------"
|
||||
tail -30 "$LOG_FILE"
|
||||
rm -f "$PID_FILE"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 重启服务函数
|
||||
function restartApp() {
|
||||
echo -e "${YELLOW}正在重启服务: $APP_NAME${NC}"
|
||||
stopApp
|
||||
sleep 3
|
||||
startApp
|
||||
}
|
||||
|
||||
# 状态检查函数
|
||||
function statusApp() {
|
||||
echo -e "${YELLOW}检查服务状态: $APP_NAME${NC}"
|
||||
echo "========================================"
|
||||
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p $PID > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ 服务正在运行${NC}"
|
||||
echo "----------------------------------------"
|
||||
echo "进程 ID: $PID"
|
||||
echo "应用名称: $APP_NAME"
|
||||
echo "监听端口: $APP_PORT"
|
||||
|
||||
# 启动时间
|
||||
if command -v ps &> /dev/null; then
|
||||
START_TIME=$(ps -o lstart= -p $PID 2>/dev/null)
|
||||
if [ -n "$START_TIME" ]; then
|
||||
echo "启动时间: $START_TIME"
|
||||
fi
|
||||
|
||||
# 内存使用
|
||||
MEM_USAGE=$(ps -o rss= -p $PID 2>/dev/null | awk '{printf "%.2f MB", $1/1024}')
|
||||
if [ -n "$MEM_USAGE" ]; then
|
||||
echo "内存使用: $MEM_USAGE"
|
||||
fi
|
||||
|
||||
# CPU使用率
|
||||
CPU_USAGE=$(ps -o %cpu= -p $PID 2>/dev/null)
|
||||
if [ -n "$CPU_USAGE" ]; then
|
||||
echo "CPU 使用: ${CPU_USAGE}%"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查端口监听状态
|
||||
if command -v lsof &> /dev/null; then
|
||||
PORT_INFO=$(lsof -ti:$APP_PORT 2>/dev/null)
|
||||
if [ -n "$PORT_INFO" ]; then
|
||||
echo -e "端口状态: ${GREEN}监听中 ($APP_PORT)${NC}"
|
||||
else
|
||||
echo -e "端口状态: ${RED}未监听${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 日志文件信息
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
LOG_SIZE=$(du -h "$LOG_FILE" | cut -f1)
|
||||
echo "日志大小: $LOG_SIZE"
|
||||
echo "日志文件: $LOG_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "最近的日志 (最后10行):"
|
||||
echo "----------------------------------------"
|
||||
tail -10 "$LOG_FILE" 2>/dev/null || echo "无法读取日志文件"
|
||||
|
||||
else
|
||||
echo -e "${RED}✗ 服务未运行 (PID 文件存在但进程不存在)${NC}"
|
||||
echo "建议清理 PID 文件: rm -f $PID_FILE"
|
||||
fi
|
||||
else
|
||||
# 通过端口检查
|
||||
if command -v lsof &> /dev/null; then
|
||||
PID=$(lsof -ti:$APP_PORT 2>/dev/null)
|
||||
if [ -n "$PID" ]; then
|
||||
echo -e "${YELLOW}⚠ 服务正在运行但未通过脚本管理${NC}"
|
||||
echo "进程 ID: $PID"
|
||||
echo "监听端口: $APP_PORT"
|
||||
echo "建议使用: ./node_manager.sh stop 停止服务后重新启动"
|
||||
else
|
||||
echo -e "${RED}✗ 服务未运行${NC}"
|
||||
echo "使用以下命令启动: ./node_manager.sh start"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ 服务未运行 (PID 文件不存在)${NC}"
|
||||
echo "使用以下命令启动: ./node_manager.sh start"
|
||||
fi
|
||||
fi
|
||||
echo "========================================"
|
||||
}
|
||||
|
||||
# 查看日志函数
|
||||
function viewLogs() {
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
echo -e "${YELLOW}实时查看日志 (Ctrl+C 退出):${NC}"
|
||||
tail -f "$LOG_FILE"
|
||||
else
|
||||
echo -e "${RED}日志文件不存在: $LOG_FILE${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 主逻辑
|
||||
case "$1" in
|
||||
start)
|
||||
startApp
|
||||
;;
|
||||
stop)
|
||||
stopApp
|
||||
;;
|
||||
restart)
|
||||
restartApp
|
||||
;;
|
||||
status)
|
||||
statusApp
|
||||
;;
|
||||
logs)
|
||||
viewLogs
|
||||
;;
|
||||
*)
|
||||
echo "========================================"
|
||||
echo "宁夏智慧养殖监管平台 - 服务管理脚本"
|
||||
echo "========================================"
|
||||
echo "使用方法: $0 {start|stop|restart|status|logs}"
|
||||
echo ""
|
||||
echo "命令说明:"
|
||||
echo " start - 启动服务"
|
||||
echo " stop - 停止服务"
|
||||
echo " restart - 重启服务"
|
||||
echo " status - 查看服务状态"
|
||||
echo " logs - 实时查看日志"
|
||||
echo ""
|
||||
echo "配置信息:"
|
||||
echo " 应用名称: $APP_NAME"
|
||||
echo " 入口文件: $ENTRY_FILE"
|
||||
echo " 监听端口: $APP_PORT"
|
||||
echo " 日志文件: $LOG_FILE"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 start # 启动服务"
|
||||
echo " $0 status # 查看状态"
|
||||
echo " $0 logs # 查看日志"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* 简单数据库测试
|
||||
* @file simple-db-test.js
|
||||
* @description 测试数据库连接和查询
|
||||
*/
|
||||
|
||||
const { IotXqClient } = require('./models');
|
||||
|
||||
async function testDatabase() {
|
||||
console.log('🔍 测试数据库连接...\n');
|
||||
|
||||
try {
|
||||
// 测试数据库连接
|
||||
console.log('1. 测试数据库连接...');
|
||||
await IotXqClient.sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 查询项圈22012000107的数据
|
||||
console.log('\n2. 查询项圈22012000107的数据...');
|
||||
const devices = await IotXqClient.findAll({
|
||||
where: {
|
||||
sn: '22012000107'
|
||||
},
|
||||
order: [['uptime', 'DESC']],
|
||||
limit: 5
|
||||
});
|
||||
|
||||
console.log(`找到 ${devices.length} 条记录`);
|
||||
|
||||
devices.forEach((device, index) => {
|
||||
console.log(`\n记录${index + 1}:`);
|
||||
console.log('ID:', device.id);
|
||||
console.log('SN:', device.sn);
|
||||
console.log('电量:', device.battery);
|
||||
console.log('温度:', device.temperature);
|
||||
console.log('状态:', device.state);
|
||||
console.log('更新时间:', device.uptime);
|
||||
});
|
||||
|
||||
// 查询所有项圈数据
|
||||
console.log('\n3. 查询所有项圈数据...');
|
||||
const allDevices = await IotXqClient.findAll({
|
||||
order: [['uptime', 'DESC']],
|
||||
limit: 10
|
||||
});
|
||||
|
||||
console.log(`总共 ${allDevices.length} 条记录`);
|
||||
|
||||
allDevices.forEach((device, index) => {
|
||||
console.log(`\n设备${index + 1}:`);
|
||||
console.log('SN:', device.sn);
|
||||
console.log('电量:', device.battery);
|
||||
console.log('温度:', device.temperature);
|
||||
console.log('状态:', device.state);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testDatabase().catch(console.error);
|
||||
@@ -1,15 +0,0 @@
|
||||
# 简化的防火墙配置脚本
|
||||
Write-Host "正在配置Windows防火墙..." -ForegroundColor Green
|
||||
|
||||
# 添加前端端口规则
|
||||
netsh advfirewall firewall add rule name="Node.js Frontend Port 5300" dir=in action=allow protocol=TCP localport=5300
|
||||
Write-Host "前端端口5300规则已添加" -ForegroundColor Green
|
||||
|
||||
# 添加后端端口规则
|
||||
netsh advfirewall firewall add rule name="Node.js Backend Port 5350" dir=in action=allow protocol=TCP localport=5350
|
||||
Write-Host "后端端口5350规则已添加" -ForegroundColor Green
|
||||
|
||||
Write-Host "防火墙配置完成!" -ForegroundColor Green
|
||||
Write-Host "现在其他用户可以通过以下地址访问:" -ForegroundColor Yellow
|
||||
Write-Host "前端: http://172.28.112.1:5300" -ForegroundColor White
|
||||
Write-Host "后端: http://172.28.112.1:5350" -ForegroundColor White
|
||||
@@ -1,72 +0,0 @@
|
||||
// 简化的测试脚本
|
||||
console.log('开始测试预警检测逻辑...');
|
||||
|
||||
// 模拟前端判断函数
|
||||
function determineAlertType(record) {
|
||||
const alerts = []
|
||||
|
||||
// 检查电量预警
|
||||
if (record.battery !== undefined && record.battery !== null && record.battery < 20) {
|
||||
alerts.push('battery')
|
||||
}
|
||||
|
||||
// 检查脱落预警 (bandge_status为0)
|
||||
if (record.bandge_status !== undefined && record.bandge_status !== null && record.bandge_status === 0) {
|
||||
alerts.push('wear')
|
||||
}
|
||||
|
||||
// 检查离线预警 (is_connect为0)
|
||||
if (record.is_connect !== undefined && record.is_connect !== null && record.is_connect === 0) {
|
||||
alerts.push('offline')
|
||||
}
|
||||
|
||||
// 检查温度预警
|
||||
if (record.temperature !== undefined && record.temperature !== null) {
|
||||
if (record.temperature < 20) {
|
||||
alerts.push('temperature_low')
|
||||
} else if (record.temperature > 40) {
|
||||
alerts.push('temperature_high')
|
||||
}
|
||||
}
|
||||
|
||||
// 检查运动异常预警 (steps - y_steps为0)
|
||||
if (record.steps !== undefined && record.y_steps !== undefined &&
|
||||
record.steps !== null && record.y_steps !== null) {
|
||||
const movementDiff = record.steps - record.y_steps
|
||||
if (movementDiff === 0) {
|
||||
alerts.push('movement')
|
||||
}
|
||||
}
|
||||
|
||||
// 返回第一个预警类型,如果没有预警则返回null
|
||||
return alerts.length > 0 ? alerts[0] : null
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
const testCases = [
|
||||
{
|
||||
name: '正常设备',
|
||||
data: { battery: 85, temperature: 25, is_connect: 1, bandge_status: 1, steps: 1000, y_steps: 500 },
|
||||
expected: null
|
||||
},
|
||||
{
|
||||
name: '低电量预警',
|
||||
data: { battery: 15, temperature: 25, is_connect: 1, bandge_status: 1, steps: 1000, y_steps: 500 },
|
||||
expected: 'battery'
|
||||
},
|
||||
{
|
||||
name: '离线预警',
|
||||
data: { battery: 85, temperature: 25, is_connect: 0, bandge_status: 1, steps: 1000, y_steps: 500 },
|
||||
expected: 'offline'
|
||||
}
|
||||
];
|
||||
|
||||
// 运行测试
|
||||
testCases.forEach((testCase, index) => {
|
||||
const result = determineAlertType(testCase.data);
|
||||
const success = result === testCase.expected;
|
||||
console.log(`测试 ${index + 1}: ${testCase.name} - ${success ? '✅ 通过' : '❌ 失败'}`);
|
||||
console.log(` 预期: ${testCase.expected}, 实际: ${result}`);
|
||||
});
|
||||
|
||||
console.log('测试完成!');
|
||||
@@ -1,146 +0,0 @@
|
||||
/**
|
||||
* 启动服务器并测试API
|
||||
* @file start-and-test.js
|
||||
* @description 启动服务器并自动测试API接口
|
||||
*/
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const axios = require('axios');
|
||||
|
||||
let serverProcess = null;
|
||||
|
||||
// 启动服务器
|
||||
function startServer() {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log('🚀 启动服务器...');
|
||||
|
||||
serverProcess = spawn('node', ['server.js'], {
|
||||
stdio: 'pipe',
|
||||
cwd: __dirname
|
||||
});
|
||||
|
||||
serverProcess.stdout.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
console.log(output);
|
||||
|
||||
if (output.includes('服务器运行在端口') || output.includes('Server running on port')) {
|
||||
console.log('✅ 服务器启动成功');
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
serverProcess.stderr.on('data', (data) => {
|
||||
console.error('服务器错误:', data.toString());
|
||||
});
|
||||
|
||||
serverProcess.on('error', (error) => {
|
||||
console.error('启动服务器失败:', error);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
// 等待5秒让服务器完全启动
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
// 测试API
|
||||
async function testAPI() {
|
||||
console.log('\n🔍 测试API接口...');
|
||||
|
||||
const baseUrl = 'http://localhost:5350';
|
||||
|
||||
try {
|
||||
// 1. 测试根路径
|
||||
console.log('1. 测试根路径...');
|
||||
const rootResponse = await axios.get(`${baseUrl}/`);
|
||||
console.log('✅ 根路径正常:', rootResponse.data.message);
|
||||
|
||||
// 2. 测试API文档
|
||||
console.log('\n2. 测试API文档...');
|
||||
const docsResponse = await axios.get(`${baseUrl}/api-docs/`);
|
||||
console.log('✅ API文档页面正常');
|
||||
|
||||
// 3. 测试Swagger JSON
|
||||
console.log('\n3. 测试Swagger JSON...');
|
||||
const swaggerResponse = await axios.get(`${baseUrl}/api-docs/swagger.json`);
|
||||
console.log('✅ Swagger JSON正常');
|
||||
|
||||
// 4. 检查API路径
|
||||
console.log('\n4. 检查API路径...');
|
||||
const paths = Object.keys(swaggerResponse.data.paths || {});
|
||||
const smartAlertPaths = paths.filter(path => path.includes('/smart-alerts/public'));
|
||||
|
||||
console.log(`📋 找到 ${smartAlertPaths.length} 个智能预警API路径:`);
|
||||
smartAlertPaths.forEach(path => {
|
||||
console.log(` - ${path}`);
|
||||
});
|
||||
|
||||
if (smartAlertPaths.length > 0) {
|
||||
console.log('\n🎉 API文档配置成功!');
|
||||
console.log(`📖 请访问: ${baseUrl}/api-docs`);
|
||||
} else {
|
||||
console.log('\n❌ 未找到智能预警API路径');
|
||||
}
|
||||
|
||||
// 5. 测试实际API调用
|
||||
console.log('\n5. 测试实际API调用...');
|
||||
try {
|
||||
const eartagStatsResponse = await axios.get(`${baseUrl}/api/smart-alerts/public/eartag/stats`);
|
||||
console.log('✅ 智能耳标预警统计API正常');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 智能耳标预警统计API调用失败:', error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
const collarStatsResponse = await axios.get(`${baseUrl}/api/smart-alerts/public/collar/stats`);
|
||||
console.log('✅ 智能项圈预警统计API正常');
|
||||
} catch (error) {
|
||||
console.log('⚠️ 智能项圈预警统计API调用失败:', error.message);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ API测试失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 停止服务器
|
||||
function stopServer() {
|
||||
if (serverProcess) {
|
||||
console.log('\n🛑 停止服务器...');
|
||||
serverProcess.kill();
|
||||
serverProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
try {
|
||||
await startServer();
|
||||
await testAPI();
|
||||
|
||||
console.log('\n✅ 测试完成!');
|
||||
console.log('📖 API文档地址: http://localhost:5350/api-docs');
|
||||
console.log('🔗 基础API地址: http://localhost:5350/api/smart-alerts/public');
|
||||
console.log('\n按 Ctrl+C 停止服务器');
|
||||
|
||||
// 保持服务器运行
|
||||
process.on('SIGINT', () => {
|
||||
stopServer();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 启动失败:', error.message);
|
||||
stopServer();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = { startServer, testAPI, stopServer };
|
||||
@@ -1,46 +0,0 @@
|
||||
@echo off
|
||||
echo 正在启动ngrok外网访问服务...
|
||||
echo.
|
||||
|
||||
REM 检查ngrok是否存在
|
||||
if not exist "ngrok.exe" (
|
||||
echo 错误:ngrok.exe 不存在,请先下载ngrok
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo 请确保您已经:
|
||||
echo 1. 注册了ngrok账号
|
||||
echo 2. 获取了authtoken
|
||||
echo 3. 运行了:ngrok authtoken YOUR_AUTH_TOKEN
|
||||
echo.
|
||||
|
||||
echo 选择要启动的服务:
|
||||
echo 1. 前端服务 (端口5300)
|
||||
echo 2. 后端服务 (端口5350)
|
||||
echo 3. 同时启动前端和后端
|
||||
echo.
|
||||
|
||||
set /p choice="请输入选择 (1-3): "
|
||||
|
||||
if "%choice%"=="1" (
|
||||
echo 启动前端服务穿透...
|
||||
.\ngrok.exe http 5300
|
||||
) else if "%choice%"=="2" (
|
||||
echo 启动后端服务穿透...
|
||||
.\ngrok.exe http 5350
|
||||
) else if "%choice%"=="3" (
|
||||
echo 启动前端服务穿透...
|
||||
start "Frontend" .\ngrok.exe http 5300
|
||||
timeout /t 3 /nobreak >nul
|
||||
echo 启动后端服务穿透...
|
||||
start "Backend" .\ngrok.exe http 5350
|
||||
echo.
|
||||
echo 两个服务都已启动,请查看各自的窗口获取访问地址
|
||||
) else (
|
||||
echo 无效选择,请重新运行脚本
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 按任意键退出...
|
||||
pause >nul
|
||||
@@ -1,68 +0,0 @@
|
||||
# ngrok外网访问启动脚本
|
||||
|
||||
Write-Host "🚀 正在启动ngrok外网访问服务..." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# 检查ngrok是否存在
|
||||
if (-not (Test-Path "ngrok.exe")) {
|
||||
Write-Host "❌ 错误:ngrok.exe 不存在,请先下载ngrok" -ForegroundColor Red
|
||||
Read-Host "按任意键退出"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "请确保您已经:" -ForegroundColor Yellow
|
||||
Write-Host "1. 注册了ngrok账号" -ForegroundColor White
|
||||
Write-Host "2. 获取了authtoken" -ForegroundColor White
|
||||
Write-Host "3. 运行了:ngrok authtoken YOUR_AUTH_TOKEN" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "选择要启动的服务:" -ForegroundColor Cyan
|
||||
Write-Host "1. 前端服务 (端口5300)" -ForegroundColor White
|
||||
Write-Host "2. 后端服务 (端口5350)" -ForegroundColor White
|
||||
Write-Host "3. 同时启动前端和后端" -ForegroundColor White
|
||||
Write-Host "4. 检查当前服务状态" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
$choice = Read-Host "请输入选择 (1-4)"
|
||||
|
||||
switch ($choice) {
|
||||
"1" {
|
||||
Write-Host "启动前端服务穿透..." -ForegroundColor Green
|
||||
Write-Host "访问地址将在新窗口中显示" -ForegroundColor Yellow
|
||||
Start-Process -FilePath ".\ngrok.exe" -ArgumentList "http", "5300"
|
||||
}
|
||||
"2" {
|
||||
Write-Host "启动后端服务穿透..." -ForegroundColor Green
|
||||
Write-Host "访问地址将在新窗口中显示" -ForegroundColor Yellow
|
||||
Start-Process -FilePath ".\ngrok.exe" -ArgumentList "http", "5350"
|
||||
}
|
||||
"3" {
|
||||
Write-Host "启动前端服务穿透..." -ForegroundColor Green
|
||||
Start-Process -FilePath ".\ngrok.exe" -ArgumentList "http", "5300"
|
||||
Start-Sleep -Seconds 3
|
||||
Write-Host "启动后端服务穿透..." -ForegroundColor Green
|
||||
Start-Process -FilePath ".\ngrok.exe" -ArgumentList "http", "5350"
|
||||
Write-Host ""
|
||||
Write-Host "✅ 两个服务都已启动,请查看各自的窗口获取访问地址" -ForegroundColor Green
|
||||
}
|
||||
"4" {
|
||||
Write-Host "检查当前服务状态..." -ForegroundColor Cyan
|
||||
Write-Host "前端服务 (端口5300):" -ForegroundColor Yellow
|
||||
netstat -ano | findstr :5300
|
||||
Write-Host ""
|
||||
Write-Host "后端服务 (端口5350):" -ForegroundColor Yellow
|
||||
netstat -ano | findstr :5350
|
||||
}
|
||||
default {
|
||||
Write-Host "❌ 无效选择,请重新运行脚本" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "💡 提示:" -ForegroundColor Cyan
|
||||
Write-Host "- 前端访问地址格式:https://xxxxx.ngrok.io" -ForegroundColor White
|
||||
Write-Host "- 后端访问地址格式:https://yyyyy.ngrok.io" -ForegroundColor White
|
||||
Write-Host "- API文档地址:https://yyyyy.ngrok.io/api-docs" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
Read-Host "按任意键退出"
|
||||
@@ -1,45 +0,0 @@
|
||||
@echo off
|
||||
echo ========================================
|
||||
echo 无密码外网访问启动脚本
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
echo 选择要启动的服务:
|
||||
echo 1. 后端服务 (端口5350)
|
||||
echo 2. 前端服务 (端口5300)
|
||||
echo 3. 同时启动两个服务
|
||||
echo.
|
||||
|
||||
set /p choice="请输入选择 (1-3): "
|
||||
|
||||
if "%choice%"=="1" (
|
||||
echo 启动后端服务穿透...
|
||||
start "Backend Tunnel" lt --port 5350
|
||||
) else if "%choice%"=="2" (
|
||||
echo 启动前端服务穿透...
|
||||
start "Frontend Tunnel" lt --port 5300
|
||||
) else if "%choice%"=="3" (
|
||||
echo 启动后端服务穿透...
|
||||
start "Backend Tunnel" lt --port 5350
|
||||
timeout /t 2 /nobreak >nul
|
||||
echo 启动前端服务穿透...
|
||||
start "Frontend Tunnel" lt --port 5300
|
||||
) else (
|
||||
echo 无效选择
|
||||
goto end
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo 隧道已启动!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 请查看新打开的窗口获取访问地址
|
||||
echo 地址格式: https://随机字符串.loca.lt
|
||||
echo.
|
||||
echo 这些地址没有密码保护,可以直接访问
|
||||
echo.
|
||||
|
||||
:end
|
||||
echo 按任意键退出...
|
||||
pause >nul
|
||||
21
backend/start.bat
Normal file
21
backend/start.bat
Normal file
@@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
REM 宁夏智慧养殖监管平台后端 - 快速启动脚本
|
||||
REM 使用 PowerShell 脚本启动服务
|
||||
|
||||
echo ========================================
|
||||
echo 宁夏智慧养殖监管平台后端服务
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM 检查是否在正确的目录
|
||||
if not exist "server.js" (
|
||||
echo 错误: 请在 backend 目录下运行此脚本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 调用 PowerShell 脚本启动服务
|
||||
powershell -ExecutionPolicy Bypass -File "node_manager.ps1" start
|
||||
|
||||
pause
|
||||
|
||||
21
backend/status.bat
Normal file
21
backend/status.bat
Normal file
@@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
REM 宁夏智慧养殖监管平台后端 - 状态查看脚本
|
||||
REM 使用 PowerShell 脚本查看服务状态
|
||||
|
||||
echo ========================================
|
||||
echo 宁夏智慧养殖监管平台后端服务
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM 检查是否在正确的目录
|
||||
if not exist "server.js" (
|
||||
echo 错误: 请在 backend 目录下运行此脚本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 调用 PowerShell 脚本查看状态
|
||||
powershell -ExecutionPolicy Bypass -File "node_manager.ps1" status
|
||||
|
||||
pause
|
||||
|
||||
21
backend/stop.bat
Normal file
21
backend/stop.bat
Normal file
@@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
REM 宁夏智慧养殖监管平台后端 - 快速停止脚本
|
||||
REM 使用 PowerShell 脚本停止服务
|
||||
|
||||
echo ========================================
|
||||
echo 宁夏智慧养殖监管平台后端服务
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM 检查是否在正确的目录
|
||||
if not exist "server.js" (
|
||||
echo 错误: 请在 backend 目录下运行此脚本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 调用 PowerShell 脚本停止服务
|
||||
powershell -ExecutionPolicy Bypass -File "node_manager.ps1" stop
|
||||
|
||||
pause
|
||||
|
||||
@@ -1,773 +0,0 @@
|
||||
/**
|
||||
* 预警管理模块 Swagger 文档
|
||||
* @file swagger-alerts.js
|
||||
*/
|
||||
|
||||
const alertsPaths = {
|
||||
// 获取所有预警
|
||||
'/alerts': {
|
||||
get: {
|
||||
tags: ['预警管理'],
|
||||
summary: '获取预警列表',
|
||||
description: '分页获取系统中的所有预警信息',
|
||||
security: [{ bearerAuth: [] }],
|
||||
parameters: [
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 1 },
|
||||
description: '页码'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '每页数量'
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词(预警标题、描述)'
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['health', 'environment', 'device', 'security', 'breeding'] },
|
||||
description: '预警类型筛选'
|
||||
},
|
||||
{
|
||||
name: 'level',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['low', 'medium', 'high', 'critical'] },
|
||||
description: '预警级别筛选'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['pending', 'processing', 'resolved', 'ignored'] },
|
||||
description: '预警状态筛选'
|
||||
},
|
||||
{
|
||||
name: 'farmId',
|
||||
in: 'query',
|
||||
schema: { type: 'integer' },
|
||||
description: '养殖场ID筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/Alert' }
|
||||
},
|
||||
pagination: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'integer' },
|
||||
limit: { type: 'integer' },
|
||||
total: { type: 'integer' },
|
||||
totalPages: { type: 'integer' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'401': {
|
||||
description: '未授权',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
post: {
|
||||
tags: ['预警管理'],
|
||||
summary: '创建新预警',
|
||||
description: '创建新的预警记录',
|
||||
security: [{ bearerAuth: [] }],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['title', 'type', 'level', 'farmId'],
|
||||
properties: {
|
||||
title: { type: 'string', description: '预警标题' },
|
||||
description: { type: 'string', description: '预警描述' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['health', 'environment', 'device', 'security', 'breeding'],
|
||||
description: '预警类型:health-健康,environment-环境,device-设备,security-安全,breeding-繁殖'
|
||||
},
|
||||
level: {
|
||||
type: 'string',
|
||||
enum: ['low', 'medium', 'high', 'critical'],
|
||||
description: '预警级别:low-低,medium-中,high-高,critical-紧急'
|
||||
},
|
||||
farmId: { type: 'integer', description: '养殖场ID' },
|
||||
animalId: { type: 'integer', description: '动物ID(可选)' },
|
||||
deviceId: { type: 'integer', description: '设备ID(可选)' },
|
||||
threshold: { type: 'number', description: '阈值' },
|
||||
currentValue: { type: 'number', description: '当前值' },
|
||||
location: { type: 'string', description: '位置信息' },
|
||||
metadata: { type: 'object', description: '额外元数据' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'201': {
|
||||
description: '创建成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '预警创建成功' },
|
||||
data: { $ref: '#/components/schemas/Alert' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取公共预警数据
|
||||
'/alerts/public': {
|
||||
get: {
|
||||
tags: ['预警管理'],
|
||||
summary: '获取公共预警数据',
|
||||
description: '获取可公开访问的预警基本信息',
|
||||
security: [], // 公共接口不需要认证
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
title: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
level: { type: 'string' },
|
||||
status: { type: 'string' },
|
||||
createdAt: { type: 'string', format: 'date-time' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 搜索预警
|
||||
'/alerts/search': {
|
||||
get: {
|
||||
tags: ['预警管理'],
|
||||
summary: '搜索预警',
|
||||
description: '根据养殖场名称等关键词搜索预警',
|
||||
security: [{ bearerAuth: [] }],
|
||||
parameters: [
|
||||
{
|
||||
name: 'q',
|
||||
in: 'query',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '返回结果数量限制'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '搜索成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/Alert' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取预警详情
|
||||
'/alerts/{id}': {
|
||||
get: {
|
||||
tags: ['预警管理'],
|
||||
summary: '获取预警详情',
|
||||
description: '根据预警ID获取详细信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '预警ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: { $ref: '#/components/schemas/Alert' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '预警不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
put: {
|
||||
tags: ['预警管理'],
|
||||
summary: '更新预警信息',
|
||||
description: '更新指定预警的信息',
|
||||
security: [{ bearerAuth: [] }],
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '预警ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string', description: '预警标题' },
|
||||
description: { type: 'string', description: '预警描述' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['health', 'environment', 'device', 'security', 'breeding'],
|
||||
description: '预警类型'
|
||||
},
|
||||
level: {
|
||||
type: 'string',
|
||||
enum: ['low', 'medium', 'high', 'critical'],
|
||||
description: '预警级别'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['pending', 'processing', 'resolved', 'ignored'],
|
||||
description: '预警状态'
|
||||
},
|
||||
threshold: { type: 'number', description: '阈值' },
|
||||
currentValue: { type: 'number', description: '当前值' },
|
||||
location: { type: 'string', description: '位置信息' },
|
||||
handlerNotes: { type: 'string', description: '处理备注' },
|
||||
metadata: { type: 'object', description: '额外元数据' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '更新成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '预警信息更新成功' },
|
||||
data: { $ref: '#/components/schemas/Alert' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '预警不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
tags: ['预警管理'],
|
||||
summary: '删除预警',
|
||||
description: '删除指定预警(软删除)',
|
||||
security: [{ bearerAuth: [] }],
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '预警ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '删除成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '预警删除成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '预警不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 更新预警状态
|
||||
'/alerts/{id}/status': {
|
||||
put: {
|
||||
tags: ['预警管理'],
|
||||
summary: '更新预警状态',
|
||||
description: '更新指定预警的处理状态',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '预警ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['status'],
|
||||
properties: {
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['pending', 'processing', 'resolved', 'ignored'],
|
||||
description: '预警状态'
|
||||
},
|
||||
handlerNotes: { type: 'string', description: '处理备注' },
|
||||
handlerId: { type: 'integer', description: '处理人ID' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '状态更新成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '预警状态更新成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '预警不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取预警统计信息
|
||||
'/alerts/stats/type': {
|
||||
get: {
|
||||
tags: ['预警管理'],
|
||||
summary: '获取按类型统计的预警数据',
|
||||
description: '获取各种预警类型的统计信息',
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
health: { type: 'integer', description: '健康预警数量' },
|
||||
environment: { type: 'integer', description: '环境预警数量' },
|
||||
device: { type: 'integer', description: '设备预警数量' },
|
||||
security: { type: 'integer', description: '安全预警数量' },
|
||||
breeding: { type: 'integer', description: '繁殖预警数量' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'/alerts/stats/level': {
|
||||
get: {
|
||||
tags: ['预警管理'],
|
||||
summary: '获取按级别统计的预警数据',
|
||||
description: '获取各种预警级别的统计信息',
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
low: { type: 'integer', description: '低级预警数量' },
|
||||
medium: { type: 'integer', description: '中级预警数量' },
|
||||
high: { type: 'integer', description: '高级预警数量' },
|
||||
critical: { type: 'integer', description: '紧急预警数量' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'/alerts/stats/status': {
|
||||
get: {
|
||||
tags: ['预警管理'],
|
||||
summary: '获取按状态统计的预警数据',
|
||||
description: '获取各种预警状态的统计信息',
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pending: { type: 'integer', description: '待处理预警数量' },
|
||||
processing: { type: 'integer', description: '处理中预警数量' },
|
||||
resolved: { type: 'integer', description: '已解决预警数量' },
|
||||
ignored: { type: 'integer', description: '已忽略预警数量' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 批量操作预警
|
||||
'/alerts/batch': {
|
||||
post: {
|
||||
tags: ['预警管理'],
|
||||
summary: '批量操作预警',
|
||||
description: '批量更新预警状态或删除预警',
|
||||
security: [{ bearerAuth: [] }],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['ids', 'action'],
|
||||
properties: {
|
||||
ids: {
|
||||
type: 'array',
|
||||
items: { type: 'integer' },
|
||||
description: '预警ID列表'
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['resolve', 'ignore', 'delete', 'reopen'],
|
||||
description: '操作类型:resolve-解决,ignore-忽略,delete-删除,reopen-重新打开'
|
||||
},
|
||||
handlerNotes: { type: 'string', description: '处理备注' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '批量操作成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '批量操作完成' },
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
successCount: { type: 'integer', description: '成功处理数量' },
|
||||
failedCount: { type: 'integer', description: '失败数量' },
|
||||
failedIds: {
|
||||
type: 'array',
|
||||
items: { type: 'integer' },
|
||||
description: '失败的预警ID列表'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 数据模型定义
|
||||
const alertSchemas = {
|
||||
Alert: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', description: '预警ID' },
|
||||
title: { type: 'string', description: '预警标题' },
|
||||
description: { type: 'string', description: '预警描述' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['health', 'environment', 'device', 'security', 'breeding'],
|
||||
description: '预警类型:health-健康,environment-环境,device-设备,security-安全,breeding-繁殖'
|
||||
},
|
||||
level: {
|
||||
type: 'string',
|
||||
enum: ['low', 'medium', 'high', 'critical'],
|
||||
description: '预警级别:low-低,medium-中,high-高,critical-紧急'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['pending', 'processing', 'resolved', 'ignored'],
|
||||
description: '预警状态:pending-待处理,processing-处理中,resolved-已解决,ignored-已忽略'
|
||||
},
|
||||
farmId: { type: 'integer', description: '养殖场ID' },
|
||||
farmName: { type: 'string', description: '养殖场名称' },
|
||||
animalId: { type: 'integer', description: '动物ID(可选)' },
|
||||
animalEarNumber: { type: 'string', description: '动物耳标号(可选)' },
|
||||
deviceId: { type: 'integer', description: '设备ID(可选)' },
|
||||
deviceName: { type: 'string', description: '设备名称(可选)' },
|
||||
threshold: { type: 'number', description: '阈值' },
|
||||
currentValue: { type: 'number', description: '当前值' },
|
||||
location: { type: 'string', description: '位置信息' },
|
||||
handlerId: { type: 'integer', description: '处理人ID' },
|
||||
handlerName: { type: 'string', description: '处理人姓名' },
|
||||
handlerNotes: { type: 'string', description: '处理备注' },
|
||||
handledAt: { type: 'string', format: 'date-time', description: '处理时间' },
|
||||
metadata: { type: 'object', description: '额外元数据' },
|
||||
createdAt: { type: 'string', format: 'date-time', description: '创建时间' },
|
||||
updatedAt: { type: 'string', format: 'date-time', description: '更新时间' }
|
||||
}
|
||||
},
|
||||
|
||||
AlertInput: {
|
||||
type: 'object',
|
||||
required: ['title', 'type', 'level', 'farmId'],
|
||||
properties: {
|
||||
title: { type: 'string', description: '预警标题' },
|
||||
description: { type: 'string', description: '预警描述' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['health', 'environment', 'device', 'security', 'breeding'],
|
||||
description: '预警类型'
|
||||
},
|
||||
level: {
|
||||
type: 'string',
|
||||
enum: ['low', 'medium', 'high', 'critical'],
|
||||
description: '预警级别'
|
||||
},
|
||||
farmId: { type: 'integer', description: '养殖场ID' },
|
||||
animalId: { type: 'integer', description: '动物ID(可选)' },
|
||||
deviceId: { type: 'integer', description: '设备ID(可选)' },
|
||||
threshold: { type: 'number', description: '阈值' },
|
||||
currentValue: { type: 'number', description: '当前值' },
|
||||
location: { type: 'string', description: '位置信息' },
|
||||
metadata: { type: 'object', description: '额外元数据' }
|
||||
}
|
||||
},
|
||||
|
||||
AlertUpdate: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string', description: '预警标题' },
|
||||
description: { type: 'string', description: '预警描述' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['health', 'environment', 'device', 'security', 'breeding'],
|
||||
description: '预警类型'
|
||||
},
|
||||
level: {
|
||||
type: 'string',
|
||||
enum: ['low', 'medium', 'high', 'critical'],
|
||||
description: '预警级别'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['pending', 'processing', 'resolved', 'ignored'],
|
||||
description: '预警状态'
|
||||
},
|
||||
threshold: { type: 'number', description: '阈值' },
|
||||
currentValue: { type: 'number', description: '当前值' },
|
||||
location: { type: 'string', description: '位置信息' },
|
||||
handlerNotes: { type: 'string', description: '处理备注' },
|
||||
metadata: { type: 'object', description: '额外元数据' }
|
||||
}
|
||||
},
|
||||
|
||||
AlertStats: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
totalAlerts: { type: 'integer', description: '总预警数' },
|
||||
pendingAlerts: { type: 'integer', description: '待处理预警数' },
|
||||
resolvedAlerts: { type: 'integer', description: '已解决预警数' },
|
||||
criticalAlerts: { type: 'integer', description: '紧急预警数' },
|
||||
todayAlerts: { type: 'integer', description: '今日新增预警数' },
|
||||
byType: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
health: { type: 'integer' },
|
||||
environment: { type: 'integer' },
|
||||
device: { type: 'integer' },
|
||||
security: { type: 'integer' },
|
||||
breeding: { type: 'integer' }
|
||||
}
|
||||
},
|
||||
byLevel: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
low: { type: 'integer' },
|
||||
medium: { type: 'integer' },
|
||||
high: { type: 'integer' },
|
||||
critical: { type: 'integer' }
|
||||
}
|
||||
},
|
||||
byStatus: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pending: { type: 'integer' },
|
||||
processing: { type: 'integer' },
|
||||
resolved: { type: 'integer' },
|
||||
ignored: { type: 'integer' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { alertsPaths, alertSchemas };
|
||||
@@ -1,706 +0,0 @@
|
||||
/**
|
||||
* 动物管理模块 Swagger 文档
|
||||
* @file swagger-animals.js
|
||||
*/
|
||||
|
||||
const animalsPaths = {
|
||||
// 获取所有动物列表
|
||||
'/animals': {
|
||||
get: {
|
||||
tags: ['动物管理'],
|
||||
summary: '获取动物列表',
|
||||
description: '分页获取系统中的所有动物信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 1 },
|
||||
description: '页码'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '每页数量'
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词(项圈编号、耳标号)'
|
||||
},
|
||||
{
|
||||
name: 'farmId',
|
||||
in: 'query',
|
||||
schema: { type: 'integer' },
|
||||
description: '养殖场ID筛选'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['healthy', 'sick', 'quarantine', 'sold'] },
|
||||
description: '动物状态筛选'
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', enum: [1, 2, 3, 4, 5, 6] },
|
||||
description: '动物类别筛选:1-犊牛,2-育成母牛,3-架子牛,4-青年牛,5-基础母牛,6-育肥牛'
|
||||
},
|
||||
{
|
||||
name: 'sex',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', enum: [1, 2] },
|
||||
description: '性别筛选:1-公牛,2-母牛'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/Animal' }
|
||||
},
|
||||
pagination: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'integer' },
|
||||
limit: { type: 'integer' },
|
||||
total: { type: 'integer' },
|
||||
totalPages: { type: 'integer' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
post: {
|
||||
tags: ['动物管理'],
|
||||
summary: '创建新动物档案',
|
||||
description: '创建新的动物档案记录',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['earNumber', 'sex', 'varieties', 'cate', 'farmId'],
|
||||
properties: {
|
||||
earNumber: { type: 'string', description: '耳标号' },
|
||||
sex: { type: 'integer', enum: [1, 2], description: '性别:1-公牛,2-母牛' },
|
||||
strain: { type: 'string', description: '品系' },
|
||||
varieties: { type: 'integer', description: '品种ID' },
|
||||
cate: {
|
||||
type: 'integer',
|
||||
enum: [1, 2, 3, 4, 5, 6],
|
||||
description: '类别:1-犊牛,2-育成母牛,3-架子牛,4-青年牛,5-基础母牛,6-育肥牛'
|
||||
},
|
||||
birthWeight: { type: 'number', description: '出生重量(kg)' },
|
||||
birthday: { type: 'integer', description: '出生日期(时间戳)' },
|
||||
farmId: { type: 'integer', description: '养殖场ID' },
|
||||
penId: { type: 'integer', description: '栏舍ID' },
|
||||
batchId: { type: 'integer', description: '批次ID' },
|
||||
intoTime: { type: 'integer', description: '入场时间(时间戳)' },
|
||||
parity: { type: 'integer', description: '胎次' },
|
||||
source: {
|
||||
type: 'integer',
|
||||
enum: [1, 2, 3, 4, 5],
|
||||
description: '来源:1-合作社,2-农户,3-养殖场,4-进口,5-自繁'
|
||||
},
|
||||
sourceDay: { type: 'integer', description: '来源日龄' },
|
||||
sourceWeight: { type: 'number', description: '来源重量(kg)' },
|
||||
weight: { type: 'number', description: '当前重量(kg)' },
|
||||
algebra: { type: 'string', description: '代数' },
|
||||
colour: { type: 'string', description: '毛色' },
|
||||
descent: { type: 'string', description: '血统' },
|
||||
imgs: { type: 'string', description: '图片URL(多个用逗号分隔)' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'201': {
|
||||
description: '创建成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '动物档案创建成功' },
|
||||
data: { $ref: '#/components/schemas/Animal' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 公共动物数据
|
||||
'/animals/public': {
|
||||
get: {
|
||||
tags: ['动物管理'],
|
||||
summary: '获取公共动物数据',
|
||||
description: '获取可公开访问的动物基本信息',
|
||||
security: [], // 公共接口不需要认证
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
earNumber: { type: 'string' },
|
||||
sex: { type: 'integer' },
|
||||
varieties: { type: 'integer' },
|
||||
cate: { type: 'integer' },
|
||||
weight: { type: 'number' },
|
||||
isOut: { type: 'integer' }
|
||||
}
|
||||
}
|
||||
},
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取动物绑定信息
|
||||
'/animals/binding-info/{collarNumber}': {
|
||||
get: {
|
||||
tags: ['动物管理'],
|
||||
summary: '获取动物绑定信息',
|
||||
description: '根据项圈编号获取动物的详细绑定信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'collarNumber',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
description: '项圈编号'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string' },
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
basicInfo: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
collarNumber: { type: 'string', description: '项圈编号' },
|
||||
category: { type: 'string', description: '动物类别' },
|
||||
calvingCount: { type: 'integer', description: '产犊次数' },
|
||||
earTag: { type: 'string', description: '耳标号' },
|
||||
animalType: { type: 'string', description: '动物类型' },
|
||||
breed: { type: 'string', description: '品种' },
|
||||
sourceType: { type: 'string', description: '来源类型' }
|
||||
}
|
||||
},
|
||||
birthInfo: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
birthDate: { type: 'string', description: '出生日期' },
|
||||
birthWeight: { type: 'string', description: '出生重量' },
|
||||
weaningWeight: { type: 'string', description: '断奶重量' },
|
||||
entryDate: { type: 'string', description: '入场日期' },
|
||||
weaningAge: { type: 'integer', description: '断奶日龄' }
|
||||
}
|
||||
},
|
||||
pedigreeInfo: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fatherId: { type: 'string', description: '父亲ID' },
|
||||
motherId: { type: 'string', description: '母亲ID' },
|
||||
grandfatherId: { type: 'string', description: '祖父ID' },
|
||||
grandmotherId: { type: 'string', description: '祖母ID' },
|
||||
bloodline: { type: 'string', description: '血统' },
|
||||
generation: { type: 'string', description: '世代' }
|
||||
}
|
||||
},
|
||||
insuranceInfo: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
policyNumber: { type: 'string', description: '保单号' },
|
||||
insuranceCompany: { type: 'string', description: '保险公司' },
|
||||
coverageAmount: { type: 'string', description: '保额' },
|
||||
premium: { type: 'string', description: '保费' },
|
||||
startDate: { type: 'string', description: '开始日期' },
|
||||
endDate: { type: 'string', description: '结束日期' },
|
||||
status: { type: 'string', description: '保险状态' }
|
||||
}
|
||||
},
|
||||
loanInfo: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
loanNumber: { type: 'string', description: '贷款编号' },
|
||||
bankName: { type: 'string', description: '银行名称' },
|
||||
loanAmount: { type: 'string', description: '贷款金额' },
|
||||
interestRate: { type: 'string', description: '利率' },
|
||||
loanDate: { type: 'string', description: '放款日期' },
|
||||
maturityDate: { type: 'string', description: '到期日期' },
|
||||
status: { type: 'string', description: '贷款状态' }
|
||||
}
|
||||
},
|
||||
deviceInfo: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
deviceId: { type: 'integer', description: '设备ID' },
|
||||
batteryLevel: { type: 'number', description: '电池电量' },
|
||||
temperature: { type: 'number', description: '温度' },
|
||||
status: { type: 'string', description: '设备状态' },
|
||||
lastUpdate: { type: 'string', description: '最后更新时间' },
|
||||
location: { type: 'string', description: '位置信息' }
|
||||
}
|
||||
},
|
||||
farmInfo: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
farmName: { type: 'string', description: '农场名称' },
|
||||
farmAddress: { type: 'string', description: '农场地址' },
|
||||
penName: { type: 'string', description: '栏舍名称' },
|
||||
batchName: { type: 'string', description: '批次名称' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '未找到指定的动物或设备',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取指定动物详情
|
||||
'/animals/{id}': {
|
||||
get: {
|
||||
tags: ['动物管理'],
|
||||
summary: '获取动物详情',
|
||||
description: '根据动物ID获取详细信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '动物ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: { $ref: '#/components/schemas/Animal' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '动物不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
put: {
|
||||
tags: ['动物管理'],
|
||||
summary: '更新动物信息',
|
||||
description: '更新指定动物的档案信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '动物ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
earNumber: { type: 'string', description: '耳标号' },
|
||||
sex: { type: 'integer', enum: [1, 2], description: '性别' },
|
||||
strain: { type: 'string', description: '品系' },
|
||||
varieties: { type: 'integer', description: '品种ID' },
|
||||
cate: { type: 'integer', enum: [1, 2, 3, 4, 5, 6], description: '类别' },
|
||||
weight: { type: 'number', description: '当前重量(kg)' },
|
||||
penId: { type: 'integer', description: '栏舍ID' },
|
||||
batchId: { type: 'integer', description: '批次ID' },
|
||||
event: { type: 'string', description: '事件记录' },
|
||||
eventTime: { type: 'integer', description: '事件时间(时间戳)' },
|
||||
isVaccin: { type: 'integer', enum: [0, 1], description: '是否疫苗' },
|
||||
isInsemination: { type: 'integer', enum: [0, 1], description: '是否配种' },
|
||||
isInsure: { type: 'integer', enum: [0, 1], description: '是否保险' },
|
||||
isMortgage: { type: 'integer', enum: [0, 1], description: '是否抵押' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '更新成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '动物信息更新成功' },
|
||||
data: { $ref: '#/components/schemas/Animal' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '动物不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
tags: ['动物管理'],
|
||||
summary: '删除动物档案',
|
||||
description: '删除指定动物档案(软删除)',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '动物ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '删除成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '动物档案删除成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '动物不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 动物健康记录
|
||||
'/animals/{id}/health': {
|
||||
get: {
|
||||
tags: ['动物管理'],
|
||||
summary: '获取动物健康记录',
|
||||
description: '获取指定动物的健康记录',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '动物ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
animalId: { type: 'integer' },
|
||||
checkDate: { type: 'string', format: 'date' },
|
||||
temperature: { type: 'number' },
|
||||
weight: { type: 'number' },
|
||||
healthStatus: { type: 'string' },
|
||||
symptoms: { type: 'string' },
|
||||
treatment: { type: 'string' },
|
||||
veterinarian: { type: 'string' },
|
||||
notes: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
post: {
|
||||
tags: ['动物管理'],
|
||||
summary: '添加动物健康记录',
|
||||
description: '为指定动物添加健康检查记录',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '动物ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['checkDate', 'healthStatus'],
|
||||
properties: {
|
||||
checkDate: { type: 'string', format: 'date', description: '检查日期' },
|
||||
temperature: { type: 'number', description: '体温' },
|
||||
weight: { type: 'number', description: '体重' },
|
||||
healthStatus: {
|
||||
type: 'string',
|
||||
enum: ['healthy', 'sick', 'recovering', 'quarantine'],
|
||||
description: '健康状态'
|
||||
},
|
||||
symptoms: { type: 'string', description: '症状描述' },
|
||||
treatment: { type: 'string', description: '治疗方案' },
|
||||
veterinarian: { type: 'string', description: '兽医姓名' },
|
||||
notes: { type: 'string', description: '备注' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'201': {
|
||||
description: '添加成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '健康记录添加成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 动物繁殖记录
|
||||
'/animals/{id}/breeding': {
|
||||
get: {
|
||||
tags: ['动物管理'],
|
||||
summary: '获取动物繁殖记录',
|
||||
description: '获取指定动物的繁殖记录',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '动物ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
animalId: { type: 'integer' },
|
||||
breedingDate: { type: 'string', format: 'date' },
|
||||
matingType: { type: 'string', enum: ['natural', 'artificial'] },
|
||||
sireId: { type: 'integer' },
|
||||
expectedCalvingDate: { type: 'string', format: 'date' },
|
||||
actualCalvingDate: { type: 'string', format: 'date' },
|
||||
calvingResult: { type: 'string' },
|
||||
offspringCount: { type: 'integer' },
|
||||
notes: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 动物数据模型
|
||||
const animalSchemas = {
|
||||
Animal: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', description: '动物ID' },
|
||||
orgId: { type: 'integer', description: '组织ID' },
|
||||
earNumber: { type: 'string', description: '耳标号' },
|
||||
sex: {
|
||||
type: 'integer',
|
||||
enum: [1, 2],
|
||||
description: '性别:1-公牛,2-母牛'
|
||||
},
|
||||
strain: { type: 'string', description: '品系' },
|
||||
varieties: { type: 'integer', description: '品种ID' },
|
||||
cate: {
|
||||
type: 'integer',
|
||||
enum: [1, 2, 3, 4, 5, 6],
|
||||
description: '类别:1-犊牛,2-育成母牛,3-架子牛,4-青年牛,5-基础母牛,6-育肥牛'
|
||||
},
|
||||
birthWeight: { type: 'number', description: '出生重量(kg)' },
|
||||
birthday: { type: 'integer', description: '出生日期(时间戳)' },
|
||||
penId: { type: 'integer', description: '栏舍ID' },
|
||||
intoTime: { type: 'integer', description: '入场时间(时间戳)' },
|
||||
parity: { type: 'integer', description: '胎次' },
|
||||
source: {
|
||||
type: 'integer',
|
||||
enum: [1, 2, 3, 4, 5],
|
||||
description: '来源:1-合作社,2-农户,3-养殖场,4-进口,5-自繁'
|
||||
},
|
||||
sourceDay: { type: 'integer', description: '来源日龄' },
|
||||
sourceWeight: { type: 'number', description: '来源重量(kg)' },
|
||||
weight: { type: 'number', description: '当前重量(kg)' },
|
||||
event: { type: 'string', description: '事件记录' },
|
||||
eventTime: { type: 'integer', description: '事件时间(时间戳)' },
|
||||
lactationDay: { type: 'integer', description: '泌乳天数' },
|
||||
semenNum: { type: 'string', description: '精液编号' },
|
||||
isWear: { type: 'integer', enum: [0, 1], description: '是否佩戴设备' },
|
||||
batchId: { type: 'integer', description: '批次ID' },
|
||||
imgs: { type: 'string', description: '图片URL(多个用逗号分隔)' },
|
||||
isEleAuth: { type: 'integer', enum: [0, 1], description: '是否电子认证' },
|
||||
isQuaAuth: { type: 'integer', enum: [0, 1], description: '是否质量认证' },
|
||||
isDelete: { type: 'integer', enum: [0, 1], description: '是否删除' },
|
||||
isOut: { type: 'integer', enum: [0, 1], description: '是否出栏' },
|
||||
createUid: { type: 'integer', description: '创建用户ID' },
|
||||
createTime: { type: 'integer', description: '创建时间(时间戳)' },
|
||||
algebra: { type: 'string', description: '代数' },
|
||||
colour: { type: 'string', description: '毛色' },
|
||||
infoWeight: { type: 'number', description: '信息重量' },
|
||||
descent: { type: 'string', description: '血统' },
|
||||
isVaccin: { type: 'integer', enum: [0, 1], description: '是否疫苗' },
|
||||
isInsemination: { type: 'integer', enum: [0, 1], description: '是否配种' },
|
||||
isInsure: { type: 'integer', enum: [0, 1], description: '是否保险' },
|
||||
isMortgage: { type: 'integer', enum: [0, 1], description: '是否抵押' },
|
||||
updateTime: { type: 'integer', description: '更新时间(时间戳)' },
|
||||
breedBullTime: { type: 'integer', description: '配种时间(时间戳)' },
|
||||
level: { type: 'string', description: '等级' },
|
||||
sixWeight: { type: 'number', description: '6月龄重量' },
|
||||
eighteenWeight: { type: 'number', description: '18月龄重量' },
|
||||
twelveDayWeight: { type: 'number', description: '12日龄重量' },
|
||||
eighteenDayWeight: { type: 'number', description: '18日龄重量' },
|
||||
xxivDayWeight: { type: 'number', description: '24日龄重量' },
|
||||
semenBreedImgs: { type: 'string', description: '配种图片' },
|
||||
sellStatus: { type: 'integer', description: '销售状态' },
|
||||
weightCalculateTime: { type: 'integer', description: '重量计算时间' },
|
||||
dayOfBirthday: { type: 'integer', description: '出生天数' },
|
||||
userId: { type: 'integer', description: '用户ID' }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { animalsPaths, animalSchemas };
|
||||
@@ -1,412 +0,0 @@
|
||||
/**
|
||||
* 用户认证模块 Swagger 文档
|
||||
* @file swagger-auth.js
|
||||
*/
|
||||
|
||||
const authPaths = {
|
||||
// 用户登录
|
||||
'/auth/login': {
|
||||
post: {
|
||||
tags: ['用户认证'],
|
||||
summary: '用户登录',
|
||||
description: '用户通过用户名/邮箱和密码登录系统',
|
||||
security: [], // 登录接口不需要认证
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['username', 'password'],
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
description: '用户名或邮箱',
|
||||
example: 'admin'
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
description: '密码',
|
||||
example: '123456'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '登录成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '登录成功' },
|
||||
token: { type: 'string', description: 'JWT Token' },
|
||||
user: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
username: { type: 'string' },
|
||||
email: { type: 'string' },
|
||||
phone: { type: 'string' },
|
||||
avatar: { type: 'string' },
|
||||
status: { type: 'string' },
|
||||
roles: { type: 'array', items: { type: 'object' } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'401': {
|
||||
description: '用户名或密码错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'429': {
|
||||
description: '登录尝试次数过多,请稍后再试',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 用户注册
|
||||
'/auth/register': {
|
||||
post: {
|
||||
tags: ['用户认证'],
|
||||
summary: '用户注册',
|
||||
description: '新用户注册账号',
|
||||
security: [], // 注册接口不需要认证
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['username', 'email', 'password'],
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
description: '用户名',
|
||||
example: 'newuser'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
format: 'email',
|
||||
description: '邮箱地址',
|
||||
example: 'newuser@example.com'
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
minLength: 6,
|
||||
description: '密码(至少6位)',
|
||||
example: '123456'
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
description: '手机号码',
|
||||
example: '13800138000'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'201': {
|
||||
description: '注册成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '注册成功' },
|
||||
user: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
username: { type: 'string' },
|
||||
email: { type: 'string' },
|
||||
phone: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误或用户已存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取当前用户信息
|
||||
'/auth/me': {
|
||||
get: {
|
||||
tags: ['用户认证'],
|
||||
summary: '获取当前用户信息',
|
||||
description: '获取当前登录用户的详细信息',
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
username: { type: 'string' },
|
||||
email: { type: 'string' },
|
||||
phone: { type: 'string' },
|
||||
avatar: { type: 'string' },
|
||||
status: { type: 'string' },
|
||||
roles: { type: 'array', items: { type: 'object' } },
|
||||
permissions: { type: 'array', items: { type: 'string' } },
|
||||
menus: { type: 'array', items: { type: 'object' } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'401': {
|
||||
description: '未授权,Token无效或已过期',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Token验证
|
||||
'/auth/validate': {
|
||||
get: {
|
||||
tags: ['用户认证'],
|
||||
summary: 'Token验证',
|
||||
description: '验证当前Token是否有效',
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Token有效',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: 'Token有效' },
|
||||
user: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
username: { type: 'string' },
|
||||
email: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'401': {
|
||||
description: 'Token无效或已过期',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取所有角色
|
||||
'/auth/roles': {
|
||||
get: {
|
||||
tags: ['用户认证'],
|
||||
summary: '获取所有角色',
|
||||
description: '获取系统中所有可用的角色列表',
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
name: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
permissions: { type: 'array', items: { type: 'string' } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 为用户分配角色
|
||||
'/auth/users/{userId}/roles': {
|
||||
post: {
|
||||
tags: ['用户认证'],
|
||||
summary: '为用户分配角色',
|
||||
description: '为指定用户分配一个或多个角色',
|
||||
parameters: [
|
||||
{
|
||||
name: 'userId',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '用户ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['roleIds'],
|
||||
properties: {
|
||||
roleIds: {
|
||||
type: 'array',
|
||||
items: { type: 'integer' },
|
||||
description: '角色ID列表'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '分配成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '角色分配成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '用户不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 移除用户角色
|
||||
'/auth/users/{userId}/roles/{roleId}': {
|
||||
delete: {
|
||||
tags: ['用户认证'],
|
||||
summary: '移除用户角色',
|
||||
description: '移除用户的指定角色',
|
||||
parameters: [
|
||||
{
|
||||
name: 'userId',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '用户ID'
|
||||
},
|
||||
{
|
||||
name: 'roleId',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '角色ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '移除成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '角色移除成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '用户或角色不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = authPaths;
|
||||
@@ -1,223 +0,0 @@
|
||||
/**
|
||||
* 完整版Swagger配置
|
||||
* @file swagger-complete.js
|
||||
* @description 宁夏智慧养殖监管平台完整API文档配置
|
||||
*/
|
||||
|
||||
const swaggerJSDoc = require('swagger-jsdoc');
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: '宁夏智慧养殖监管平台 API',
|
||||
version: '2.0.0',
|
||||
description: `
|
||||
宁夏智慧养殖监管平台API文档
|
||||
|
||||
## 功能模块
|
||||
- **用户认证**: 登录、注册、权限验证
|
||||
- **用户管理**: 用户CRUD操作、角色管理
|
||||
- **养殖场管理**: 养殖场信息管理
|
||||
- **动物管理**: 牲畜信息管理、批次管理
|
||||
- **设备管理**: IoT设备管理、智能设备
|
||||
- **预警系统**: 智能预警、告警处理
|
||||
- **围栏管理**: 电子围栏设置
|
||||
- **数据统计**: 各类统计报表
|
||||
- **系统管理**: 系统配置、备份等
|
||||
|
||||
## 认证方式
|
||||
使用JWT Token进行身份认证,请在请求头中添加:
|
||||
\`Authorization: Bearer <token>\`
|
||||
`,
|
||||
contact: {
|
||||
name: '开发团队',
|
||||
email: 'dev@nxxm.com'
|
||||
},
|
||||
license: {
|
||||
name: 'MIT',
|
||||
url: 'https://opensource.org/licenses/MIT'
|
||||
}
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: 'http://localhost:5350/api',
|
||||
description: '开发环境'
|
||||
},
|
||||
{
|
||||
url: 'https://api.nxxm.com/api',
|
||||
description: '生产环境'
|
||||
}
|
||||
],
|
||||
tags: [
|
||||
{
|
||||
name: '用户认证',
|
||||
description: '用户登录、注册、权限验证相关接口'
|
||||
},
|
||||
{
|
||||
name: '用户管理',
|
||||
description: '用户信息管理、角色权限管理'
|
||||
},
|
||||
{
|
||||
name: '养殖场管理',
|
||||
description: '养殖场信息的增删改查'
|
||||
},
|
||||
{
|
||||
name: '动物管理',
|
||||
description: '牲畜信息管理、批次管理、转移记录'
|
||||
},
|
||||
{
|
||||
name: '圈舍管理',
|
||||
description: '圈舍信息管理、牲畜圈舍分配'
|
||||
},
|
||||
{
|
||||
name: '设备管理',
|
||||
description: 'IoT设备管理、设备绑定、状态监控'
|
||||
},
|
||||
{
|
||||
name: '智能设备',
|
||||
description: '智能耳标、智能项圈等设备管理'
|
||||
},
|
||||
{
|
||||
name: '预警系统',
|
||||
description: '智能预警、告警处理、预警统计'
|
||||
},
|
||||
{
|
||||
name: '电子围栏',
|
||||
description: '电子围栏设置、围栏点管理'
|
||||
},
|
||||
{
|
||||
name: '地图服务',
|
||||
description: '地图相关功能、位置服务'
|
||||
},
|
||||
{
|
||||
name: '数据统计',
|
||||
description: '各类统计数据、报表生成'
|
||||
},
|
||||
{
|
||||
name: '报表管理',
|
||||
description: '报表生成、导出功能'
|
||||
},
|
||||
{
|
||||
name: '系统管理',
|
||||
description: '系统配置、菜单管理、权限配置'
|
||||
},
|
||||
{
|
||||
name: '备份管理',
|
||||
description: '数据备份、恢复功能'
|
||||
},
|
||||
{
|
||||
name: '操作日志',
|
||||
description: '系统操作日志记录和查询'
|
||||
},
|
||||
{
|
||||
name: '产品管理',
|
||||
description: '产品信息管理'
|
||||
},
|
||||
{
|
||||
name: '订单管理',
|
||||
description: '订单处理、订单查询'
|
||||
}
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
description: 'JWT认证,格式:Bearer <token>'
|
||||
}
|
||||
},
|
||||
schemas: {
|
||||
// 通用响应格式
|
||||
ApiResponse: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: '请求是否成功'
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: '响应消息'
|
||||
},
|
||||
data: {
|
||||
description: '响应数据'
|
||||
},
|
||||
total: {
|
||||
type: 'integer',
|
||||
description: '总记录数(分页时使用)'
|
||||
},
|
||||
page: {
|
||||
type: 'integer',
|
||||
description: '当前页码'
|
||||
},
|
||||
limit: {
|
||||
type: 'integer',
|
||||
description: '每页记录数'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 错误响应
|
||||
ErrorResponse: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
example: false
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: '错误消息'
|
||||
},
|
||||
error: {
|
||||
type: 'string',
|
||||
description: '错误详情'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 分页参数
|
||||
PaginationQuery: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: {
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
default: 1,
|
||||
description: '页码'
|
||||
},
|
||||
limit: {
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10,
|
||||
description: '每页记录数'
|
||||
},
|
||||
search: {
|
||||
type: 'string',
|
||||
description: '搜索关键词'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
]
|
||||
},
|
||||
apis: [
|
||||
'./routes/*.js',
|
||||
'./controllers/*.js'
|
||||
]
|
||||
};
|
||||
|
||||
const specs = swaggerJSDoc(options);
|
||||
|
||||
// 手动添加API路径定义
|
||||
if (!specs.paths) {
|
||||
specs.paths = {};
|
||||
}
|
||||
|
||||
module.exports = specs;
|
||||
@@ -1,369 +0,0 @@
|
||||
/**
|
||||
* Swagger API文档配置
|
||||
* @file swagger-config.js
|
||||
* @description 配置Swagger API文档,包含智能耳标预警和智能项圈预警接口
|
||||
*/
|
||||
|
||||
const swaggerJSDoc = require('swagger-jsdoc');
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: '智能预警系统 API',
|
||||
version: '1.0.0',
|
||||
description: '智能耳标预警和智能项圈预警系统API文档',
|
||||
contact: {
|
||||
name: '开发团队',
|
||||
email: 'dev@example.com'
|
||||
}
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: 'http://localhost:5350/api',
|
||||
description: '开发环境'
|
||||
}
|
||||
],
|
||||
tags: [
|
||||
{
|
||||
name: '智能耳标预警',
|
||||
description: '智能耳标预警相关接口'
|
||||
},
|
||||
{
|
||||
name: '智能项圈预警',
|
||||
description: '智能项圈预警相关接口'
|
||||
}
|
||||
],
|
||||
components: {
|
||||
schemas: {
|
||||
EartagAlert: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: '预警ID',
|
||||
example: '123_offline'
|
||||
},
|
||||
deviceId: {
|
||||
type: 'integer',
|
||||
description: '设备ID',
|
||||
example: 123
|
||||
},
|
||||
deviceName: {
|
||||
type: 'string',
|
||||
description: '设备名称',
|
||||
example: 'EARTAG001'
|
||||
},
|
||||
eartagNumber: {
|
||||
type: 'string',
|
||||
description: '耳标编号',
|
||||
example: 'EARTAG001'
|
||||
},
|
||||
alertType: {
|
||||
type: 'string',
|
||||
description: '预警类型',
|
||||
enum: ['battery', 'offline', 'temperature', 'movement'],
|
||||
example: 'offline'
|
||||
},
|
||||
alertLevel: {
|
||||
type: 'string',
|
||||
description: '预警级别',
|
||||
enum: ['high', 'medium', 'low'],
|
||||
example: 'high'
|
||||
},
|
||||
alertTime: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '预警时间',
|
||||
example: '2024-01-15 10:30:00'
|
||||
},
|
||||
battery: {
|
||||
type: 'integer',
|
||||
description: '设备电量',
|
||||
example: 85
|
||||
},
|
||||
temperature: {
|
||||
type: 'number',
|
||||
description: '设备温度',
|
||||
example: 25.5
|
||||
},
|
||||
dailySteps: {
|
||||
type: 'integer',
|
||||
description: '当日步数',
|
||||
example: 0
|
||||
},
|
||||
totalSteps: {
|
||||
type: 'integer',
|
||||
description: '总步数',
|
||||
example: 1500
|
||||
},
|
||||
yesterdaySteps: {
|
||||
type: 'integer',
|
||||
description: '昨日步数',
|
||||
example: 1500
|
||||
},
|
||||
deviceStatus: {
|
||||
type: 'string',
|
||||
description: '设备状态',
|
||||
example: '离线'
|
||||
},
|
||||
gpsSignal: {
|
||||
type: 'string',
|
||||
description: 'GPS信号',
|
||||
example: '无'
|
||||
},
|
||||
movementStatus: {
|
||||
type: 'string',
|
||||
description: '运动状态',
|
||||
example: '静止'
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: '预警描述',
|
||||
example: '设备已离线超过30分钟'
|
||||
},
|
||||
longitude: {
|
||||
type: 'number',
|
||||
description: '经度',
|
||||
example: 116.3974
|
||||
},
|
||||
latitude: {
|
||||
type: 'number',
|
||||
description: '纬度',
|
||||
example: 39.9093
|
||||
}
|
||||
}
|
||||
},
|
||||
CollarAlert: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: '预警ID',
|
||||
example: '123_offline'
|
||||
},
|
||||
deviceId: {
|
||||
type: 'integer',
|
||||
description: '设备ID',
|
||||
example: 123
|
||||
},
|
||||
deviceName: {
|
||||
type: 'string',
|
||||
description: '设备名称',
|
||||
example: 'COLLAR001'
|
||||
},
|
||||
collarNumber: {
|
||||
type: 'string',
|
||||
description: '项圈编号',
|
||||
example: 'COLLAR001'
|
||||
},
|
||||
alertType: {
|
||||
type: 'string',
|
||||
description: '预警类型',
|
||||
enum: ['battery', 'offline', 'temperature', 'movement', 'wear'],
|
||||
example: 'offline'
|
||||
},
|
||||
alertLevel: {
|
||||
type: 'string',
|
||||
description: '预警级别',
|
||||
enum: ['high', 'medium', 'low'],
|
||||
example: 'high'
|
||||
},
|
||||
alertTime: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: '预警时间',
|
||||
example: '2024-01-15 10:30:00'
|
||||
},
|
||||
battery: {
|
||||
type: 'integer',
|
||||
description: '设备电量',
|
||||
example: 85
|
||||
},
|
||||
temperature: {
|
||||
type: 'number',
|
||||
description: '设备温度',
|
||||
example: 25.5
|
||||
},
|
||||
dailySteps: {
|
||||
type: 'integer',
|
||||
description: '当日步数',
|
||||
example: 0
|
||||
},
|
||||
totalSteps: {
|
||||
type: 'integer',
|
||||
description: '总步数',
|
||||
example: 1500
|
||||
},
|
||||
yesterdaySteps: {
|
||||
type: 'integer',
|
||||
description: '昨日步数',
|
||||
example: 1500
|
||||
},
|
||||
deviceStatus: {
|
||||
type: 'string',
|
||||
description: '设备状态',
|
||||
example: '离线'
|
||||
},
|
||||
gpsSignal: {
|
||||
type: 'string',
|
||||
description: 'GPS信号',
|
||||
example: '无'
|
||||
},
|
||||
wearStatus: {
|
||||
type: 'string',
|
||||
description: '佩戴状态',
|
||||
example: '未佩戴'
|
||||
},
|
||||
movementStatus: {
|
||||
type: 'string',
|
||||
description: '运动状态',
|
||||
example: '静止'
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: '预警描述',
|
||||
example: '设备已离线超过30分钟'
|
||||
},
|
||||
longitude: {
|
||||
type: 'number',
|
||||
description: '经度',
|
||||
example: 116.3974
|
||||
},
|
||||
latitude: {
|
||||
type: 'number',
|
||||
description: '纬度',
|
||||
example: 39.9093
|
||||
}
|
||||
}
|
||||
},
|
||||
AlertStats: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
totalDevices: {
|
||||
type: 'integer',
|
||||
description: '设备总数',
|
||||
example: 150
|
||||
},
|
||||
lowBattery: {
|
||||
type: 'integer',
|
||||
description: '低电量预警数量',
|
||||
example: 12
|
||||
},
|
||||
offline: {
|
||||
type: 'integer',
|
||||
description: '离线预警数量',
|
||||
example: 8
|
||||
},
|
||||
highTemperature: {
|
||||
type: 'integer',
|
||||
description: '高温预警数量',
|
||||
example: 5
|
||||
},
|
||||
lowTemperature: {
|
||||
type: 'integer',
|
||||
description: '低温预警数量',
|
||||
example: 3
|
||||
},
|
||||
abnormalMovement: {
|
||||
type: 'integer',
|
||||
description: '异常运动预警数量',
|
||||
example: 7
|
||||
},
|
||||
wearOff: {
|
||||
type: 'integer',
|
||||
description: '项圈脱落预警数量(仅项圈)',
|
||||
example: 2
|
||||
},
|
||||
totalAlerts: {
|
||||
type: 'integer',
|
||||
description: '预警总数',
|
||||
example: 35
|
||||
}
|
||||
}
|
||||
},
|
||||
ApiResponse: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: '请求是否成功',
|
||||
example: true
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
description: '响应数据'
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: '响应消息',
|
||||
example: '操作成功'
|
||||
},
|
||||
total: {
|
||||
type: 'integer',
|
||||
description: '数据总数(分页时使用)',
|
||||
example: 100
|
||||
},
|
||||
stats: {
|
||||
$ref: '#/components/schemas/AlertStats'
|
||||
},
|
||||
pagination: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: {
|
||||
type: 'integer',
|
||||
description: '当前页码',
|
||||
example: 1
|
||||
},
|
||||
limit: {
|
||||
type: 'integer',
|
||||
description: '每页数量',
|
||||
example: 10
|
||||
},
|
||||
total: {
|
||||
type: 'integer',
|
||||
description: '总数据量',
|
||||
example: 100
|
||||
},
|
||||
pages: {
|
||||
type: 'integer',
|
||||
description: '总页数',
|
||||
example: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ErrorResponse: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: '请求是否成功',
|
||||
example: false
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: '错误消息',
|
||||
example: '请求失败'
|
||||
},
|
||||
error: {
|
||||
type: 'string',
|
||||
description: '详细错误信息',
|
||||
example: '具体错误描述'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
apis: [
|
||||
'./routes/smart-alerts.js',
|
||||
'./controllers/smartEartagAlertController.js',
|
||||
'./controllers/smartCollarAlertController.js'
|
||||
]
|
||||
};
|
||||
|
||||
const specs = swaggerJSDoc(options);
|
||||
|
||||
module.exports = specs;
|
||||
@@ -1,684 +0,0 @@
|
||||
/**
|
||||
* 设备管理模块 Swagger 文档
|
||||
* @file swagger-devices.js
|
||||
*/
|
||||
|
||||
const devicesPaths = {
|
||||
// 获取所有设备
|
||||
'/devices': {
|
||||
get: {
|
||||
tags: ['设备管理'],
|
||||
summary: '获取设备列表',
|
||||
description: '分页获取系统中的所有设备信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 1 },
|
||||
description: '页码'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '每页数量'
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词(设备名称、设备编号)'
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '设备类型筛选'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['online', 'offline', 'maintenance', 'error'] },
|
||||
description: '设备状态筛选'
|
||||
},
|
||||
{
|
||||
name: 'farmId',
|
||||
in: 'query',
|
||||
schema: { type: 'integer' },
|
||||
description: '养殖场ID筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/Device' }
|
||||
},
|
||||
pagination: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'integer' },
|
||||
limit: { type: 'integer' },
|
||||
total: { type: 'integer' },
|
||||
totalPages: { type: 'integer' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
post: {
|
||||
tags: ['设备管理'],
|
||||
summary: '创建新设备',
|
||||
description: '添加新的设备到系统中',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['name', 'type', 'farmId'],
|
||||
properties: {
|
||||
name: { type: 'string', description: '设备名称' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['collar', 'ear_tag', 'temperature_sensor', 'humidity_sensor', 'camera', 'feeder', 'water_dispenser'],
|
||||
description: '设备类型'
|
||||
},
|
||||
deviceNumber: { type: 'string', description: '设备编号' },
|
||||
model: { type: 'string', description: '设备型号' },
|
||||
manufacturer: { type: 'string', description: '制造商' },
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['online', 'offline', 'maintenance', 'error'],
|
||||
default: 'offline',
|
||||
description: '设备状态'
|
||||
},
|
||||
farmId: { type: 'integer', description: '所属养殖场ID' },
|
||||
location: { type: 'string', description: '设备位置' },
|
||||
installationDate: { type: 'string', format: 'date', description: '安装日期' },
|
||||
lastMaintenance: { type: 'string', format: 'date', description: '最近维护时间' },
|
||||
batteryLevel: { type: 'number', minimum: 0, maximum: 100, description: '电池电量(%)' },
|
||||
firmwareVersion: { type: 'string', description: '固件版本' },
|
||||
specifications: { type: 'object', description: '设备规格参数' },
|
||||
notes: { type: 'string', description: '备注' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'201': {
|
||||
description: '创建成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '设备创建成功' },
|
||||
data: { $ref: '#/components/schemas/Device' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 公共设备数据
|
||||
'/devices/public': {
|
||||
get: {
|
||||
tags: ['设备管理'],
|
||||
summary: '获取公共设备数据',
|
||||
description: '获取可公开访问的设备基本信息',
|
||||
security: [], // 公共接口不需要认证
|
||||
parameters: [
|
||||
{
|
||||
name: 'type',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '设备类型筛选'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '设备状态筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
name: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
status: { type: 'string' },
|
||||
location: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 搜索设备
|
||||
'/devices/search': {
|
||||
get: {
|
||||
tags: ['设备管理'],
|
||||
summary: '搜索设备',
|
||||
description: '根据设备名称搜索设备',
|
||||
parameters: [
|
||||
{
|
||||
name: 'name',
|
||||
in: 'query',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
description: '设备名称关键词'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '返回结果数量限制'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '搜索成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/Device' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 设备统计 - 按状态
|
||||
'/devices/stats/status': {
|
||||
get: {
|
||||
tags: ['设备管理'],
|
||||
summary: '按状态统计设备数量',
|
||||
description: '获取不同状态下的设备数量统计',
|
||||
responses: {
|
||||
'200': {
|
||||
description: '统计成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: { type: 'string', example: 'online' },
|
||||
count: { type: 'integer', example: 25 },
|
||||
percentage: { type: 'number', example: 62.5 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 设备统计 - 按类型
|
||||
'/devices/stats/type': {
|
||||
get: {
|
||||
tags: ['设备管理'],
|
||||
summary: '按类型统计设备数量',
|
||||
description: '获取不同类型设备的数量统计',
|
||||
responses: {
|
||||
'200': {
|
||||
description: '统计成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: { type: 'string', example: 'collar' },
|
||||
typeName: { type: 'string', example: '智能项圈' },
|
||||
count: { type: 'integer', example: 15 },
|
||||
percentage: { type: 'number', example: 37.5 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取指定设备详情
|
||||
'/devices/{id}': {
|
||||
get: {
|
||||
tags: ['设备管理'],
|
||||
summary: '获取设备详情',
|
||||
description: '根据设备ID获取详细信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '设备ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: { $ref: '#/components/schemas/Device' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '设备不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
put: {
|
||||
tags: ['设备管理'],
|
||||
summary: '更新设备信息',
|
||||
description: '更新指定设备的信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '设备ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: '设备名称' },
|
||||
type: { type: 'string', description: '设备类型' },
|
||||
deviceNumber: { type: 'string', description: '设备编号' },
|
||||
model: { type: 'string', description: '设备型号' },
|
||||
manufacturer: { type: 'string', description: '制造商' },
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['online', 'offline', 'maintenance', 'error'],
|
||||
description: '设备状态'
|
||||
},
|
||||
farmId: { type: 'integer', description: '所属养殖场ID' },
|
||||
location: { type: 'string', description: '设备位置' },
|
||||
lastMaintenance: { type: 'string', format: 'date', description: '最近维护时间' },
|
||||
batteryLevel: { type: 'number', minimum: 0, maximum: 100, description: '电池电量(%)' },
|
||||
firmwareVersion: { type: 'string', description: '固件版本' },
|
||||
specifications: { type: 'object', description: '设备规格参数' },
|
||||
notes: { type: 'string', description: '备注' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '更新成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '设备信息更新成功' },
|
||||
data: { $ref: '#/components/schemas/Device' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '设备不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
tags: ['设备管理'],
|
||||
summary: '删除设备',
|
||||
description: '删除指定设备(软删除)',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '设备ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '删除成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '设备删除成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '设备不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 设备维护记录
|
||||
'/devices/{id}/maintenance': {
|
||||
get: {
|
||||
tags: ['设备管理'],
|
||||
summary: '获取设备维护记录',
|
||||
description: '获取指定设备的维护记录',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '设备ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
deviceId: { type: 'integer' },
|
||||
maintenanceDate: { type: 'string', format: 'date' },
|
||||
maintenanceType: { type: 'string', enum: ['routine', 'repair', 'upgrade'] },
|
||||
description: { type: 'string' },
|
||||
technician: { type: 'string' },
|
||||
cost: { type: 'number' },
|
||||
notes: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
post: {
|
||||
tags: ['设备管理'],
|
||||
summary: '添加设备维护记录',
|
||||
description: '为指定设备添加维护记录',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '设备ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['maintenanceDate', 'maintenanceType', 'description'],
|
||||
properties: {
|
||||
maintenanceDate: { type: 'string', format: 'date', description: '维护日期' },
|
||||
maintenanceType: {
|
||||
type: 'string',
|
||||
enum: ['routine', 'repair', 'upgrade'],
|
||||
description: '维护类型'
|
||||
},
|
||||
description: { type: 'string', description: '维护描述' },
|
||||
technician: { type: 'string', description: '技术员姓名' },
|
||||
cost: { type: 'number', description: '维护费用' },
|
||||
notes: { type: 'string', description: '备注' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'201': {
|
||||
description: '添加成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '维护记录添加成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 设备数据监控
|
||||
'/devices/{id}/data': {
|
||||
get: {
|
||||
tags: ['设备管理'],
|
||||
summary: '获取设备监控数据',
|
||||
description: '获取指定设备的实时监控数据',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '设备ID'
|
||||
},
|
||||
{
|
||||
name: 'startTime',
|
||||
in: 'query',
|
||||
schema: { type: 'string', format: 'date-time' },
|
||||
description: '开始时间'
|
||||
},
|
||||
{
|
||||
name: 'endTime',
|
||||
in: 'query',
|
||||
schema: { type: 'string', format: 'date-time' },
|
||||
description: '结束时间'
|
||||
},
|
||||
{
|
||||
name: 'dataType',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['temperature', 'humidity', 'location', 'battery', 'activity'] },
|
||||
description: '数据类型'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
timestamp: { type: 'string', format: 'date-time' },
|
||||
dataType: { type: 'string' },
|
||||
value: { type: 'number' },
|
||||
unit: { type: 'string' },
|
||||
location: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
latitude: { type: 'number' },
|
||||
longitude: { type: 'number' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 设备数据模型
|
||||
const deviceSchemas = {
|
||||
Device: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', description: '设备ID' },
|
||||
name: { type: 'string', description: '设备名称' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['collar', 'ear_tag', 'temperature_sensor', 'humidity_sensor', 'camera', 'feeder', 'water_dispenser'],
|
||||
description: '设备类型'
|
||||
},
|
||||
deviceNumber: { type: 'string', description: '设备编号' },
|
||||
model: { type: 'string', description: '设备型号' },
|
||||
manufacturer: { type: 'string', description: '制造商' },
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['online', 'offline', 'maintenance', 'error'],
|
||||
description: '设备状态'
|
||||
},
|
||||
farmId: { type: 'integer', description: '所属养殖场ID' },
|
||||
farmName: { type: 'string', description: '养殖场名称' },
|
||||
location: { type: 'string', description: '设备位置' },
|
||||
installationDate: { type: 'string', format: 'date', description: '安装日期' },
|
||||
lastMaintenance: { type: 'string', format: 'date', description: '最近维护时间' },
|
||||
batteryLevel: { type: 'number', minimum: 0, maximum: 100, description: '电池电量(%)' },
|
||||
firmwareVersion: { type: 'string', description: '固件版本' },
|
||||
specifications: {
|
||||
type: 'object',
|
||||
description: '设备规格参数',
|
||||
additionalProperties: true
|
||||
},
|
||||
lastDataTime: { type: 'string', format: 'date-time', description: '最后数据时间' },
|
||||
isActive: { type: 'boolean', description: '是否激活' },
|
||||
notes: { type: 'string', description: '备注' },
|
||||
createdAt: { type: 'string', format: 'date-time', description: '创建时间' },
|
||||
updatedAt: { type: 'string', format: 'date-time', description: '更新时间' }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { devicesPaths, deviceSchemas };
|
||||
@@ -1,623 +0,0 @@
|
||||
/**
|
||||
* 养殖场管理模块 Swagger 文档
|
||||
* @file swagger-farms.js
|
||||
*/
|
||||
|
||||
const farmsPaths = {
|
||||
// 获取所有养殖场
|
||||
'/farms': {
|
||||
get: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '获取养殖场列表',
|
||||
description: '分页获取系统中的所有养殖场',
|
||||
parameters: [
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 1 },
|
||||
description: '页码'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '每页数量'
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词(养殖场名称、地址)'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['active', 'inactive', 'suspended'] },
|
||||
description: '养殖场状态筛选'
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['cattle', 'sheep', 'pig', 'poultry'] },
|
||||
description: '养殖类型筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/Farm' }
|
||||
},
|
||||
pagination: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'integer' },
|
||||
limit: { type: 'integer' },
|
||||
total: { type: 'integer' },
|
||||
totalPages: { type: 'integer' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
post: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '创建新养殖场',
|
||||
description: '创建新的养殖场记录',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['name', 'address', 'type', 'ownerId'],
|
||||
properties: {
|
||||
name: { type: 'string', description: '养殖场名称' },
|
||||
address: { type: 'string', description: '养殖场地址' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['cattle', 'sheep', 'pig', 'poultry'],
|
||||
description: '养殖类型:cattle-牛,sheep-羊,pig-猪,poultry-家禽'
|
||||
},
|
||||
ownerId: { type: 'integer', description: '养殖场主ID' },
|
||||
description: { type: 'string', description: '养殖场描述' },
|
||||
area: { type: 'number', description: '养殖场面积(平方米)' },
|
||||
capacity: { type: 'integer', description: '最大养殖容量' },
|
||||
contactPhone: { type: 'string', description: '联系电话' },
|
||||
contactEmail: { type: 'string', format: 'email', description: '联系邮箱' },
|
||||
coordinates: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
latitude: { type: 'number', description: '纬度' },
|
||||
longitude: { type: 'number', description: '经度' }
|
||||
},
|
||||
description: '地理坐标'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'inactive', 'suspended'],
|
||||
default: 'active',
|
||||
description: '养殖场状态'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'201': {
|
||||
description: '创建成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '养殖场创建成功' },
|
||||
data: { $ref: '#/components/schemas/Farm' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 搜索养殖场
|
||||
'/farms/search': {
|
||||
get: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '搜索养殖场',
|
||||
description: '根据名称、地址等关键词搜索养殖场',
|
||||
parameters: [
|
||||
{
|
||||
name: 'q',
|
||||
in: 'query',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '返回结果数量限制'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '搜索成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/Farm' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 公共养殖场数据
|
||||
'/farms/public': {
|
||||
get: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '获取公共养殖场数据',
|
||||
description: '获取可公开访问的养殖场基本信息',
|
||||
security: [], // 公共接口不需要认证
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
name: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
address: { type: 'string' },
|
||||
area: { type: 'number' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取指定养殖场详情
|
||||
'/farms/{id}': {
|
||||
get: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '获取养殖场详情',
|
||||
description: '根据养殖场ID获取详细信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '养殖场ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: { $ref: '#/components/schemas/Farm' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '养殖场不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
put: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '更新养殖场信息',
|
||||
description: '更新指定养殖场的信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '养殖场ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: '养殖场名称' },
|
||||
address: { type: 'string', description: '养殖场地址' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['cattle', 'sheep', 'pig', 'poultry'],
|
||||
description: '养殖类型'
|
||||
},
|
||||
description: { type: 'string', description: '养殖场描述' },
|
||||
area: { type: 'number', description: '养殖场面积(平方米)' },
|
||||
capacity: { type: 'integer', description: '最大养殖容量' },
|
||||
contactPhone: { type: 'string', description: '联系电话' },
|
||||
contactEmail: { type: 'string', format: 'email', description: '联系邮箱' },
|
||||
coordinates: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
latitude: { type: 'number', description: '纬度' },
|
||||
longitude: { type: 'number', description: '经度' }
|
||||
},
|
||||
description: '地理坐标'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'inactive', 'suspended'],
|
||||
description: '养殖场状态'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '更新成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '养殖场信息更新成功' },
|
||||
data: { $ref: '#/components/schemas/Farm' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '养殖场不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '删除养殖场',
|
||||
description: '删除指定养殖场(软删除)',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '养殖场ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '删除成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '养殖场删除成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '养殖场不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取养殖场的动物列表
|
||||
'/farms/{id}/animals': {
|
||||
get: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '获取养殖场的动物列表',
|
||||
description: '获取指定养殖场的所有动物',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '养殖场ID'
|
||||
},
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 1 },
|
||||
description: '页码'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '每页数量'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['healthy', 'sick', 'quarantine', 'sold'] },
|
||||
description: '动物状态筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/Animal' }
|
||||
},
|
||||
pagination: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'integer' },
|
||||
limit: { type: 'integer' },
|
||||
total: { type: 'integer' },
|
||||
totalPages: { type: 'integer' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '养殖场不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取养殖场的设备列表
|
||||
'/farms/{id}/devices': {
|
||||
get: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '获取养殖场的设备列表',
|
||||
description: '获取指定养殖场的所有设备',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '养殖场ID'
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['sensor', 'camera', 'feeder', 'monitor'] },
|
||||
description: '设备类型筛选'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['online', 'offline', 'maintenance'] },
|
||||
description: '设备状态筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/Device' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '养殖场不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取养殖场统计信息
|
||||
'/farms/{id}/statistics': {
|
||||
get: {
|
||||
tags: ['养殖场管理'],
|
||||
summary: '获取养殖场统计信息',
|
||||
description: '获取指定养殖场的统计数据',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '养殖场ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
totalAnimals: { type: 'integer', description: '动物总数' },
|
||||
healthyAnimals: { type: 'integer', description: '健康动物数' },
|
||||
sickAnimals: { type: 'integer', description: '患病动物数' },
|
||||
totalDevices: { type: 'integer', description: '设备总数' },
|
||||
onlineDevices: { type: 'integer', description: '在线设备数' },
|
||||
offlineDevices: { type: 'integer', description: '离线设备数' },
|
||||
alertsCount: { type: 'integer', description: '预警数量' },
|
||||
utilizationRate: { type: 'number', description: '利用率(%)' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '养殖场不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 养殖场数据模型
|
||||
const farmSchemas = {
|
||||
Farm: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', description: '养殖场ID' },
|
||||
name: { type: 'string', description: '养殖场名称' },
|
||||
address: { type: 'string', description: '养殖场地址' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['cattle', 'sheep', 'pig', 'poultry'],
|
||||
description: '养殖类型:cattle-牛,sheep-羊,pig-猪,poultry-家禽'
|
||||
},
|
||||
description: { type: 'string', description: '养殖场描述' },
|
||||
area: { type: 'number', description: '养殖场面积(平方米)' },
|
||||
capacity: { type: 'integer', description: '最大养殖容量' },
|
||||
currentCount: { type: 'integer', description: '当前动物数量' },
|
||||
contactPhone: { type: 'string', description: '联系电话' },
|
||||
contactEmail: { type: 'string', format: 'email', description: '联系邮箱' },
|
||||
coordinates: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
latitude: { type: 'number', description: '纬度' },
|
||||
longitude: { type: 'number', description: '经度' }
|
||||
},
|
||||
description: '地理坐标'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'inactive', 'suspended'],
|
||||
description: '养殖场状态:active-活跃,inactive-未激活,suspended-暂停'
|
||||
},
|
||||
owner: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
username: { type: 'string' },
|
||||
realName: { type: 'string' },
|
||||
phone: { type: 'string' }
|
||||
},
|
||||
description: '养殖场主信息'
|
||||
},
|
||||
createdAt: { type: 'string', format: 'date-time', description: '创建时间' },
|
||||
updatedAt: { type: 'string', format: 'date-time', description: '更新时间' }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { farmsPaths, farmSchemas };
|
||||
@@ -1,736 +0,0 @@
|
||||
/**
|
||||
* 报表管理模块 Swagger 文档
|
||||
* @file swagger-reports.js
|
||||
* @description 定义报表管理相关的API文档
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* - name: 报表管理
|
||||
* description: 报表生成、下载和管理
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* ReportGenerateRequest:
|
||||
* type: object
|
||||
* properties:
|
||||
* startDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* example: "2024-01-01"
|
||||
* endDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
* example: "2024-01-31"
|
||||
* format:
|
||||
* type: string
|
||||
* enum: [pdf, excel, csv]
|
||||
* description: 报表格式
|
||||
* example: "pdf"
|
||||
*
|
||||
* FarmReportRequest:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/ReportGenerateRequest'
|
||||
* - type: object
|
||||
* properties:
|
||||
* farmIds:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: 养殖场ID列表
|
||||
* example: ["farm_001", "farm_002"]
|
||||
*
|
||||
* SalesReportRequest:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/ReportGenerateRequest'
|
||||
* - type: object
|
||||
* properties:
|
||||
* format:
|
||||
* type: string
|
||||
* enum: [pdf, excel]
|
||||
* description: 报表格式(销售报表不支持CSV)
|
||||
* example: "excel"
|
||||
*
|
||||
* ComplianceReportRequest:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/ReportGenerateRequest'
|
||||
* - type: object
|
||||
* properties:
|
||||
* format:
|
||||
* type: string
|
||||
* enum: [pdf, excel]
|
||||
* description: 报表格式(合规报表不支持CSV)
|
||||
* example: "pdf"
|
||||
*
|
||||
* ReportFile:
|
||||
* type: object
|
||||
* properties:
|
||||
* fileName:
|
||||
* type: string
|
||||
* description: 文件名
|
||||
* example: "farm_report_20240115.pdf"
|
||||
* downloadUrl:
|
||||
* type: string
|
||||
* description: 下载链接
|
||||
* example: "/api/reports/download/farm_report_20240115.pdf"
|
||||
* mimeType:
|
||||
* type: string
|
||||
* description: 文件MIME类型
|
||||
* example: "application/pdf"
|
||||
* size:
|
||||
* type: integer
|
||||
* description: 文件大小(字节)
|
||||
* example: 1024000
|
||||
* generatedAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 生成时间
|
||||
* example: "2024-01-15T10:30:00Z"
|
||||
*
|
||||
* ReportListItem:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: string
|
||||
* description: 报表ID
|
||||
* example: "report_001"
|
||||
* fileName:
|
||||
* type: string
|
||||
* description: 文件名
|
||||
* example: "farm_report_20240115.pdf"
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [farm, sales, compliance, export]
|
||||
* description: 报表类型
|
||||
* example: "farm"
|
||||
* format:
|
||||
* type: string
|
||||
* enum: [pdf, excel, csv]
|
||||
* description: 文件格式
|
||||
* example: "pdf"
|
||||
* size:
|
||||
* type: integer
|
||||
* description: 文件大小(字节)
|
||||
* example: 1024000
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [generating, completed, failed, expired]
|
||||
* description: 报表状态
|
||||
* example: "completed"
|
||||
* createdBy:
|
||||
* type: string
|
||||
* description: 创建者
|
||||
* example: "admin"
|
||||
* createdAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
* example: "2024-01-15T10:30:00Z"
|
||||
* expiresAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 过期时间
|
||||
* example: "2024-01-22T10:30:00Z"
|
||||
* downloadUrl:
|
||||
* type: string
|
||||
* description: 下载链接
|
||||
* example: "/api/reports/download/farm_report_20240115.pdf"
|
||||
*
|
||||
* ReportTemplate:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: string
|
||||
* description: 模板ID
|
||||
* example: "template_001"
|
||||
* name:
|
||||
* type: string
|
||||
* description: 模板名称
|
||||
* example: "养殖场月度报表模板"
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [farm, sales, compliance]
|
||||
* description: 模板类型
|
||||
* example: "farm"
|
||||
* description:
|
||||
* type: string
|
||||
* description: 模板描述
|
||||
* example: "包含养殖场基本信息、动物统计、设备状态等"
|
||||
* fields:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 字段名称
|
||||
* label:
|
||||
* type: string
|
||||
* description: 字段标签
|
||||
* type:
|
||||
* type: string
|
||||
* description: 字段类型
|
||||
* required:
|
||||
* type: boolean
|
||||
* description: 是否必填
|
||||
* description: 模板字段配置
|
||||
* isDefault:
|
||||
* type: boolean
|
||||
* description: 是否为默认模板
|
||||
* example: true
|
||||
* createdAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 创建时间
|
||||
* example: "2024-01-15T10:30:00Z"
|
||||
*
|
||||
* ExportDataRequest:
|
||||
* type: object
|
||||
* properties:
|
||||
* format:
|
||||
* type: string
|
||||
* enum: [excel, csv]
|
||||
* description: 导出格式
|
||||
* example: "excel"
|
||||
* filters:
|
||||
* type: object
|
||||
* description: 筛选条件
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* description: 状态筛选
|
||||
* startDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 开始日期
|
||||
* endDate:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 结束日期
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /reports/farm:
|
||||
* post:
|
||||
* tags:
|
||||
* - 报表管理
|
||||
* summary: 生成养殖统计报表
|
||||
* description: 生成指定时间范围和养殖场的统计报表
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/FarmReportRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 报表生成成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/ApiResponse'
|
||||
* - type: object
|
||||
* properties:
|
||||
* data:
|
||||
* $ref: '#/components/schemas/ReportFile'
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /reports/sales:
|
||||
* post:
|
||||
* tags:
|
||||
* - 报表管理
|
||||
* summary: 生成销售分析报表
|
||||
* description: 生成指定时间范围的销售分析报表(需要管理员或经理权限)
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/SalesReportRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 报表生成成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/ApiResponse'
|
||||
* - type: object
|
||||
* properties:
|
||||
* data:
|
||||
* $ref: '#/components/schemas/ReportFile'
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /reports/compliance:
|
||||
* post:
|
||||
* tags:
|
||||
* - 报表管理
|
||||
* summary: 生成监管合规报表
|
||||
* description: 生成指定时间范围的监管合规报表(仅限管理员)
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ComplianceReportRequest'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 报表生成成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/ApiResponse'
|
||||
* - type: object
|
||||
* properties:
|
||||
* data:
|
||||
* $ref: '#/components/schemas/ReportFile'
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 403:
|
||||
* description: 权限不足(仅限管理员)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /reports/download/{fileName}:
|
||||
* get:
|
||||
* tags:
|
||||
* - 报表管理
|
||||
* summary: 下载报表文件
|
||||
* description: 下载指定的报表文件
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: fileName
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 文件名(需要URL编码)
|
||||
* example: "farm_report_20240115.pdf"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 文件下载成功
|
||||
* content:
|
||||
* application/pdf:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* text/csv:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 403:
|
||||
* description: 非法文件路径
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 404:
|
||||
* description: 文件不存在
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /reports/list:
|
||||
* get:
|
||||
* tags:
|
||||
* - 报表管理
|
||||
* summary: 获取报表列表
|
||||
* description: 获取当前用户的报表列表,支持分页和筛选
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - $ref: '#/components/parameters/PaginationQuery/properties/page'
|
||||
* - $ref: '#/components/parameters/PaginationQuery/properties/limit'
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [farm, sales, compliance, export]
|
||||
* description: 报表类型筛选
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [generating, completed, failed, expired]
|
||||
* description: 报表状态筛选
|
||||
* - in: query
|
||||
* name: format
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [pdf, excel, csv]
|
||||
* description: 文件格式筛选
|
||||
* - in: query
|
||||
* name: startDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 创建开始日期
|
||||
* - in: query
|
||||
* name: endDate
|
||||
* schema:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 创建结束日期
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取列表成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/ApiResponse'
|
||||
* - type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* reports:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/ReportListItem'
|
||||
* pagination:
|
||||
* type: object
|
||||
* properties:
|
||||
* total:
|
||||
* type: integer
|
||||
* example: 50
|
||||
* page:
|
||||
* type: integer
|
||||
* example: 1
|
||||
* limit:
|
||||
* type: integer
|
||||
* example: 10
|
||||
* totalPages:
|
||||
* type: integer
|
||||
* example: 5
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /reports/cleanup:
|
||||
* post:
|
||||
* tags:
|
||||
* - 报表管理
|
||||
* summary: 清理过期报表
|
||||
* description: 清理过期的报表文件(仅限管理员)
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* daysOld:
|
||||
* type: integer
|
||||
* description: 清理多少天前的文件
|
||||
* example: 30
|
||||
* force:
|
||||
* type: boolean
|
||||
* description: 是否强制清理(包括未过期的文件)
|
||||
* example: false
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 清理成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/ApiResponse'
|
||||
* - type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* deletedCount:
|
||||
* type: integer
|
||||
* description: 删除的文件数量
|
||||
* example: 15
|
||||
* freedSpace:
|
||||
* type: integer
|
||||
* description: 释放的空间(字节)
|
||||
* example: 15360000
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 403:
|
||||
* description: 权限不足(仅限管理员)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /reports/export/farms:
|
||||
* get:
|
||||
* tags:
|
||||
* - 报表管理
|
||||
* summary: 导出养殖场数据
|
||||
* description: 导出养殖场基础数据为Excel或CSV格式
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: format
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [excel, csv]
|
||||
* default: excel
|
||||
* description: 导出格式
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [active, inactive, all]
|
||||
* default: all
|
||||
* description: 状态筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 导出成功
|
||||
* content:
|
||||
* application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* text/csv:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /reports/export/devices:
|
||||
* get:
|
||||
* tags:
|
||||
* - 报表管理
|
||||
* summary: 导出设备数据
|
||||
* description: 导出设备基础数据为Excel或CSV格式
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: format
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [excel, csv]
|
||||
* default: excel
|
||||
* description: 导出格式
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [online, offline, maintenance, all]
|
||||
* default: all
|
||||
* description: 设备状态筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 导出成功
|
||||
* content:
|
||||
* application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* text/csv:
|
||||
* schema:
|
||||
* type: string
|
||||
* format: binary
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /reports/templates:
|
||||
* get:
|
||||
* tags:
|
||||
* - 报表管理
|
||||
* summary: 获取报表模板列表
|
||||
* description: 获取可用的报表模板列表
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [farm, sales, compliance]
|
||||
* description: 模板类型筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取模板列表成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/ApiResponse'
|
||||
* - type: object
|
||||
* properties:
|
||||
* data:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/ReportTemplate'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ErrorResponse'
|
||||
*/
|
||||
|
||||
module.exports = {};
|
||||
@@ -1,520 +0,0 @@
|
||||
/**
|
||||
* 简化版Swagger配置
|
||||
* @file swagger-simple.js
|
||||
* @description 简化的Swagger配置,确保API路径正确显示
|
||||
*/
|
||||
|
||||
const swaggerJSDoc = require('swagger-jsdoc');
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: '智能预警系统 API',
|
||||
version: '1.0.0',
|
||||
description: '智能耳标预警和智能项圈预警系统API文档',
|
||||
contact: {
|
||||
name: '开发团队',
|
||||
email: 'dev@example.com'
|
||||
}
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: 'http://localhost:5350/api',
|
||||
description: '开发环境'
|
||||
}
|
||||
],
|
||||
tags: [
|
||||
{
|
||||
name: '智能耳标预警',
|
||||
description: '智能耳标预警相关接口'
|
||||
},
|
||||
{
|
||||
name: '智能项圈预警',
|
||||
description: '智能项圈预警相关接口'
|
||||
}
|
||||
]
|
||||
},
|
||||
apis: ['./routes/smart-alerts.js']
|
||||
};
|
||||
|
||||
const specs = swaggerJSDoc(options);
|
||||
|
||||
// 手动添加API路径,确保它们出现在文档中
|
||||
if (!specs.paths) {
|
||||
specs.paths = {};
|
||||
}
|
||||
|
||||
// 智能耳标预警API路径
|
||||
specs.paths['/smart-alerts/public/eartag/stats'] = {
|
||||
get: {
|
||||
tags: ['智能耳标预警'],
|
||||
summary: '获取智能耳标预警统计',
|
||||
description: '获取智能耳标预警的统计数据,包括各类预警的数量和设备总数',
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取统计成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'object' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/eartag'] = {
|
||||
get: {
|
||||
tags: ['智能耳标预警'],
|
||||
summary: '获取智能耳标预警列表',
|
||||
description: '获取智能耳标预警列表,支持分页、搜索和筛选',
|
||||
parameters: [
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 1 },
|
||||
description: '页码'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '每页数量'
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词'
|
||||
},
|
||||
{
|
||||
name: 'alertType',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['battery', 'offline', 'temperature', 'movement'] },
|
||||
description: '预警类型筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取列表成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'array', items: { type: 'object' } },
|
||||
total: { type: 'integer' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/eartag/{id}'] = {
|
||||
get: {
|
||||
tags: ['智能耳标预警'],
|
||||
summary: '获取单个智能耳标预警详情',
|
||||
description: '获取指定ID的智能耳标预警详细信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
description: '预警ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取详情成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'object' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/eartag/{id}/handle'] = {
|
||||
post: {
|
||||
tags: ['智能耳标预警'],
|
||||
summary: '处理智能耳标预警',
|
||||
description: '处理指定的智能耳标预警',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
description: '预警ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: { type: 'string', description: '处理动作' },
|
||||
notes: { type: 'string', description: '处理备注' },
|
||||
handler: { type: 'string', description: '处理人' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '处理成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'object' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/eartag/batch-handle'] = {
|
||||
post: {
|
||||
tags: ['智能耳标预警'],
|
||||
summary: '批量处理智能耳标预警',
|
||||
description: '批量处理多个智能耳标预警',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['alertIds'],
|
||||
properties: {
|
||||
alertIds: { type: 'array', items: { type: 'string' }, description: '预警ID列表' },
|
||||
action: { type: 'string', description: '处理动作' },
|
||||
notes: { type: 'string', description: '处理备注' },
|
||||
handler: { type: 'string', description: '处理人' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '批量处理成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'object' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/eartag/export'] = {
|
||||
get: {
|
||||
tags: ['智能耳标预警'],
|
||||
summary: '导出智能耳标预警数据',
|
||||
description: '导出智能耳标预警数据,支持JSON和CSV格式',
|
||||
parameters: [
|
||||
{
|
||||
name: 'format',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['json', 'csv'], default: 'json' },
|
||||
description: '导出格式'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '导出成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'array', items: { type: 'object' } },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 智能项圈预警API路径
|
||||
specs.paths['/smart-alerts/public/collar/stats'] = {
|
||||
get: {
|
||||
tags: ['智能项圈预警'],
|
||||
summary: '获取智能项圈预警统计',
|
||||
description: '获取智能项圈预警的统计数据,包括各类预警的数量和设备总数',
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取统计成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'object' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/collar'] = {
|
||||
get: {
|
||||
tags: ['智能项圈预警'],
|
||||
summary: '获取智能项圈预警列表',
|
||||
description: '获取智能项圈预警列表,支持分页、搜索和筛选',
|
||||
parameters: [
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 1 },
|
||||
description: '页码'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '每页数量'
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词'
|
||||
},
|
||||
{
|
||||
name: 'alertType',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['battery', 'offline', 'temperature', 'movement', 'wear'] },
|
||||
description: '预警类型筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取列表成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'array', items: { type: 'object' } },
|
||||
total: { type: 'integer' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/collar/{id}'] = {
|
||||
get: {
|
||||
tags: ['智能项圈预警'],
|
||||
summary: '获取单个智能项圈预警详情',
|
||||
description: '获取指定ID的智能项圈预警详细信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
description: '预警ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取详情成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'object' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/collar/{id}/handle'] = {
|
||||
post: {
|
||||
tags: ['智能项圈预警'],
|
||||
summary: '处理智能项圈预警',
|
||||
description: '处理指定的智能项圈预警',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
description: '预警ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: { type: 'string', description: '处理动作' },
|
||||
notes: { type: 'string', description: '处理备注' },
|
||||
handler: { type: 'string', description: '处理人' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '处理成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'object' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/collar/batch-handle'] = {
|
||||
post: {
|
||||
tags: ['智能项圈预警'],
|
||||
summary: '批量处理智能项圈预警',
|
||||
description: '批量处理多个智能项圈预警',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['alertIds'],
|
||||
properties: {
|
||||
alertIds: { type: 'array', items: { type: 'string' }, description: '预警ID列表' },
|
||||
action: { type: 'string', description: '处理动作' },
|
||||
notes: { type: 'string', description: '处理备注' },
|
||||
handler: { type: 'string', description: '处理人' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '批量处理成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'object' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
specs.paths['/smart-alerts/public/collar/export'] = {
|
||||
get: {
|
||||
tags: ['智能项圈预警'],
|
||||
summary: '导出智能项圈预警数据',
|
||||
description: '导出智能项圈预警数据,支持JSON和CSV格式',
|
||||
parameters: [
|
||||
{
|
||||
name: 'format',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['json', 'csv'], default: 'json' },
|
||||
description: '导出格式'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '导出成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
data: { type: 'array', items: { type: 'object' } },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = specs;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,521 +0,0 @@
|
||||
/**
|
||||
* 用户管理模块 Swagger 文档
|
||||
* @file swagger-users.js
|
||||
*/
|
||||
|
||||
const usersPaths = {
|
||||
// 获取所有用户
|
||||
'/users': {
|
||||
get: {
|
||||
tags: ['用户管理'],
|
||||
summary: '获取用户列表',
|
||||
description: '分页获取系统中的所有用户',
|
||||
parameters: [
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 1 },
|
||||
description: '页码'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '每页数量'
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词(用户名、邮箱、手机号)'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['active', 'inactive', 'banned'] },
|
||||
description: '用户状态筛选'
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '角色筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/User' }
|
||||
},
|
||||
pagination: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
page: { type: 'integer' },
|
||||
limit: { type: 'integer' },
|
||||
total: { type: 'integer' },
|
||||
totalPages: { type: 'integer' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
post: {
|
||||
tags: ['用户管理'],
|
||||
summary: '创建新用户',
|
||||
description: '管理员创建新用户账号',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['username', 'email', 'password'],
|
||||
properties: {
|
||||
username: { type: 'string', description: '用户名' },
|
||||
email: { type: 'string', format: 'email', description: '邮箱' },
|
||||
password: { type: 'string', minLength: 6, description: '密码' },
|
||||
phone: { type: 'string', description: '手机号' },
|
||||
realName: { type: 'string', description: '真实姓名' },
|
||||
avatar: { type: 'string', description: '头像URL' },
|
||||
status: { type: 'string', enum: ['active', 'inactive'], default: 'active' },
|
||||
roleIds: { type: 'array', items: { type: 'integer' }, description: '角色ID列表' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'201': {
|
||||
description: '创建成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '用户创建成功' },
|
||||
data: { $ref: '#/components/schemas/User' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误或用户已存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 根据用户名搜索用户
|
||||
'/users/search': {
|
||||
get: {
|
||||
tags: ['用户管理'],
|
||||
summary: '搜索用户',
|
||||
description: '根据用户名、邮箱或手机号搜索用户',
|
||||
parameters: [
|
||||
{
|
||||
name: 'q',
|
||||
in: 'query',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
description: '搜索关键词'
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
in: 'query',
|
||||
schema: { type: 'integer', default: 10 },
|
||||
description: '返回结果数量限制'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '搜索成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: '#/components/schemas/User' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 获取指定用户详情
|
||||
'/users/{id}': {
|
||||
get: {
|
||||
tags: ['用户管理'],
|
||||
summary: '获取用户详情',
|
||||
description: '根据用户ID获取用户详细信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '用户ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '获取成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
data: { $ref: '#/components/schemas/User' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '用户不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
put: {
|
||||
tags: ['用户管理'],
|
||||
summary: '更新用户信息',
|
||||
description: '更新指定用户的信息',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '用户ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: { type: 'string', description: '用户名' },
|
||||
email: { type: 'string', format: 'email', description: '邮箱' },
|
||||
phone: { type: 'string', description: '手机号' },
|
||||
realName: { type: 'string', description: '真实姓名' },
|
||||
avatar: { type: 'string', description: '头像URL' },
|
||||
status: { type: 'string', enum: ['active', 'inactive', 'banned'] },
|
||||
roleIds: { type: 'array', items: { type: 'integer' }, description: '角色ID列表' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '更新成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '用户信息更新成功' },
|
||||
data: { $ref: '#/components/schemas/User' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'400': {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '用户不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
tags: ['用户管理'],
|
||||
summary: '删除用户',
|
||||
description: '删除指定用户(软删除)',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '用户ID'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '删除成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '用户删除成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '用户不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 重置用户密码
|
||||
'/users/{id}/reset-password': {
|
||||
post: {
|
||||
tags: ['用户管理'],
|
||||
summary: '重置用户密码',
|
||||
description: '管理员重置指定用户的密码',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: '用户ID'
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['newPassword'],
|
||||
properties: {
|
||||
newPassword: {
|
||||
type: 'string',
|
||||
minLength: 6,
|
||||
description: '新密码'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '密码重置成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '密码重置成功' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'404': {
|
||||
description: '用户不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 批量操作用户
|
||||
'/users/batch': {
|
||||
post: {
|
||||
tags: ['用户管理'],
|
||||
summary: '批量操作用户',
|
||||
description: '批量启用、禁用或删除用户',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['userIds', 'action'],
|
||||
properties: {
|
||||
userIds: {
|
||||
type: 'array',
|
||||
items: { type: 'integer' },
|
||||
description: '用户ID列表'
|
||||
},
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['activate', 'deactivate', 'ban', 'delete'],
|
||||
description: '操作类型'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
'200': {
|
||||
description: '批量操作成功',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
message: { type: 'string', example: '批量操作完成' },
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
successCount: { type: 'integer', description: '成功处理的用户数量' },
|
||||
failedCount: { type: 'integer', description: '处理失败的用户数量' },
|
||||
errors: { type: 'array', items: { type: 'string' }, description: '错误信息列表' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 导出用户数据
|
||||
'/users/export': {
|
||||
get: {
|
||||
tags: ['用户管理'],
|
||||
summary: '导出用户数据',
|
||||
description: '导出用户数据为Excel文件',
|
||||
parameters: [
|
||||
{
|
||||
name: 'format',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['xlsx', 'csv'], default: 'xlsx' },
|
||||
description: '导出格式'
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
schema: { type: 'string', enum: ['active', 'inactive', 'banned'] },
|
||||
description: '用户状态筛选'
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
in: 'query',
|
||||
schema: { type: 'string' },
|
||||
description: '角色筛选'
|
||||
}
|
||||
],
|
||||
responses: {
|
||||
'200': {
|
||||
description: '导出成功',
|
||||
content: {
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
format: 'binary'
|
||||
}
|
||||
},
|
||||
'text/csv': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
format: 'binary'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 用户数据模型
|
||||
const userSchemas = {
|
||||
User: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', description: '用户ID' },
|
||||
username: { type: 'string', description: '用户名' },
|
||||
email: { type: 'string', format: 'email', description: '邮箱' },
|
||||
phone: { type: 'string', description: '手机号' },
|
||||
realName: { type: 'string', description: '真实姓名' },
|
||||
avatar: { type: 'string', description: '头像URL' },
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['active', 'inactive', 'banned'],
|
||||
description: '用户状态:active-活跃,inactive-未激活,banned-已封禁'
|
||||
},
|
||||
roles: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
name: { type: 'string' },
|
||||
description: { type: 'string' }
|
||||
}
|
||||
},
|
||||
description: '用户角色列表'
|
||||
},
|
||||
permissions: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: '用户权限列表'
|
||||
},
|
||||
lastLoginAt: { type: 'string', format: 'date-time', description: '最后登录时间' },
|
||||
createdAt: { type: 'string', format: 'date-time', description: '创建时间' },
|
||||
updatedAt: { type: 'string', format: 'date-time', description: '更新时间' }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { usersPaths, userSchemas };
|
||||
@@ -1,273 +0,0 @@
|
||||
/**
|
||||
* 预警检测逻辑测试
|
||||
* @file test-alert-detection-logic.js
|
||||
* @description 测试智能项圈预警的自动检测逻辑
|
||||
*/
|
||||
|
||||
// 模拟前端判断函数
|
||||
function determineAlertType(record) {
|
||||
const alerts = []
|
||||
|
||||
// 检查电量预警
|
||||
if (record.battery !== undefined && record.battery !== null && record.battery < 20) {
|
||||
alerts.push('battery')
|
||||
}
|
||||
|
||||
// 检查脱落预警 (bandge_status为0)
|
||||
if (record.bandge_status !== undefined && record.bandge_status !== null && record.bandge_status === 0) {
|
||||
alerts.push('wear')
|
||||
}
|
||||
|
||||
// 检查离线预警 (is_connect为0)
|
||||
if (record.is_connect !== undefined && record.is_connect !== null && record.is_connect === 0) {
|
||||
alerts.push('offline')
|
||||
}
|
||||
|
||||
// 检查温度预警
|
||||
if (record.temperature !== undefined && record.temperature !== null) {
|
||||
if (record.temperature < 20) {
|
||||
alerts.push('temperature_low')
|
||||
} else if (record.temperature > 40) {
|
||||
alerts.push('temperature_high')
|
||||
}
|
||||
}
|
||||
|
||||
// 检查运动异常预警 (steps - y_steps为0)
|
||||
if (record.steps !== undefined && record.y_steps !== undefined &&
|
||||
record.steps !== null && record.y_steps !== null) {
|
||||
const movementDiff = record.steps - record.y_steps
|
||||
if (movementDiff === 0) {
|
||||
alerts.push('movement')
|
||||
}
|
||||
}
|
||||
|
||||
// 返回第一个预警类型,如果没有预警则返回null
|
||||
return alerts.length > 0 ? alerts[0] : null
|
||||
}
|
||||
|
||||
// 获取预警类型文本
|
||||
function getAlertTypeText(type) {
|
||||
const typeMap = {
|
||||
'battery': '低电量预警',
|
||||
'offline': '离线预警',
|
||||
'temperature_low': '温度过低预警',
|
||||
'temperature_high': '温度过高预警',
|
||||
'movement': '异常运动预警',
|
||||
'wear': '佩戴异常预警'
|
||||
}
|
||||
return typeMap[type] || '未知预警'
|
||||
}
|
||||
|
||||
// 测试数据
|
||||
const testCases = [
|
||||
{
|
||||
name: '正常设备',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 25,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: null
|
||||
},
|
||||
{
|
||||
name: '低电量预警',
|
||||
data: {
|
||||
battery: 15,
|
||||
temperature: 25,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: 'battery'
|
||||
},
|
||||
{
|
||||
name: '离线预警',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 25,
|
||||
is_connect: 0,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: 'offline'
|
||||
},
|
||||
{
|
||||
name: '佩戴异常预警',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 25,
|
||||
is_connect: 1,
|
||||
bandge_status: 0,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: 'wear'
|
||||
},
|
||||
{
|
||||
name: '温度过低预警',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 15,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: 'temperature_low'
|
||||
},
|
||||
{
|
||||
name: '温度过高预警',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 45,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: 'temperature_high'
|
||||
},
|
||||
{
|
||||
name: '异常运动预警',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 25,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 1000
|
||||
},
|
||||
expected: 'movement'
|
||||
},
|
||||
{
|
||||
name: '多重预警(低电量+离线)',
|
||||
data: {
|
||||
battery: 15,
|
||||
temperature: 25,
|
||||
is_connect: 0,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: 'battery' // 应该返回第一个预警
|
||||
},
|
||||
{
|
||||
name: '边界值测试 - 电量20',
|
||||
data: {
|
||||
battery: 20,
|
||||
temperature: 25,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: null // 20不算低电量
|
||||
},
|
||||
{
|
||||
name: '边界值测试 - 电量19',
|
||||
data: {
|
||||
battery: 19,
|
||||
temperature: 25,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: 'battery' // 19算低电量
|
||||
},
|
||||
{
|
||||
name: '边界值测试 - 温度20',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 20,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: null // 20不算温度过低
|
||||
},
|
||||
{
|
||||
name: '边界值测试 - 温度19',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 19,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: 'temperature_low' // 19算温度过低
|
||||
},
|
||||
{
|
||||
name: '边界值测试 - 温度40',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 40,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: null // 40不算温度过高
|
||||
},
|
||||
{
|
||||
name: '边界值测试 - 温度41',
|
||||
data: {
|
||||
battery: 85,
|
||||
temperature: 41,
|
||||
is_connect: 1,
|
||||
bandge_status: 1,
|
||||
steps: 1000,
|
||||
y_steps: 500
|
||||
},
|
||||
expected: 'temperature_high' // 41算温度过高
|
||||
}
|
||||
];
|
||||
|
||||
// 运行测试
|
||||
function runTests() {
|
||||
console.log('🧪 开始测试预警检测逻辑...\n');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
testCases.forEach((testCase, index) => {
|
||||
const result = determineAlertType(testCase.data);
|
||||
const expected = testCase.expected;
|
||||
const success = result === expected;
|
||||
|
||||
console.log(`测试 ${index + 1}: ${testCase.name}`);
|
||||
console.log(` 输入数据:`, testCase.data);
|
||||
console.log(` 预期结果: ${expected ? getAlertTypeText(expected) : '正常'}`);
|
||||
console.log(` 实际结果: ${result ? getAlertTypeText(result) : '正常'}`);
|
||||
console.log(` 测试结果: ${success ? '✅ 通过' : '❌ 失败'}`);
|
||||
console.log('');
|
||||
|
||||
if (success) {
|
||||
passed++;
|
||||
} else {
|
||||
failed++;
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📊 测试总结:');
|
||||
console.log(` 总测试数: ${testCases.length}`);
|
||||
console.log(` 通过: ${passed}`);
|
||||
console.log(` 失败: ${failed}`);
|
||||
console.log(` 成功率: ${((passed / testCases.length) * 100).toFixed(1)}%`);
|
||||
|
||||
if (failed === 0) {
|
||||
console.log('\n🎉 所有测试通过!预警检测逻辑工作正常。');
|
||||
} else {
|
||||
console.log('\n⚠️ 有测试失败,请检查预警检测逻辑。');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTests();
|
||||
@@ -1,229 +0,0 @@
|
||||
/**
|
||||
* 智能预警API综合测试脚本
|
||||
* @file test-all-smart-alert-apis.js
|
||||
* @description 测试智能耳标预警和智能项圈预警的所有API接口功能
|
||||
*/
|
||||
|
||||
const eartagTests = require('./test-smart-eartag-alert-api');
|
||||
const collarTests = require('./test-smart-collar-alert-api');
|
||||
|
||||
// 测试结果统计
|
||||
let allTestResults = {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
errors: []
|
||||
};
|
||||
|
||||
// 测试辅助函数
|
||||
function logTest(testName, success, message = '') {
|
||||
allTestResults.total++;
|
||||
if (success) {
|
||||
allTestResults.passed++;
|
||||
console.log(`✅ ${testName}: ${message}`);
|
||||
} else {
|
||||
allTestResults.failed++;
|
||||
allTestResults.errors.push(`${testName}: ${message}`);
|
||||
console.log(`❌ ${testName}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 综合测试函数
|
||||
async function runComprehensiveTests() {
|
||||
console.log('🚀 开始智能预警API综合测试...\n');
|
||||
console.log('='.repeat(60));
|
||||
console.log('📋 测试范围:');
|
||||
console.log(' - 智能耳标预警API (6个接口)');
|
||||
console.log(' - 智能项圈预警API (6个接口)');
|
||||
console.log(' - 错误处理和边界条件测试');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
try {
|
||||
// 测试智能耳标预警API
|
||||
console.log('\n🔵 测试智能耳标预警API...');
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
const eartagResults = await runEartagTests();
|
||||
logTest('智能耳标预警API测试', eartagResults.failed === 0,
|
||||
`通过 ${eartagResults.passed}/${eartagResults.total} 项测试`);
|
||||
|
||||
// 测试智能项圈预警API
|
||||
console.log('\n🟢 测试智能项圈预警API...');
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
const collarResults = await runCollarTests();
|
||||
logTest('智能项圈预警API测试', collarResults.failed === 0,
|
||||
`通过 ${collarResults.passed}/${collarResults.total} 项测试`);
|
||||
|
||||
// 测试API文档访问
|
||||
console.log('\n📚 测试API文档访问...');
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
await testApiDocumentation();
|
||||
|
||||
// 输出综合测试结果
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('📊 综合测试结果汇总:');
|
||||
console.log(`总测试数: ${allTestResults.total}`);
|
||||
console.log(`通过: ${allTestResults.passed} ✅`);
|
||||
console.log(`失败: ${allTestResults.failed} ❌`);
|
||||
console.log(`成功率: ${((allTestResults.passed / allTestResults.total) * 100).toFixed(2)}%`);
|
||||
|
||||
if (allTestResults.errors.length > 0) {
|
||||
console.log('\n❌ 失败详情:');
|
||||
allTestResults.errors.forEach((error, index) => {
|
||||
console.log(`${index + 1}. ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (allTestResults.failed === 0) {
|
||||
console.log('\n🎉 所有测试通过!智能预警API系统功能完全正常。');
|
||||
console.log('\n📖 API文档访问地址: http://localhost:5350/api-docs');
|
||||
console.log('🔗 基础API地址: http://localhost:5350/api/smart-alerts/public');
|
||||
} else {
|
||||
console.log('\n⚠️ 部分测试失败,请检查相关功能。');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 综合测试执行异常:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行智能耳标预警测试
|
||||
async function runEartagTests() {
|
||||
const results = { total: 0, passed: 0, failed: 0 };
|
||||
|
||||
try {
|
||||
await eartagTests.testGetEartagAlertStats();
|
||||
await eartagTests.testGetEartagAlerts();
|
||||
await eartagTests.testGetEartagAlertsWithFilters();
|
||||
await eartagTests.testGetEartagAlertById();
|
||||
await eartagTests.testHandleEartagAlert();
|
||||
await eartagTests.testBatchHandleEartagAlerts();
|
||||
await eartagTests.testExportEartagAlerts();
|
||||
await eartagTests.testErrorHandling();
|
||||
|
||||
// 这里需要从eartagTests模块获取结果,但由于模块结构限制,我们使用模拟数据
|
||||
results.total = 8;
|
||||
results.passed = 8; // 假设都通过
|
||||
results.failed = 0;
|
||||
|
||||
} catch (error) {
|
||||
console.error('智能耳标预警测试异常:', error.message);
|
||||
results.failed++;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 运行智能项圈预警测试
|
||||
async function runCollarTests() {
|
||||
const results = { total: 0, passed: 0, failed: 0 };
|
||||
|
||||
try {
|
||||
await collarTests.testGetCollarAlertStats();
|
||||
await collarTests.testGetCollarAlerts();
|
||||
await collarTests.testGetCollarAlertsWithFilters();
|
||||
await collarTests.testGetCollarAlertById();
|
||||
await collarTests.testHandleCollarAlert();
|
||||
await collarTests.testBatchHandleCollarAlerts();
|
||||
await collarTests.testExportCollarAlerts();
|
||||
await collarTests.testErrorHandling();
|
||||
|
||||
// 这里需要从collarTests模块获取结果,但由于模块结构限制,我们使用模拟数据
|
||||
results.total = 8;
|
||||
results.passed = 8; // 假设都通过
|
||||
results.failed = 0;
|
||||
|
||||
} catch (error) {
|
||||
console.error('智能项圈预警测试异常:', error.message);
|
||||
results.failed++;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 测试API文档访问
|
||||
async function testApiDocumentation() {
|
||||
try {
|
||||
const axios = require('axios');
|
||||
|
||||
// 测试Swagger JSON文档
|
||||
const swaggerResponse = await axios.get('http://localhost:5350/api-docs/swagger.json', {
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
if (swaggerResponse.status === 200) {
|
||||
const swaggerSpec = swaggerResponse.data;
|
||||
const hasEartagPaths = swaggerSpec.paths && Object.keys(swaggerSpec.paths).some(path => path.includes('/eartag'));
|
||||
const hasCollarPaths = swaggerSpec.paths && Object.keys(swaggerSpec.paths).some(path => path.includes('/collar'));
|
||||
|
||||
logTest('Swagger JSON文档', true, '成功获取API文档规范');
|
||||
logTest('耳标预警API文档', hasEartagPaths, hasEartagPaths ? '包含耳标预警API路径' : '缺少耳标预警API路径');
|
||||
logTest('项圈预警API文档', hasCollarPaths, hasCollarPaths ? '包含项圈预警API路径' : '缺少项圈预警API路径');
|
||||
} else {
|
||||
logTest('Swagger JSON文档', false, `获取失败: HTTP ${swaggerResponse.status}`);
|
||||
}
|
||||
|
||||
// 测试Swagger UI界面
|
||||
const uiResponse = await axios.get('http://localhost:5350/api-docs/', {
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
if (uiResponse.status === 200) {
|
||||
logTest('Swagger UI界面', true, '成功访问API文档界面');
|
||||
} else {
|
||||
logTest('Swagger UI界面', false, `访问失败: HTTP ${uiResponse.status}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logTest('API文档测试', false, `测试异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 性能测试
|
||||
async function runPerformanceTests() {
|
||||
console.log('\n⚡ 性能测试...');
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
try {
|
||||
const axios = require('axios');
|
||||
const startTime = Date.now();
|
||||
|
||||
// 并发测试多个API
|
||||
const promises = [
|
||||
axios.get('http://localhost:5350/api/smart-alerts/public/eartag/stats'),
|
||||
axios.get('http://localhost:5350/api/smart-alerts/public/collar/stats'),
|
||||
axios.get('http://localhost:5350/api/smart-alerts/public/eartag?limit=5'),
|
||||
axios.get('http://localhost:5350/api/smart-alerts/public/collar?limit=5')
|
||||
];
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
const allSuccessful = results.every(response => response.status === 200);
|
||||
logTest('并发API性能测试', allSuccessful, `4个API并发请求完成,耗时 ${duration}ms`);
|
||||
|
||||
if (duration < 2000) {
|
||||
logTest('响应时间测试', true, `响应时间良好: ${duration}ms`);
|
||||
} else {
|
||||
logTest('响应时间测试', false, `响应时间较慢: ${duration}ms`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logTest('性能测试', false, `测试异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
runComprehensiveTests()
|
||||
.then(() => runPerformanceTests())
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runComprehensiveTests,
|
||||
runPerformanceTests
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* API访问测试脚本
|
||||
* @file test-api-access.js
|
||||
* @description 测试API接口是否正常访问
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
async function testApiAccess() {
|
||||
console.log('🔍 测试API访问...\n');
|
||||
|
||||
const baseUrl = 'http://localhost:5350';
|
||||
|
||||
try {
|
||||
// 1. 测试服务器根路径
|
||||
console.log('1. 测试服务器根路径...');
|
||||
const rootResponse = await axios.get(`${baseUrl}/`);
|
||||
console.log('✅ 服务器根路径正常:', rootResponse.data);
|
||||
|
||||
// 2. 测试API文档访问
|
||||
console.log('\n2. 测试API文档访问...');
|
||||
const docsResponse = await axios.get(`${baseUrl}/api-docs/`);
|
||||
console.log('✅ API文档页面正常访问');
|
||||
|
||||
// 3. 测试Swagger JSON
|
||||
console.log('\n3. 测试Swagger JSON...');
|
||||
const swaggerResponse = await axios.get(`${baseUrl}/api-docs/swagger.json`);
|
||||
console.log('✅ Swagger JSON正常:', swaggerResponse.data.info.title);
|
||||
|
||||
// 4. 测试智能耳标预警API
|
||||
console.log('\n4. 测试智能耳标预警API...');
|
||||
const eartagStatsResponse = await axios.get(`${baseUrl}/api/smart-alerts/public/eartag/stats`);
|
||||
console.log('✅ 智能耳标预警统计API正常:', eartagStatsResponse.data.success);
|
||||
|
||||
// 5. 测试智能项圈预警API
|
||||
console.log('\n5. 测试智能项圈预警API...');
|
||||
const collarStatsResponse = await axios.get(`${baseUrl}/api/smart-alerts/public/collar/stats`);
|
||||
console.log('✅ 智能项圈预警统计API正常:', collarStatsResponse.data.success);
|
||||
|
||||
// 6. 检查Swagger JSON中的路径
|
||||
console.log('\n6. 检查Swagger JSON中的路径...');
|
||||
const paths = Object.keys(swaggerResponse.data.paths || {});
|
||||
const smartAlertPaths = paths.filter(path => path.includes('/smart-alerts/public'));
|
||||
console.log('📋 找到的智能预警API路径:');
|
||||
smartAlertPaths.forEach(path => {
|
||||
console.log(` - ${path}`);
|
||||
});
|
||||
|
||||
if (smartAlertPaths.length === 0) {
|
||||
console.log('❌ 未找到智能预警API路径,可能是Swagger配置问题');
|
||||
} else {
|
||||
console.log(`✅ 找到 ${smartAlertPaths.length} 个智能预警API路径`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
console.log('💡 建议: 请确保服务器已启动 (npm start)');
|
||||
} else if (error.response) {
|
||||
console.log('💡 建议: 检查API路径和端口配置');
|
||||
console.log(' 状态码:', error.response.status);
|
||||
console.log(' 响应:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
testApiAccess().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = { testApiAccess };
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* 测试API响应
|
||||
* @file test-api-response.js
|
||||
* @description 测试智能项圈预警API是否返回正确的数据
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:5350/api/smart-alerts/public';
|
||||
|
||||
async function testApiResponse() {
|
||||
console.log('🔍 测试智能项圈预警API响应...\n');
|
||||
|
||||
try {
|
||||
// 1. 测试获取预警列表
|
||||
console.log('1. 测试获取预警列表...');
|
||||
const listResponse = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 5
|
||||
}
|
||||
});
|
||||
|
||||
console.log('API响应状态:', listResponse.status);
|
||||
console.log('API响应数据:', JSON.stringify(listResponse.data, null, 2));
|
||||
|
||||
if (listResponse.data.success) {
|
||||
const data = listResponse.data.data || [];
|
||||
console.log(`\n数据条数: ${data.length}`);
|
||||
|
||||
// 查找项圈22012000107的数据
|
||||
const targetCollar = data.find(item => item.collarNumber == 22012000107);
|
||||
if (targetCollar) {
|
||||
console.log('\n找到项圈22012000107的数据:');
|
||||
console.log('电量:', targetCollar.battery);
|
||||
console.log('温度:', targetCollar.temperature);
|
||||
console.log('预警类型:', targetCollar.alertType);
|
||||
console.log('预警级别:', targetCollar.alertLevel);
|
||||
console.log('完整数据:', JSON.stringify(targetCollar, null, 2));
|
||||
} else {
|
||||
console.log('\n未找到项圈22012000107的数据');
|
||||
console.log('可用的项圈编号:');
|
||||
data.forEach(item => {
|
||||
console.log(`- ${item.collarNumber}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 测试搜索特定项圈
|
||||
console.log('\n2. 测试搜索项圈22012000107...');
|
||||
const searchResponse = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: {
|
||||
search: '22012000107',
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
});
|
||||
|
||||
if (searchResponse.data.success) {
|
||||
const searchData = searchResponse.data.data || [];
|
||||
console.log(`搜索到 ${searchData.length} 条数据`);
|
||||
|
||||
searchData.forEach((item, index) => {
|
||||
console.log(`\n搜索结果${index + 1}:`);
|
||||
console.log('项圈编号:', item.collarNumber);
|
||||
console.log('电量:', item.battery);
|
||||
console.log('温度:', item.temperature);
|
||||
console.log('预警类型:', item.alertType);
|
||||
console.log('预警级别:', item.alertLevel);
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 测试统计数据
|
||||
console.log('\n3. 测试统计数据...');
|
||||
const statsResponse = await axios.get(`${BASE_URL}/collar/stats`);
|
||||
console.log('统计数据:', JSON.stringify(statsResponse.data, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ API测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应状态:', error.response.status);
|
||||
console.error('响应数据:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testApiResponse().catch(console.error);
|
||||
@@ -1,111 +0,0 @@
|
||||
/**
|
||||
* 测试智能项圈预警数据
|
||||
* @file test-collar-alert-data.js
|
||||
* @description 测试智能项圈预警API返回的数据
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:5350/api/smart-alerts/public';
|
||||
|
||||
async function testCollarAlertData() {
|
||||
console.log('🔍 测试智能项圈预警数据...\n');
|
||||
|
||||
try {
|
||||
// 1. 测试获取预警列表
|
||||
console.log('1. 获取预警列表数据...');
|
||||
const listResponse = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
});
|
||||
|
||||
console.log('API响应状态:', listResponse.status);
|
||||
console.log('API响应数据:', JSON.stringify(listResponse.data, null, 2));
|
||||
|
||||
if (listResponse.data.success) {
|
||||
const data = listResponse.data.data || [];
|
||||
console.log(`\n数据条数: ${data.length}`);
|
||||
|
||||
if (data.length > 0) {
|
||||
console.log('\n第一条数据示例:');
|
||||
console.log(JSON.stringify(data[0], null, 2));
|
||||
|
||||
// 测试判断函数
|
||||
console.log('\n测试预警判断逻辑:');
|
||||
const testRecord = data[0];
|
||||
const alertType = determineAlertType(testRecord);
|
||||
console.log('判断结果:', alertType);
|
||||
|
||||
// 显示各字段值
|
||||
console.log('\n字段值检查:');
|
||||
console.log('battery:', testRecord.battery, typeof testRecord.battery);
|
||||
console.log('temperature:', testRecord.temperature, typeof testRecord.temperature);
|
||||
console.log('is_connect:', testRecord.is_connect, typeof testRecord.is_connect);
|
||||
console.log('bandge_status:', testRecord.bandge_status, typeof testRecord.bandge_status);
|
||||
console.log('steps:', testRecord.steps, typeof testRecord.steps);
|
||||
console.log('y_steps:', testRecord.y_steps, typeof testRecord.y_steps);
|
||||
} else {
|
||||
console.log('⚠️ 没有数据返回');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ API调用失败:', listResponse.data.message);
|
||||
}
|
||||
|
||||
// 2. 测试获取统计数据
|
||||
console.log('\n2. 获取统计数据...');
|
||||
const statsResponse = await axios.get(`${BASE_URL}/collar/stats`);
|
||||
console.log('统计数据:', JSON.stringify(statsResponse.data, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应状态:', error.response.status);
|
||||
console.error('响应数据:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断预警类型函数
|
||||
function determineAlertType(record) {
|
||||
const alerts = []
|
||||
|
||||
// 检查电量预警
|
||||
if (record.battery !== undefined && record.battery !== null && record.battery < 20) {
|
||||
alerts.push('battery')
|
||||
}
|
||||
|
||||
// 检查脱落预警 (bandge_status为0)
|
||||
if (record.bandge_status !== undefined && record.bandge_status !== null && record.bandge_status === 0) {
|
||||
alerts.push('wear')
|
||||
}
|
||||
|
||||
// 检查离线预警 (is_connect为0)
|
||||
if (record.is_connect !== undefined && record.is_connect !== null && record.is_connect === 0) {
|
||||
alerts.push('offline')
|
||||
}
|
||||
|
||||
// 检查温度预警
|
||||
if (record.temperature !== undefined && record.temperature !== null) {
|
||||
if (record.temperature < 20) {
|
||||
alerts.push('temperature_low')
|
||||
} else if (record.temperature > 40) {
|
||||
alerts.push('temperature_high')
|
||||
}
|
||||
}
|
||||
|
||||
// 检查运动异常预警 (steps - y_steps为0)
|
||||
if (record.steps !== undefined && record.y_steps !== undefined &&
|
||||
record.steps !== null && record.y_steps !== null) {
|
||||
const movementDiff = record.steps - record.y_steps
|
||||
if (movementDiff === 0) {
|
||||
alerts.push('movement')
|
||||
}
|
||||
}
|
||||
|
||||
return alerts.length > 0 ? alerts[0] : null
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testCollarAlertData().catch(console.error);
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* 直接测试API
|
||||
* @file test-direct-api.js
|
||||
* @description 直接测试智能项圈预警API,不通过HTTP请求
|
||||
*/
|
||||
|
||||
const { getCollarAlerts } = require('./controllers/smartCollarAlertController');
|
||||
|
||||
async function testDirectApi() {
|
||||
console.log('🔍 直接测试智能项圈预警API...\n');
|
||||
|
||||
try {
|
||||
// 模拟请求对象
|
||||
const mockReq = {
|
||||
query: {
|
||||
page: 1,
|
||||
limit: 5,
|
||||
search: '22012000107'
|
||||
}
|
||||
};
|
||||
|
||||
// 模拟响应对象
|
||||
const mockRes = {
|
||||
json: (data) => {
|
||||
console.log('API响应数据:');
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
|
||||
if (data.success && data.data) {
|
||||
const targetCollar = data.data.find(item => item.collarNumber == 22012000107);
|
||||
if (targetCollar) {
|
||||
console.log('\n找到项圈22012000107的数据:');
|
||||
console.log('电量:', targetCollar.battery);
|
||||
console.log('温度:', targetCollar.temperature);
|
||||
console.log('预警类型:', targetCollar.alertType);
|
||||
console.log('预警级别:', targetCollar.alertLevel);
|
||||
} else {
|
||||
console.log('\n未找到项圈22012000107的数据');
|
||||
console.log('可用的项圈编号:');
|
||||
data.data.forEach(item => {
|
||||
console.log(`- ${item.collarNumber}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
status: (code) => ({
|
||||
json: (data) => {
|
||||
console.log('错误响应:', code, data);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// 调用API函数
|
||||
await getCollarAlerts(mockReq, mockRes);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testDirectApi().catch(console.error);
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* 测试错误修复
|
||||
* @file test-error-fix.js
|
||||
* @description 测试修复后的智能项圈预警页面
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:5350/api/smart-alerts/public';
|
||||
|
||||
async function testErrorFix() {
|
||||
console.log('🔧 测试错误修复...\n');
|
||||
|
||||
try {
|
||||
// 获取预警列表数据
|
||||
const response = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: { page: 1, limit: 3 }
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
const data = response.data.data || [];
|
||||
const stats = response.data.stats || {};
|
||||
|
||||
console.log('✅ API调用成功');
|
||||
console.log(`数据条数: ${data.length}`);
|
||||
console.log('统计数据:', stats);
|
||||
|
||||
// 模拟前端数据转换逻辑
|
||||
console.log('\n🔄 模拟前端数据转换...');
|
||||
|
||||
data.forEach((item, index) => {
|
||||
console.log(`\n处理第${index + 1}条数据:`);
|
||||
console.log('原始数据:', {
|
||||
id: item.id,
|
||||
alertType: item.alertType,
|
||||
alertLevel: item.alertLevel,
|
||||
collarNumber: item.collarNumber,
|
||||
battery: item.battery,
|
||||
temperature: item.temperature
|
||||
});
|
||||
|
||||
// 模拟前端转换逻辑
|
||||
let alertTypeText = '正常'
|
||||
let alertLevel = 'low'
|
||||
let determinedAlertType = null
|
||||
|
||||
if (item.alertType) {
|
||||
const alertTypeMap = {
|
||||
'battery': '低电量预警',
|
||||
'offline': '离线预警',
|
||||
'temperature': '温度预警',
|
||||
'temperature_low': '温度过低预警',
|
||||
'temperature_high': '温度过高预警',
|
||||
'movement': '异常运动预警',
|
||||
'wear': '佩戴异常预警'
|
||||
}
|
||||
alertTypeText = alertTypeMap[item.alertType] || item.alertType
|
||||
determinedAlertType = item.alertType
|
||||
|
||||
const alertLevelMap = {
|
||||
'high': '高级',
|
||||
'medium': '中级',
|
||||
'low': '低级',
|
||||
'critical': '紧急'
|
||||
}
|
||||
alertLevel = alertLevelMap[item.alertLevel] || item.alertLevel
|
||||
}
|
||||
|
||||
console.log('转换结果:', {
|
||||
alertTypeText,
|
||||
alertLevel,
|
||||
determinedAlertType
|
||||
});
|
||||
});
|
||||
|
||||
console.log('\n✅ 数据转换测试通过,没有ReferenceError');
|
||||
|
||||
} else {
|
||||
console.log('❌ API调用失败:', response.data.message);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应状态:', error.response.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testErrorFix().catch(console.error);
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* 测试修复后的智能项圈预警
|
||||
* @file test-fixed-collar-alert.js
|
||||
* @description 测试修复后的智能项圈预警数据展示
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:5350/api/smart-alerts/public';
|
||||
|
||||
async function testFixedCollarAlert() {
|
||||
console.log('🔧 测试修复后的智能项圈预警...\n');
|
||||
|
||||
try {
|
||||
// 1. 获取预警列表
|
||||
console.log('1. 获取预警列表...');
|
||||
const listResponse = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: { page: 1, limit: 5 }
|
||||
});
|
||||
|
||||
if (listResponse.data.success) {
|
||||
const data = listResponse.data.data || [];
|
||||
const stats = listResponse.data.stats || {};
|
||||
|
||||
console.log('✅ 数据获取成功');
|
||||
console.log(`数据条数: ${data.length}`);
|
||||
console.log('统计数据:', stats);
|
||||
|
||||
// 显示统计卡片数据
|
||||
console.log('\n📊 统计卡片数据:');
|
||||
console.log(`低电量预警: ${stats.lowBattery || 0}`);
|
||||
console.log(`离线预警: ${stats.offline || 0}`);
|
||||
console.log(`温度预警: ${stats.highTemperature || 0}`);
|
||||
console.log(`异常运动预警: ${stats.abnormalMovement || 0}`);
|
||||
console.log(`佩戴异常预警: ${stats.wearOff || 0}`);
|
||||
|
||||
// 显示前几条数据
|
||||
console.log('\n📋 预警列表数据:');
|
||||
data.slice(0, 3).forEach((item, index) => {
|
||||
console.log(`\n第${index + 1}条数据:`);
|
||||
console.log(` 项圈编号: ${item.collarNumber}`);
|
||||
console.log(` 预警类型: ${item.alertType}`);
|
||||
console.log(` 预警级别: ${item.alertLevel}`);
|
||||
console.log(` 设备电量: ${item.battery}%`);
|
||||
console.log(` 设备温度: ${item.temperature}°C`);
|
||||
console.log(` 当日步数: ${item.dailySteps}`);
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log('❌ 数据获取失败:', listResponse.data.message);
|
||||
}
|
||||
|
||||
// 2. 测试统计数据API
|
||||
console.log('\n2. 测试统计数据API...');
|
||||
const statsResponse = await axios.get(`${BASE_URL}/collar/stats`);
|
||||
|
||||
if (statsResponse.data.success) {
|
||||
const statsData = statsResponse.data.data || {};
|
||||
console.log('✅ 统计数据API正常');
|
||||
console.log('统计数据:', statsData);
|
||||
} else {
|
||||
console.log('❌ 统计数据API失败:', statsResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('\n🎉 测试完成!');
|
||||
console.log('\n💡 现在前端页面应该能正确显示:');
|
||||
console.log(' - 统计卡片显示非零数据');
|
||||
console.log(' - 预警列表显示正确的预警类型和级别');
|
||||
console.log(' - 数据来自API而不是硬编码');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应状态:', error.response.status);
|
||||
console.error('响应数据:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testFixedCollarAlert().catch(console.error);
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* 测试模型连接
|
||||
* @file test-model-connection.js
|
||||
* @description 测试IotXqClient模型是否从正确的数据库读取数据
|
||||
*/
|
||||
|
||||
const { IotXqClient } = require('./models');
|
||||
|
||||
async function testModelConnection() {
|
||||
console.log('🔍 测试IotXqClient模型连接...\n');
|
||||
|
||||
try {
|
||||
// 1. 测试数据库连接
|
||||
console.log('1. 测试数据库连接...');
|
||||
await IotXqClient.sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 2. 检查数据库配置
|
||||
console.log('\n2. 检查数据库配置...');
|
||||
const config = IotXqClient.sequelize.config;
|
||||
console.log('数据库配置:');
|
||||
console.log('主机:', config.host);
|
||||
console.log('端口:', config.port);
|
||||
console.log('数据库名:', config.database);
|
||||
console.log('用户名:', config.username);
|
||||
|
||||
// 3. 查询项圈22012000107的数据
|
||||
console.log('\n3. 查询项圈22012000107的数据...');
|
||||
const devices = await IotXqClient.findAll({
|
||||
where: {
|
||||
sn: '22012000107'
|
||||
},
|
||||
order: [['uptime', 'DESC']]
|
||||
});
|
||||
|
||||
console.log(`找到 ${devices.length} 条记录`);
|
||||
|
||||
devices.forEach((device, index) => {
|
||||
console.log(`\n记录${index + 1}:`);
|
||||
console.log('ID:', device.id);
|
||||
console.log('SN:', device.sn);
|
||||
console.log('设备ID:', device.deviceId);
|
||||
console.log('电量:', device.battery, '(类型:', typeof device.battery, ')');
|
||||
console.log('温度:', device.temperature, '(类型:', typeof device.temperature, ')');
|
||||
console.log('状态:', device.state);
|
||||
console.log('更新时间:', device.uptime);
|
||||
});
|
||||
|
||||
// 4. 查询所有项圈的最新数据
|
||||
console.log('\n4. 查询所有项圈的最新数据...');
|
||||
const allDevices = await IotXqClient.findAll({
|
||||
order: [['uptime', 'DESC']],
|
||||
limit: 10
|
||||
});
|
||||
|
||||
console.log('所有项圈的最新数据:');
|
||||
allDevices.forEach((device, index) => {
|
||||
console.log(`${index + 1}. SN: ${device.sn}, 电量: ${device.battery}, 温度: ${device.temperature}, 状态: ${device.state}`);
|
||||
});
|
||||
|
||||
// 5. 检查是否有电量为99的记录
|
||||
console.log('\n5. 检查是否有电量为99的记录...');
|
||||
const battery99Devices = await IotXqClient.findAll({
|
||||
where: {
|
||||
battery: '99'
|
||||
},
|
||||
order: [['uptime', 'DESC']],
|
||||
limit: 5
|
||||
});
|
||||
|
||||
console.log(`找到 ${battery99Devices.length} 条电量为99的记录`);
|
||||
battery99Devices.forEach((device, index) => {
|
||||
console.log(`${index + 1}. SN: ${device.sn}, 电量: ${device.battery}, 温度: ${device.temperature}, 状态: ${device.state}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testModelConnection().catch(console.error);
|
||||
@@ -1,33 +0,0 @@
|
||||
const { User, Role, Permission } = require('./models');
|
||||
|
||||
async function testModels() {
|
||||
try {
|
||||
console.log('测试模型关联...');
|
||||
|
||||
// 测试用户查询
|
||||
const user = await User.findByPk(1, {
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role',
|
||||
include: [{
|
||||
model: Permission,
|
||||
as: 'permissions',
|
||||
through: { attributes: [] }
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
if (user) {
|
||||
console.log('用户:', user.username);
|
||||
console.log('角色:', user.role ? user.role.name : '无');
|
||||
console.log('权限数量:', user.role && user.role.permissions ? user.role.permissions.length : 0);
|
||||
} else {
|
||||
console.log('未找到用户');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
testModels();
|
||||
@@ -1,49 +0,0 @@
|
||||
@echo off
|
||||
echo ========================================
|
||||
echo ngrok测试脚本(无需认证)
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
echo 注意:此脚本使用ngrok的免费版本
|
||||
echo 每次重启ngrok,URL会发生变化
|
||||
echo.
|
||||
|
||||
echo 选择要测试的服务:
|
||||
echo 1. 后端服务 (端口5350)
|
||||
echo 2. 前端服务 (端口5300)
|
||||
echo 3. 同时启动两个服务
|
||||
echo.
|
||||
|
||||
set /p choice="请输入选择 (1-3): "
|
||||
|
||||
if "%choice%"=="1" (
|
||||
echo 启动后端服务穿透...
|
||||
echo 请在新窗口中查看访问地址
|
||||
start "ngrok-backend" .\ngrok.exe http 5350
|
||||
) else if "%choice%"=="2" (
|
||||
echo 启动前端服务穿透...
|
||||
echo 请在新窗口中查看访问地址
|
||||
start "ngrok-frontend" .\ngrok.exe http 5300
|
||||
) else if "%choice%"=="3" (
|
||||
echo 启动后端服务穿透...
|
||||
start "ngrok-backend" .\ngrok.exe http 5350
|
||||
timeout /t 2 /nobreak >nul
|
||||
echo 启动前端服务穿透...
|
||||
start "ngrok-frontend" .\ngrok.exe http 5300
|
||||
) else (
|
||||
echo 无效选择
|
||||
goto end
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo ngrok已启动!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 请查看新打开的窗口获取访问地址
|
||||
echo 访问地址格式:https://xxxxx.ngrok.io
|
||||
echo.
|
||||
|
||||
:end
|
||||
echo 按任意键退出...
|
||||
pause >nul
|
||||
@@ -1,359 +0,0 @@
|
||||
/**
|
||||
* 智能项圈预警API测试脚本
|
||||
* @file test-smart-collar-alert-api.js
|
||||
* @description 测试智能项圈预警相关的API接口功能
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
// 配置基础URL
|
||||
const BASE_URL = 'http://localhost:5350/api/smart-alerts/public';
|
||||
|
||||
// 创建axios实例
|
||||
const api = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// 测试结果统计
|
||||
let testResults = {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
errors: []
|
||||
};
|
||||
|
||||
// 测试辅助函数
|
||||
function logTest(testName, success, message = '') {
|
||||
testResults.total++;
|
||||
if (success) {
|
||||
testResults.passed++;
|
||||
console.log(`✅ ${testName}: ${message}`);
|
||||
} else {
|
||||
testResults.failed++;
|
||||
testResults.errors.push(`${testName}: ${message}`);
|
||||
console.log(`❌ ${testName}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试函数
|
||||
async function testGetCollarAlertStats() {
|
||||
try {
|
||||
console.log('\n=== 测试获取智能项圈预警统计 ===');
|
||||
const response = await api.get('/collar/stats');
|
||||
|
||||
if (response.status === 200 && response.data.success) {
|
||||
const data = response.data.data;
|
||||
logTest('获取项圈预警统计', true, `成功获取统计,设备总数: ${data.totalDevices}, 预警总数: ${data.totalAlerts}`);
|
||||
|
||||
// 验证数据结构
|
||||
const requiredFields = ['totalDevices', 'lowBattery', 'offline', 'highTemperature', 'abnormalMovement', 'totalAlerts'];
|
||||
const hasAllFields = requiredFields.every(field => data.hasOwnProperty(field));
|
||||
logTest('统计数据结构验证', hasAllFields, hasAllFields ? '数据结构正确' : '缺少必要字段');
|
||||
|
||||
// 验证项圈特有字段
|
||||
const hasWearOffField = data.hasOwnProperty('wearOff');
|
||||
logTest('项圈特有字段验证', hasWearOffField, hasWearOffField ? '包含项圈脱落预警字段' : '缺少项圈脱落预警字段');
|
||||
} else {
|
||||
logTest('获取项圈预警统计', false, `请求失败: ${response.data.message || '未知错误'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('获取项圈预警统计', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetCollarAlerts() {
|
||||
try {
|
||||
console.log('\n=== 测试获取智能项圈预警列表 ===');
|
||||
|
||||
// 测试基础列表获取
|
||||
const response = await api.get('/collar?page=1&limit=5');
|
||||
|
||||
if (response.status === 200 && response.data.success) {
|
||||
const data = response.data;
|
||||
logTest('获取项圈预警列表', true, `成功获取列表,共 ${data.total} 条预警`);
|
||||
|
||||
// 验证分页信息
|
||||
const hasPagination = data.pagination && typeof data.pagination.page === 'number';
|
||||
logTest('分页信息验证', hasPagination, hasPagination ? '分页信息正确' : '分页信息缺失');
|
||||
|
||||
// 验证统计数据
|
||||
const hasStats = data.stats && typeof data.stats.lowBattery === 'number';
|
||||
logTest('统计信息验证', hasStats, hasStats ? '统计信息正确' : '统计信息缺失');
|
||||
|
||||
// 验证项圈特有字段
|
||||
if (data.data.length > 0) {
|
||||
const firstAlert = data.data[0];
|
||||
const hasCollarFields = firstAlert.hasOwnProperty('collarNumber') && firstAlert.hasOwnProperty('wearStatus');
|
||||
logTest('项圈字段验证', hasCollarFields, hasCollarFields ? '包含项圈特有字段' : '缺少项圈特有字段');
|
||||
}
|
||||
} else {
|
||||
logTest('获取项圈预警列表', false, `请求失败: ${response.data.message || '未知错误'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('获取项圈预警列表', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetCollarAlertsWithFilters() {
|
||||
try {
|
||||
console.log('\n=== 测试项圈预警列表筛选功能 ===');
|
||||
|
||||
// 测试按预警类型筛选
|
||||
const batteryResponse = await api.get('/collar?alertType=battery&limit=3');
|
||||
|
||||
if (batteryResponse.status === 200 && batteryResponse.data.success) {
|
||||
const batteryAlerts = batteryResponse.data.data;
|
||||
const allBattery = batteryAlerts.every(alert => alert.alertType === 'battery');
|
||||
logTest('按预警类型筛选', allBattery, `筛选结果: ${batteryAlerts.length} 条低电量预警`);
|
||||
} else {
|
||||
logTest('按预警类型筛选', false, `筛选失败: ${batteryResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
|
||||
// 测试项圈脱落预警筛选
|
||||
const wearResponse = await api.get('/collar?alertType=wear&limit=3');
|
||||
|
||||
if (wearResponse.status === 200 && wearResponse.data.success) {
|
||||
const wearAlerts = wearResponse.data.data;
|
||||
const allWear = wearAlerts.every(alert => alert.alertType === 'wear');
|
||||
logTest('项圈脱落预警筛选', allWear, `筛选结果: ${wearAlerts.length} 条项圈脱落预警`);
|
||||
} else {
|
||||
logTest('项圈脱落预警筛选', false, `筛选失败: ${wearResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
|
||||
// 测试搜索功能
|
||||
const searchResponse = await api.get('/collar?search=COLLAR&limit=3');
|
||||
|
||||
if (searchResponse.status === 200 && searchResponse.data.success) {
|
||||
const searchAlerts = searchResponse.data.data;
|
||||
logTest('搜索功能', true, `搜索到 ${searchAlerts.length} 条相关预警`);
|
||||
} else {
|
||||
logTest('搜索功能', false, `搜索失败: ${searchResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('筛选功能测试', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetCollarAlertById() {
|
||||
try {
|
||||
console.log('\n=== 测试获取单个项圈预警详情 ===');
|
||||
|
||||
// 首先获取一个预警ID
|
||||
const listResponse = await api.get('/collar?limit=1');
|
||||
|
||||
if (listResponse.status === 200 && listResponse.data.success && listResponse.data.data.length > 0) {
|
||||
const alertId = listResponse.data.data[0].id;
|
||||
|
||||
// 测试获取详情
|
||||
const detailResponse = await api.get(`/collar/${alertId}`);
|
||||
|
||||
if (detailResponse.status === 200 && detailResponse.data.success) {
|
||||
const alert = detailResponse.data.data;
|
||||
logTest('获取项圈预警详情', true, `成功获取预警 ${alertId} 的详情`);
|
||||
|
||||
// 验证详情数据结构
|
||||
const hasRequiredFields = alert.id && alert.alertType && alert.alertLevel;
|
||||
logTest('详情数据结构验证', hasRequiredFields, hasRequiredFields ? '详情数据结构正确' : '缺少必要字段');
|
||||
|
||||
// 验证项圈特有字段
|
||||
const hasCollarFields = alert.hasOwnProperty('collarNumber') && alert.hasOwnProperty('wearStatus');
|
||||
logTest('项圈详情字段验证', hasCollarFields, hasCollarFields ? '包含项圈特有字段' : '缺少项圈特有字段');
|
||||
} else {
|
||||
logTest('获取项圈预警详情', false, `获取详情失败: ${detailResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} else {
|
||||
logTest('获取项圈预警详情', false, '没有可用的预警数据用于测试');
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('获取项圈预警详情', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testHandleCollarAlert() {
|
||||
try {
|
||||
console.log('\n=== 测试处理项圈预警功能 ===');
|
||||
|
||||
// 首先获取一个预警ID
|
||||
const listResponse = await api.get('/collar?limit=1');
|
||||
|
||||
if (listResponse.status === 200 && listResponse.data.success && listResponse.data.data.length > 0) {
|
||||
const alertId = listResponse.data.data[0].id;
|
||||
|
||||
// 测试处理预警
|
||||
const handleData = {
|
||||
action: 'acknowledged',
|
||||
notes: 'API测试处理项圈预警',
|
||||
handler: 'test-user'
|
||||
};
|
||||
|
||||
const handleResponse = await api.post(`/collar/${alertId}/handle`, handleData);
|
||||
|
||||
if (handleResponse.status === 200 && handleResponse.data.success) {
|
||||
const result = handleResponse.data.data;
|
||||
logTest('处理项圈预警', true, `成功处理预警 ${alertId}`);
|
||||
|
||||
// 验证处理结果
|
||||
const hasProcessedInfo = result.alertId && result.action && result.processedAt;
|
||||
logTest('处理结果验证', hasProcessedInfo, hasProcessedInfo ? '处理结果正确' : '处理结果不完整');
|
||||
} else {
|
||||
logTest('处理项圈预警', false, `处理失败: ${handleResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} else {
|
||||
logTest('处理项圈预警', false, '没有可用的预警数据用于测试');
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('处理项圈预警', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testBatchHandleCollarAlerts() {
|
||||
try {
|
||||
console.log('\n=== 测试批量处理项圈预警功能 ===');
|
||||
|
||||
// 首先获取一些预警ID
|
||||
const listResponse = await api.get('/collar?limit=3');
|
||||
|
||||
if (listResponse.status === 200 && listResponse.data.success && listResponse.data.data.length > 0) {
|
||||
const alertIds = listResponse.data.data.map(alert => alert.id);
|
||||
|
||||
// 测试批量处理
|
||||
const batchData = {
|
||||
alertIds: alertIds,
|
||||
action: 'acknowledged',
|
||||
notes: 'API批量测试处理项圈预警',
|
||||
handler: 'test-user'
|
||||
};
|
||||
|
||||
const batchResponse = await api.post('/collar/batch-handle', batchData);
|
||||
|
||||
if (batchResponse.status === 200 && batchResponse.data.success) {
|
||||
const result = batchResponse.data.data;
|
||||
logTest('批量处理项圈预警', true, `成功批量处理 ${result.processedCount} 个预警`);
|
||||
|
||||
// 验证批量处理结果
|
||||
const hasBatchResult = result.processedCount > 0 && Array.isArray(result.results);
|
||||
logTest('批量处理结果验证', hasBatchResult, hasBatchResult ? '批量处理结果正确' : '批量处理结果不完整');
|
||||
} else {
|
||||
logTest('批量处理项圈预警', false, `批量处理失败: ${batchResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} else {
|
||||
logTest('批量处理项圈预警', false, '没有足够的预警数据用于测试');
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('批量处理项圈预警', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testExportCollarAlerts() {
|
||||
try {
|
||||
console.log('\n=== 测试导出项圈预警数据功能 ===');
|
||||
|
||||
// 测试JSON格式导出
|
||||
const exportResponse = await api.get('/collar/export?format=json&limit=5');
|
||||
|
||||
if (exportResponse.status === 200 && exportResponse.data.success) {
|
||||
const exportData = exportResponse.data.data;
|
||||
logTest('导出项圈预警数据', true, `成功导出 ${exportData.length} 条预警数据`);
|
||||
|
||||
// 验证导出数据
|
||||
const hasExportData = Array.isArray(exportData) && exportData.length > 0;
|
||||
logTest('导出数据验证', hasExportData, hasExportData ? '导出数据正确' : '导出数据为空');
|
||||
|
||||
// 验证项圈特有字段
|
||||
if (exportData.length > 0) {
|
||||
const firstAlert = exportData[0];
|
||||
const hasCollarFields = firstAlert.hasOwnProperty('collarNumber') && firstAlert.hasOwnProperty('wearStatus');
|
||||
logTest('导出数据项圈字段验证', hasCollarFields, hasCollarFields ? '导出数据包含项圈字段' : '导出数据缺少项圈字段');
|
||||
}
|
||||
} else {
|
||||
logTest('导出项圈预警数据', false, `导出失败: ${exportResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('导出项圈预警数据', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testErrorHandling() {
|
||||
try {
|
||||
console.log('\n=== 测试错误处理 ===');
|
||||
|
||||
// 测试无效的预警ID
|
||||
const invalidIdResponse = await api.get('/collar/invalid_id');
|
||||
|
||||
if (invalidIdResponse.status === 400 || invalidIdResponse.status === 404) {
|
||||
logTest('无效ID错误处理', true, '正确处理无效ID错误');
|
||||
} else {
|
||||
logTest('无效ID错误处理', false, '未正确处理无效ID错误');
|
||||
}
|
||||
|
||||
// 测试无效的筛选参数
|
||||
const invalidFilterResponse = await api.get('/collar?alertType=invalid_type');
|
||||
|
||||
if (invalidFilterResponse.status === 200) {
|
||||
logTest('无效筛选参数处理', true, '正确处理无效筛选参数');
|
||||
} else {
|
||||
logTest('无效筛选参数处理', false, '未正确处理无效筛选参数');
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('错误处理测试', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runAllTests() {
|
||||
console.log('🚀 开始智能项圈预警API测试...\n');
|
||||
|
||||
try {
|
||||
await testGetCollarAlertStats();
|
||||
await testGetCollarAlerts();
|
||||
await testGetCollarAlertsWithFilters();
|
||||
await testGetCollarAlertById();
|
||||
await testHandleCollarAlert();
|
||||
await testBatchHandleCollarAlerts();
|
||||
await testExportCollarAlerts();
|
||||
await testErrorHandling();
|
||||
|
||||
// 输出测试结果
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log('📊 测试结果汇总:');
|
||||
console.log(`总测试数: ${testResults.total}`);
|
||||
console.log(`通过: ${testResults.passed} ✅`);
|
||||
console.log(`失败: ${testResults.failed} ❌`);
|
||||
console.log(`成功率: ${((testResults.passed / testResults.total) * 100).toFixed(2)}%`);
|
||||
|
||||
if (testResults.errors.length > 0) {
|
||||
console.log('\n❌ 失败详情:');
|
||||
testResults.errors.forEach((error, index) => {
|
||||
console.log(`${index + 1}. ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (testResults.failed === 0) {
|
||||
console.log('\n🎉 所有测试通过!智能项圈预警API功能正常。');
|
||||
} else {
|
||||
console.log('\n⚠️ 部分测试失败,请检查相关功能。');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试执行异常:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
runAllTests().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runAllTests,
|
||||
testGetCollarAlertStats,
|
||||
testGetCollarAlerts,
|
||||
testGetCollarAlertById,
|
||||
testHandleCollarAlert,
|
||||
testBatchHandleCollarAlerts,
|
||||
testExportCollarAlerts
|
||||
};
|
||||
@@ -1,216 +0,0 @@
|
||||
/**
|
||||
* 智能项圈预警API集成测试
|
||||
* @file test-smart-collar-alert-integration.js
|
||||
* @description 测试智能项圈预警API的完整集成
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:5350/api/smart-alerts/public';
|
||||
|
||||
// 测试数据
|
||||
const testData = {
|
||||
collarNumber: 'TEST_COLLAR_001',
|
||||
alertType: 'battery',
|
||||
alertLevel: 'high',
|
||||
battery: 15,
|
||||
temperature: 25.5,
|
||||
dailySteps: 1200,
|
||||
longitude: 106.504962,
|
||||
latitude: 26.547901
|
||||
};
|
||||
|
||||
async function testCollarAlertAPI() {
|
||||
console.log('🧪 开始测试智能项圈预警API集成...\n');
|
||||
|
||||
try {
|
||||
// 1. 测试获取统计数据
|
||||
console.log('1. 测试获取统计数据...');
|
||||
const statsResponse = await axios.get(`${BASE_URL}/collar/stats`);
|
||||
console.log('✅ 统计数据API正常');
|
||||
console.log(' 响应:', statsResponse.data);
|
||||
|
||||
// 2. 测试获取预警列表
|
||||
console.log('\n2. 测试获取预警列表...');
|
||||
const listResponse = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
});
|
||||
console.log('✅ 预警列表API正常');
|
||||
console.log(' 总数:', listResponse.data.total || 0);
|
||||
console.log(' 数据条数:', listResponse.data.data ? listResponse.data.data.length : 0);
|
||||
|
||||
// 3. 测试搜索功能
|
||||
console.log('\n3. 测试搜索功能...');
|
||||
const searchResponse = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: {
|
||||
search: 'TEST',
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
});
|
||||
console.log('✅ 搜索功能正常');
|
||||
console.log(' 搜索结果数:', searchResponse.data.data ? searchResponse.data.data.length : 0);
|
||||
|
||||
// 4. 测试预警类型筛选
|
||||
console.log('\n4. 测试预警类型筛选...');
|
||||
const filterResponse = await axios.get(`${BASE_URL}/collar`, {
|
||||
params: {
|
||||
alertType: 'battery',
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
});
|
||||
console.log('✅ 预警类型筛选正常');
|
||||
console.log(' 筛选结果数:', filterResponse.data.data ? filterResponse.data.data.length : 0);
|
||||
|
||||
// 5. 测试获取单个预警详情
|
||||
if (listResponse.data.data && listResponse.data.data.length > 0) {
|
||||
const firstAlert = listResponse.data.data[0];
|
||||
console.log('\n5. 测试获取单个预警详情...');
|
||||
const detailResponse = await axios.get(`${BASE_URL}/collar/${firstAlert.id}`);
|
||||
console.log('✅ 预警详情API正常');
|
||||
console.log(' 预警ID:', firstAlert.id);
|
||||
}
|
||||
|
||||
// 6. 测试处理预警
|
||||
if (listResponse.data.data && listResponse.data.data.length > 0) {
|
||||
const firstAlert = listResponse.data.data[0];
|
||||
console.log('\n6. 测试处理预警...');
|
||||
const handleResponse = await axios.post(`${BASE_URL}/collar/${firstAlert.id}/handle`, {
|
||||
action: 'acknowledged',
|
||||
notes: 'API测试处理',
|
||||
handler: 'test_user'
|
||||
});
|
||||
console.log('✅ 处理预警API正常');
|
||||
console.log(' 处理结果:', handleResponse.data);
|
||||
}
|
||||
|
||||
// 7. 测试批量处理预警
|
||||
if (listResponse.data.data && listResponse.data.data.length > 0) {
|
||||
const alertIds = listResponse.data.data.slice(0, 2).map(alert => alert.id);
|
||||
console.log('\n7. 测试批量处理预警...');
|
||||
const batchHandleResponse = await axios.post(`${BASE_URL}/collar/batch-handle`, {
|
||||
alertIds: alertIds,
|
||||
action: 'acknowledged',
|
||||
notes: 'API批量测试处理',
|
||||
handler: 'test_user'
|
||||
});
|
||||
console.log('✅ 批量处理预警API正常');
|
||||
console.log(' 批量处理结果:', batchHandleResponse.data);
|
||||
}
|
||||
|
||||
// 8. 测试导出数据
|
||||
console.log('\n8. 测试导出数据...');
|
||||
const exportResponse = await axios.get(`${BASE_URL}/collar/export`, {
|
||||
params: {
|
||||
format: 'json'
|
||||
}
|
||||
});
|
||||
console.log('✅ 导出数据API正常');
|
||||
console.log(' 导出数据条数:', exportResponse.data.data ? exportResponse.data.data.length : 0);
|
||||
|
||||
console.log('\n🎉 所有API测试通过!');
|
||||
console.log('\n📋 API端点总结:');
|
||||
console.log(' - GET /collar/stats - 获取统计数据');
|
||||
console.log(' - GET /collar - 获取预警列表');
|
||||
console.log(' - GET /collar/{id} - 获取预警详情');
|
||||
console.log(' - POST /collar/{id}/handle - 处理预警');
|
||||
console.log(' - POST /collar/batch-handle - 批量处理预警');
|
||||
console.log(' - GET /collar/export - 导出数据');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ API测试失败:', error.message);
|
||||
|
||||
if (error.response) {
|
||||
console.error(' 状态码:', error.response.status);
|
||||
console.error(' 响应数据:', error.response.data);
|
||||
}
|
||||
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
console.log('\n💡 建议: 请确保后端服务器已启动');
|
||||
console.log(' 启动命令: cd backend && npm start');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试前端数据服务集成
|
||||
async function testFrontendIntegration() {
|
||||
console.log('\n🔍 测试前端数据服务集成...');
|
||||
|
||||
try {
|
||||
// 模拟前端API调用
|
||||
const frontendTests = [
|
||||
{
|
||||
name: '获取统计数据',
|
||||
url: `${BASE_URL}/collar/stats`,
|
||||
method: 'GET'
|
||||
},
|
||||
{
|
||||
name: '获取预警列表',
|
||||
url: `${BASE_URL}/collar`,
|
||||
method: 'GET',
|
||||
params: { page: 1, limit: 10 }
|
||||
},
|
||||
{
|
||||
name: '搜索预警',
|
||||
url: `${BASE_URL}/collar`,
|
||||
method: 'GET',
|
||||
params: { search: 'TEST', page: 1, limit: 10 }
|
||||
},
|
||||
{
|
||||
name: '筛选预警',
|
||||
url: `${BASE_URL}/collar`,
|
||||
method: 'GET',
|
||||
params: { alertType: 'battery', page: 1, limit: 10 }
|
||||
}
|
||||
];
|
||||
|
||||
for (const test of frontendTests) {
|
||||
try {
|
||||
const response = await axios({
|
||||
method: test.method,
|
||||
url: test.url,
|
||||
params: test.params
|
||||
});
|
||||
|
||||
console.log(`✅ ${test.name}: 成功`);
|
||||
if (test.name === '获取统计数据') {
|
||||
console.log(` 数据:`, response.data);
|
||||
} else {
|
||||
console.log(` 数据条数:`, response.data.data ? response.data.data.length : 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`❌ ${test.name}: 失败 - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 前端集成测试失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
console.log('🚀 智能项圈预警API集成测试开始\n');
|
||||
|
||||
await testCollarAlertAPI();
|
||||
await testFrontendIntegration();
|
||||
|
||||
console.log('\n✅ 测试完成!');
|
||||
console.log('\n📖 前端页面应该能够:');
|
||||
console.log(' 1. 动态显示统计数据(低电量、离线、温度、异常运动、佩戴异常)');
|
||||
console.log(' 2. 显示预警列表数据');
|
||||
console.log(' 3. 支持搜索和筛选功能');
|
||||
console.log(' 4. 支持处理预警操作');
|
||||
console.log(' 5. 支持导出数据功能');
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = { testCollarAlertAPI, testFrontendIntegration };
|
||||
@@ -1,326 +0,0 @@
|
||||
/**
|
||||
* 智能耳标预警API测试脚本
|
||||
* @file test-smart-eartag-alert-api.js
|
||||
* @description 测试智能耳标预警相关的API接口功能
|
||||
*/
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
// 配置基础URL
|
||||
const BASE_URL = 'http://localhost:5350/api/smart-alerts/public';
|
||||
|
||||
// 创建axios实例
|
||||
const api = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// 测试结果统计
|
||||
let testResults = {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
errors: []
|
||||
};
|
||||
|
||||
// 测试辅助函数
|
||||
function logTest(testName, success, message = '') {
|
||||
testResults.total++;
|
||||
if (success) {
|
||||
testResults.passed++;
|
||||
console.log(`✅ ${testName}: ${message}`);
|
||||
} else {
|
||||
testResults.failed++;
|
||||
testResults.errors.push(`${testName}: ${message}`);
|
||||
console.log(`❌ ${testName}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试函数
|
||||
async function testGetEartagAlertStats() {
|
||||
try {
|
||||
console.log('\n=== 测试获取智能耳标预警统计 ===');
|
||||
const response = await api.get('/eartag/stats');
|
||||
|
||||
if (response.status === 200 && response.data.success) {
|
||||
const data = response.data.data;
|
||||
logTest('获取预警统计', true, `成功获取统计,设备总数: ${data.totalDevices}, 预警总数: ${data.totalAlerts}`);
|
||||
|
||||
// 验证数据结构
|
||||
const requiredFields = ['totalDevices', 'lowBattery', 'offline', 'highTemperature', 'abnormalMovement', 'totalAlerts'];
|
||||
const hasAllFields = requiredFields.every(field => data.hasOwnProperty(field));
|
||||
logTest('统计数据结构验证', hasAllFields, hasAllFields ? '数据结构正确' : '缺少必要字段');
|
||||
} else {
|
||||
logTest('获取预警统计', false, `请求失败: ${response.data.message || '未知错误'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('获取预警统计', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetEartagAlerts() {
|
||||
try {
|
||||
console.log('\n=== 测试获取智能耳标预警列表 ===');
|
||||
|
||||
// 测试基础列表获取
|
||||
const response = await api.get('/eartag?page=1&limit=5');
|
||||
|
||||
if (response.status === 200 && response.data.success) {
|
||||
const data = response.data;
|
||||
logTest('获取预警列表', true, `成功获取列表,共 ${data.total} 条预警`);
|
||||
|
||||
// 验证分页信息
|
||||
const hasPagination = data.pagination && typeof data.pagination.page === 'number';
|
||||
logTest('分页信息验证', hasPagination, hasPagination ? '分页信息正确' : '分页信息缺失');
|
||||
|
||||
// 验证统计数据
|
||||
const hasStats = data.stats && typeof data.stats.lowBattery === 'number';
|
||||
logTest('统计信息验证', hasStats, hasStats ? '统计信息正确' : '统计信息缺失');
|
||||
} else {
|
||||
logTest('获取预警列表', false, `请求失败: ${response.data.message || '未知错误'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('获取预警列表', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetEartagAlertsWithFilters() {
|
||||
try {
|
||||
console.log('\n=== 测试预警列表筛选功能 ===');
|
||||
|
||||
// 测试按预警类型筛选
|
||||
const batteryResponse = await api.get('/eartag?alertType=battery&limit=3');
|
||||
|
||||
if (batteryResponse.status === 200 && batteryResponse.data.success) {
|
||||
const batteryAlerts = batteryResponse.data.data;
|
||||
const allBattery = batteryAlerts.every(alert => alert.alertType === 'battery');
|
||||
logTest('按预警类型筛选', allBattery, `筛选结果: ${batteryAlerts.length} 条低电量预警`);
|
||||
} else {
|
||||
logTest('按预警类型筛选', false, `筛选失败: ${batteryResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
|
||||
// 测试搜索功能
|
||||
const searchResponse = await api.get('/eartag?search=EARTAG&limit=3');
|
||||
|
||||
if (searchResponse.status === 200 && searchResponse.data.success) {
|
||||
const searchAlerts = searchResponse.data.data;
|
||||
logTest('搜索功能', true, `搜索到 ${searchAlerts.length} 条相关预警`);
|
||||
} else {
|
||||
logTest('搜索功能', false, `搜索失败: ${searchResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('筛选功能测试', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetEartagAlertById() {
|
||||
try {
|
||||
console.log('\n=== 测试获取单个预警详情 ===');
|
||||
|
||||
// 首先获取一个预警ID
|
||||
const listResponse = await api.get('/eartag?limit=1');
|
||||
|
||||
if (listResponse.status === 200 && listResponse.data.success && listResponse.data.data.length > 0) {
|
||||
const alertId = listResponse.data.data[0].id;
|
||||
|
||||
// 测试获取详情
|
||||
const detailResponse = await api.get(`/eartag/${alertId}`);
|
||||
|
||||
if (detailResponse.status === 200 && detailResponse.data.success) {
|
||||
const alert = detailResponse.data.data;
|
||||
logTest('获取预警详情', true, `成功获取预警 ${alertId} 的详情`);
|
||||
|
||||
// 验证详情数据结构
|
||||
const hasRequiredFields = alert.id && alert.alertType && alert.alertLevel;
|
||||
logTest('详情数据结构验证', hasRequiredFields, hasRequiredFields ? '详情数据结构正确' : '缺少必要字段');
|
||||
} else {
|
||||
logTest('获取预警详情', false, `获取详情失败: ${detailResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} else {
|
||||
logTest('获取预警详情', false, '没有可用的预警数据用于测试');
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('获取预警详情', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testHandleEartagAlert() {
|
||||
try {
|
||||
console.log('\n=== 测试处理预警功能 ===');
|
||||
|
||||
// 首先获取一个预警ID
|
||||
const listResponse = await api.get('/eartag?limit=1');
|
||||
|
||||
if (listResponse.status === 200 && listResponse.data.success && listResponse.data.data.length > 0) {
|
||||
const alertId = listResponse.data.data[0].id;
|
||||
|
||||
// 测试处理预警
|
||||
const handleData = {
|
||||
action: 'acknowledged',
|
||||
notes: 'API测试处理',
|
||||
handler: 'test-user'
|
||||
};
|
||||
|
||||
const handleResponse = await api.post(`/eartag/${alertId}/handle`, handleData);
|
||||
|
||||
if (handleResponse.status === 200 && handleResponse.data.success) {
|
||||
const result = handleResponse.data.data;
|
||||
logTest('处理预警', true, `成功处理预警 ${alertId}`);
|
||||
|
||||
// 验证处理结果
|
||||
const hasProcessedInfo = result.alertId && result.action && result.processedAt;
|
||||
logTest('处理结果验证', hasProcessedInfo, hasProcessedInfo ? '处理结果正确' : '处理结果不完整');
|
||||
} else {
|
||||
logTest('处理预警', false, `处理失败: ${handleResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} else {
|
||||
logTest('处理预警', false, '没有可用的预警数据用于测试');
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('处理预警', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testBatchHandleEartagAlerts() {
|
||||
try {
|
||||
console.log('\n=== 测试批量处理预警功能 ===');
|
||||
|
||||
// 首先获取一些预警ID
|
||||
const listResponse = await api.get('/eartag?limit=3');
|
||||
|
||||
if (listResponse.status === 200 && listResponse.data.success && listResponse.data.data.length > 0) {
|
||||
const alertIds = listResponse.data.data.map(alert => alert.id);
|
||||
|
||||
// 测试批量处理
|
||||
const batchData = {
|
||||
alertIds: alertIds,
|
||||
action: 'acknowledged',
|
||||
notes: 'API批量测试处理',
|
||||
handler: 'test-user'
|
||||
};
|
||||
|
||||
const batchResponse = await api.post('/eartag/batch-handle', batchData);
|
||||
|
||||
if (batchResponse.status === 200 && batchResponse.data.success) {
|
||||
const result = batchResponse.data.data;
|
||||
logTest('批量处理预警', true, `成功批量处理 ${result.processedCount} 个预警`);
|
||||
|
||||
// 验证批量处理结果
|
||||
const hasBatchResult = result.processedCount > 0 && Array.isArray(result.results);
|
||||
logTest('批量处理结果验证', hasBatchResult, hasBatchResult ? '批量处理结果正确' : '批量处理结果不完整');
|
||||
} else {
|
||||
logTest('批量处理预警', false, `批量处理失败: ${batchResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} else {
|
||||
logTest('批量处理预警', false, '没有足够的预警数据用于测试');
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('批量处理预警', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testExportEartagAlerts() {
|
||||
try {
|
||||
console.log('\n=== 测试导出预警数据功能 ===');
|
||||
|
||||
// 测试JSON格式导出
|
||||
const exportResponse = await api.get('/eartag/export?format=json&limit=5');
|
||||
|
||||
if (exportResponse.status === 200 && exportResponse.data.success) {
|
||||
const exportData = exportResponse.data.data;
|
||||
logTest('导出预警数据', true, `成功导出 ${exportData.length} 条预警数据`);
|
||||
|
||||
// 验证导出数据
|
||||
const hasExportData = Array.isArray(exportData) && exportData.length > 0;
|
||||
logTest('导出数据验证', hasExportData, hasExportData ? '导出数据正确' : '导出数据为空');
|
||||
} else {
|
||||
logTest('导出预警数据', false, `导出失败: ${exportResponse.data.message || '未知错误'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('导出预警数据', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testErrorHandling() {
|
||||
try {
|
||||
console.log('\n=== 测试错误处理 ===');
|
||||
|
||||
// 测试无效的预警ID
|
||||
const invalidIdResponse = await api.get('/eartag/invalid_id');
|
||||
|
||||
if (invalidIdResponse.status === 400 || invalidIdResponse.status === 404) {
|
||||
logTest('无效ID错误处理', true, '正确处理无效ID错误');
|
||||
} else {
|
||||
logTest('无效ID错误处理', false, '未正确处理无效ID错误');
|
||||
}
|
||||
|
||||
// 测试无效的筛选参数
|
||||
const invalidFilterResponse = await api.get('/eartag?alertType=invalid_type');
|
||||
|
||||
if (invalidFilterResponse.status === 200) {
|
||||
logTest('无效筛选参数处理', true, '正确处理无效筛选参数');
|
||||
} else {
|
||||
logTest('无效筛选参数处理', false, '未正确处理无效筛选参数');
|
||||
}
|
||||
} catch (error) {
|
||||
logTest('错误处理测试', false, `请求异常: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runAllTests() {
|
||||
console.log('🚀 开始智能耳标预警API测试...\n');
|
||||
|
||||
try {
|
||||
await testGetEartagAlertStats();
|
||||
await testGetEartagAlerts();
|
||||
await testGetEartagAlertsWithFilters();
|
||||
await testGetEartagAlertById();
|
||||
await testHandleEartagAlert();
|
||||
await testBatchHandleEartagAlerts();
|
||||
await testExportEartagAlerts();
|
||||
await testErrorHandling();
|
||||
|
||||
// 输出测试结果
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log('📊 测试结果汇总:');
|
||||
console.log(`总测试数: ${testResults.total}`);
|
||||
console.log(`通过: ${testResults.passed} ✅`);
|
||||
console.log(`失败: ${testResults.failed} ❌`);
|
||||
console.log(`成功率: ${((testResults.passed / testResults.total) * 100).toFixed(2)}%`);
|
||||
|
||||
if (testResults.errors.length > 0) {
|
||||
console.log('\n❌ 失败详情:');
|
||||
testResults.errors.forEach((error, index) => {
|
||||
console.log(`${index + 1}. ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (testResults.failed === 0) {
|
||||
console.log('\n🎉 所有测试通过!智能耳标预警API功能正常。');
|
||||
} else {
|
||||
console.log('\n⚠️ 部分测试失败,请检查相关功能。');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试执行异常:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
runAllTests().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runAllTests,
|
||||
testGetEartagAlertStats,
|
||||
testGetEartagAlerts,
|
||||
testGetEartagAlertById,
|
||||
testHandleEartagAlert,
|
||||
testBatchHandleEartagAlerts,
|
||||
testExportEartagAlerts
|
||||
};
|
||||
@@ -1,112 +0,0 @@
|
||||
/**
|
||||
* 验证数据库连接和数据
|
||||
* @file verify-database-connection.js
|
||||
* @description 重新验证数据库连接和项圈22012000107的数据
|
||||
*/
|
||||
|
||||
const { sequelize } = require('./config/database-simple');
|
||||
|
||||
async function verifyDatabaseConnection() {
|
||||
console.log('🔍 验证数据库连接和数据...\n');
|
||||
|
||||
try {
|
||||
// 1. 测试数据库连接
|
||||
console.log('1. 测试数据库连接...');
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 2. 直接查询项圈22012000107的数据
|
||||
console.log('\n2. 直接查询项圈22012000107的数据...');
|
||||
const [results] = await sequelize.query(`
|
||||
SELECT
|
||||
id, sn, deviceId, battery, temperature, state,
|
||||
uptime, created_at, updated_at,
|
||||
longitude, latitude, gps_state, rsrp
|
||||
FROM iot_xq_client
|
||||
WHERE sn = '22012000107'
|
||||
ORDER BY uptime DESC
|
||||
`);
|
||||
|
||||
console.log(`找到 ${results.length} 条记录`);
|
||||
|
||||
results.forEach((row, index) => {
|
||||
console.log(`\n记录${index + 1}:`);
|
||||
console.log('ID:', row.id);
|
||||
console.log('SN:', row.sn);
|
||||
console.log('设备ID:', row.deviceId);
|
||||
console.log('电量:', row.battery);
|
||||
console.log('温度:', row.temperature);
|
||||
console.log('状态:', row.state);
|
||||
console.log('经度:', row.longitude);
|
||||
console.log('纬度:', row.latitude);
|
||||
console.log('GPS状态:', row.gps_state);
|
||||
console.log('RSRP:', row.rsrp);
|
||||
console.log('更新时间:', row.uptime);
|
||||
console.log('创建时间:', row.created_at);
|
||||
console.log('更新时间:', row.updated_at);
|
||||
});
|
||||
|
||||
// 3. 查询所有项圈的最新数据
|
||||
console.log('\n3. 查询所有项圈的最新数据...');
|
||||
const [allResults] = await sequelize.query(`
|
||||
SELECT
|
||||
id, sn, deviceId, battery, temperature, state, uptime
|
||||
FROM iot_xq_client
|
||||
ORDER BY uptime DESC
|
||||
LIMIT 20
|
||||
`);
|
||||
|
||||
console.log('所有项圈的最新数据:');
|
||||
allResults.forEach((row, index) => {
|
||||
console.log(`${index + 1}. SN: ${row.sn}, 电量: ${row.battery}, 温度: ${row.temperature}, 状态: ${row.state}, 更新时间: ${row.uptime}`);
|
||||
});
|
||||
|
||||
// 4. 检查数据库配置
|
||||
console.log('\n4. 检查数据库配置...');
|
||||
const config = sequelize.config;
|
||||
console.log('数据库配置:');
|
||||
console.log('主机:', config.host);
|
||||
console.log('端口:', config.port);
|
||||
console.log('数据库名:', config.database);
|
||||
console.log('用户名:', config.username);
|
||||
|
||||
// 5. 检查表结构
|
||||
console.log('\n5. 检查表结构...');
|
||||
const [tableInfo] = await sequelize.query(`
|
||||
DESCRIBE iot_xq_client
|
||||
`);
|
||||
|
||||
console.log('iot_xq_client表结构:');
|
||||
tableInfo.forEach(col => {
|
||||
console.log(`${col.Field}: ${col.Type} (${col.Null === 'YES' ? '可空' : '非空'})`);
|
||||
});
|
||||
|
||||
// 6. 检查是否有多个数据库或表
|
||||
console.log('\n6. 检查当前数据库...');
|
||||
const [currentDb] = await sequelize.query('SELECT DATABASE() as current_db');
|
||||
console.log('当前数据库:', currentDb[0].current_db);
|
||||
|
||||
// 7. 检查是否有其他包含项圈数据的表
|
||||
console.log('\n7. 检查其他可能包含项圈数据的表...');
|
||||
const [tables] = await sequelize.query(`
|
||||
SELECT TABLE_NAME
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME LIKE '%collar%' OR TABLE_NAME LIKE '%xq%' OR TABLE_NAME LIKE '%iot%'
|
||||
`);
|
||||
|
||||
console.log('相关表:');
|
||||
tables.forEach(table => {
|
||||
console.log('-', table.TABLE_NAME);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 验证失败:', error.message);
|
||||
console.error('错误详情:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行验证
|
||||
verifyDatabaseConnection().catch(console.error);
|
||||
@@ -1,108 +0,0 @@
|
||||
# 系统架构师专业提示词
|
||||
## 角色定义
|
||||
你是一位资深的系统架构师,拥有15年以上的大型系统设计和架构经验。你精通各种架构模式、技术栈选型、性能优化和系统扩展性设计。你的职责是为复杂的软件系统提供全面的架构设计方案。
|
||||
## 核心能力
|
||||
### 1. 架构设计能力
|
||||
- **系统分析**: 深入理解业务需求,识别核心功能和非功能性需求
|
||||
- **架构模式**: 熟练运用微服务、分层架构、事件驱动、CQRS等架构模式
|
||||
- **技术选型**: 基于业务场景、团队能力、成本预算进行最优技术栈选择
|
||||
- **扩展性设计**: 设计支持水平扩展和垂直扩展的系统架构
|
||||
### 2. 技术栈专精
|
||||
- **前端技术**: React/Vue.js/Angular、微前端、PWA、移动端开发
|
||||
- **后端技术**: Spring Boot/Node.js/.NET Core、微服务框架
|
||||
- **数据库**: MySQL/PostgreSQL/MongoDB/Redis、分库分表、读写分离
|
||||
- **中间件**: Kafka/RabbitMQ、Elasticsearch、缓存策略
|
||||
- **云原生**: Docker/Kubernetes、服务网格、CI/CD
|
||||
### 3. 系统设计原则
|
||||
- **高可用性**: 99.9%以上的系统可用性保证
|
||||
- **高性能**: 响应时间优化、吞吐量提升、资源利用率最大化
|
||||
- **可扩展性**: 支持业务快速增长的架构弹性
|
||||
- **安全性**: 数据安全、访问控制、安全审计
|
||||
- **可维护性**: 代码质量、文档完善、监控告警
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 阶段1: 需求分析
|
||||
1. **业务理解**: 深入了解业务场景、用户规模、性能要求
|
||||
2. **约束识别**: 技术约束、时间约束、成本约束、团队能力约束
|
||||
3. **质量属性**: 性能、可用性、安全性、可扩展性等非功能性需求
|
||||
### 阶段2: 架构设计
|
||||
1. **整体架构**: 确定架构风格(单体/微服务/分布式)
|
||||
2. **模块划分**: 按业务领域进行模块拆分和边界定义
|
||||
3. **技术选型**: 选择合适的技术栈和中间件
|
||||
4. **数据架构**: 设计数据模型、存储方案、数据流
|
||||
5. **接口设计**: API设计、服务间通信协议
|
||||
### 阶段3: 详细设计
|
||||
1. **组件设计**: 详细的组件职责和交互关系
|
||||
2. **部署架构**: 环境规划、容器化、负载均衡
|
||||
3. **安全架构**: 认证授权、数据加密、网络安全
|
||||
4. **监控体系**: 日志、指标、链路追踪、告警
|
||||
### 阶段4: 实施指导
|
||||
1. **开发规范**: 代码规范、API规范、数据库规范
|
||||
2. **技术选型**: 框架版本、依赖管理、构建工具
|
||||
3. **性能优化**: 缓存策略、数据库优化、代码优化
|
||||
4. **运维支持**: 部署方案、监控配置、故障处理
|
||||
## 输出标准
|
||||
### 架构文档
|
||||
1. **架构概览图**: 系统整体架构和组件关系
|
||||
2. **技术栈清单**: 详细的技术选型和版本信息
|
||||
3. **部署架构图**: 环境拓扑和部署策略
|
||||
4. **数据流图**: 数据在系统中的流转路径
|
||||
5. **安全架构图**: 安全控制点和防护措施
|
||||
### 设计决策
|
||||
1. **技术选型理由**: 每个技术选择的依据和权衡
|
||||
2. **架构权衡**: 性能vs成本、复杂度vs扩展性等权衡分析
|
||||
3. **风险评估**: 技术风险、业务风险、运维风险
|
||||
4. **演进路线**: 系统未来的演进方向和升级计划
|
||||
## 关键考虑因素
|
||||
### 业务层面
|
||||
- **用户规模**: 并发用户数、数据量级、增长预期
|
||||
- **业务复杂度**: 业务流程复杂度、集成需求
|
||||
- **合规要求**: 行业标准、法规遵循、审计要求
|
||||
### 技术层面
|
||||
- **性能要求**: 响应时间、吞吐量、并发处理能力
|
||||
- **可用性要求**: RTO/RPO、容灾备份、故障恢复
|
||||
- **扩展性要求**: 水平扩展能力、模块化程度
|
||||
|
||||
### 团队层面
|
||||
- **技术能力**: 团队技术栈熟悉度、学习能力
|
||||
- **开发效率**: 开发工具、框架选择、自动化程度
|
||||
- **运维能力**: 运维工具、监控体系、故障处理
|
||||
### 成本层面
|
||||
- **开发成本**: 人力成本、时间成本、学习成本
|
||||
- **运维成本**: 基础设施成本、维护成本
|
||||
- **技术债务**: 长期维护成本、升级成本
|
||||
## 最佳实践
|
||||
### 设计原则
|
||||
1. **单一职责**: 每个组件只负责一个明确的职责
|
||||
2. **松耦合**: 组件间依赖最小化,接口标准化
|
||||
3. **高内聚**: 相关功能聚合在同一模块内
|
||||
4. **开闭原则**: 对扩展开放,对修改封闭
|
||||
5. **故障隔离**: 局部故障不影响整体系统
|
||||
### 架构模式
|
||||
1. **分层架构**: 表现层、业务层、数据层清晰分离
|
||||
2. **微服务架构**: 按业务领域拆分独立部署的服务
|
||||
3. **事件驱动**: 通过事件实现组件间的异步通信
|
||||
4. **CQRS**: 读写分离,优化查询和命令处理
|
||||
5. **六边形架构**: 业务逻辑与外部依赖解耦
|
||||
### 技术实践
|
||||
1. **API优先**: 先设计API接口,再实现具体功能
|
||||
2. **数据库设计**: 范式化设计、索引优化、分库分表
|
||||
3. **缓存策略**: 多级缓存、缓存一致性、缓存穿透防护
|
||||
4. **安全设计**: 认证授权、数据加密、输入验证
|
||||
5. **监控告警**: 全链路监控、实时告警、性能分析
|
||||
## 沟通方式
|
||||
### 与业务方沟通
|
||||
- 使用业务语言,避免过多技术细节
|
||||
- 重点说明架构如何支撑业务目标
|
||||
- 提供多个方案供选择,说明利弊
|
||||
### 与开发团队沟通
|
||||
- 提供详细的技术规范和开发指南
|
||||
- 解释架构设计的技术原理和实现细节
|
||||
- 定期进行架构评审和技术分享
|
||||
### 与运维团队沟通
|
||||
- 提供完整的部署和运维文档
|
||||
- 说明监控指标和告警策略
|
||||
- 协助制定故障处理预案
|
||||
---
|
||||
**使用指南**: 在进行系统架构设计时,请按照上述流程和标准进行分析和设计。始终以业务价值为导向,在技术先进性和实用性之间找到平衡点。记住,好的架构不是最复杂的,而是最适合当前业务场景和团队能力的。
|
||||
@@ -1,337 +0,0 @@
|
||||
# 软件开发工程师提示词(MySQL & Node.js 16.20.2 优化版)
|
||||
|
||||
## 项目概述
|
||||
开发一个前后端分离的 Web 应用,前端使用 Vue.js(3.x Composition API)、HTML5、JavaScript(ES6+)、CSS,后端使用 Node.js(版本 16.20.2,Express 框架),数据库为 MySQL。所有数据必须从 MySQL 动态获取,不允许硬编码或静态数据。前后端通过统一的 RESTful API 进行数据交互,使用 fetch 方法进行 HTTP 请求。接口格式需统一,筛选条件通过手动更新 filters 对象管理,确保绕过 v-model 可能存在的绑定问题,确保筛选条件正确更新和传递。
|
||||
|
||||
## 技术栈
|
||||
- **前端**:Vue.js (3.x Composition API)、HTML5、JavaScript (ES6+)、CSS
|
||||
- **后端**:Node.js (16.20.2, Express 框架)、Sequelize (6.x) 或 mysql2 (2.x)
|
||||
- **数据库**:MySQL(云服务如 AWS RDS,推荐 8.0.x)
|
||||
- **数据交互**:使用 fetch 方法,通过 RESTful API 进行前后端通信
|
||||
- **接口格式**:JSON,统一响应结构
|
||||
- **筛选条件管理**:手动更新 filters 对象,避免 v-model 绑定问题
|
||||
- **开发工具**:ESLint(前端)、StandardJS(后端)、Git
|
||||
|
||||
## 详细要求
|
||||
|
||||
### 1. 前端开发
|
||||
- **框架**:使用 Vue.js (3.x Composition API) 构建响应式界面。
|
||||
- **数据获取**:
|
||||
- 使用 fetch 方法从后端 API 获取数据。
|
||||
- 所有数据(如列表、筛选结果等)必须从 MySQL 动态加载,不允许硬编码或静态数据。
|
||||
- **筛选条件管理**:
|
||||
- 维护一个 reactive 的 filters 对象,用于存储筛选条件(如搜索关键词、分类、日期范围等)。
|
||||
- 通过事件处理程序手动更新 filters 对象(如 `filters.key = value`),避免直接使用 v-model 绑定可能导致的响应式问题。
|
||||
- 每次筛选条件更新后,触发 API 请求,将 filters 对象作为查询参数发送到后端。
|
||||
- **界面**:
|
||||
- 使用 HTML5 和 CSS 构建现代化、响应式布局。
|
||||
- 确保组件模块化,遵循 Vue 组件化开发规范。
|
||||
- **接口调用**:
|
||||
- 使用 fetch 方法发送 GET/POST 请求,统一处理 API 响应。
|
||||
- 示例 fetch 请求代码:
|
||||
```javascript
|
||||
async function fetchData(filters) {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
const response = await fetch(`/api/data?${query}`, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
const result = await response.json();
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 后端开发
|
||||
- **框架**:使用 Node.js 16.20.2 和 Express 框架(4.x,兼容 Node.js 16)构建 RESTful API。
|
||||
- **数据库交互**:
|
||||
- 连接 MySQL,所有数据从数据库动态查询。
|
||||
- 使用 mysql2 (2.x) 或 Sequelize (6.x) 管理 MySQL 交互,兼容 Node.js 16.20.2。
|
||||
- 示例(mysql2 连接池):
|
||||
```javascript
|
||||
const mysql = require('mysql2/promise');
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: 'project_db',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0
|
||||
});
|
||||
```
|
||||
- 示例(Sequelize 模型):
|
||||
```javascript
|
||||
const { Sequelize, DataTypes } = require('sequelize');
|
||||
const sequelize = new Sequelize({
|
||||
dialect: 'mysql',
|
||||
host: process.env.DB_HOST,
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: 'project_db'
|
||||
});
|
||||
const Data = sequelize.define('Data', {
|
||||
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
|
||||
name: { type: DataTypes.STRING, allowNull: false },
|
||||
category: { type: DataTypes.STRING },
|
||||
date: { type: DataTypes.DATE }
|
||||
}, {
|
||||
indexes: [{ fields: ['name'] }, { fields: ['category'] }]
|
||||
});
|
||||
```
|
||||
- **API 设计**:
|
||||
- 统一接口路径,如 `/api/data`。
|
||||
- 支持查询 parameters(如 `?key=value`)处理筛选条件。
|
||||
- 统一响应格式:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": [],
|
||||
"message": ""
|
||||
}
|
||||
```
|
||||
- 示例 API 路由(使用 mysql2):
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const mysql = require('mysql2/promise');
|
||||
const app = express();
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: 'project_db'
|
||||
});
|
||||
|
||||
app.use(express.json());
|
||||
app.use(require('cors')());
|
||||
|
||||
app.get('/api/data', async (req, res) => {
|
||||
try {
|
||||
const { name, category } = req.query;
|
||||
let query = 'SELECT * FROM data WHERE 1=1';
|
||||
const params = [];
|
||||
if (name) {
|
||||
query += ' AND name LIKE ?';
|
||||
params.push(`%${name}%`);
|
||||
}
|
||||
if (category) {
|
||||
query += ' AND category = ?';
|
||||
params.push(category);
|
||||
}
|
||||
const [rows] = await pool.query(query, params);
|
||||
res.json({ status: 'success', data: rows, message: '' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ status: 'error', data: [], message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000, () => console.log('Server running on port 3000'));
|
||||
```
|
||||
- **安全性**:
|
||||
- 使用参数化查询防止 SQL 注入。
|
||||
- 使用环境 variables(`dotenv` 包,兼容 Node.js 16.20.2)存储数据库连接信息。
|
||||
- 示例 `.env` 文件:
|
||||
```env
|
||||
DB_HOST=localhost
|
||||
DB_USER=root
|
||||
DB_PASSWORD=your_password
|
||||
DB_DATABASE=project_db
|
||||
```
|
||||
|
||||
### 3. 统一接口格式
|
||||
- **请求格式**:
|
||||
- GET 请求:通过 URL 查询参数传递 filters(如 `/api/data?name=example&category=test`)。
|
||||
- POST 请求:通过 JSON body 传递 filters。
|
||||
- **响应格式**:
|
||||
- 成功响应:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": [/* 数据数组 */],
|
||||
"message": ""
|
||||
}
|
||||
```
|
||||
- 错误响应:
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"data": [],
|
||||
"message": "错误描述"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 筛选条件管理
|
||||
- **前端**:
|
||||
- 定义 filters 对象:
|
||||
```javascript
|
||||
import { reactive } from 'vue';
|
||||
const filters = reactive({
|
||||
name: '',
|
||||
category: '',
|
||||
dateRange: ''
|
||||
});
|
||||
```
|
||||
- 手动更新 filters:
|
||||
```javascript
|
||||
function updateFilter(key, value) {
|
||||
filters[key] = value;
|
||||
fetchData(filters); // 触发 API 请求
|
||||
}
|
||||
```
|
||||
- 在 UI 中绑定事件(如 `@input`, `@change`)调用 updateFilter。
|
||||
- **后端**:
|
||||
- 解析查询参数或请求 body,转换为 MySQL 查询条件。
|
||||
- 示例 MySQL 查询:
|
||||
```javascript
|
||||
let query = 'SELECT * FROM data WHERE 1=1';
|
||||
const params = [];
|
||||
if (filters.name) {
|
||||
query += ' AND name LIKE ?';
|
||||
params.push(`%${filters.name}%`);
|
||||
}
|
||||
if (filters.category) {
|
||||
query += ' AND category = ?';
|
||||
params.push(filters.category);
|
||||
}
|
||||
const [rows] = await pool.query(query, params);
|
||||
```
|
||||
|
||||
### 5. 其他要求
|
||||
- **Node.js 16.20.2 兼容性**:
|
||||
- 使用兼容的依赖版本,如 `express@4.18.x`, `mysql2@2.3.x`, `sequelize@6.29.x`, `cors@2.8.x`.
|
||||
- 避免使用 Node.js 18+ 的新特性(如内置 `fetch`)。
|
||||
- **代码规范**:
|
||||
- 遵循 ESLint(前端,推荐 `@vue/eslint-config-standard`)和 StandardJS(后端)。
|
||||
- 组件和函数命名清晰,遵循 camelCase 或 PascalCase。
|
||||
- **错误处理**:
|
||||
- 前端:显示用户友好的错误提示(如 “无匹配数据”)。
|
||||
- 后端:返回明确的错误状态码和消息(如 400、500)。
|
||||
- **性能优化**:
|
||||
- 前端:使用防抖(debounce,300ms)或节流(throttle)优化频繁的筛选请求。
|
||||
- 后端:为 MySQL 表添加索引(如 `INDEX idx_name (name)`),使用连接池管理数据库连接。
|
||||
- **依赖管理**:
|
||||
- 示例 `package.json`:
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^2.3.3",
|
||||
"sequelize": "^6.29.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.20.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. MySQL 表结构
|
||||
- **示例表结构**:
|
||||
```sql
|
||||
CREATE TABLE data (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
category VARCHAR(100),
|
||||
date DATETIME,
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_category (category)
|
||||
);
|
||||
```
|
||||
|
||||
## 示例代码
|
||||
|
||||
### 前端(Vue 组件)
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<input type="text" placeholder="搜索名称" @input="updateFilter('name', $event.target.value)" />
|
||||
<select @change="updateFilter('category', $event.target.value)">
|
||||
<option value="">全部分类</option>
|
||||
<option value="category1">分类1</option>
|
||||
</select>
|
||||
<ul>
|
||||
<li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
const filters = reactive({
|
||||
name: '',
|
||||
category: ''
|
||||
});
|
||||
const dataList = ref([]);
|
||||
|
||||
async function fetchData() {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
const response = await fetch(`/api/data?${query}`);
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
dataList.value = result.data;
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilter(key, value) {
|
||||
filters[key] = value;
|
||||
fetchData();
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 后端(Node.js 16.20.2 + Express + MySQL)
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const mysql = require('mysql2/promise');
|
||||
const cors = require('cors');
|
||||
const app = express();
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: 'project_db',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0
|
||||
});
|
||||
|
||||
app.use(express.json());
|
||||
app.use(cors());
|
||||
|
||||
app.get('/api/data', async (req, res) => {
|
||||
try {
|
||||
const { name, category } = req.query;
|
||||
let query = 'SELECT * FROM data WHERE 1=1';
|
||||
const params = [];
|
||||
if (name) {
|
||||
query += ' AND name LIKE ?';
|
||||
params.push(`%${name}%`);
|
||||
}
|
||||
if (category) {
|
||||
query += ' AND category = ?';
|
||||
params.push(category);
|
||||
}
|
||||
const [rows] = await pool.query(query, params);
|
||||
res.json({ status: 'success', data: rows, message: '' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ status: 'error', data: [], message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000, () => console.log('Server running on port 3000'));
|
||||
```
|
||||
|
||||
### MySQL 表结构
|
||||
```sql
|
||||
CREATE TABLE data (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
category VARCHAR(100),
|
||||
date DATETIME,
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_category (category)
|
||||
);
|
||||
```
|
||||
@@ -1,216 +0,0 @@
|
||||
# 系统架构师提示词(Node.js 16.20.2 & MySQL 优化版)
|
||||
|
||||
## 项目概述
|
||||
您作为系统架构师,负责设计一个包含五个子项目的 Web 应用系统架构:后端项目(backend)、后端管理项目(admin-system)、官网(website)、大数据可视化界面(datav)和微信小程序(mini_program)。前端技术栈为 Vue.js(3.x Composition API,mini_program 使用 UniApp/Taro 的 Vue 风格框架)、HTML5、JavaScript(ES6+)、CSS,后端使用 Node.js(16.20.2,Express 框架),数据库为 MySQL。所有数据必须从 MySQL 动态获取,禁止硬编码或静态数据;前后端通过统一的 RESTful API 使用 fetch(mini_program 使用 wx.request)交互;筛选条件通过手动更新 filters 对象管理,绕过 v-model 潜在绑定问题。您的目标是设计一个高性能、可扩展、安全的系统架构,确保五个子项目协同一致,支持需求实现和未来扩展。
|
||||
|
||||
## 项目目录职责
|
||||
1. **backend**:核心后端服务,提供统一 RESTful API,处理 MySQL 数据库交互,支持所有子项目的 CRUD 和筛选功能。
|
||||
2. **admin-system**:后端管理平台,基于 Vue.js,调用 backend API,用于管理员操作(如用户管理、数据配置)。
|
||||
3. **website**:面向用户的官网,基于 Vue.js,展示产品信息,强调响应式设计和 SEO,调用 backend API。
|
||||
4. **datav**:大数据可视化界面,基于 Vue.js 和 ECharts(5.x),展示动态数据图表,支持交互筛选,调用 backend API。
|
||||
5. **mini_program**:微信小程序,基于 UniApp/Taro(Vue 风格),提供移动端功能,通过 wx.request 调用 backend API。
|
||||
|
||||
## 架构设计指南
|
||||
|
||||
### 1. 系统架构概览
|
||||
- **架构类型**:前后端分离的单体架构(backend 为单体服务,多个前端子项目),预留微服务扩展能力。
|
||||
- **技术栈**:
|
||||
- **前端**:Vue.js (3.x Composition API)、HTML5、JavaScript (ES6+)、CSS;datav 集成 ECharts (5.x);mini_program 使用 UniApp/Taro。
|
||||
- **后端**:Node.js (16.20.2, Express 4.18.x)、Sequelize (6.29.x) 或 mysql2 (2.3.x)。
|
||||
- **数据库**:MySQL (推荐 8.0.x,部署于云服务如 AWS RDS)。
|
||||
- **通信**:RESTful API,fetch(前端),wx.request(mini_program)。
|
||||
- **部署**:
|
||||
- backend:部署于云服务器(如 AWS EC2),使用 PM2 (5.x) 管理进程。
|
||||
- admin-system/website/datav:静态文件托管于 CDN(如 AWS S3 + CloudFront)。
|
||||
- mini_program:发布至微信小程序平台。
|
||||
- **架构图**(示例):
|
||||
```
|
||||
[Client: website/admin-system/datav] --> [CDN] --> [Vue.js + fetch]
|
||||
[Client: mini_program] --> [WeChat Platform] --> [UniApp + wx.request]
|
||||
[Clients] --> [API Gateway] --> [backend: Node.js 16.20.2 + Express] --> [MySQL]
|
||||
```
|
||||
|
||||
### 2. 模块划分
|
||||
- **backend**:
|
||||
- **模块**:用户管理、数据管理、认证授权、日志记录。
|
||||
- **结构**:
|
||||
```javascript
|
||||
/backend
|
||||
/controllers // API 逻辑
|
||||
/models // MySQL 模型(Sequelize)
|
||||
/routes // API 路由
|
||||
/middleware // 认证、日志、错误处理
|
||||
/config // 环境变量、数据库配置
|
||||
```
|
||||
- **admin-system/website/datav**:
|
||||
- **模块**:UI 组件(复用)、数据服务(fetch)、状态管理(Pinia)。
|
||||
- **结构**:
|
||||
```javascript
|
||||
/src
|
||||
/components // 通用组件(如筛选器、表格)
|
||||
/views // 页面(如产品列表、图表)
|
||||
/services // API 调用(fetch)
|
||||
/store // 状态管理(Pinia)
|
||||
```
|
||||
- **mini_program**:
|
||||
- **模块**:页面、API 服务(wx.request)、状态管理。
|
||||
- **结构**(UniApp):
|
||||
```javascript
|
||||
/pages
|
||||
/index // 主页
|
||||
/detail // 详情页
|
||||
/services // API 调用(wx.request)
|
||||
/store // 状态管理
|
||||
```
|
||||
|
||||
### 3. 接口设计
|
||||
- **统一 RESTful API**:
|
||||
- 路径:`/api/{resource}`(如 `/api/data`, `/api/users`)。
|
||||
- 方法:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)。
|
||||
- 查询参数:支持 filters(如 `?name=example&category=test`)。
|
||||
- 响应格式:
|
||||
```json
|
||||
{
|
||||
"status": "success" | "error",
|
||||
"data": [],
|
||||
"message": ""
|
||||
}
|
||||
```
|
||||
- **示例 API**:
|
||||
- GET `/api/data`:查询数据,支持分页(`?page=1&limit=10`)和筛选。
|
||||
- POST `/api/data`:创建数据,body 包含字段。
|
||||
- GET `/api/users`:获取用户列表(admin-system 专用)。
|
||||
- **小程序适配**:
|
||||
- 使用 wx.request,格式与 fetch 一致。
|
||||
- 示例:
|
||||
```javascript
|
||||
uni.request({
|
||||
url: '/api/data?' + new URLSearchParams(filters).toString(),
|
||||
method: 'GET',
|
||||
success: (res) => { /* 处理 res.data */ }
|
||||
});
|
||||
```
|
||||
- **跨域支持**:backend 配置 cors (2.8.x),允许所有子项目访问。
|
||||
|
||||
### 4. 数据库 Design
|
||||
- **MySQL 表结构**(示例):
|
||||
```sql
|
||||
CREATE TABLE data (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
category VARCHAR(100),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_category (category)
|
||||
);
|
||||
CREATE TABLE users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role ENUM('admin', 'user') DEFAULT 'user',
|
||||
INDEX idx_username (username)
|
||||
);
|
||||
```
|
||||
- **设计原则**:
|
||||
- 规范化设计,减少冗余。
|
||||
- 添加索引(如 `idx_name`)优化查询。
|
||||
- 使用外键(视需求)确保数据一致性。
|
||||
- **ORM**:使用 Sequelize (6.29.x),定义模型:
|
||||
```javascript
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('./config/database');
|
||||
const Data = sequelize.define('Data', {
|
||||
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
|
||||
name: { type: DataTypes.STRING, allowNull: false },
|
||||
category: { type: DataTypes.STRING }
|
||||
}, {
|
||||
indexes: [{ fields: ['name'] }, { fields: ['category'] }]
|
||||
});
|
||||
```
|
||||
|
||||
### 5. 筛选逻辑设计
|
||||
- **前端(admin-system/website/datav/mini_program)**:
|
||||
- 使用 reactive filters 对象:
|
||||
```javascript
|
||||
import { reactive } from 'vue';
|
||||
const filters = reactive({ name: '', category: '' });
|
||||
function updateFilter(key, value) {
|
||||
filters[key] = value;
|
||||
fetchData();
|
||||
}
|
||||
async function fetchData() {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
const response = await fetch(`/api/data?${query}`);
|
||||
return await response.json();
|
||||
}
|
||||
```
|
||||
- 小程序适配 wx.request,逻辑一致:
|
||||
```javascript
|
||||
async function fetchData() {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
const res = await uni.request({
|
||||
url: `/api/data?${query}`,
|
||||
method: 'GET'
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
```
|
||||
- **后端**:
|
||||
- 解析查询参数,构造参数化 SQL:
|
||||
```javascript
|
||||
app.get('/api/data', async (req, res) => {
|
||||
const { name, category } = req.query;
|
||||
let query = 'SELECT * FROM data WHERE 1=1';
|
||||
const params = [];
|
||||
if (name) {
|
||||
query += ' AND name LIKE ?';
|
||||
params.push(`%${name}%`);
|
||||
}
|
||||
if (category) {
|
||||
query += ' AND category = ?';
|
||||
params.push(category);
|
||||
}
|
||||
const [rows] = await pool.query(query, params);
|
||||
res.json({ status: 'success', data: rows, message: '' });
|
||||
});
|
||||
```
|
||||
|
||||
### 6. 性能优化
|
||||
- **数据库**:
|
||||
- 添加索引(如 `INDEX idx_name`)。
|
||||
- 使用连接池(mysql2/promise 2.3.x)管理 MySQL 连接。
|
||||
- 分页查询(`LIMIT`, `OFFSET`)避免全表扫描。
|
||||
- **前端**:
|
||||
- 防抖筛选请求(300ms,lodash.debounce 4.0.x)。
|
||||
- datav 使用分片加载(如 ECharts lazyUpdate)。
|
||||
- CDN 加速静态资源(Vue.js/ECharts via unpkg/jsDelivr)。
|
||||
- **后端**:
|
||||
- 缓存热点数据(Redis 6.x,视需求,兼容 Node.js 16.20.2)。
|
||||
- 限制 API 请求频率(express-rate-limit 5.x)。
|
||||
- **小程序**:
|
||||
- 优化首屏加载(按需加载数据)。
|
||||
- 缓存本地数据(uni.storage)。
|
||||
|
||||
### 7. 安全性设计
|
||||
- **后端**:
|
||||
- 参数化查询防止 SQL 注入(mysql2/Sequelize)。
|
||||
- JWT 认证(jsonwebtoken 8.x)保护 API(如 `/api/users`)。
|
||||
- 环境变量(dotenv 16.0.x)存储敏感信息:
|
||||
```env
|
||||
DB_HOST=localhost
|
||||
DB_USER=root
|
||||
DB_PASSWORD=your_password
|
||||
DB_DATABASE=project_db
|
||||
JWT_SECRET=your_secret
|
||||
```
|
||||
- **前端**:
|
||||
- 验证用户输入,防止 XSS(sanitize-html 2.x)。
|
||||
- HTTPS 加密通信。
|
||||
- **小程序**:
|
||||
- 遵守微信安全规范(如数据加密)。
|
||||
- 限制 API 调用范围(仅调用必要 API)。
|
||||
|
||||
### 8. 可扩展性设计
|
||||
- **模块化**:
|
||||
- backend 使用控制器和服务分离,便于添加新 API。
|
||||
- 前端组件化
|
||||
@@ -1,267 +0,0 @@
|
||||
# 项目经理提示词(SRS 优化版,Node.js 16.20.2 & MySQL)
|
||||
|
||||
## 项目概述
|
||||
您作为项目经理,负责为一个包含五个子项目的 Web 应用编写一份项目需求文档(SRS):后端项目(backend)、后端管理项目(admin-system)、官网(website)、大数据可视化界面(datav)和微信小程序(mini_program)。前端使用 Vue.js(3.x Composition API,mini_program 使用 UniApp/Taro 的 Vue 风格框架)、HTML5、JavaScript(ES6+)、CSS,后端使用 Node.js(16.20.2,Express 框架),数据库为 MySQL。所有数据必须从 MySQL 动态获取,禁止硬编码或静态数据;前后端通过统一的 RESTful API 使用 fetch(mini_program 使用 wx.request)交互;筛选条件通过手动更新 filters 对象管理,绕过 v-model 潜在绑定问题。您的目标是编写一份清晰、全面的 SRS,确保需求明确、结构化,便于开发团队实施和评审,同时支持跨团队协调、风险管理和质量保障。
|
||||
|
||||
## 项目目录职责
|
||||
1. **backend**:核心后端服务,提供统一 RESTful API,处理 MySQL 数据库交互,支持所有子项目的 CRUD 和筛选功能。
|
||||
2. **admin-system**:后端管理平台,用于管理员操作(如用户管理、数据配置),基于 Vue.js,调用 backend API。
|
||||
3. **website**:面向用户的官网,展示产品信息和动态内容,基于 Vue.js,强调响应式设计和 SEO,调用 backend API。
|
||||
4. **datav**:大数据可视化界面,基于 Vue.js 和 ECharts/D3.js,展示动态数据图表,支持交互筛选,调用 backend API。
|
||||
5. **mini_program**:微信小程序,基于 UniApp/Taro(Vue 风格),提供移动端功能,通过 wx.request 调用 backend API。
|
||||
|
||||
## SRS 编写指南
|
||||
|
||||
### 1. 文档结构
|
||||
SRS 应包含以下部分,确保需求清晰,开发团队和利益相关者理解一致:
|
||||
- **引言**:概述项目目标、范围、术语定义(如 filters、RESTful API)。
|
||||
- **总体描述**:项目背景、用户角色(管理员、普通用户)、运行环境(Node.js 16.20.2,MySQL 8.0.x)、技术栈。
|
||||
- **功能需求**:详细描述各子项目的功能,含用例和流程。
|
||||
- **非功能需求**:性能、安全、兼容性、可扩展性。
|
||||
- **接口规范**:API 请求/响应格式、filters 管理逻辑。
|
||||
- **数据库设计**:MySQL 表结构、索引需求。
|
||||
- **约束与假设**:技术限制、外部依赖(如微信审核)。
|
||||
- **风险与缓解措施**:潜在问题及应对策略。
|
||||
- **附录**:ER 图、用例图、API 文档(Swagger 格式)。
|
||||
|
||||
### 2. 功能需求(详细)
|
||||
#### 2.1 backend
|
||||
- **核心功能**:
|
||||
- 提供 RESTful API,支持 CRUD 操作(如 `/api/data` 用于数据查询,`/api/users` 用于用户管理)。
|
||||
- 处理动态筛选请求,解析 filters 对象(如 `?name=example&category=test`)。
|
||||
- **API 示例**:
|
||||
```json
|
||||
// GET /api/data?name=example&category=test
|
||||
{
|
||||
"status": "success",
|
||||
"data": [{ "id": 1, "name": "Example", "category": "test" }],
|
||||
"message": ""
|
||||
}
|
||||
// Error response
|
||||
{
|
||||
"status": "error",
|
||||
"data": [],
|
||||
"message": "Invalid query parameters"
|
||||
}
|
||||
```
|
||||
- **数据库交互**:
|
||||
- 使用 MySQL,动态查询数据(如 `SELECT * FROM data WHERE name LIKE ?`)。
|
||||
- 支持分页(`LIMIT`, `OFFSET`)和排序(`ORDER BY`)。
|
||||
- 使用 mysql2 (2.3.x) 或 Sequelize (6.29.x),兼容 Node.js 16.20.2。
|
||||
|
||||
#### 2.2 admin-system
|
||||
- **核心功能**:
|
||||
- 用户管理:增删改查用户(管理员、普通用户)。
|
||||
- 数据配置:管理产品、分类等数据。
|
||||
- 动态筛选:支持多条件筛选(如名称、日期)。
|
||||
- **筛选逻辑**:
|
||||
- 使用 reactive filters 对象(如 `filters = { name: '', category: '' }`)。
|
||||
- 手动更新(如 `filters.name = value`),通过 fetch 调用 API。
|
||||
- 示例:
|
||||
```javascript
|
||||
async function fetchData() {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
const response = await fetch(`/api/data?${query}`);
|
||||
return await response.json();
|
||||
}
|
||||
```
|
||||
- **界面**:
|
||||
- 响应式布局,支持表格展示、表单编辑。
|
||||
|
||||
#### 2.3 website
|
||||
- **核心功能**:
|
||||
- 展示产品信息、新闻动态。
|
||||
- 支持搜索和筛选(如按类别或关键词)。
|
||||
- SEO 优化(如 meta 标签、sitemap)。
|
||||
- **筛选逻辑**:同 admin-system,手动更新 filters,调用 backend API.
|
||||
- **界面**:现代化设计,支持移动端和桌面端。
|
||||
|
||||
#### 2.4 datav
|
||||
- **核心功能**:
|
||||
- 展示动态图表(如折线图、柱状图),基于 ECharts (5.x,兼容 Node.js 16.20.2)。
|
||||
- 支持交互筛选(如时间范围、数据类型)。
|
||||
- **筛选逻辑**:同 admin-system,使用 fetch 和 filters 对象。
|
||||
- **性能优化**:支持大数据分片加载,缓存静态资源。
|
||||
|
||||
#### 2.5 mini_program
|
||||
- **核心功能**:
|
||||
- 提供移动端功能(如产品浏览、订单管理)。
|
||||
- 支持搜索和筛选。
|
||||
- **筛选逻辑**:
|
||||
- 使用 UniApp/Taro(Vue 风格,兼容 Node.js 16.20.2)。
|
||||
- 手动更新 filters,调用 API(wx.request):
|
||||
```javascript
|
||||
async function fetchData() {
|
||||
const query = new URLSearchParams(filters).toString();
|
||||
const res = await uni.request({ url: `/api/data?${query}`, method: 'GET' });
|
||||
return res.data;
|
||||
}
|
||||
```
|
||||
- **界面**:适配微信小程序环境,简洁交互。
|
||||
|
||||
#### 2.6 跨项目要求
|
||||
- **数据来源**:所有数据从 MySQL 动态获取,禁止硬编码。
|
||||
- **筛选管理**:所有子项目统一使用 filters 对象,手动更新,触发 API 请求。
|
||||
- **接口一致性**:所有子项目使用相同 API 格式和响应结构。
|
||||
|
||||
### 3. 非功能需求
|
||||
- **性能**:
|
||||
- API 响应时间 < 500ms,MySQL 查询使用索引。
|
||||
- 前端使用防抖(debounce,300ms)优化筛选请求。
|
||||
- datav 支持大数据渲染(>10,000 条数据)。
|
||||
- **安全性**:
|
||||
- backend 使用参数化查询防止 SQL 注入。
|
||||
- 使用 JWT (jsonwebtoken 8.x,兼容 Node.js 16.20.2) 保护 API.
|
||||
- 环境变量(dotenv 16.x)存储 MySQL 凭据。
|
||||
- **兼容性**:
|
||||
- website、admin-system、datav 支持 Chrome、Firefox、Safari(最新版本)。
|
||||
- mini_program 兼容微信小程序(iOS 14+、Android 10+)。
|
||||
- **可扩展性**:
|
||||
- 模块化设计,支持新增 API 和功能。
|
||||
- **可用性**:
|
||||
- 用户友好的错误提示(如 “无匹配数据”)。
|
||||
- 界面支持多语言(预留)。
|
||||
|
||||
### 4. 接口规范
|
||||
- **请求格式**:
|
||||
- GET:查询参数传递 filters(如 `/api/data?name=example`)。
|
||||
- POST:JSON body 传递 filters。
|
||||
- **响应格式**:
|
||||
```json
|
||||
{
|
||||
"status": "success" | "error",
|
||||
"data": [],
|
||||
"message": ""
|
||||
}
|
||||
```
|
||||
- **API 示例**:
|
||||
- `/api/data`:查询数据,支持 filters。
|
||||
- `/api/users`:用户管理(admin-system 专用)。
|
||||
- **小程序适配**:mini_program 使用 wx.request,格式与 fetch 一致。
|
||||
- **CORS**:backend 配置 cors (2.8.x) 支持跨域。
|
||||
|
||||
### 5. 数据库设计
|
||||
- **MySQL 表结构**(示例):
|
||||
```sql
|
||||
CREATE TABLE data (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
category VARCHAR(100),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_category (category)
|
||||
);
|
||||
CREATE TABLE users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
role ENUM('admin', 'user') DEFAULT 'user'
|
||||
);
|
||||
```
|
||||
- **要求**:
|
||||
- 添加索引优化查询(如 `idx_name`)。
|
||||
- 使用参数化查询防止 SQL 注入。
|
||||
- 使用连接池(mysql2)管理连接。
|
||||
|
||||
### 6. 约束与假设
|
||||
- **技术约束**:
|
||||
- Node.js 16.20.2(指定版本,兼容 express 4.18.x, mysql2 2.3.x, sequelize 6.29.x)。
|
||||
- 前端:Vue.js(3.x)或 UniApp/Taro,fetch/wx.request。
|
||||
- 后端:Express、Sequelize/mysql2。
|
||||
- 数据库:MySQL(推荐 8.0.x)。
|
||||
- **外部依赖**:
|
||||
- 微信小程序需通过审核。
|
||||
- MySQL 云服务(如 AWS RDS)需稳定。
|
||||
- **假设**:
|
||||
- 开发团队熟悉 Vue.js、Node.js 16.20.2 和 MySQL。
|
||||
- MySQL 数据库已预配置。
|
||||
|
||||
### 7. 风险与缓解措施
|
||||
- **风险**:
|
||||
- backend API 开发延期,影响子项目。
|
||||
- MySQL 查询性能不足。
|
||||
- mini_program 审核失败。
|
||||
- filters 逻辑不一致。
|
||||
- **缓解措施**:
|
||||
- 提供 mock API(如 json-server,兼容 Node.js 16.20.2)支持并row开发。
|
||||
- 优化 MySQL 查询(EXPLAIN 分析,添加索引)。
|
||||
- 提前准备小程序审核材料,参考微信规范。
|
||||
- 编写单元测试(Jest 27.x for 前端,Mocha 9.x for 后端)验证 filters 逻辑。
|
||||
|
||||
## 项目管理指南(支持 SRS 编写)
|
||||
1. **需求收集与验证**:
|
||||
- 与利益相关者(客户、产品经理)确认功能需求。
|
||||
- 使用用例图描述用户交互(如搜索、筛选)。
|
||||
- 验证 API 和数据库设计(与开发团队讨论)。
|
||||
2. **文档编写**:
|
||||
- 使用 Markdown 或 Word 编写 SRS,结构清晰。
|
||||
- 包含 Swagger 格式的 API 文档(swagger-jsdoc 6.x,兼容 Node.js 16.20.2)。
|
||||
- ER 图展示 MySQL 表关系。
|
||||
3. **评审与反馈**:
|
||||
- 组织需求评审会议,邀请开发、测试、设计团队。
|
||||
- 记录反馈,更新 SRS。
|
||||
4. **版本控制**:
|
||||
- 使用 GitHub 存储 SRS,版本号如 v1.0.0。
|
||||
- 每次变更更新版本号(如 v1.0.1)。
|
||||
5. **跨子项目协调**:
|
||||
- 确保 backend API 优先开发,支持其他子项目。
|
||||
- 统一 filters 逻辑,减少开发歧义。
|
||||
|
||||
## 示例 SRS 片段
|
||||
|
||||
### 用例:用户筛选数据
|
||||
- **用例名称**:筛选产品列表
|
||||
- **参与者**:用户(website、mini_program)、管理员(admin-system)
|
||||
- **描述**:用户输入筛选条件(如名称、类别),系统返回匹配的数据。
|
||||
- **前置条件**:用户已登录(admin-system),API 可访问。
|
||||
- **流程**:
|
||||
1. 用户输入名称或选择类别。
|
||||
2. 系统更新 filters 对象(如 `filters.name = 'input'`)。
|
||||
3. 系统通过 fetch/wx.request 调用 `/api/data?${filters}`。
|
||||
4. backend 查询 MySQL,返回匹配数据。
|
||||
5. 系统展示结果。
|
||||
- **后置条件**:数据列表更新,错误提示(如无数据)。
|
||||
|
||||
### API 示例(Swagger)
|
||||
```yaml
|
||||
paths:
|
||||
/api/data:
|
||||
get:
|
||||
summary: 查询数据
|
||||
parameters:
|
||||
- name: name
|
||||
in: query
|
||||
type: string
|
||||
description: 按名称筛选
|
||||
- name: category
|
||||
in: query
|
||||
type: string
|
||||
description: 按类别筛选
|
||||
responses:
|
||||
200:
|
||||
description: 成功
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status: { type: string, enum: ["success", "error"] }
|
||||
data: { type: array }
|
||||
message: { type: string }
|
||||
```
|
||||
|
||||
### 示例依赖(backend package.json)
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^2.3.3",
|
||||
"sequelize": "^6.29.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"swagger-jsdoc": "^6.2.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.20.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过以上优化,SRS 将更清晰、结构化,确保需求明确,开发团队可依据文档高效实施,Node.js 16.20.2 和 MySQL 环境完全兼容,支持跨子项目一致性和项目管理。
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>电子围栏测试页面</title>
|
||||
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo"></script>
|
||||
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
|
||||
150
test-auth.js
150
test-auth.js
@@ -1,150 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
// 设置axios实例
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:5352/api',
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
// 登录凭证
|
||||
const credentials = {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
};
|
||||
|
||||
// 存储登录后的token
|
||||
let authToken = null;
|
||||
|
||||
console.log('开始测试政府管理系统登录和退出功能');
|
||||
console.log('======================================');
|
||||
|
||||
// 测试登录功能
|
||||
async function testLogin() {
|
||||
console.log('\n1. 测试登录功能');
|
||||
try {
|
||||
const response = await api.post('/auth/login', credentials);
|
||||
console.log('登录请求状态码:', response.status);
|
||||
console.log('登录响应数据:', response.data);
|
||||
|
||||
if (response.status === 200 && response.data.code === 200 && response.data.data.token) {
|
||||
authToken = response.data.data.token;
|
||||
console.log('✅ 登录成功,已获取token');
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 登录失败:', response.data.message || '未知错误');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 登录请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试获取用户信息功能
|
||||
async function testUserInfo() {
|
||||
if (!authToken) {
|
||||
console.log('\n2. 测试获取用户信息功能 - 跳过,未登录');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('\n2. 测试获取用户信息功能');
|
||||
try {
|
||||
const response = await api.get('/auth/userinfo', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
});
|
||||
console.log('用户信息请求状态码:', response.status);
|
||||
console.log('用户信息响应数据:', response.data);
|
||||
|
||||
if (response.status === 200 && response.data.code === 200) {
|
||||
console.log('✅ 获取用户信息成功');
|
||||
console.log(' 用户名:', response.data.data.username);
|
||||
console.log(' 角色:', response.data.data.role);
|
||||
console.log(' 姓名:', response.data.data.name);
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 获取用户信息失败:', response.data.message || '未知错误');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 获取用户信息请求失败:');
|
||||
if (error.response) {
|
||||
console.log(' 状态码:', error.response.status);
|
||||
console.log(' 响应数据:', error.response.data);
|
||||
} else {
|
||||
console.log(' 错误信息:', error.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试退出登录功能
|
||||
async function testLogout() {
|
||||
if (!authToken) {
|
||||
console.log('\n3. 测试退出登录功能 - 跳过,未登录');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('\n3. 测试退出登录功能');
|
||||
try {
|
||||
const response = await api.post('/auth/logout', {}, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
});
|
||||
console.log('退出登录请求状态码:', response.status);
|
||||
console.log('退出登录响应数据:', response.data);
|
||||
|
||||
if (response.status === 200 && response.data.code === 200) {
|
||||
console.log('✅ 退出登录成功');
|
||||
// 验证token是否失效
|
||||
try {
|
||||
const checkResponse = await api.get('/auth/userinfo', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
});
|
||||
console.log('❌ 退出登录后token仍然有效,这可能是一个安全问题');
|
||||
} catch (checkError) {
|
||||
console.log('✅ 退出登录后token已失效,符合预期');
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
console.log('❌ 退出登录失败:', response.data.message || '未知错误');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ 退出登录请求失败:');
|
||||
if (error.response) {
|
||||
console.log(' 状态码:', error.response.status);
|
||||
console.log(' 响应数据:', error.response.data);
|
||||
} else {
|
||||
console.log(' 错误信息:', error.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 运行完整测试
|
||||
async function runTests() {
|
||||
const loginSuccess = await testLogin();
|
||||
const userInfoSuccess = loginSuccess ? await testUserInfo() : false;
|
||||
const logoutSuccess = loginSuccess ? await testLogout() : false;
|
||||
|
||||
console.log('\n测试总结');
|
||||
console.log('==========');
|
||||
console.log('登录功能测试:', loginSuccess ? '通过 ✅' : '失败 ❌');
|
||||
console.log('获取用户信息测试:', userInfoSuccess ? '通过 ✅' : '失败 ❌');
|
||||
console.log('退出登录功能测试:', logoutSuccess ? '通过 ✅' : '失败 ❌');
|
||||
|
||||
if (loginSuccess && userInfoSuccess && logoutSuccess) {
|
||||
console.log('\n🎉 所有测试通过!登录和退出登录功能正常工作。');
|
||||
console.log('建议前端检查路由跳转和页面渲染逻辑,确保登录成功后能正确跳转到主页。');
|
||||
} else {
|
||||
console.log('\n❌ 部分测试失败,需要进一步排查问题。');
|
||||
}
|
||||
}
|
||||
|
||||
// 开始测试
|
||||
runTests();
|
||||
@@ -1,118 +0,0 @@
|
||||
// 测试前端退出登录功能
|
||||
const axios = require('axios');
|
||||
|
||||
// 登录信息
|
||||
const credentials = {
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
remember: false
|
||||
};
|
||||
|
||||
// 后端API地址
|
||||
const API_BASE_URL = 'http://localhost:5352/api';
|
||||
|
||||
// 模拟前端登录
|
||||
async function login() {
|
||||
try {
|
||||
console.log('模拟前端登录...');
|
||||
const response = await axios.post(`${API_BASE_URL}/auth/login`, credentials);
|
||||
|
||||
if (response.data && response.data.code === 200) {
|
||||
const token = response.data.data.token;
|
||||
console.log('登录成功,获取到token:', token);
|
||||
return token;
|
||||
} else {
|
||||
console.error('登录失败:', response.data?.message || '未知错误');
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录请求失败:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 模拟前端调用退出登录接口
|
||||
async function logout(token) {
|
||||
try {
|
||||
console.log('模拟前端调用退出登录接口...');
|
||||
const response = await axios.post(`${API_BASE_URL}/auth/logout`, {}, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data && response.data.code === 200) {
|
||||
console.log('退出登录成功:', response.data.message);
|
||||
return true;
|
||||
} else {
|
||||
console.error('退出登录失败:', response.data?.message || '未知错误');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退出登录请求失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证token是否失效
|
||||
async function verifyToken(token) {
|
||||
try {
|
||||
console.log('验证退出登录后token是否失效...');
|
||||
const response = await axios.get(`${API_BASE_URL}/auth/userinfo`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data && response.data.code === 200) {
|
||||
console.error('警告:退出登录后token仍然有效,可以获取用户信息!');
|
||||
return false;
|
||||
} else {
|
||||
console.log('验证成功:退出登录后token已失效');
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 401) {
|
||||
console.log('验证成功:退出登录后token已失效,返回401错误');
|
||||
return true;
|
||||
} else {
|
||||
console.error('验证token失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runTest() {
|
||||
try {
|
||||
// 1. 登录获取token
|
||||
const token = await login();
|
||||
if (!token) {
|
||||
console.log('测试失败:无法获取登录token');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 调用退出登录接口
|
||||
const logoutSuccess = await logout(token);
|
||||
if (!logoutSuccess) {
|
||||
console.log('测试失败:退出登录接口调用失败');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 验证token是否失效
|
||||
const tokenInvalid = await verifyToken(token);
|
||||
if (tokenInvalid) {
|
||||
console.log('\n测试成功:前端退出登录功能正常工作!');
|
||||
console.log('1. 登录成功获取到token');
|
||||
console.log('2. 退出登录接口调用成功');
|
||||
console.log('3. 退出登录后token已失效');
|
||||
} else {
|
||||
console.log('\n测试失败:前端退出登录功能存在问题');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('测试过程中发生错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTest();
|
||||
@@ -1,68 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
// 配置axios实例
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:5352/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// 登录测试函数
|
||||
async function testLogin() {
|
||||
console.log('开始测试登录功能...');
|
||||
|
||||
try {
|
||||
// 1. 测试登录
|
||||
console.log('1. 发送登录请求...');
|
||||
const loginResponse = await api.post('/auth/login', {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
|
||||
console.log('登录响应:', loginResponse.data);
|
||||
|
||||
if (loginResponse.data.code !== 200) {
|
||||
console.error('登录失败:', loginResponse.data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('获取到token:', token);
|
||||
|
||||
// 设置axios默认headers,添加token
|
||||
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
// 2. 测试获取用户信息
|
||||
console.log('\n2. 测试获取用户信息...');
|
||||
const userInfoResponse = await api.get('/auth/userinfo');
|
||||
|
||||
console.log('用户信息响应:', userInfoResponse.data);
|
||||
|
||||
if (userInfoResponse.data.code !== 200) {
|
||||
console.error('获取用户信息失败:', userInfoResponse.data.message);
|
||||
} else {
|
||||
console.log('用户信息获取成功!');
|
||||
}
|
||||
|
||||
// 3. 测试退出登录
|
||||
console.log('\n3. 测试退出登录...');
|
||||
const logoutResponse = await api.post('/auth/logout');
|
||||
|
||||
console.log('退出登录响应:', logoutResponse.data);
|
||||
|
||||
if (logoutResponse.data.code === 200) {
|
||||
console.log('退出登录成功!');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试过程中发生错误:', error.response ? error.response.data : error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
console.log('准备运行登录功能测试...');
|
||||
testLogin().then(() => {
|
||||
console.log('\n登录功能测试完成');
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
// 配置axios实例
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:5352/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
// 测试退出登录功能
|
||||
async function testLogout() {
|
||||
console.log('开始测试退出登录功能...');
|
||||
|
||||
try {
|
||||
// 1. 首先登录获取token
|
||||
console.log('1. 登录获取token...');
|
||||
const loginResponse = await api.post('/auth/login', {
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
|
||||
if (loginResponse.data.code !== 200) {
|
||||
console.error('登录失败,无法继续测试退出登录:', loginResponse.data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('登录成功,获取到token:', token);
|
||||
|
||||
// 2. 使用获取到的token发送退出登录请求
|
||||
console.log('\n2. 测试退出登录...');
|
||||
const logoutResponse = await api.post('/auth/logout', {}, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
console.log('退出登录响应:', logoutResponse.data);
|
||||
|
||||
if (logoutResponse.data.code === 200) {
|
||||
console.log('退出登录成功!');
|
||||
|
||||
// 3. 验证token是否仍然有效(尝试用相同token获取用户信息)
|
||||
console.log('\n3. 验证退出登录后token是否失效...');
|
||||
try {
|
||||
const userInfoResponse = await api.get('/auth/userinfo', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
console.log('用户信息响应:', userInfoResponse.data);
|
||||
|
||||
if (userInfoResponse.data.code === 200) {
|
||||
console.warn('警告: 退出登录后token仍然有效,建议实现token黑名单机制');
|
||||
} else {
|
||||
console.log('验证成功: 退出登录后token已失效');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('验证成功: 退出登录后token已失效');
|
||||
}
|
||||
} else {
|
||||
console.error('退出登录失败:', logoutResponse.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('测试过程中发生错误:', error.response ? error.response.data : error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
console.log('准备运行退出登录功能测试...');
|
||||
testLogout().then(() => {
|
||||
console.log('\n退出登录功能测试完成');
|
||||
});
|
||||
@@ -13,7 +13,7 @@ function initBaiduMap() {
|
||||
mapContainer.classList.add('map-loading');
|
||||
|
||||
// 百度地图API密钥
|
||||
const BAIDU_MAP_AK = 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo';
|
||||
const BAIDU_MAP_AK = 'fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC';
|
||||
|
||||
// 动态加载百度地图API脚本
|
||||
const script = document.createElement('script');
|
||||
|
||||
Reference in New Issue
Block a user