修改百度地图AK

This commit is contained in:
shenquanyi
2025-11-04 09:43:23 +08:00
parent 07bd3ce5da
commit eca2040e5b
80 changed files with 975 additions and 15841 deletions

View File

@@ -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

View File

@@ -16,7 +16,7 @@
### 百度地图配置
| 变量名 | 默认值 | 说明 |
|--------|--------|------|
| `VITE_BAIDU_MAP_API_KEY` | `SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo` | 百度地图API密钥 |
| `VITE_BAIDU_MAP_API_KEY` | `fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC` | 百度地图API密钥 |
### 应用配置
| 变量名 | 默认值 | 说明 |

View File

@@ -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',

View File

@@ -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加载重试次数已达上限');
}

View File

@@ -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;

View File

@@ -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
**状态**: 已实现并测试

View File

@@ -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版本
---
**注意**: 确保在运行任何测试之前,数据库服务正在运行,并且所有必要的依赖都已安装。

View File

@@ -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): 初始版本,包含基础预警查询、统计、处理和导出功能

View File

@@ -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
**检查结果**: 数据一致,无问题
**状态**: 已确认

View File

@@ -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
**状态**: 已修复并测试通过

View File

@@ -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地址访问服务
- 防火墙规则已正确添加
- 网络诊断脚本显示端口可以正常监听
如果按照以上步骤操作后仍有问题,请检查网络环境或联系网络管理员。

View File

@@ -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
```
## 📊 免费版限制
- 每次重启ngrokURL会变化
- 同时只能运行1个隧道
- 有连接数限制
- 有带宽限制
## 💰 付费版优势
- 固定子域名
- 多个隧道
- 更高带宽
- 更多功能
## 🚨 注意事项
1. **安全性**
- 外网访问会暴露您的服务
- 建议设置访问密码
- 不要在生产环境使用
2. **性能**
- 外网访问比内网慢
- 免费版有带宽限制
3. **稳定性**
- 免费版URL会变化
- 付费版更稳定
## 🛠️ 故障排除
### 问题1ngrok启动失败
```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"
```
### 问题3URL无法访问
- 检查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地址访问您的开发服务器了
记住:
- 每次重启ngrokURL会变化
- 免费版有使用限制
- 建议在开发测试时使用

View File

@@ -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
**维护者**: 开发团队

View File

@@ -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
**维护者**: 开发团队

View 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

View File

@@ -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
**测试状态**: 待验证

View File

@@ -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. **备份配置**: 保存防火墙配置以便快速恢复
---
**问题已完全解决!** 🎉
现在其他用户应该能够正常访问您的开发服务器了。如果还有任何问题,请检查上述故障排除步骤。

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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 "按任意键退出"

View File

@@ -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);
}

View File

@@ -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

View 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
View 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
View 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

View File

@@ -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);

View File

@@ -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

View File

@@ -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('测试完成!');

View File

@@ -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 };

View File

@@ -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

View File

@@ -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 "按任意键退出"

View File

@@ -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
View 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
View 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
View 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

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 = {};

View File

@@ -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

View File

@@ -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 };

View File

@@ -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();

View File

@@ -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
};

View File

@@ -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 };

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();

View File

@@ -1,49 +0,0 @@
@echo off
echo ========================================
echo ngrok测试脚本无需认证
echo ========================================
echo.
echo 注意此脚本使用ngrok的免费版本
echo 每次重启ngrokURL会发生变化
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

View File

@@ -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
};

View File

@@ -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 };

View File

@@ -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
};

View File

@@ -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);

View File

@@ -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. **监控告警**: 全链路监控、实时告警、性能分析
## 沟通方式
### 与业务方沟通
- 使用业务语言,避免过多技术细节
- 重点说明架构如何支撑业务目标
- 提供多个方案供选择,说明利弊
### 与开发团队沟通
- 提供详细的技术规范和开发指南
- 解释架构设计的技术原理和实现细节
- 定期进行架构评审和技术分享
### 与运维团队沟通
- 提供完整的部署和运维文档
- 说明监控指标和告警策略
- 协助制定故障处理预案
---
**使用指南**: 在进行系统架构设计时,请按照上述流程和标准进行分析和设计。始终以业务价值为导向,在技术先进性和实用性之间找到平衡点。记住,好的架构不是最复杂的,而是最适合当前业务场景和团队能力的。

View File

@@ -1,337 +0,0 @@
# 软件开发工程师提示词MySQL & Node.js 16.20.2 优化版)
## 项目概述
开发一个前后端分离的 Web 应用,前端使用 Vue.js3.x Composition API、HTML5、JavaScriptES6+、CSS后端使用 Node.js版本 16.20.2Express 框架),数据库为 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
- **性能优化**
- 前端使用防抖debounce300ms或节流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)
);
```

