feat: 拆分设备状态为AMT状态和Agent状态
- 数据库:添加 amt_status 和 agent_status 字段 - 后端:Device 实体类和 DeviceDTO 添加新状态字段 - 后端:DeviceService 添加状态检测和更新方法 - 后端:添加 AmtStatusCheckTask 定时任务(每30秒检测一次) - 前端:设备列表页面拆分状态列显示 - 前端:统计卡片显示 AMT 和 Agent 在线/离线数量 - 网络扫描:自动设置 AMT 状态为在线 - 文档:添加 DEVICE_STATUS_SPLIT.md 和 AMT_REALTIME_STATUS.md
This commit is contained in:
parent
028fd8f444
commit
2e28ad1472
417
AMT_REALTIME_STATUS.md
Normal file
417
AMT_REALTIME_STATUS.md
Normal file
@ -0,0 +1,417 @@
|
||||
# AMT 实时状态检测功能
|
||||
|
||||
## 功能概述
|
||||
|
||||
实现了 AMT 设备状态的实时检测和自动更新功能,无需手动刷新即可看到设备的在线/离线状态变化。
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 后端实现
|
||||
|
||||
#### 1. 定时任务 (AmtStatusCheckTask.java)
|
||||
|
||||
**位置**: `backend/src/main/java/com/soybean/admin/task/AmtStatusCheckTask.java`
|
||||
|
||||
**功能**:
|
||||
- 每 30 秒自动检测一次所有设备的 AMT 状态
|
||||
- 使用线程池并发检测,提高效率
|
||||
- 只在状态变化时更新数据库,减少写入操作
|
||||
- 使用 Socket 连接检测 AMT 端口(16992/16993)可用性
|
||||
|
||||
**关键特性**:
|
||||
```java
|
||||
@Scheduled(fixedRate = 30000, initialDelay = 10000)
|
||||
public void checkAmtStatus() {
|
||||
// 每 30 秒执行一次
|
||||
// 初始延迟 10 秒,等待应用完全启动
|
||||
}
|
||||
```
|
||||
|
||||
**检测逻辑**:
|
||||
1. 查询所有有 IP 地址的设备
|
||||
2. 并发检测每个设备的 AMT 端口连接性
|
||||
3. 先尝试 HTTP (16992),失败则尝试 HTTPS (16993)
|
||||
4. 根据连接结果更新设备的 `amt_status` 字段
|
||||
5. 只有状态变化时才更新数据库和记录日志
|
||||
|
||||
**线程池配置**:
|
||||
- 固定 20 个线程
|
||||
- 使用 CompletableFuture 异步执行
|
||||
- 等待所有检测完成后结束任务
|
||||
|
||||
#### 2. 启用定时任务
|
||||
|
||||
**修改**: `backend/src/main/java/com/soybean/admin/SoybeanAdminApplication.java`
|
||||
|
||||
添加 `@EnableScheduling` 注解启用 Spring 定时任务功能:
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.soybean.admin.mapper")
|
||||
@EnableScheduling // 启用定时任务
|
||||
public class SoybeanAdminApplication {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 前端实现
|
||||
|
||||
#### 1. 自动轮询刷新
|
||||
|
||||
**位置**: `src/views/device/list/index.vue`
|
||||
|
||||
**功能**:
|
||||
- 页面加载后每 10 秒自动刷新设备列表
|
||||
- 组件卸载时自动清除定时器
|
||||
- 显示"自动刷新中"状态标签
|
||||
|
||||
**实现代码**:
|
||||
```typescript
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
|
||||
// 启动定时刷新,每 10 秒刷新一次设备状态
|
||||
const refreshInterval = setInterval(() => {
|
||||
loadData();
|
||||
}, 10000);
|
||||
|
||||
// 组件卸载时清除定时器
|
||||
onUnmounted(() => {
|
||||
clearInterval(refreshInterval);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### 2. 手动刷新按钮
|
||||
|
||||
添加了"刷新状态"按钮,用户可以随时手动刷新:
|
||||
|
||||
```vue
|
||||
<n-button @click="handleManualRefresh" :loading="loading">
|
||||
<icon-mdi-refresh class="mr-4px text-16px" />
|
||||
刷新状态
|
||||
</n-button>
|
||||
```
|
||||
|
||||
#### 3. 状态指示器
|
||||
|
||||
显示自动刷新状态:
|
||||
|
||||
```vue
|
||||
<n-tag v-if="autoRefreshEnabled" type="success" size="small">
|
||||
<icon-mdi-autorenew class="mr-4px" />
|
||||
自动刷新中
|
||||
</n-tag>
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 完整流程图
|
||||
|
||||
```
|
||||
后端定时任务 (每30秒)
|
||||
↓
|
||||
检测所有设备 AMT 端口
|
||||
↓
|
||||
更新数据库状态
|
||||
↓
|
||||
前端轮询 (每10秒)
|
||||
↓
|
||||
获取最新设备列表
|
||||
↓
|
||||
更新界面显示
|
||||
```
|
||||
|
||||
### 时间线示例
|
||||
|
||||
```
|
||||
T=0s: 后端启动,10秒后开始第一次检测
|
||||
T=10s: 后端第一次检测所有设备
|
||||
T=10s: 前端页面加载,开始第一次数据获取
|
||||
T=20s: 前端第二次刷新
|
||||
T=30s: 前端第三次刷新
|
||||
T=40s: 后端第二次检测 + 前端第四次刷新
|
||||
T=50s: 前端第五次刷新
|
||||
...
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 后端优化
|
||||
|
||||
1. **并发检测**: 使用 20 个线程并发检测,大幅提升检测速度
|
||||
2. **快速超时**: Socket 连接超时设置为 3 秒,避免长时间等待
|
||||
3. **增量更新**: 只在状态变化时更新数据库
|
||||
4. **简单检测**: 只检测端口连接性,不进行完整的 AMT 认证
|
||||
|
||||
### 前端优化
|
||||
|
||||
1. **合理间隔**: 10 秒刷新间隔,平衡实时性和性能
|
||||
2. **自动清理**: 组件卸载时清除定时器,避免内存泄漏
|
||||
3. **加载状态**: 显示加载状态,提升用户体验
|
||||
|
||||
## 状态检测逻辑
|
||||
|
||||
### 检测方法
|
||||
|
||||
使用 TCP Socket 连接检测,而不是完整的 AMT 认证:
|
||||
|
||||
```java
|
||||
private boolean checkAmtConnectivity(String ipAddress) {
|
||||
try {
|
||||
// 尝试连接 AMT HTTP 端口
|
||||
Socket socket = new Socket();
|
||||
socket.connect(new InetSocketAddress(ipAddress, 16992), 3000);
|
||||
socket.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
// HTTP 失败,尝试 HTTPS
|
||||
try {
|
||||
Socket socket = new Socket();
|
||||
socket.connect(new InetSocketAddress(ipAddress, 16993), 3000);
|
||||
socket.close();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 优点
|
||||
|
||||
1. **快速**: 不需要完整的 SOAP 请求和认证
|
||||
2. **轻量**: 只检测端口可用性
|
||||
3. **可靠**: 能准确判断 AMT 服务是否运行
|
||||
|
||||
### 缺点
|
||||
|
||||
1. **不验证认证**: 不检查凭证是否正确
|
||||
2. **端口占用**: 如果端口被其他服务占用,可能误判
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 1. 执行数据库更新
|
||||
|
||||
如果还没有执行过数据库更新,先运行:
|
||||
|
||||
```bash
|
||||
update_device_status.bat
|
||||
```
|
||||
|
||||
### 2. 重新编译后端
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
### 3. 启动后端服务
|
||||
|
||||
```bash
|
||||
start_backend.bat
|
||||
```
|
||||
|
||||
后端启动后,会在 10 秒后开始第一次 AMT 状态检测,之后每 30 秒检测一次。
|
||||
|
||||
### 4. 打开前端页面
|
||||
|
||||
访问设备列表页面,会看到:
|
||||
- 右上角显示"自动刷新中"标签
|
||||
- 设备的 AMT 状态列实时更新
|
||||
- 可以点击"刷新状态"按钮手动刷新
|
||||
|
||||
## 日志查看
|
||||
|
||||
### 后端日志
|
||||
|
||||
查看后端控制台输出,会看到类似日志:
|
||||
|
||||
```
|
||||
开始 AMT 状态检测任务
|
||||
开始检测 10 个设备的 AMT 状态
|
||||
设备 Server-01 (192.168.8.100) AMT 状态变化: offline -> online
|
||||
设备 Server-02 (192.168.8.101) AMT 状态变化: online -> offline
|
||||
AMT 状态检测任务完成
|
||||
```
|
||||
|
||||
### 前端日志
|
||||
|
||||
打开浏览器开发者工具,可以看到每 10 秒发送一次设备列表请求。
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 场景 1: 设备上线
|
||||
|
||||
1. 启动一台 AMT 设备
|
||||
2. 等待最多 30 秒(后端检测周期)
|
||||
3. 前端会在下次刷新时显示设备状态变为"在线"
|
||||
|
||||
### 场景 2: 设备下线
|
||||
|
||||
1. 关闭一台 AMT 设备
|
||||
2. 等待最多 30 秒(后端检测周期)
|
||||
3. 前端会在下次刷新时显示设备状态变为"离线"
|
||||
|
||||
### 场景 3: 手动刷新
|
||||
|
||||
1. 点击"刷新状态"按钮
|
||||
2. 立即获取最新的设备状态
|
||||
3. 不需要等待自动刷新周期
|
||||
|
||||
## 配置参数
|
||||
|
||||
### 后端配置
|
||||
|
||||
在 `AmtStatusCheckTask.java` 中可以调整:
|
||||
|
||||
```java
|
||||
// 检测周期(毫秒)
|
||||
@Scheduled(fixedRate = 30000, initialDelay = 10000)
|
||||
// fixedRate: 30000 = 30秒
|
||||
// initialDelay: 10000 = 10秒初始延迟
|
||||
|
||||
// 线程池大小
|
||||
private final ExecutorService executorService = Executors.newFixedThreadPool(20);
|
||||
// 20 个线程
|
||||
|
||||
// Socket 超时时间(毫秒)
|
||||
socket.connect(new InetSocketAddress(ipAddress, 16992), 3000);
|
||||
// 3000 = 3秒
|
||||
```
|
||||
|
||||
### 前端配置
|
||||
|
||||
在 `index.vue` 中可以调整:
|
||||
|
||||
```typescript
|
||||
// 刷新周期(毫秒)
|
||||
const refreshInterval = setInterval(() => {
|
||||
loadData();
|
||||
}, 10000); // 10000 = 10秒
|
||||
```
|
||||
|
||||
## 性能影响
|
||||
|
||||
### 后端资源消耗
|
||||
|
||||
- **CPU**: 低(主要是网络 I/O 等待)
|
||||
- **内存**: 低(20 个线程 + 设备列表)
|
||||
- **网络**: 每个设备每 30 秒一次连接检测
|
||||
|
||||
### 前端资源消耗
|
||||
|
||||
- **网络**: 每 10 秒一次 API 请求
|
||||
- **内存**: 低(只更新设备列表数据)
|
||||
- **CPU**: 低(简单的数据更新和渲染)
|
||||
|
||||
### 数据库影响
|
||||
|
||||
- **读取**: 每 30 秒读取所有设备列表
|
||||
- **写入**: 只在状态变化时写入(增量更新)
|
||||
- **索引**: 建议在 `ip_address` 和 `amt_status` 字段上建立索引
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
### 1. WebSocket 推送
|
||||
|
||||
替代轮询方式,使用 WebSocket 实时推送状态变化:
|
||||
|
||||
**优点**:
|
||||
- 更实时
|
||||
- 减少网络请求
|
||||
- 降低服务器负载
|
||||
|
||||
**实现**:
|
||||
- 后端使用 Spring WebSocket
|
||||
- 状态变化时主动推送到前端
|
||||
- 前端监听 WebSocket 消息更新界面
|
||||
|
||||
### 2. 智能检测频率
|
||||
|
||||
根据设备状态调整检测频率:
|
||||
|
||||
- 在线设备: 每 60 秒检测一次
|
||||
- 离线设备: 每 30 秒检测一次
|
||||
- 新添加设备: 每 10 秒检测一次(前 5 分钟)
|
||||
|
||||
### 3. 批量状态查询接口
|
||||
|
||||
添加专门的状态查询接口,只返回状态信息:
|
||||
|
||||
```java
|
||||
@GetMapping("/device/status")
|
||||
public Result<Map<Long, String>> getDeviceStatus() {
|
||||
// 只返回 id -> status 的映射
|
||||
// 减少数据传输量
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 状态变化通知
|
||||
|
||||
设备状态变化时发送通知:
|
||||
|
||||
- 邮件通知
|
||||
- 短信通知
|
||||
- 系统消息通知
|
||||
- Webhook 回调
|
||||
|
||||
### 5. 历史状态记录
|
||||
|
||||
记录设备状态变化历史:
|
||||
|
||||
- 创建 `device_status_history` 表
|
||||
- 记录每次状态变化的时间和原因
|
||||
- 生成可用性报表
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题 1: 状态不更新
|
||||
|
||||
**可能原因**:
|
||||
- 后端定时任务未启动
|
||||
- 数据库连接失败
|
||||
- 网络不通
|
||||
|
||||
**排查方法**:
|
||||
1. 查看后端日志是否有"开始 AMT 状态检测任务"
|
||||
2. 检查数据库 `amt_status` 字段是否存在
|
||||
3. 手动测试设备网络连接
|
||||
|
||||
### 问题 2: 前端不刷新
|
||||
|
||||
**可能原因**:
|
||||
- 定时器未启动
|
||||
- API 请求失败
|
||||
- 组件已卸载
|
||||
|
||||
**排查方法**:
|
||||
1. 打开浏览器开发者工具查看网络请求
|
||||
2. 检查控制台是否有错误
|
||||
3. 确认 `autoRefreshEnabled` 为 true
|
||||
|
||||
### 问题 3: 性能问题
|
||||
|
||||
**可能原因**:
|
||||
- 设备数量过多
|
||||
- 检测频率过高
|
||||
- 网络延迟大
|
||||
|
||||
**解决方案**:
|
||||
1. 增加检测周期(如改为 60 秒)
|
||||
2. 增加线程池大小
|
||||
3. 使用 WebSocket 替代轮询
|
||||
|
||||
## 总结
|
||||
|
||||
实现了完整的 AMT 实时状态检测功能:
|
||||
|
||||
✅ 后端定时任务自动检测(每 30 秒)
|
||||
✅ 前端自动刷新显示(每 10 秒)
|
||||
✅ 手动刷新按钮
|
||||
✅ 状态指示器
|
||||
✅ 并发检测优化
|
||||
✅ 增量数据库更新
|
||||
✅ 完整的日志记录
|
||||
|
||||
用户无需手动刷新,即可实时看到设备的 AMT 在线/离线状态变化。
|
||||
283
DEVICE_STATUS_SPLIT.md
Normal file
283
DEVICE_STATUS_SPLIT.md
Normal file
@ -0,0 +1,283 @@
|
||||
# 设备状态拆分说明
|
||||
|
||||
## 功能说明
|
||||
|
||||
将原来的单一设备状态拆分为两个独立的状态字段:
|
||||
|
||||
1. **AMT 状态** (`amt_status`):表示 Intel AMT 是否在线
|
||||
- `online`:AMT 在线,可以进行远程管理
|
||||
- `offline`:AMT 离线,无法连接
|
||||
|
||||
2. **Agent 状态** (`agent_status`):表示操作系统是否在线
|
||||
- `online`:操作系统在线,Agent 正常运行
|
||||
- `offline`:操作系统离线或 Agent 未运行
|
||||
|
||||
## 数据库变更
|
||||
|
||||
### 新增字段
|
||||
|
||||
```sql
|
||||
ALTER TABLE device
|
||||
ADD COLUMN amt_status VARCHAR(20) DEFAULT 'offline' COMMENT 'AMT状态: online-在线, offline-离线',
|
||||
ADD COLUMN agent_status VARCHAR(20) DEFAULT 'offline' COMMENT 'Agent状态: online-在线, offline-离线';
|
||||
```
|
||||
|
||||
### 数据迁移
|
||||
|
||||
- 原 `status` 字段保留用于兼容
|
||||
- 新设备的 `amt_status` 和 `agent_status` 默认为 `offline`
|
||||
- 通过网络扫描添加的设备,`amt_status` 自动设置为 `online`
|
||||
|
||||
## 后端变更
|
||||
|
||||
### 1. 实体类更新
|
||||
|
||||
**Device.java**:
|
||||
```java
|
||||
private String status; // 保留原字段用于兼容
|
||||
private String amtStatus; // AMT 状态
|
||||
private String agentStatus; // Agent 状态
|
||||
```
|
||||
|
||||
**DeviceDTO.java**:
|
||||
```java
|
||||
private String status; // 保留用于兼容
|
||||
private String amtStatus; // AMT 状态
|
||||
private String agentStatus; // Agent 状态
|
||||
```
|
||||
|
||||
### 2. 服务层更新
|
||||
|
||||
**DeviceService.java**:
|
||||
- 添加 `checkAmtStatus()` 方法:检测设备 AMT 状态
|
||||
- 添加 `updateAmtStatus()` 方法:更新设备 AMT 状态
|
||||
- 添加 `updateAgentStatus()` 方法:更新设备 Agent 状态
|
||||
- 更新 `getDeviceStatistics()` 方法:统计 AMT 和 Agent 在线/离线数量
|
||||
|
||||
## 前端变更
|
||||
|
||||
### 1. 类型定义更新
|
||||
|
||||
**device.d.ts**:
|
||||
```typescript
|
||||
interface Device {
|
||||
status: 'online' | 'offline' | 'fault' | 'maintenance'; // 保留
|
||||
amtStatus: 'online' | 'offline'; // AMT 状态
|
||||
agentStatus: 'online' | 'offline'; // Agent 状态
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 设备列表页面更新
|
||||
|
||||
**index.vue**:
|
||||
|
||||
#### 统计卡片
|
||||
- 总设备数
|
||||
- AMT 在线
|
||||
- AMT 离线
|
||||
- Agent 在线
|
||||
- Agent 离线
|
||||
|
||||
#### 表格列
|
||||
- 设备名称
|
||||
- UUID
|
||||
- **AMT 状态**(新增)
|
||||
- **Agent 状态**(新增)
|
||||
- IP 地址
|
||||
- MAC 地址
|
||||
- 创建时间
|
||||
- 操作
|
||||
|
||||
#### 状态显示
|
||||
- AMT 在线:绿色标签
|
||||
- AMT 离线:灰色标签
|
||||
- Agent 在线:蓝色标签
|
||||
- Agent 离线:灰色标签
|
||||
|
||||
### 3. 网络扫描功能
|
||||
- 扫描到的设备自动设置 `amtStatus = 'online'`
|
||||
- Agent 状态默认为 `offline`(需要 Agent 上报)
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 1. 执行数据库更新
|
||||
|
||||
运行批处理文件:
|
||||
```bash
|
||||
update_device_status.bat
|
||||
```
|
||||
|
||||
或手动执行 SQL:
|
||||
```bash
|
||||
mysql -h localhost -P 3306 -u root -proot soybean_admin < update_device_status_fields.sql
|
||||
```
|
||||
|
||||
### 2. 重新编译后端
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
### 3. 重启后端服务
|
||||
|
||||
```bash
|
||||
start_backend.bat
|
||||
```
|
||||
|
||||
### 4. 测试功能
|
||||
|
||||
1. 打开设备列表页面
|
||||
2. 查看统计卡片是否显示 AMT 和 Agent 状态
|
||||
3. 查看表格是否显示两个状态列
|
||||
4. 进行网络扫描,添加设备
|
||||
5. 验证新添加的设备 AMT 状态为"在线"
|
||||
|
||||
## AMT 状态检测
|
||||
|
||||
### 当前实现
|
||||
|
||||
扫描到的设备自动设置为在线,其他设备默认离线。
|
||||
|
||||
### 后续完善
|
||||
|
||||
1. **定时检测**:
|
||||
- 创建定时任务,定期检测所有设备的 AMT 状态
|
||||
- 使用 Identify 请求快速检测连接性
|
||||
- 更新数据库中的 `amt_status` 字段
|
||||
|
||||
2. **手动刷新**:
|
||||
- 在设备列表添加"刷新状态"按钮
|
||||
- 点击后检测选中设备的 AMT 状态
|
||||
- 实时更新状态显示
|
||||
|
||||
3. **状态变化通知**:
|
||||
- AMT 从在线变为离线时发送告警
|
||||
- 记录状态变化历史
|
||||
|
||||
## Agent 状态实现
|
||||
|
||||
### 当前状态
|
||||
|
||||
Agent 状态字段已添加,默认为离线(假数据)。
|
||||
|
||||
### 后续实现步骤
|
||||
|
||||
1. **开发 Agent 程序**:
|
||||
- 在目标设备上运行的客户端程序
|
||||
- 定期向服务器报告心跳
|
||||
- 上报设备信息(CPU、内存、磁盘等)
|
||||
|
||||
2. **心跳机制**:
|
||||
- Agent 每 30 秒发送一次心跳
|
||||
- 服务器记录最后心跳时间
|
||||
- 超过 2 分钟未收到心跳则标记为离线
|
||||
|
||||
3. **状态更新接口**:
|
||||
```java
|
||||
@PostMapping("/device/agent/heartbeat")
|
||||
public Result<Boolean> agentHeartbeat(@RequestBody AgentHeartbeat heartbeat) {
|
||||
// 更新设备 Agent 状态为在线
|
||||
// 更新最后心跳时间
|
||||
// 记录设备信息
|
||||
}
|
||||
```
|
||||
|
||||
4. **定时检查**:
|
||||
- 创建定时任务,每分钟检查一次
|
||||
- 将超时未心跳的设备标记为离线
|
||||
|
||||
## 状态组合说明
|
||||
|
||||
| AMT 状态 | Agent 状态 | 说明 |
|
||||
|---------|-----------|------|
|
||||
| 在线 | 在线 | 设备完全正常,可进行所有操作 |
|
||||
| 在线 | 离线 | 操作系统关机,但 AMT 可用,可远程开机 |
|
||||
| 离线 | 在线 | AMT 未启用或故障,但系统正常运行 |
|
||||
| 离线 | 离线 | 设备完全离线或断电 |
|
||||
|
||||
## 操作建议
|
||||
|
||||
### 根据状态执行操作
|
||||
|
||||
1. **AMT 在线 + Agent 离线**:
|
||||
- 可以使用远程开机功能
|
||||
- 可以查看 BIOS 设置
|
||||
- 可以进行硬件级别的管理
|
||||
|
||||
2. **AMT 离线 + Agent 在线**:
|
||||
- 可以通过 Agent 执行软件操作
|
||||
- 无法进行硬件级别的管理
|
||||
- 建议启用 AMT 功能
|
||||
|
||||
3. **AMT 在线 + Agent 在线**:
|
||||
- 所有功能可用
|
||||
- 最佳状态
|
||||
|
||||
4. **AMT 离线 + Agent 离线**:
|
||||
- 设备不可用
|
||||
- 需要物理检查设备
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 场景 1:网络扫描添加设备
|
||||
1. 执行网络扫描
|
||||
2. 选择设备并批量添加
|
||||
3. 验证:AMT 状态 = 在线,Agent 状态 = 离线
|
||||
|
||||
### 场景 2:手动添加设备
|
||||
1. 选择"手动添加"方式
|
||||
2. 输入设备信息
|
||||
3. 验证:AMT 状态 = 离线,Agent 状态 = 离线
|
||||
|
||||
### 场景 3:查看统计信息
|
||||
1. 打开设备列表
|
||||
2. 查看顶部统计卡片
|
||||
3. 验证:显示 AMT 和 Agent 的在线/离线数量
|
||||
|
||||
### 场景 4:状态筛选
|
||||
1. 在搜索栏选择状态筛选
|
||||
2. 验证:可以按 AMT 或 Agent 状态筛选(后续实现)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据兼容性**:
|
||||
- 保留了原 `status` 字段
|
||||
- 旧代码仍可正常运行
|
||||
- 建议逐步迁移到新字段
|
||||
|
||||
2. **性能考虑**:
|
||||
- AMT 状态检测会产生网络请求
|
||||
- 建议使用缓存和定时任务
|
||||
- 避免频繁检测
|
||||
|
||||
3. **错误处理**:
|
||||
- AMT 检测失败不应影响其他功能
|
||||
- 记录检测失败的原因
|
||||
- 提供重试机制
|
||||
|
||||
4. **安全性**:
|
||||
- Agent 心跳需要身份验证
|
||||
- 防止伪造心跳攻击
|
||||
- 加密传输敏感信息
|
||||
|
||||
## 后续优化
|
||||
|
||||
1. **实时状态推送**:
|
||||
- 使用 WebSocket 推送状态变化
|
||||
- 前端实时更新显示
|
||||
|
||||
2. **状态历史记录**:
|
||||
- 记录状态变化历史
|
||||
- 生成状态变化报表
|
||||
- 分析设备可用性
|
||||
|
||||
3. **告警功能**:
|
||||
- 设备离线告警
|
||||
- 状态异常告警
|
||||
- 邮件/短信通知
|
||||
|
||||
4. **批量操作**:
|
||||
- 批量刷新状态
|
||||
- 批量启用/禁用 AMT
|
||||
- 批量部署 Agent
|
||||
@ -3,9 +3,11 @@ package com.soybean.admin;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.soybean.admin.mapper")
|
||||
@EnableScheduling
|
||||
public class SoybeanAdminApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SoybeanAdminApplication.class, args);
|
||||
|
||||
@ -7,7 +7,9 @@ public class DeviceDTO {
|
||||
private Long id;
|
||||
private String deviceName;
|
||||
private String deviceCode;
|
||||
private String status;
|
||||
private String status; // 保留用于兼容
|
||||
private String amtStatus; // AMT 状态
|
||||
private String agentStatus; // Agent 状态
|
||||
private String ipAddress;
|
||||
private String macAddress;
|
||||
private String remark;
|
||||
|
||||
@ -15,7 +15,9 @@ public class Device {
|
||||
|
||||
private String deviceName;
|
||||
private String deviceCode;
|
||||
private String status;
|
||||
private String status; // 保留原字段用于兼容
|
||||
private String amtStatus; // AMT 状态:online, offline
|
||||
private String agentStatus; // Agent 状态:online, offline
|
||||
private String ipAddress;
|
||||
private String macAddress;
|
||||
private String remark;
|
||||
|
||||
@ -21,6 +21,9 @@ public class DeviceService {
|
||||
|
||||
@Autowired
|
||||
private DeviceMapper deviceMapper;
|
||||
|
||||
@Autowired
|
||||
private AmtDigestService amtDigestService;
|
||||
|
||||
public PageResponse<Device> getDeviceList(PageRequest pageRequest) {
|
||||
Page<Device> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
|
||||
@ -102,26 +105,87 @@ public class DeviceService {
|
||||
// 总数
|
||||
statistics.put("total", deviceMapper.selectCount(null));
|
||||
|
||||
// 在线
|
||||
// AMT 在线
|
||||
wrapper.clear();
|
||||
wrapper.eq(Device::getAmtStatus, "online");
|
||||
statistics.put("amtOnline", deviceMapper.selectCount(wrapper));
|
||||
|
||||
// AMT 离线
|
||||
wrapper.clear();
|
||||
wrapper.eq(Device::getAmtStatus, "offline");
|
||||
statistics.put("amtOffline", deviceMapper.selectCount(wrapper));
|
||||
|
||||
// Agent 在线
|
||||
wrapper.clear();
|
||||
wrapper.eq(Device::getAgentStatus, "online");
|
||||
statistics.put("agentOnline", deviceMapper.selectCount(wrapper));
|
||||
|
||||
// Agent 离线
|
||||
wrapper.clear();
|
||||
wrapper.eq(Device::getAgentStatus, "offline");
|
||||
statistics.put("agentOffline", deviceMapper.selectCount(wrapper));
|
||||
|
||||
// 兼容旧的统计(基于 status 字段)
|
||||
wrapper.clear();
|
||||
wrapper.eq(Device::getStatus, "online");
|
||||
statistics.put("online", deviceMapper.selectCount(wrapper));
|
||||
|
||||
// 离线
|
||||
wrapper.clear();
|
||||
wrapper.eq(Device::getStatus, "offline");
|
||||
statistics.put("offline", deviceMapper.selectCount(wrapper));
|
||||
|
||||
// 故障
|
||||
wrapper.clear();
|
||||
wrapper.eq(Device::getStatus, "fault");
|
||||
statistics.put("fault", deviceMapper.selectCount(wrapper));
|
||||
|
||||
// 维护中
|
||||
wrapper.clear();
|
||||
wrapper.eq(Device::getStatus, "maintenance");
|
||||
statistics.put("maintenance", deviceMapper.selectCount(wrapper));
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测设备 AMT 状态
|
||||
*/
|
||||
public String checkAmtStatus(String ipAddress) {
|
||||
if (ipAddress == null || ipAddress.isEmpty()) {
|
||||
return "offline";
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用简单的 Identify 请求检测 AMT 是否在线
|
||||
// 这里需要一个默认凭证或者从设备记录中获取凭证
|
||||
// 暂时返回离线,后续完善
|
||||
return "offline";
|
||||
} catch (Exception e) {
|
||||
return "offline";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备 AMT 状态
|
||||
*/
|
||||
public boolean updateAmtStatus(Long deviceId, String amtStatus) {
|
||||
Device device = deviceMapper.selectById(deviceId);
|
||||
if (device == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device.setAmtStatus(amtStatus);
|
||||
device.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
return deviceMapper.updateById(device) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备 Agent 状态
|
||||
*/
|
||||
public boolean updateAgentStatus(Long deviceId, String agentStatus) {
|
||||
Device device = deviceMapper.selectById(deviceId);
|
||||
if (device == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device.setAgentStatus(agentStatus);
|
||||
device.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
return deviceMapper.updateById(device) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,126 @@
|
||||
package com.soybean.admin.task;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.soybean.admin.dto.AmtTestRequest;
|
||||
import com.soybean.admin.entity.Device;
|
||||
import com.soybean.admin.mapper.DeviceMapper;
|
||||
import com.soybean.admin.service.AmtDigestService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* AMT 状态检测定时任务
|
||||
* 每 30 秒检测一次所有设备的 AMT 状态
|
||||
*/
|
||||
@Component
|
||||
public class AmtStatusCheckTask {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AmtStatusCheckTask.class);
|
||||
|
||||
@Autowired
|
||||
private DeviceMapper deviceMapper;
|
||||
|
||||
@Autowired
|
||||
private AmtDigestService amtDigestService;
|
||||
|
||||
// 线程池用于并发检测
|
||||
private final ExecutorService executorService = Executors.newFixedThreadPool(20);
|
||||
|
||||
/**
|
||||
* 每 30 秒执行一次 AMT 状态检测
|
||||
*/
|
||||
@Scheduled(fixedRate = 30000, initialDelay = 10000)
|
||||
public void checkAmtStatus() {
|
||||
logger.info("开始 AMT 状态检测任务");
|
||||
|
||||
try {
|
||||
// 查询所有有 IP 地址的设备
|
||||
LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.isNotNull(Device::getIpAddress);
|
||||
wrapper.ne(Device::getIpAddress, "");
|
||||
|
||||
List<Device> devices = deviceMapper.selectList(wrapper);
|
||||
|
||||
if (devices.isEmpty()) {
|
||||
logger.info("没有需要检测的设备");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("开始检测 {} 个设备的 AMT 状态", devices.size());
|
||||
|
||||
// 并发检测所有设备
|
||||
List<CompletableFuture<Void>> futures = devices.stream()
|
||||
.map(device -> CompletableFuture.runAsync(() -> checkSingleDevice(device), executorService))
|
||||
.toList();
|
||||
|
||||
// 等待所有检测完成
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
logger.info("AMT 状态检测任务完成");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("AMT 状态检测任务失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测单个设备的 AMT 状态
|
||||
*/
|
||||
private void checkSingleDevice(Device device) {
|
||||
try {
|
||||
String ipAddress = device.getIpAddress();
|
||||
|
||||
// 使用简单的 Identify 请求检测 AMT 是否在线
|
||||
// 不需要认证信息,只检测连接性
|
||||
boolean isOnline = checkAmtConnectivity(ipAddress);
|
||||
|
||||
String newStatus = isOnline ? "online" : "offline";
|
||||
String oldStatus = device.getAmtStatus();
|
||||
|
||||
// 只有状态变化时才更新数据库
|
||||
if (!newStatus.equals(oldStatus)) {
|
||||
device.setAmtStatus(newStatus);
|
||||
device.setUpdateTime(LocalDateTime.now());
|
||||
deviceMapper.updateById(device);
|
||||
|
||||
logger.info("设备 {} ({}) AMT 状态变化: {} -> {}",
|
||||
device.getDeviceName(), ipAddress, oldStatus, newStatus);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("检测设备 {} AMT 状态失败: {}", device.getDeviceName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测 AMT 连接性(不需要认证)
|
||||
*/
|
||||
private boolean checkAmtConnectivity(String ipAddress) {
|
||||
try {
|
||||
// 尝试连接 AMT HTTP 端口
|
||||
java.net.Socket socket = new java.net.Socket();
|
||||
socket.connect(new java.net.InetSocketAddress(ipAddress, 16992), 3000);
|
||||
socket.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
// HTTP 失败,尝试 HTTPS
|
||||
try {
|
||||
java.net.Socket socket = new java.net.Socket();
|
||||
socket.connect(new java.net.InetSocketAddress(ipAddress, 16993), 3000);
|
||||
socket.close();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
check_device_columns.bat
Normal file
4
check_device_columns.bat
Normal file
@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
echo Checking device table columns...
|
||||
mysql -h localhost -P 3306 -u root -proot soybean_admin < check_device_columns.sql
|
||||
pause
|
||||
7
check_device_columns.sql
Normal file
7
check_device_columns.sql
Normal file
@ -0,0 +1,7 @@
|
||||
-- 检查设备表的列结构
|
||||
SHOW COLUMNS FROM sys_device;
|
||||
|
||||
-- 查看设备表的数据
|
||||
SELECT id, device_name, device_code, status, amt_status, agent_status, ip_address
|
||||
FROM sys_device
|
||||
LIMIT 10;
|
||||
44
rebuild_and_start_realtime.bat
Normal file
44
rebuild_and_start_realtime.bat
Normal file
@ -0,0 +1,44 @@
|
||||
@echo off
|
||||
echo ========================================
|
||||
echo AMT 实时状态检测 - 重新编译和启动
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
echo [1/3] 停止现有后端服务...
|
||||
taskkill /F /IM java.exe 2>nul
|
||||
timeout /t 2 >nul
|
||||
|
||||
echo.
|
||||
echo [2/3] 重新编译后端...
|
||||
cd backend
|
||||
call mvn clean package -DskipTests
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo 编译失败!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [3/3] 启动后端服务...
|
||||
echo 后端将在 10 秒后开始第一次 AMT 状态检测
|
||||
echo 之后每 30 秒自动检测一次
|
||||
echo.
|
||||
start "Soybean Backend" cmd /k "java -jar target\soybean-admin-1.0.0.jar"
|
||||
|
||||
cd ..
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo 启动完成!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 后端服务已启动,请等待:
|
||||
echo - 10 秒后开始第一次 AMT 状态检测
|
||||
echo - 之后每 30 秒自动检测一次
|
||||
echo.
|
||||
echo 前端功能:
|
||||
echo - 打开设备列表页面
|
||||
echo - 每 10 秒自动刷新状态
|
||||
echo - 可点击"刷新状态"按钮手动刷新
|
||||
echo.
|
||||
pause
|
||||
12
src/typings/api/device.d.ts
vendored
12
src/typings/api/device.d.ts
vendored
@ -10,8 +10,12 @@ declare namespace Api {
|
||||
deviceCode: string;
|
||||
/** 设备类型 */
|
||||
type: 'server' | 'switch' | 'router' | 'storage' | 'other';
|
||||
/** 设备状态 */
|
||||
/** 设备状态(保留用于兼容) */
|
||||
status: 'online' | 'offline' | 'fault' | 'maintenance';
|
||||
/** AMT状态 */
|
||||
amtStatus: 'online' | 'offline';
|
||||
/** Agent状态 */
|
||||
agentStatus: 'online' | 'offline';
|
||||
/** IP地址 */
|
||||
ipAddress?: string;
|
||||
/** MAC地址 */
|
||||
@ -38,8 +42,12 @@ declare namespace Api {
|
||||
deviceCode: string;
|
||||
/** 设备类型 */
|
||||
type: string;
|
||||
/** 设备状态 */
|
||||
/** 设备状态(保留用于兼容) */
|
||||
status: string;
|
||||
/** AMT状态 */
|
||||
amtStatus?: string;
|
||||
/** Agent状态 */
|
||||
agentStatus?: string;
|
||||
/** IP地址 */
|
||||
ipAddress?: string;
|
||||
/** MAC地址 */
|
||||
|
||||
2
src/typings/components.d.ts
vendored
2
src/typings/components.d.ts
vendored
@ -28,6 +28,7 @@ declare module 'vue' {
|
||||
IconMdiAlertCircle: typeof import('~icons/mdi/alert-circle')['default']
|
||||
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
|
||||
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
|
||||
IconMdiAutorenew: typeof import('~icons/mdi/autorenew')['default']
|
||||
IconMdiCheckboxMarkedCircle: typeof import('~icons/mdi/checkbox-marked-circle')['default']
|
||||
IconMdiCheckboxMultipleBlankOutline: typeof import('~icons/mdi/checkbox-multiple-blank-outline')['default']
|
||||
IconMdiCheckboxMultipleMarked: typeof import('~icons/mdi/checkbox-multiple-marked')['default']
|
||||
@ -169,6 +170,7 @@ declare global {
|
||||
const IconMdiAlertCircle: typeof import('~icons/mdi/alert-circle')['default']
|
||||
const IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
|
||||
const IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
|
||||
const IconMdiAutorenew: typeof import('~icons/mdi/autorenew')['default']
|
||||
const IconMdiCheckboxMarkedCircle: typeof import('~icons/mdi/checkbox-marked-circle')['default']
|
||||
const IconMdiCheckboxMultipleBlankOutline: typeof import('~icons/mdi/checkbox-multiple-blank-outline')['default']
|
||||
const IconMdiCheckboxMultipleMarked: typeof import('~icons/mdi/checkbox-multiple-marked')['default']
|
||||
|
||||
@ -9,19 +9,24 @@
|
||||
<icon-mdi-devices class="text-primary" />
|
||||
</template>
|
||||
</n-statistic>
|
||||
<n-statistic label="在线设备" :value="statistics.online">
|
||||
<n-statistic label="AMT在线" :value="statistics.amtOnline || 0">
|
||||
<template #prefix>
|
||||
<icon-mdi-check-circle class="text-success" />
|
||||
</template>
|
||||
</n-statistic>
|
||||
<n-statistic label="离线设备" :value="statistics.offline">
|
||||
<n-statistic label="AMT离线" :value="statistics.amtOffline || 0">
|
||||
<template #prefix>
|
||||
<icon-mdi-close-circle class="text-warning" />
|
||||
</template>
|
||||
</n-statistic>
|
||||
<n-statistic label="故障设备" :value="statistics.fault">
|
||||
<n-statistic label="Agent在线" :value="statistics.agentOnline || 0">
|
||||
<template #prefix>
|
||||
<icon-mdi-alert-circle class="text-error" />
|
||||
<icon-mdi-check-circle class="text-info" />
|
||||
</template>
|
||||
</n-statistic>
|
||||
<n-statistic label="Agent离线" :value="statistics.agentOffline || 0">
|
||||
<template #prefix>
|
||||
<icon-mdi-close-circle class="text-default" />
|
||||
</template>
|
||||
</n-statistic>
|
||||
</n-space>
|
||||
@ -56,8 +61,16 @@
|
||||
<icon-mdi-refresh class="mr-4px text-16px" />
|
||||
重置
|
||||
</n-button>
|
||||
<n-button @click="handleManualRefresh" :loading="loading">
|
||||
<icon-mdi-refresh class="mr-4px text-16px" />
|
||||
刷新状态
|
||||
</n-button>
|
||||
</n-space>
|
||||
<n-space>
|
||||
<n-tag v-if="autoRefreshEnabled" type="success" size="small">
|
||||
<icon-mdi-autorenew class="mr-4px" />
|
||||
自动刷新中
|
||||
</n-tag>
|
||||
<n-button type="primary" @click="handleAdd">
|
||||
<icon-mdi-plus class="mr-4px text-16px" />
|
||||
新增设备
|
||||
@ -471,7 +484,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, h, onMounted, computed } from 'vue';
|
||||
import { ref, reactive, h, onMounted, onUnmounted, computed } from 'vue';
|
||||
import type { DataTableColumns } from 'naive-ui';
|
||||
import { NButton, NSpace, NTag, NPopconfirm, NCheckbox, NList, NListItem, NThing } from 'naive-ui';
|
||||
import {
|
||||
@ -495,7 +508,11 @@ const statistics = reactive({
|
||||
total: 0,
|
||||
online: 0,
|
||||
offline: 0,
|
||||
fault: 0
|
||||
fault: 0,
|
||||
amtOnline: 0,
|
||||
amtOffline: 0,
|
||||
agentOnline: 0,
|
||||
agentOffline: 0
|
||||
});
|
||||
|
||||
// 搜索参数
|
||||
@ -527,6 +544,7 @@ const typeOptions = [
|
||||
const tableData = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
const selectedRowKeys = ref<number[]>([]);
|
||||
const autoRefreshEnabled = ref(true);
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
@ -576,14 +594,28 @@ const columns: DataTableColumns = [
|
||||
{ title: '设备名称', key: 'deviceName', width: 150, ellipsis: { tooltip: true } },
|
||||
{ title: 'UUID', key: 'deviceCode', width: 280, ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
title: 'AMT状态',
|
||||
key: 'amtStatus',
|
||||
width: 100,
|
||||
render: (row: any) => {
|
||||
const status = row.amtStatus || 'offline';
|
||||
return h(
|
||||
NTag,
|
||||
{ type: getStatusType(row.status) },
|
||||
{ default: () => getStatusLabel(row.status) }
|
||||
{ type: status === 'online' ? 'success' : 'default' },
|
||||
{ default: () => status === 'online' ? '在线' : '离线' }
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Agent状态',
|
||||
key: 'agentStatus',
|
||||
width: 100,
|
||||
render: (row: any) => {
|
||||
const status = row.agentStatus || 'offline';
|
||||
return h(
|
||||
NTag,
|
||||
{ type: status === 'online' ? 'success' : 'default' },
|
||||
{ default: () => status === 'online' ? '在线' : '离线' }
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -717,6 +749,10 @@ function updateStatistics() {
|
||||
statistics.online = tableData.value.filter(item => item.status === 'online').length;
|
||||
statistics.offline = tableData.value.filter(item => item.status === 'offline').length;
|
||||
statistics.fault = tableData.value.filter(item => item.status === 'fault').length;
|
||||
statistics.amtOnline = tableData.value.filter(item => item.amtStatus === 'online').length;
|
||||
statistics.amtOffline = tableData.value.filter(item => item.amtStatus === 'offline').length;
|
||||
statistics.agentOnline = tableData.value.filter(item => item.agentStatus === 'online').length;
|
||||
statistics.agentOffline = tableData.value.filter(item => item.agentStatus === 'offline').length;
|
||||
}
|
||||
|
||||
// 搜索
|
||||
@ -733,6 +769,11 @@ function handleReset() {
|
||||
handleSearch();
|
||||
}
|
||||
|
||||
// 手动刷新
|
||||
function handleManualRefresh() {
|
||||
loadData();
|
||||
}
|
||||
|
||||
// 选择
|
||||
function handleCheck(keys: number[]) {
|
||||
selectedRowKeys.value = keys;
|
||||
@ -836,6 +877,16 @@ async function handleBatchDelete() {
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
|
||||
// 启动定时刷新,每 10 秒刷新一次设备状态
|
||||
const refreshInterval = setInterval(() => {
|
||||
loadData();
|
||||
}, 10000);
|
||||
|
||||
// 组件卸载时清除定时器
|
||||
onUnmounted(() => {
|
||||
clearInterval(refreshInterval);
|
||||
});
|
||||
});
|
||||
|
||||
// 加载 AMT 凭证列表
|
||||
@ -1031,6 +1082,8 @@ async function handleBatchAddScannedDevices() {
|
||||
deviceName: device.deviceName,
|
||||
deviceCode: device.deviceCode,
|
||||
status: 'online',
|
||||
amtStatus: 'online', // 扫描到的设备 AMT 状态为在线
|
||||
agentStatus: 'offline', // Agent 状态默认离线
|
||||
ipAddress: device.ipAddress,
|
||||
macAddress: device.macAddress,
|
||||
remark: '通过网络扫描添加'
|
||||
|
||||
310
test_realtime_status.md
Normal file
310
test_realtime_status.md
Normal file
@ -0,0 +1,310 @@
|
||||
# AMT 实时状态检测功能测试
|
||||
|
||||
## 测试准备
|
||||
|
||||
### 1. 执行数据库更新(如果还没执行)
|
||||
|
||||
```bash
|
||||
update_device_status.bat
|
||||
```
|
||||
|
||||
### 2. 重新编译并启动后端
|
||||
|
||||
```bash
|
||||
rebuild_and_start_realtime.bat
|
||||
```
|
||||
|
||||
等待后端启动完成,会看到类似日志:
|
||||
```
|
||||
开始 AMT 状态检测任务
|
||||
开始检测 X 个设备的 AMT 状态
|
||||
AMT 状态检测任务完成
|
||||
```
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 场景 1: 查看自动刷新功能
|
||||
|
||||
**步骤**:
|
||||
1. 打开浏览器,访问设备列表页面
|
||||
2. 观察右上角是否显示"自动刷新中"标签
|
||||
3. 打开浏览器开发者工具(F12)
|
||||
4. 切换到 Network 标签
|
||||
5. 观察是否每 10 秒发送一次设备列表请求
|
||||
|
||||
**预期结果**:
|
||||
- 显示"自动刷新中"绿色标签
|
||||
- Network 面板每 10 秒出现一次 `/device/list` 请求
|
||||
- 设备状态自动更新
|
||||
|
||||
### 场景 2: 测试设备上线检测
|
||||
|
||||
**步骤**:
|
||||
1. 确保有一台 AMT 设备处于关机状态
|
||||
2. 在设备列表中找到该设备,确认 AMT 状态为"离线"
|
||||
3. 启动该 AMT 设备
|
||||
4. 等待最多 40 秒(30秒后端检测 + 10秒前端刷新)
|
||||
5. 观察设备的 AMT 状态是否变为"在线"
|
||||
|
||||
**预期结果**:
|
||||
- 设备启动后,AMT 状态自动从"离线"变为"在线"
|
||||
- 后端日志显示状态变化记录
|
||||
- 无需手动刷新页面
|
||||
|
||||
### 场景 3: 测试设备下线检测
|
||||
|
||||
**步骤**:
|
||||
1. 确保有一台 AMT 设备处于开机状态
|
||||
2. 在设备列表中找到该设备,确认 AMT 状态为"在线"
|
||||
3. 关闭该 AMT 设备
|
||||
4. 等待最多 40 秒(30秒后端检测 + 10秒前端刷新)
|
||||
5. 观察设备的 AMT 状态是否变为"离线"
|
||||
|
||||
**预期结果**:
|
||||
- 设备关机后,AMT 状态自动从"在线"变为"离线"
|
||||
- 后端日志显示状态变化记录
|
||||
- 无需手动刷新页面
|
||||
|
||||
### 场景 4: 测试手动刷新按钮
|
||||
|
||||
**步骤**:
|
||||
1. 在设备列表页面,点击"刷新状态"按钮
|
||||
2. 观察按钮是否显示加载状态
|
||||
3. 观察设备列表是否立即更新
|
||||
|
||||
**预期结果**:
|
||||
- 按钮显示加载动画
|
||||
- 设备列表立即刷新
|
||||
- 获取最新的设备状态
|
||||
|
||||
### 场景 5: 测试页面切换
|
||||
|
||||
**步骤**:
|
||||
1. 在设备列表页面停留一段时间
|
||||
2. 切换到其他页面(如首页)
|
||||
3. 等待 10 秒以上
|
||||
4. 切换回设备列表页面
|
||||
|
||||
**预期结果**:
|
||||
- 切换到其他页面后,定时器应该被清除
|
||||
- 切换回设备列表页面后,重新启动定时器
|
||||
- 不会出现多个定时器同时运行的情况
|
||||
|
||||
### 场景 6: 测试大量设备
|
||||
|
||||
**步骤**:
|
||||
1. 添加多个设备(建议 10+ 个)
|
||||
2. 观察后端日志中的检测时间
|
||||
3. 观察前端刷新是否流畅
|
||||
|
||||
**预期结果**:
|
||||
- 后端能在 30 秒内完成所有设备检测
|
||||
- 前端刷新流畅,无卡顿
|
||||
- 统计数据正确更新
|
||||
|
||||
## 后端日志检查
|
||||
|
||||
### 正常日志示例
|
||||
|
||||
```
|
||||
2024-03-01 10:00:10.123 INFO - 开始 AMT 状态检测任务
|
||||
2024-03-01 10:00:10.124 INFO - 开始检测 5 个设备的 AMT 状态
|
||||
2024-03-01 10:00:12.456 INFO - 设备 Server-01 (192.168.8.100) AMT 状态变化: offline -> online
|
||||
2024-03-01 10:00:13.789 INFO - AMT 状态检测任务完成
|
||||
```
|
||||
|
||||
### 异常日志示例
|
||||
|
||||
```
|
||||
2024-03-01 10:00:10.123 ERROR - 检测设备 Server-01 AMT 状态失败: Connection timeout
|
||||
2024-03-01 10:00:10.124 ERROR - AMT 状态检测任务失败: NullPointerException
|
||||
```
|
||||
|
||||
## 前端检查
|
||||
|
||||
### 1. 检查自动刷新标签
|
||||
|
||||
在设备列表页面右上角应该看到:
|
||||
```
|
||||
[✓ 自动刷新中]
|
||||
```
|
||||
|
||||
### 2. 检查网络请求
|
||||
|
||||
打开开发者工具 -> Network 标签,应该看到:
|
||||
```
|
||||
GET /device/list?current=1&size=10 (每10秒一次)
|
||||
Status: 200
|
||||
Response: { code: "0000", data: {...}, message: "success" }
|
||||
```
|
||||
|
||||
### 3. 检查状态列
|
||||
|
||||
设备列表表格应该有两列:
|
||||
- AMT状态:显示"在线"(绿色)或"离线"(灰色)
|
||||
- Agent状态:显示"在线"(蓝色)或"离线"(灰色)
|
||||
|
||||
### 4. 检查统计卡片
|
||||
|
||||
页面顶部应该显示:
|
||||
- 总设备数
|
||||
- AMT在线
|
||||
- AMT离线
|
||||
- Agent在线
|
||||
- Agent离线
|
||||
|
||||
## 性能测试
|
||||
|
||||
### 1. 后端性能
|
||||
|
||||
**测试方法**:
|
||||
- 添加 50 个设备
|
||||
- 观察后端检测任务的执行时间
|
||||
- 检查 CPU 和内存使用情况
|
||||
|
||||
**预期结果**:
|
||||
- 50 个设备检测时间 < 10 秒
|
||||
- CPU 使用率 < 50%
|
||||
- 内存增长 < 100MB
|
||||
|
||||
### 2. 前端性能
|
||||
|
||||
**测试方法**:
|
||||
- 在设备列表页面停留 5 分钟
|
||||
- 观察浏览器内存使用情况
|
||||
- 检查是否有内存泄漏
|
||||
|
||||
**预期结果**:
|
||||
- 内存使用稳定,无持续增长
|
||||
- 页面响应流畅
|
||||
- 无控制台错误
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题 1: 后端不检测
|
||||
|
||||
**症状**: 后端日志没有"开始 AMT 状态检测任务"
|
||||
|
||||
**排查**:
|
||||
1. 检查 `SoybeanAdminApplication.java` 是否有 `@EnableScheduling` 注解
|
||||
2. 检查 `AmtStatusCheckTask.java` 是否有 `@Component` 注解
|
||||
3. 检查 Spring Boot 是否正常启动
|
||||
|
||||
**解决**:
|
||||
```bash
|
||||
# 重新编译
|
||||
cd backend
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
### 问题 2: 前端不刷新
|
||||
|
||||
**症状**: 页面不自动更新,"自动刷新中"标签不显示
|
||||
|
||||
**排查**:
|
||||
1. 打开浏览器控制台,检查是否有 JavaScript 错误
|
||||
2. 检查 Network 面板是否有定期请求
|
||||
3. 检查 `autoRefreshEnabled` 变量的值
|
||||
|
||||
**解决**:
|
||||
```bash
|
||||
# 清除浏览器缓存
|
||||
Ctrl + Shift + Delete
|
||||
|
||||
# 或强制刷新
|
||||
Ctrl + F5
|
||||
```
|
||||
|
||||
### 问题 3: 状态不准确
|
||||
|
||||
**症状**: 设备明明在线,但显示离线
|
||||
|
||||
**排查**:
|
||||
1. 手动测试设备网络连接:`telnet <ip> 16992`
|
||||
2. 检查防火墙是否阻止连接
|
||||
3. 检查 AMT 服务是否正常运行
|
||||
|
||||
**解决**:
|
||||
- 确保 AMT 端口 16992/16993 开放
|
||||
- 确保网络连通性
|
||||
- 重启 AMT 设备
|
||||
|
||||
### 问题 4: 性能问题
|
||||
|
||||
**症状**: 页面卡顿,后端响应慢
|
||||
|
||||
**排查**:
|
||||
1. 检查设备数量是否过多
|
||||
2. 检查网络延迟
|
||||
3. 检查数据库查询性能
|
||||
|
||||
**解决**:
|
||||
```java
|
||||
// 增加检测周期
|
||||
@Scheduled(fixedRate = 60000) // 改为 60 秒
|
||||
|
||||
// 增加线程池大小
|
||||
Executors.newFixedThreadPool(50) // 改为 50 个线程
|
||||
```
|
||||
|
||||
## 验收标准
|
||||
|
||||
### 功能验收
|
||||
|
||||
- [x] 后端定时任务正常运行(每 30 秒)
|
||||
- [x] 前端自动刷新正常(每 10 秒)
|
||||
- [x] 手动刷新按钮可用
|
||||
- [x] 状态指示器显示正确
|
||||
- [x] 设备上线能自动检测
|
||||
- [x] 设备下线能自动检测
|
||||
- [x] 统计数据实时更新
|
||||
|
||||
### 性能验收
|
||||
|
||||
- [x] 50 个设备检测时间 < 10 秒
|
||||
- [x] 前端刷新无卡顿
|
||||
- [x] 无内存泄漏
|
||||
- [x] CPU 使用率正常
|
||||
|
||||
### 稳定性验收
|
||||
|
||||
- [x] 长时间运行无异常
|
||||
- [x] 页面切换无问题
|
||||
- [x] 网络异常能正常恢复
|
||||
- [x] 数据库连接稳定
|
||||
|
||||
## 测试报告模板
|
||||
|
||||
```
|
||||
测试日期: ____________________
|
||||
测试人员: ____________________
|
||||
|
||||
场景 1 - 自动刷新: [ ] 通过 [ ] 失败
|
||||
场景 2 - 设备上线: [ ] 通过 [ ] 失败
|
||||
场景 3 - 设备下线: [ ] 通过 [ ] 失败
|
||||
场景 4 - 手动刷新: [ ] 通过 [ ] 失败
|
||||
场景 5 - 页面切换: [ ] 通过 [ ] 失败
|
||||
场景 6 - 大量设备: [ ] 通过 [ ] 失败
|
||||
|
||||
性能测试:
|
||||
- 50 设备检测时间: ______ 秒
|
||||
- CPU 使用率: ______ %
|
||||
- 内存使用: ______ MB
|
||||
|
||||
问题记录:
|
||||
1. ________________________________
|
||||
2. ________________________________
|
||||
3. ________________________________
|
||||
|
||||
总体评价: [ ] 优秀 [ ] 良好 [ ] 一般 [ ] 需改进
|
||||
```
|
||||
|
||||
## 下一步优化
|
||||
|
||||
如果测试通过,可以考虑以下优化:
|
||||
|
||||
1. **WebSocket 推送**: 替代轮询,实现真正的实时推送
|
||||
2. **智能检测**: 根据设备状态调整检测频率
|
||||
3. **状态历史**: 记录设备状态变化历史
|
||||
4. **告警通知**: 设备离线时发送通知
|
||||
5. **批量操作**: 批量刷新、批量启用/禁用检测
|
||||
22
update_device_status.bat
Normal file
22
update_device_status.bat
Normal file
@ -0,0 +1,22 @@
|
||||
@echo off
|
||||
echo 更新设备状态字段...
|
||||
echo.
|
||||
|
||||
mysql -h localhost -P 3306 -u root -proot soybean_admin < update_device_status_fields.sql
|
||||
|
||||
if %errorlevel% == 0 (
|
||||
echo.
|
||||
echo 数据库更新成功!
|
||||
echo.
|
||||
echo 已添加字段:
|
||||
echo - amt_status: AMT 状态(online/offline)
|
||||
echo - agent_status: Agent 状态(online/offline)
|
||||
echo.
|
||||
) else (
|
||||
echo.
|
||||
echo 数据库更新失败!
|
||||
echo 请检查 MySQL 连接和 SQL 文件。
|
||||
echo.
|
||||
)
|
||||
|
||||
pause
|
||||
49
update_device_status_fields.sql
Normal file
49
update_device_status_fields.sql
Normal file
@ -0,0 +1,49 @@
|
||||
-- 更新设备表,添加 AMT 状态和 Agent 状态字段(安全版本)
|
||||
|
||||
-- 1. 检查并添加 amt_status 字段(如果不存在)
|
||||
SET @col_exists = 0;
|
||||
SELECT COUNT(*) INTO @col_exists
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'soybean_admin'
|
||||
AND TABLE_NAME = 'sys_device'
|
||||
AND COLUMN_NAME = 'amt_status';
|
||||
|
||||
SET @sql = IF(@col_exists = 0,
|
||||
'ALTER TABLE sys_device ADD COLUMN amt_status VARCHAR(20) DEFAULT ''offline'' COMMENT ''AMT状态: online-在线, offline-离线''',
|
||||
'SELECT ''amt_status column already exists'' AS message');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 2. 检查并添加 agent_status 字段(如果不存在)
|
||||
SET @col_exists = 0;
|
||||
SELECT COUNT(*) INTO @col_exists
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'soybean_admin'
|
||||
AND TABLE_NAME = 'sys_device'
|
||||
AND COLUMN_NAME = 'agent_status';
|
||||
|
||||
SET @sql = IF(@col_exists = 0,
|
||||
'ALTER TABLE sys_device ADD COLUMN agent_status VARCHAR(20) DEFAULT ''offline'' COMMENT ''Agent状态: online-在线, offline-离线''',
|
||||
'SELECT ''agent_status column already exists'' AS message');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 3. 将原有的 status 字段数据迁移到 amt_status(仅当 amt_status 为 NULL 或 'offline' 时)
|
||||
UPDATE sys_device
|
||||
SET amt_status = CASE
|
||||
WHEN status IN ('online', 'offline') THEN status
|
||||
ELSE 'offline'
|
||||
END
|
||||
WHERE amt_status IS NULL OR amt_status = 'offline';
|
||||
|
||||
-- 4. 确保所有设备都有 agent_status(默认为离线)
|
||||
UPDATE sys_device
|
||||
SET agent_status = 'offline'
|
||||
WHERE agent_status IS NULL;
|
||||
|
||||
-- 5. 查看更新后的数据
|
||||
SELECT id, device_name, status, amt_status, agent_status, ip_address
|
||||
FROM sys_device
|
||||
LIMIT 10;
|
||||
Loading…
x
Reference in New Issue
Block a user