View File

@@ -1,216 +0,0 @@
# 系统架构师提示词Node.js 16.20.2 & MySQL 优化版)
## 项目概述
您作为系统架构师,负责设计一个包含五个子项目的 Web 应用系统架构后端项目backend、后端管理项目admin-system、官网website、大数据可视化界面datav和微信小程序mini_program。前端技术栈为 Vue.js3.x Composition APImini_program 使用 UniApp/Taro 的 Vue 风格框架、HTML5、JavaScriptES6+、CSS后端使用 Node.js16.20.2Express 框架),数据库为 MySQL。所有数据必须从 MySQL 动态获取,禁止硬编码或静态数据;前后端通过统一的 RESTful API 使用 fetchmini_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 和 ECharts5.x展示动态数据图表支持交互筛选调用 backend API。
5. **mini_program**:微信小程序,基于 UniApp/TaroVue 风格),提供移动端功能,通过 wx.request 调用 backend API。
## 架构设计指南
### 1. 系统架构概览
- **架构类型**前后端分离的单体架构backend 为单体服务,多个前端子项目),预留微服务扩展能力。
- **技术栈**
- **前端**Vue.js (3.x Composition API)、HTML5、JavaScript (ES6+)、CSSdatav 集成 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 APIfetch前端wx.requestmini_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`)避免全表扫描。
- **前端**
- 防抖筛选请求300mslodash.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
```
- **前端**
- 验证用户输入,防止 XSSsanitize-html 2.x
- HTTPS 加密通信。
- **小程序**
- 遵守微信安全规范(如数据加密)。
- 限制 API 调用范围(仅调用必要 API
### 8. 可扩展性设计
- **模块化**
- backend 使用控制器和服务分离,便于添加新 API。
- 前端组件化

View File

@@ -1,267 +0,0 @@
# 项目经理提示词SRS 优化版Node.js 16.20.2 & MySQL
## 项目概述
您作为项目经理,负责为一个包含五个子项目的 Web 应用编写一份项目需求文档SRS后端项目backend、后端管理项目admin-system、官网website、大数据可视化界面datav和微信小程序mini_program。前端使用 Vue.js3.x Composition APImini_program 使用 UniApp/Taro 的 Vue 风格框架、HTML5、JavaScriptES6+、CSS后端使用 Node.js16.20.2Express 框架),数据库为 MySQL。所有数据必须从 MySQL 动态获取,禁止硬编码或静态数据;前后端通过统一的 RESTful API 使用 fetchmini_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/TaroVue 风格),提供移动端功能,通过 wx.request 调用 backend API。
## SRS 编写指南
### 1. 文档结构
SRS 应包含以下部分,确保需求清晰,开发团队和利益相关者理解一致:
- **引言**:概述项目目标、范围、术语定义(如 filters、RESTful API
- **总体描述**项目背景、用户角色管理员、普通用户、运行环境Node.js 16.20.2MySQL 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/TaroVue 风格,兼容 Node.js 16.20.2)。
- 手动更新 filters调用 APIwx.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 响应时间 < 500msMySQL 查询使用索引。
- 前端使用防抖debounce300ms优化筛选请求。
- 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`)。
- POSTJSON 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.js3.x或 UniApp/Tarofetch/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-systemAPI 可访问。
- **流程**
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 环境完全兼容,支持跨子项目一致性和项目管理。

View File

@@ -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;

View File

@@ -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();

View File

@@ -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();

View File

@@ -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登录功能测试完成');
});

View File

@@ -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退出登录功能测试完成');
});

View File

@@ -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');