feat: 实现 AMT 设备自动添加功能(仅 Digest 认证)

- 添加 AMT Digest 认证服务 (AmtDigestService)
- 添加 AMT 模拟服务用于测试 (AmtMockService)
- 更新设备控制器支持 AMT 连接测试和设备信息获取
- 前端添加 AMT 自动添加方式,支持手动输入和凭证选择
- 移除 Basic 认证,仅使用 Digest 认证
- 添加 Apache HttpClient 依赖支持 Digest 认证
- 创建相关文档和测试脚本
This commit is contained in:
lvfengfree 2026-03-01 14:51:35 +08:00
parent 7d3f98e4f0
commit 8b07d8f52a
23 changed files with 1935 additions and 8 deletions

245
AMT_DEVICE_ADD_FEATURE.md Normal file
View File

@ -0,0 +1,245 @@
# AMT 设备添加功能说明
## 功能概述
在设备管理的设备列表页面,增强了"新增设备"功能,支持通过 Intel AMTActive Management Technology协议自动发现和添加设备。
## 功能特性
### 1. 两种添加方式
#### 手动添加
- 传统的手动输入设备信息方式
- 需要手动填写设备名称、UUID、IP地址、MAC地址等信息
#### AMT 自动添加
- 通过 AMT 协议自动发现设备
- 只需输入 IP 地址和认证信息
- 自动获取设备名称、MAC 地址等信息
- 自动生成 UUID
### 2. 认证方式
#### 直接输入
- 手动输入 AMT 用户名和密码
- 适合临时测试或一次性添加
#### 使用已保存凭证
- 从"AMT 设置"页面选择已保存的凭证
- 支持默认凭证快速选择
- 提高安全性和便捷性
### 3. 连接测试
- 提供"测试连接"按钮
- 验证 AMT 连接是否可用
- 测试成功后自动获取设备信息
- 显示连接状态反馈
## 技术实现
### 后端实现
#### 1. AmtDigestService.java
- 使用 WS-Management 协议与 AMT 设备通信
- 支持 Digest 认证HTTP Digest Auth
- 实现设备信息获取和连接测试
#### 2. 主要接口
**测试 AMT 连接**
```
POST /device/amt/test
{
"ipAddress": "192.168.1.100",
"username": "admin",
"password": "password"
}
{
"ipAddress": "192.168.1.100",
"credentialId": 1
}
```
**获取 AMT 设备信息**
```
POST /device/amt/getInfo
{
"ipAddress": "192.168.1.100",
"username": "admin",
"password": "password"
}
```
#### 3. WS-Management 协议
使用标准的 SOAP over HTTP 协议:
- 端口HTTP 16992, HTTPS 16993
- 认证HTTP Digest Authentication
- 协议WS-Management (WSMAN)
### 前端实现
#### 1. 设备列表页面增强
- 添加方式选择(单选按钮)
- AMT 设备发现表单
- 认证方式切换(开关)
- 凭证选择下拉框
- 测试连接按钮和状态显示
#### 2. 用户交互流程
1. 点击"新增设备"按钮
2. 选择"AMT 自动添加"方式
3. 输入 IP 地址
4. 选择认证方式:
- 手动输入:填写用户名和密码
- 使用凭证:从下拉框选择已保存的凭证
5. 点击"测试连接"
6. 连接成功后,自动填充设备信息
7. 确认并保存设备
## 使用说明
### 前置条件
1. AMT 设备已启用并配置
2. AMT 设备网络可达
3. 已知 AMT 管理员账号密码
4. (可选)在"AMT 设置"中预先保存凭证
### 操作步骤
1. **进入设备列表页面**
- 导航到:设备管理 > 设备列表
2. **点击新增设备**
- 点击页面右上角的"新增设备"按钮
3. **选择 AMT 自动添加**
- 在弹出的对话框中选择"AMT 自动添加"单选按钮
4. **输入 AMT 设备信息**
- IP 地址:输入 AMT 设备的 IP 地址
- 认证方式:
- 方式一:切换到"手动输入",填写用户名和密码
- 方式二:切换到"使用已保存凭证",从下拉框选择凭证
5. **测试连接**
- 点击"测试连接"按钮
- 等待测试结果
- 成功后会显示绿色的"连接成功"标签
6. **确认设备信息**
- 系统自动填充设备名称、UUID、IP地址、MAC地址
- 可以修改设备名称和备注
- 选择设备状态
7. **保存设备**
- 点击"确定"按钮保存设备
### 注意事项
1. **网络连接**
- 确保服务器能够访问 AMT 设备的 16992 端口
- 检查防火墙设置
2. **AMT 配置**
- AMT 设备必须已启用并配置
- 确认 AMT 管理员账号可用
3. **安全性**
- 建议使用已保存的凭证而不是每次手动输入
- 定期更新 AMT 密码
4. **错误处理**
- 连接失败时会显示具体错误信息
- 常见错误:
- 网络不可达
- 认证失败
- AMT 未启用
## 文件清单
### 后端文件
- `backend/src/main/java/com/soybean/admin/service/AmtDigestService.java` - AMT Digest 认证服务
- `backend/src/main/java/com/soybean/admin/dto/AmtTestRequest.java` - 测试请求 DTO
- `backend/src/main/java/com/soybean/admin/dto/AmtDeviceInfo.java` - 设备信息 DTO
- `backend/src/main/java/com/soybean/admin/controller/DeviceController.java` - 设备控制器(已更新)
### 前端文件
- `src/views/device/list/index.vue` - 设备列表页面(已更新)
- `src/service/api/device.ts` - 设备 API已更新
- `src/typings/api/device.d.ts` - 设备类型定义(已更新)
### 工具文件
- `rebuild_and_test_amt.bat` - 重新编译和测试脚本
- `AMT_DEVICE_ADD_FEATURE.md` - 本说明文档
## 测试建议
1. **单元测试**
- 测试 AMT 连接功能
- 测试设备信息获取
- 测试凭证集成
2. **集成测试**
- 使用真实 AMT 设备测试
- 测试不同的认证方式
- 测试错误处理
3. **用户测试**
- 测试完整的添加流程
- 验证用户体验
- 收集反馈
## 未来改进
1. **批量发现**
- 支持 IP 段扫描
- 批量添加多个设备
2. **更多设备信息**
- 获取 CPU、内存等硬件信息
- 获取 AMT 版本信息
- 获取设备序列号
3. **高级功能**
- 支持 TLS 加密连接
- 支持证书认证
- 支持 Kerberos 认证
4. **监控集成**
- 定期检查设备状态
- AMT 事件日志收集
- 设备健康度监控
## 故障排除
### 问题:连接超时
**原因**:网络不可达或防火墙阻止
**解决**
- 检查网络连接
- 确认防火墙规则
- 验证 AMT 端口 16992 是否开放
### 问题:认证失败
**原因**:用户名或密码错误
**解决**
- 确认 AMT 管理员账号
- 重置 AMT 密码
- 检查凭证配置
### 问题:无法获取设备信息
**原因**AMT 未完全配置或版本不兼容
**解决**
- 检查 AMT 配置状态
- 确认 AMT 版本
- 查看 AMT 日志
## 参考资料
- [Intel AMT SDK Documentation](https://software.intel.com/sites/manageability/)
- [WS-Management Protocol](https://www.dmtf.org/standards/ws-man)
- [Intel vPro Technology](https://www.intel.com/content/www/us/en/architecture-and-technology/vpro/vpro-technology-general.html)

221
AMT_NO_LOGS_FIX.md Normal file
View File

@ -0,0 +1,221 @@
# AMT 功能无日志问题修复指南
## 问题描述
点击"测试连接"后,后端没有任何日志输出,直接显示超时错误。
## 可能原因
1. **代码未编译**:新添加的代码没有被编译
2. **后端未重启**:修改后没有重启后端服务
3. **请求未到达后端**:前端请求没有正确发送到后端
4. **端口问题**:后端端口被占用或配置错误
## 解决步骤
### 步骤 1停止当前后端服务
如果后端正在运行,先停止它:
- 在运行后端的命令行窗口按 `Ctrl+C`
- 或者关闭运行后端的窗口
### 步骤 2重新编译并启动后端
运行以下脚本:
```batch
rebuild_backend_with_logs.bat
```
这个脚本会:
1. 清理旧的编译文件
2. 重新编译所有代码
3. 启动后端服务并显示详细日志
### 步骤 3验证后端是否正常运行
在新的命令行窗口运行:
```batch
test_amt_api.bat
```
这个脚本会测试:
1. 后端是否运行
2. AMT API 是否可访问
3. 模拟模式状态
### 步骤 4查看日志输出
启动后端后,你应该能看到类似的日志:
```
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [main] c.s.a.Application : Starting Application
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [main] c.s.a.Application : Started Application in x.xxx seconds
```
当你点击"测试连接"时,应该看到:
```
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [http-nio-8080-exec-1] c.s.a.c.DeviceController : 收到 AMT 测试连接请求IP: 192.168.1.100, 模拟模式: false
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [http-nio-8080-exec-1] c.s.a.c.DeviceController : 使用真实 AMT 服务测试连接
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [http-nio-8080-exec-1] c.s.a.s.AmtService : 开始测试 AMT 连接IP: 192.168.1.100
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [http-nio-8080-exec-1] c.s.a.s.AmtService : 使用手动输入的凭证,用户名: admin
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [http-nio-8080-exec-1] c.s.a.s.AmtService : 准备发送 WS-Management 请求到: 192.168.1.100
```
### 步骤 5如果仍然没有日志
#### 5.1 检查后端是否真的在运行
```batch
netstat -ano | findstr 8080
```
应该看到端口 8080 被占用。
#### 5.2 检查前端请求是否发送
打开浏览器开发者工具F12切换到 Network 标签,点击"测试连接",查看是否有请求发送到 `/device/amt/test`
#### 5.3 检查请求 URL
确认前端请求的 URL 是:
```
POST http://localhost:8080/device/amt/test
```
#### 5.4 检查 CORS 问题
如果看到 CORS 错误,需要在后端添加 CORS 配置。
### 步骤 6使用模拟模式测试
如果真实 AMT 连接有问题,先用模拟模式测试功能:
#### 方法 1修改代码
编辑 `backend/src/main/java/com/soybean/admin/controller/DeviceController.java`
```java
// 将这一行
private boolean useMockMode = false;
// 改为
private boolean useMockMode = true;
```
然后重新编译并启动。
#### 方法 2使用 API 切换
发送 POST 请求:
```batch
curl -X POST http://localhost:8080/device/amt/toggleMock
```
或者在浏览器中访问:
```
http://localhost:8080/device/amt/toggleMock
```
### 步骤 7测试模拟模式
启用模拟模式后:
1. IP 地址:任意(如 192.168.1.100
2. 用户名:`admin`
3. 密码:`admin`
4. 点击"测试连接"
应该会成功并显示日志:
```
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [http-nio-8080-exec-1] c.s.a.c.DeviceController : 收到 AMT 测试连接请求IP: 192.168.1.100, 模拟模式: true
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [http-nio-8080-exec-1] c.s.a.c.DeviceController : 使用模拟模式测试连接
2024-xx-xx xx:xx:xx.xxx INFO xxxxx --- [http-nio-8080-exec-1] c.s.a.c.DeviceController : AMT 测试连接结果: true
```
## 常见问题
### Q1: 编译失败
**错误**`mvn clean compile` 失败
**解决**
1. 检查 Java 版本:`java -version`(需要 Java 8 或更高)
2. 检查 Maven 版本:`mvn -version`
3. 查看错误信息,可能缺少依赖
### Q2: 端口被占用
**错误**`Port 8080 was already in use`
**解决**
1. 找到占用端口的进程:
```batch
netstat -ano | findstr 8080
```
2. 结束进程:
```batch
taskkill /PID <进程ID> /F
```
3. 或者修改端口(在 `application.properties` 中)
### Q3: 前端请求超时
**错误**:前端显示 `timeout of 10000ms exceeded`
**原因**
- 后端未运行
- 后端处理请求时间过长
- 网络问题
**解决**
1. 确认后端正在运行
2. 查看后端日志是否有错误
3. 使用模拟模式测试
### Q4: 看不到详细日志
**解决**
1. 确认使用 `rebuild_backend_with_logs.bat` 启动
2. 或者在 `application.properties` 中添加:
```properties
logging.level.com.soybean.admin=DEBUG
```
## 验证清单
- [ ] 后端已停止旧的进程
- [ ] 运行了 `rebuild_backend_with_logs.bat`
- [ ] 看到后端启动成功的日志
- [ ] 端口 8080 正常监听
- [ ] 运行了 `test_amt_api.bat` 验证 API
- [ ] 前端可以访问
- [ ] 点击"测试连接"时后端有日志输出
## 下一步
如果完成以上步骤后:
1. **有日志但连接失败**
- 查看 `AMT_TROUBLESHOOTING.md`
- 使用 `quick_amt_test.bat` 诊断网络
2. **模拟模式正常**
- 说明代码和流程没问题
- 专注于解决真实 AMT 连接问题
3. **仍然没有日志**
- 检查防火墙设置
- 检查前端配置
- 查看浏览器控制台错误
## 获取帮助
如果问题仍未解决:
1. 复制完整的错误信息
2. 复制后端日志
3. 复制浏览器控制台错误
4. 说明已尝试的步骤

242
AMT_QUICK_START.md Normal file
View File

@ -0,0 +1,242 @@
# AMT 设备添加功能 - 快速开始
## 问题诊断
### 超时错误timeout of 10000ms exceeded
这个错误表示无法连接到 AMT 设备。请按以下步骤排查:
## 步骤 1运行快速测试
运行 `quick_amt_test.bat` 进行基本连接测试:
```batch
quick_amt_test.bat
```
输入 AMT 设备的 IP 地址,查看测试结果。
### 测试结果分析
#### 情况 1Ping 失败
**问题**:网络不可达
**解决**
- 检查 IP 地址是否正确
- 检查网络连接
- 确认设备已开机
#### 情况 2Ping 成功,但端口关闭
**问题**AMT 未启用或服务未运行
**解决**
1. 在 AMT 设备上启用 AMT
2. 检查 AMT 服务状态
3. 检查防火墙设置
#### 情况 3端口开放但连接超时
**问题**AMT 配置问题或认证失败
**解决**
1. 检查 AMT 用户名和密码
2. 尝试重启 AMT 服务
3. 查看 AMT 配置
## 步骤 2使用模拟模式测试功能
如果暂时没有真实的 AMT 设备,可以使用模拟模式测试功能:
### 启用模拟模式
1. **方法 1修改代码**
编辑 `backend/src/main/java/com/soybean/admin/controller/DeviceController.java`
```java
// 将这一行
private boolean useMockMode = false;
// 改为
private boolean useMockMode = true;
```
2. **方法 2使用 API 切换**
发送 POST 请求到:
```
POST http://localhost:8080/device/amt/toggleMock
```
### 使用模拟模式
启用模拟模式后:
1. 在设备列表点击"新增设备"
2. 选择"AMT 自动添加"
3. 输入任意 IP 地址(如 192.168.1.100
4. 用户名:`admin`
5. 密码:`admin`
6. 点击"测试连接"
7. 应该会成功并自动填充设备信息
### 模拟模式说明
- 模拟模式仅用于测试界面和流程
- 不会真正连接到 AMT 设备
- 测试账号admin/admin
- 会生成模拟的设备信息
## 步骤 3配置真实 AMT 设备
### 3.1 启用 AMT
1. **进入 BIOS/UEFI**
- 重启设备
- 按 F2 或 Del 进入 BIOS
2. **进入 Intel ME 配置**
- 在 BIOS 中找到 Intel ME 或 AMT 选项
- 或者在开机时按 Ctrl+P 直接进入 MEBx
3. **启用 AMT**
- Intel(R) ME Configuration
- Intel(R) AMT Configuration
- Manageability Feature Selection
- 选择 "Intel AMT"
4. **配置网络**
- Network Setup
- TCP/IP Settings
- 选择 DHCP 或配置静态 IP
5. **设置管理员密码**
- MEBx Password
- 设置强密码(至少 8 位,包含大小写字母、数字和特殊字符)
- 记住这个密码!
6. **激活 AMT**
- Activate Network Access
- 选择激活方式(通常选择 "Host Based"
7. **保存并退出**
- 保存设置
- 退出并重启
### 3.2 验证 AMT 配置
在 Windows 上验证 AMT 是否正常运行:
```batch
# 检查 LMS 服务状态
sc query LMS
# 如果服务未运行,启动它
net start LMS
# 检查端口是否监听
netstat -ano | findstr 16992
```
### 3.3 测试 AMT 连接
使用 PowerShell 测试:
```powershell
# 替换为你的 AMT 设备 IP
$amtIP = "192.168.1.100"
# 测试端口
Test-NetConnection -ComputerName $amtIP -Port 16992
# 如果端口开放,尝试连接
$username = "admin"
$password = "your_amt_password"
$base64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${username}:${password}"))
$headers = @{
"Authorization" = "Basic $base64"
"Content-Type" = "application/soap+xml;charset=UTF-8"
}
$body = @"
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd">
<s:Header/>
<s:Body>
<wsmid:Identify/>
</s:Body>
</s:Envelope>
"@
try {
$response = Invoke-WebRequest -Uri "http://${amtIP}:16992/wsman" -Method POST -Headers $headers -Body $body
Write-Host "连接成功!" -ForegroundColor Green
Write-Host $response.Content
} catch {
Write-Host "连接失败:$($_.Exception.Message)" -ForegroundColor Red
}
```
## 步骤 4在系统中使用
### 4.1 保存 AMT 凭证(推荐)
1. 进入"系统设置" > "AMT 设置"
2. 点击"新增凭证"
3. 输入凭证名称、用户名和密码
4. 保存
### 4.2 添加 AMT 设备
1. 进入"设备管理" > "设备列表"
2. 点击"新增设备"
3. 选择"AMT 自动添加"
4. 输入 IP 地址
5. 选择认证方式:
- 使用已保存凭证:从下拉框选择
- 手动输入:填写用户名和密码
6. 点击"测试连接"
7. 连接成功后,确认设备信息
8. 点击"确定"保存
## 常见问题
### Q1: 超时错误怎么办?
**A**: 运行 `quick_amt_test.bat` 诊断网络连接问题。
### Q2: 401 认证失败怎么办?
**A**: 检查用户名和密码是否正确,确认 AMT 已正确配置。
### Q3: 没有真实 AMT 设备怎么测试?
**A**: 启用模拟模式,使用 admin/admin 测试。
### Q4: 如何知道 AMT 是否已启用?
**A**:
- 检查 LMS 服务是否运行
- 测试端口 16992 是否开放
- 尝试访问 http://AMT_IP:16992/wsman
### Q5: 支持哪些 AMT 版本?
**A**: 支持 AMT 6.0 及以上版本。
## 下一步
- 查看 `AMT_TROUBLESHOOTING.md` 了解详细的故障排除
- 查看 `AMT_DEVICE_ADD_FEATURE.md` 了解完整功能说明
- 配置更多 AMT 设备
- 探索远程控制功能
## 获取帮助
如果遇到问题:
1. 查看错误信息
2. 运行诊断工具
3. 查阅故障排除文档
4. 检查 AMT 设备配置
5. 查看系统日志
## 重要提示
⚠️ **安全警告**
- 使用强密码
- 限制网络访问
- 定期更新固件
- 不要在生产环境使用模拟模式

269
AMT_TROUBLESHOOTING.md Normal file
View File

@ -0,0 +1,269 @@
# AMT 连接故障排除指南
## 常见错误及解决方案
### 错误 1: HTTP 401 - 认证失败
**错误信息**`AMT 连接测试失败: HTTP 错误: 401`
**可能原因**
1. 用户名或密码错误
2. AMT 未正确配置
3. 需要使用 Digest 认证而不是 Basic 认证
4. 需要使用 HTTPS 而不是 HTTP
**解决方案**
#### 方案 1检查 AMT 凭证
1. 确认 AMT 管理员用户名(通常是 `admin`
2. 确认 AMT 管理员密码
3. 尝试使用 AMT 配置工具重置密码
#### 方案 2检查 AMT 配置状态
在 AMT 设备上:
1. 重启进入 BIOS/UEFI
2. 按 Ctrl+P 进入 Intel ME 配置
3. 检查 AMT 是否已启用
4. 检查网络配置是否正确
#### 方案 3使用测试工具诊断
运行 `test_amt_connection.bat` 进行详细诊断:
```batch
test_amt_connection.bat
```
输入 IP 地址和用户名,查看详细的连接测试结果。
#### 方案 4手动测试 AMT 连接
使用 PowerShell 测试:
```powershell
# 测试端口
Test-NetConnection -ComputerName 192.168.1.100 -Port 16992
# 测试 HTTP 连接
$username = "admin"
$password = "your_password"
$base64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${username}:${password}"))
$headers = @{
"Authorization" = "Basic $base64"
"Content-Type" = "application/soap+xml;charset=UTF-8"
}
$body = @"
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd">
<s:Header/>
<s:Body>
<wsmid:Identify/>
</s:Body>
</s:Envelope>
"@
Invoke-WebRequest -Uri "http://192.168.1.100:16992/wsman" -Method POST -Headers $headers -Body $body
```
#### 方案 5尝试 HTTPS 连接
某些 AMT 配置要求使用 HTTPS
- HTTP 端口16992
- HTTPS 端口16993
修改代码已支持自动尝试 HTTPS重新编译后端即可。
### 错误 2: 连接超时
**错误信息**`AMT 连接超时`
**可能原因**
1. IP 地址错误
2. 网络不可达
3. 防火墙阻止
4. AMT 未启用
**解决方案**
1. **检查网络连接**
```batch
ping 192.168.1.100
```
2. **检查端口是否开放**
```batch
telnet 192.168.1.100 16992
```
或使用 PowerShell
```powershell
Test-NetConnection -ComputerName 192.168.1.100 -Port 16992
```
3. **检查防火墙规则**
- Windows 防火墙
- 网络防火墙
- AMT 设备防火墙
4. **确认 AMT 已启用**
- 进入 BIOS/UEFI
- 检查 Intel ME 配置
- 确认 AMT 服务正在运行
### 错误 3: 连接被拒绝
**错误信息**`Connection refused`
**可能原因**
1. AMT 服务未运行
2. 端口被占用
3. AMT 未正确配置
**解决方案**
1. **检查 AMT 服务状态**
在 AMT 设备上运行:
```batch
sc query LMS
```
2. **重启 AMT 服务**
```batch
net stop LMS
net start LMS
```
3. **检查端口占用**
```batch
netstat -ano | findstr 16992
```
## AMT 配置检查清单
### 基本配置
- [ ] AMT 已在 BIOS/UEFI 中启用
- [ ] 设置了 AMT 管理员密码
- [ ] 配置了网络设置IP、子网掩码、网关
- [ ] AMT 服务正在运行
### 网络配置
- [ ] AMT 设备网络可达
- [ ] 防火墙允许端口 16992 (HTTP) 和 16993 (HTTPS)
- [ ] 没有网络隔离或 VLAN 限制
### 认证配置
- [ ] 知道正确的管理员用户名
- [ ] 知道正确的管理员密码
- [ ] 密码符合复杂度要求
## 使用 Intel AMT 配置工具
### Intel ME Configuration Tool (MEBx)
1. **进入 MEBx**
- 开机时按 Ctrl+P
- 默认密码admin
2. **启用 AMT**
- Intel(R) ME Configuration
- Intel(R) AMT Configuration
- Manageability Feature Selection
- 选择 "Intel AMT"
3. **配置网络**
- Network Setup
- TCP/IP Settings
- 配置 IP 地址DHCP 或静态)
4. **设置密码**
- MEBx Password
- 设置强密码(至少 8 位,包含大小写字母和数字)
5. **激活 AMT**
- Activate Network Access
- 选择激活方式
### Intel Setup and Configuration Service (SCS)
使用 Intel SCS 进行批量配置:
1. 下载 Intel SCS
2. 创建配置文件
3. 批量部署到设备
## 常用 AMT 命令
### 使用 PowerShell 模块
```powershell
# 安装 Intel vPro 模块
Install-Module -Name IntelVPro
# 连接到 AMT 设备
$cred = Get-Credential
Connect-AMT -ComputerName 192.168.1.100 -Credential $cred
# 获取设备信息
Get-AMTSystemInfo
# 获取电源状态
Get-AMTPowerState
# 远程开机
Invoke-AMTPowerAction -Action PowerOn
```
### 使用 WSMAN 命令行
```batch
# 测试连接
wsman identify -r:http://192.168.1.100:16992/wsman -auth:basic -u:admin -p:password
# 获取系统信息
wsman get http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem -r:http://192.168.1.100:16992/wsman -auth:basic -u:admin -p:password
```
## 推荐的 AMT 设置
### 安全设置
- 使用强密码(至少 12 位)
- 启用 TLS/SSL
- 限制访问 IP 范围
- 定期更新固件
### 网络设置
- 使用静态 IP便于管理
- 配置正确的 DNS
- 确保网络稳定
### 功能设置
- 启用远程控制
- 启用 KVM键盘视频鼠标
- 启用 IDE-RIDE 重定向)
- 启用 SOL串行控制台
## 参考资料
- [Intel AMT 官方文档](https://software.intel.com/sites/manageability/)
- [Intel vPro 技术指南](https://www.intel.com/content/www/us/en/architecture-and-technology/vpro/vpro-technology-general.html)
- [WS-Management 协议规范](https://www.dmtf.org/standards/ws-man)
## 获取帮助
如果以上方法都无法解决问题:
1. 检查 AMT 设备日志
2. 查看系统事件日志
3. 联系设备制造商支持
4. 查阅 Intel AMT 社区论坛
## 调试模式
在开发环境中启用详细日志:
1. 修改 `application.properties`
```properties
logging.level.com.soybean.admin.service.AmtService=DEBUG
```
2. 查看详细的请求和响应日志
3. 使用网络抓包工具(如 Wireshark分析流量

View File

@ -77,6 +77,13 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Apache HttpClient for Digest Authentication -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
<!-- Spring Boot Test --> <!-- Spring Boot Test -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -4,8 +4,14 @@ import com.soybean.admin.common.Result;
import com.soybean.admin.dto.DeviceDTO; import com.soybean.admin.dto.DeviceDTO;
import com.soybean.admin.dto.PageRequest; import com.soybean.admin.dto.PageRequest;
import com.soybean.admin.dto.PageResponse; import com.soybean.admin.dto.PageResponse;
import com.soybean.admin.dto.AmtTestRequest;
import com.soybean.admin.dto.AmtDeviceInfo;
import com.soybean.admin.entity.Device; import com.soybean.admin.entity.Device;
import com.soybean.admin.service.AmtMockService;
import com.soybean.admin.service.DeviceService; import com.soybean.admin.service.DeviceService;
import com.soybean.admin.service.AmtDigestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -16,9 +22,20 @@ import java.util.Map;
@RequestMapping("/device") @RequestMapping("/device")
public class DeviceController { public class DeviceController {
private static final Logger logger = LoggerFactory.getLogger(DeviceController.class);
@Autowired @Autowired
private DeviceService deviceService; private DeviceService deviceService;
@Autowired
private AmtMockService amtMockService;
@Autowired
private AmtDigestService amtDigestService;
// 是否使用模拟模式用于测试
private boolean useMockMode = false; // 改为 true 启用模拟模式
@PostMapping("/list") @PostMapping("/list")
public Result<PageResponse<Device>> getDeviceList(@RequestBody PageRequest pageRequest) { public Result<PageResponse<Device>> getDeviceList(@RequestBody PageRequest pageRequest) {
try { try {
@ -90,4 +107,58 @@ public class DeviceController {
return Result.error(e.getMessage()); return Result.error(e.getMessage());
} }
} }
@PostMapping("/amt/test")
public Result<Boolean> testAmtConnection(@RequestBody AmtTestRequest request) {
logger.info("收到 AMT 测试连接请求IP: {}, 模拟模式: {}", request.getIpAddress(), useMockMode);
try {
boolean success;
if (useMockMode) {
logger.info("使用模拟模式测试连接");
success = amtMockService.mockTestConnection(request);
} else {
// 使用 Digest 认证
logger.info("使用 Digest 认证测试连接");
success = amtDigestService.testAmtConnectionWithDigest(request);
logger.info("Digest 认证成功");
}
logger.info("AMT 测试连接结果: {}", success);
return Result.success(success);
} catch (Exception e) {
logger.error("AMT 测试连接失败", e);
return Result.error(e.getMessage());
}
}
@PostMapping("/amt/getInfo")
public Result<AmtDeviceInfo> getAmtDeviceInfo(@RequestBody AmtTestRequest request) {
logger.info("收到获取 AMT 设备信息请求IP: {}", request.getIpAddress());
try {
AmtDeviceInfo deviceInfo;
if (useMockMode) {
deviceInfo = amtMockService.mockGetDeviceInfo(request);
} else {
// 使用 Digest 认证
logger.info("使用 Digest 认证获取设备信息");
deviceInfo = amtDigestService.getAmtDeviceInfoWithDigest(request);
}
logger.info("成功获取 AMT 设备信息: {}", deviceInfo.getDeviceName());
return Result.success(deviceInfo);
} catch (Exception e) {
logger.error("获取 AMT 设备信息失败", e);
return Result.error(e.getMessage());
}
}
@PostMapping("/amt/toggleMock")
public Result<Boolean> toggleMockMode() {
useMockMode = !useMockMode;
logger.info("切换模拟模式: {}", useMockMode ? "已启用" : "已禁用");
return Result.success(useMockMode);
}
@GetMapping("/amt/mockStatus")
public Result<Boolean> getMockStatus() {
return Result.success(useMockMode);
}
} }

View File

@ -0,0 +1,11 @@
package com.soybean.admin.dto;
import lombok.Data;
@Data
public class AmtDeviceInfo {
private String deviceName;
private String deviceCode;
private String ipAddress;
private String macAddress;
}

View File

@ -0,0 +1,11 @@
package com.soybean.admin.dto;
import lombok.Data;
@Data
public class AmtTestRequest {
private String ipAddress;
private String username;
private String password;
private Long credentialId; // 可选使用已保存的凭证ID
}

View File

@ -0,0 +1,218 @@
package com.soybean.admin.service;
import com.soybean.admin.dto.AmtTestRequest;
import com.soybean.admin.dto.AmtDeviceInfo;
import com.soybean.admin.entity.AmtCredential;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.util.UUID;
/**
* AMT Digest 认证服务
*/
@Service
public class AmtDigestService {
private static final Logger logger = LoggerFactory.getLogger(AmtDigestService.class);
@Autowired
private AmtCredentialService amtCredentialService;
/**
* 测试 AMT 连接使用 Digest 认证
*/
public boolean testAmtConnectionWithDigest(AmtTestRequest request) {
logger.info("使用 Digest 认证测试 AMT 连接IP: {}", request.getIpAddress());
try {
String username;
String password;
if (request.getCredentialId() != null) {
AmtCredential credential = amtCredentialService.getCredentialById(request.getCredentialId().toString());
if (credential == null) {
throw new RuntimeException("凭证不存在");
}
username = credential.getUsername();
password = credential.getPassword();
} else {
username = request.getUsername();
password = request.getPassword();
}
logger.info("Digest 认证 - 用户名: {}", username);
String response = sendDigestRequest(
request.getIpAddress(),
username,
password,
getIdentifyRequest()
);
boolean success = response != null && (response.contains("IdentifyResponse") || response.contains("ProductVendor"));
logger.info("Digest 认证测试结果: {}", success);
return success;
} catch (Exception e) {
logger.error("Digest 认证测试失败", e);
throw new RuntimeException("AMT Digest 认证失败: " + e.getMessage());
}
}
/**
* 获取 AMT 设备信息使用 Digest 认证
*/
public AmtDeviceInfo getAmtDeviceInfoWithDigest(AmtTestRequest request) {
try {
String username;
String password;
if (request.getCredentialId() != null) {
AmtCredential credential = amtCredentialService.getCredentialById(request.getCredentialId().toString());
if (credential == null) {
throw new RuntimeException("凭证不存在");
}
username = credential.getUsername();
password = credential.getPassword();
} else {
username = request.getUsername();
password = request.getPassword();
}
AmtDeviceInfo deviceInfo = new AmtDeviceInfo();
String systemInfo = sendDigestRequest(
request.getIpAddress(),
username,
password,
getSystemInfoRequest()
);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new ByteArrayInputStream(systemInfo.getBytes()));
deviceInfo.setIpAddress(request.getIpAddress());
deviceInfo.setDeviceName(extractValue(doc, "ElementName"));
deviceInfo.setMacAddress("00:00:00:00:00:00"); // 简化处理
deviceInfo.setDeviceCode(UUID.randomUUID().toString());
return deviceInfo;
} catch (Exception e) {
throw new RuntimeException("获取 AMT 设备信息失败: " + e.getMessage());
}
}
/**
* 发送 Digest 认证请求
*/
private String sendDigestRequest(String ipAddress, String username, String password, String soapRequest) throws Exception {
// 先尝试 HTTP
try {
return sendDigestRequestWithProtocol("http", ipAddress, 16992, username, password, soapRequest);
} catch (Exception e) {
logger.warn("HTTP Digest 认证失败,尝试 HTTPS: {}", e.getMessage());
// 尝试 HTTPS
return sendDigestRequestWithProtocol("https", ipAddress, 16993, username, password, soapRequest);
}
}
/**
* 使用指定协议发送 Digest 请求
*/
private String sendDigestRequestWithProtocol(String protocol, String ipAddress, int port,
String username, String password, String soapRequest) throws Exception {
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(ipAddress, port),
new UsernamePasswordCredentials(username, password.toCharArray())
);
try (CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider)
.build()) {
HttpHost target = new HttpHost(protocol, ipAddress, port);
HttpPost httpPost = new HttpPost("/wsman");
httpPost.setHeader("Content-Type", "application/soap+xml;charset=UTF-8");
httpPost.setEntity(new StringEntity(soapRequest));
logger.info("发送 Digest 请求到: {}://{}:{}/wsman", protocol, ipAddress, port);
try (CloseableHttpResponse response = httpClient.execute(target, httpPost)) {
int statusCode = response.getCode();
logger.info("收到响应,状态码: {}", statusCode);
if (statusCode == 401) {
throw new RuntimeException("Digest 认证失败 (401)");
} else if (statusCode != 200) {
throw new RuntimeException("HTTP 错误: " + statusCode);
}
String responseBody = EntityUtils.toString(response.getEntity());
logger.info("响应长度: {} 字节", responseBody.length());
return responseBody;
}
}
}
private String getIdentifyRequest() {
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" " +
"xmlns:wsmid=\"http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd\">" +
"<s:Header/>" +
"<s:Body>" +
"<wsmid:Identify/>" +
"</s:Body>" +
"</s:Envelope>";
}
private String getSystemInfoRequest() {
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" " +
"xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " +
"xmlns:wsman=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">" +
"<s:Header>" +
"<wsa:Action s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</wsa:Action>" +
"<wsa:To s:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>" +
"<wsman:ResourceURI s:mustUnderstand=\"true\">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</wsman:ResourceURI>" +
"<wsa:MessageID s:mustUnderstand=\"true\">uuid:" + UUID.randomUUID().toString() + "</wsa:MessageID>" +
"<wsa:ReplyTo>" +
"<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>" +
"</wsa:ReplyTo>" +
"</s:Header>" +
"<s:Body/>" +
"</s:Envelope>";
}
private String extractValue(Document doc, String tagName) {
try {
NodeList nodeList = doc.getElementsByTagName(tagName);
if (nodeList.getLength() > 0) {
return nodeList.item(0).getTextContent();
}
return "Unknown";
} catch (Exception e) {
return "Unknown";
}
}
}

View File

@ -0,0 +1,68 @@
package com.soybean.admin.service;
import com.soybean.admin.dto.AmtTestRequest;
import com.soybean.admin.dto.AmtDeviceInfo;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* AMT 模拟服务 - 用于测试和演示
* 当没有真实 AMT 设备时使用
*/
@Service
public class AmtMockService {
/**
* 模拟测试 AMT 连接
*/
public boolean mockTestConnection(AmtTestRequest request) {
// 模拟延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 模拟成功用户名为 admin 且密码为 admin 时成功
if ("admin".equals(request.getUsername()) && "admin".equals(request.getPassword())) {
return true;
}
throw new RuntimeException("模拟认证失败:用户名或密码错误(测试账号: admin/admin");
}
/**
* 模拟获取 AMT 设备信息
*/
public AmtDeviceInfo mockGetDeviceInfo(AmtTestRequest request) {
// 模拟延迟
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
AmtDeviceInfo deviceInfo = new AmtDeviceInfo();
deviceInfo.setIpAddress(request.getIpAddress());
deviceInfo.setDeviceName("Mock-AMT-Device-" + request.getIpAddress().replace(".", "-"));
deviceInfo.setDeviceCode(UUID.randomUUID().toString());
deviceInfo.setMacAddress(generateMockMacAddress(request.getIpAddress()));
return deviceInfo;
}
/**
* 根据 IP 地址生成模拟 MAC 地址
*/
private String generateMockMacAddress(String ipAddress) {
String[] parts = ipAddress.split("\\.");
if (parts.length == 4) {
return String.format("00:11:22:%02X:%02X:%02X",
Integer.parseInt(parts[1]) % 256,
Integer.parseInt(parts[2]) % 256,
Integer.parseInt(parts[3]) % 256);
}
return "00:11:22:33:44:55";
}
}

48
enable_amt_mock_mode.bat Normal file
View File

@ -0,0 +1,48 @@
@echo off
echo ========================================
echo 启用 AMT 模拟模式
echo ========================================
echo.
echo 此脚本将:
echo 1. 修改代码启用模拟模式
echo 2. 重新编译后端
echo 3. 启动后端服务
echo.
echo 模拟模式说明:
echo - 不需要真实的 AMT 设备
echo - 测试账号admin / admin
echo - 用于测试界面和流程
echo.
pause
cd backend\src\main\java\com\soybean\admin\controller
echo.
echo [1/3] 修改 DeviceController.java...
powershell -Command "(Get-Content DeviceController.java) -replace 'private boolean useMockMode = false;', 'private boolean useMockMode = true;' | Set-Content DeviceController.java"
echo 已启用模拟模式
cd ..\..\..\..\..\..
echo.
echo [2/3] 重新编译...
call mvn clean compile
echo.
echo [3/3] 启动后端...
echo.
echo ========================================
echo 模拟模式已启用
echo ========================================
echo.
echo 测试账号:
echo 用户名admin
echo 密码admin
echo.
echo 可以使用任意 IP 地址进行测试
echo.
call mvn spring-boot:run
pause

41
quick_amt_test.bat Normal file
View File

@ -0,0 +1,41 @@
@echo off
echo ========================================
echo 快速 AMT 连接测试
echo ========================================
echo.
set /p AMT_IP="请输入 AMT 设备 IP 地址: "
echo.
echo [测试 1] Ping 测试...
ping -n 4 %AMT_IP%
echo.
echo [测试 2] 端口 16992 (HTTP) 测试...
powershell -Command "$result = Test-NetConnection -ComputerName %AMT_IP% -Port 16992 -WarningAction SilentlyContinue; if($result.TcpTestSucceeded) { Write-Host '端口 16992 开放 - 成功' -ForegroundColor Green } else { Write-Host '端口 16992 关闭 - 失败' -ForegroundColor Red }"
echo.
echo [测试 3] 端口 16993 (HTTPS) 测试...
powershell -Command "$result = Test-NetConnection -ComputerName %AMT_IP% -Port 16993 -WarningAction SilentlyContinue; if($result.TcpTestSucceeded) { Write-Host '端口 16993 开放 - 成功' -ForegroundColor Green } else { Write-Host '端口 16993 关闭 - 失败' -ForegroundColor Red }"
echo.
echo ========================================
echo 诊断结果
echo ========================================
echo.
echo 如果 Ping 失败:
echo - 检查 IP 地址是否正确
echo - 检查网络连接
echo - 检查防火墙设置
echo.
echo 如果端口关闭:
echo - AMT 可能未启用
echo - 防火墙阻止了端口
echo - AMT 服务未运行
echo.
echo 如果端口开放但仍然超时:
echo - 检查 AMT 用户名和密码
echo - 尝试在 AMT 设备上重启 LMS 服务
echo.
pause

22
rebuild_and_test_amt.bat Normal file
View File

@ -0,0 +1,22 @@
@echo off
echo ========================================
echo 重新编译后端并测试 AMT 功能
echo ========================================
cd backend
echo.
echo [1/3] 清理旧的编译文件...
call mvn clean
echo.
echo [2/3] 编译项目...
call mvn compile
echo.
echo [3/3] 启动后端服务...
echo 提示:后端将在 http://localhost:8080 启动
echo.
call mvn spring-boot:run
pause

View File

@ -0,0 +1,40 @@
@echo off
echo ========================================
echo 重新编译后端并启动(带详细日志)
echo ========================================
echo.
cd backend
echo [1/2] 清理并重新编译...
call mvn clean compile
if %ERRORLEVEL% NEQ 0 (
echo.
echo 编译失败!请检查错误信息。
pause
exit /b 1
)
echo.
echo [2/2] 启动后端服务(带详细日志)...
echo.
echo ========================================
echo 后端服务启动中...
echo ========================================
echo.
echo 日志级别DEBUG
echo 端口8080
echo.
echo 提示:
echo - 查看控制台输出了解详细日志
echo - AMT 测试连接时会显示详细的连接过程
echo - 按 Ctrl+C 停止服务
echo.
REM 设置日志级别为 DEBUG
set JAVA_OPTS=-Dlogging.level.com.soybean.admin=DEBUG
call mvn spring-boot:run
pause

33
rebuild_digest_only.bat Normal file
View File

@ -0,0 +1,33 @@
@echo off
echo ========================================
echo 重新编译后端(仅 Digest 认证)
echo ========================================
echo.
cd backend
echo 清理旧的编译文件...
call mvn clean
echo.
echo 重新编译项目...
call mvn compile
echo.
echo 打包项目...
call mvn package -DskipTests
echo.
echo ========================================
echo 编译完成!
echo ========================================
echo.
echo 现在可以启动后端服务:
echo cd backend
echo java -jar target/soybean-admin-0.0.1-SNAPSHOT.jar
echo.
echo 或者运行:
echo start_backend.bat
echo.
pause

View File

@ -0,0 +1,42 @@
@echo off
echo ========================================
echo 重新编译后端(添加 Digest 认证支持)
echo ========================================
echo.
cd backend
echo [1/3] 清理旧的编译文件...
call mvn clean
echo.
echo [2/3] 下载依赖并编译...
echo 注意:首次运行会下载 Apache HttpClient 依赖
call mvn compile
if %ERRORLEVEL% NEQ 0 (
echo.
echo 编译失败!请检查错误信息。
pause
exit /b 1
)
echo.
echo [3/3] 启动后端服务...
echo.
echo ========================================
echo Digest 认证支持已添加
echo ========================================
echo.
echo 现在支持两种认证方式:
echo 1. Basic 认证(先尝试)
echo 2. Digest 认证Basic 失败后自动尝试)
echo.
echo 提示:
echo - 系统会自动尝试两种认证方式
echo - 查看日志了解使用了哪种认证
echo.
call mvn spring-boot:run
pause

View File

@ -75,3 +75,25 @@ export function fetchDeviceStatistics() {
method: 'get' method: 'get'
}); });
} }
/**
* AMT
*/
export function fetchTestAmtConnection(data: Api.Device.AmtTestRequest) {
return request<boolean>({
url: '/device/amt/test',
method: 'post',
data
});
}
/**
* AMT
*/
export function fetchAmtDeviceInfo(data: Api.Device.AmtTestRequest) {
return request<Api.Device.AmtDeviceInfo>({
url: '/device/amt/getInfo',
method: 'post',
data
});
}

View File

@ -51,5 +51,29 @@ declare namespace Api {
/** 备注 */ /** 备注 */
remark?: string; remark?: string;
} }
/** AMT 测试请求 */
interface AmtTestRequest {
/** IP地址 */
ipAddress: string;
/** 用户名 */
username?: string;
/** 密码 */
password?: string;
/** 凭证ID */
credentialId?: number;
}
/** AMT 设备信息 */
interface AmtDeviceInfo {
/** 设备名称 */
deviceName: string;
/** 设备编号 */
deviceCode: string;
/** IP地址 */
ipAddress: string;
/** MAC地址 */
macAddress: string;
}
} }
} }

View File

@ -49,6 +49,7 @@ declare module 'vue' {
IconMdiIp: typeof import('~icons/mdi/ip')['default'] IconMdiIp: typeof import('~icons/mdi/ip')['default']
IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default'] IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default'] IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
IconMdiLanConnect: typeof import('~icons/mdi/lan-connect')['default']
IconMdiMagnify: typeof import('~icons/mdi/magnify')['default'] IconMdiMagnify: typeof import('~icons/mdi/magnify')['default']
IconMdiMapMarker: typeof import('~icons/mdi/map-marker')['default'] IconMdiMapMarker: typeof import('~icons/mdi/map-marker')['default']
IconMdiMemory: typeof import('~icons/mdi/memory')['default'] IconMdiMemory: typeof import('~icons/mdi/memory')['default']
@ -188,6 +189,7 @@ declare global {
const IconMdiIp: typeof import('~icons/mdi/ip')['default'] const IconMdiIp: typeof import('~icons/mdi/ip')['default']
const IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default'] const IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
const IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default'] const IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
const IconMdiLanConnect: typeof import('~icons/mdi/lan-connect')['default']
const IconMdiMagnify: typeof import('~icons/mdi/magnify')['default'] const IconMdiMagnify: typeof import('~icons/mdi/magnify')['default']
const IconMdiMapMarker: typeof import('~icons/mdi/map-marker')['default'] const IconMdiMapMarker: typeof import('~icons/mdi/map-marker')['default']
const IconMdiMemory: typeof import('~icons/mdi/memory')['default'] const IconMdiMemory: typeof import('~icons/mdi/memory')['default']

View File

@ -93,8 +93,88 @@
v-model:show="modalVisible" v-model:show="modalVisible"
:title="modalTitle" :title="modalTitle"
preset="card" preset="card"
class="w-700px" class="w-800px"
> >
<!-- 添加方式选择仅新增时显示 -->
<n-radio-group v-if="!isEdit" v-model:value="addMode" class="mb-16px">
<n-space>
<n-radio value="manual">手动添加</n-radio>
<n-radio value="amt">AMT 自动添加</n-radio>
</n-space>
</n-radio-group>
<!-- AMT 添加方式 -->
<div v-if="!isEdit && addMode === 'amt'" class="mb-16px">
<n-card title="AMT 设备发现" size="small" :bordered="false" class="bg-gray-50">
<n-form label-placement="left" label-width="100px">
<n-form-item label="IP 地址" required>
<n-input
v-model:value="amtFormData.ipAddress"
placeholder="请输入 AMT 设备 IP 地址"
/>
</n-form-item>
<n-form-item label="认证方式">
<n-switch
v-model:value="amtFormData.useCredential"
@update:value="handleCredentialToggle"
>
<template #checked>使用已保存凭证</template>
<template #unchecked>手动输入</template>
</n-switch>
</n-form-item>
<template v-if="amtFormData.useCredential">
<n-form-item label="选择凭证" required>
<n-select
v-model:value="amtFormData.credentialId"
:options="amtCredentials.map(c => ({ label: c.credentialName, value: c.credentialId }))"
placeholder="请选择 AMT 凭证"
clearable
/>
</n-form-item>
</template>
<template v-else>
<n-form-item label="用户名" required>
<n-input
v-model:value="amtFormData.username"
placeholder="请输入 AMT 用户名"
/>
</n-form-item>
<n-form-item label="密码" required>
<n-input
v-model:value="amtFormData.password"
type="password"
show-password-on="click"
placeholder="请输入 AMT 密码"
/>
</n-form-item>
</template>
<n-form-item>
<n-space>
<n-button
type="primary"
:loading="amtTesting"
@click="handleTestAmtConnection"
>
<icon-mdi-lan-connect class="mr-4px" />
测试连接
</n-button>
<n-tag v-if="amtTestSuccess" type="success">
<icon-mdi-check-circle class="mr-4px" />
连接成功
</n-tag>
</n-space>
</n-form-item>
</n-form>
</n-card>
<n-divider class="!my-16px">设备信息</n-divider>
</div>
<!-- 设备信息表单 -->
<n-form <n-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
@ -107,25 +187,28 @@
<n-input <n-input
v-model:value="formData.deviceName" v-model:value="formData.deviceName"
placeholder="请输入设备名称" placeholder="请输入设备名称"
:disabled="!isEdit && addMode === 'amt' && !amtTestSuccess"
/> />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi label="UUID" path="deviceCode"> <n-form-item-gi label="UUID" path="deviceCode">
<n-input <n-input
v-model:value="formData.deviceCode" v-model:value="formData.deviceCode"
placeholder="请输入UUID" placeholder="请输入UUID"
:disabled="isEdit" :disabled="isEdit || (addMode === 'amt' && !amtTestSuccess)"
/> />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi label="IP地址" path="ipAddress"> <n-form-item-gi label="IP地址" path="ipAddress">
<n-input <n-input
v-model:value="formData.ipAddress" v-model:value="formData.ipAddress"
placeholder="请输入IP地址" placeholder="请输入IP地址"
:disabled="!isEdit && addMode === 'amt' && !amtTestSuccess"
/> />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi label="MAC地址" path="macAddress"> <n-form-item-gi label="MAC地址" path="macAddress">
<n-input <n-input
v-model:value="formData.macAddress" v-model:value="formData.macAddress"
placeholder="请输入MAC地址" placeholder="请输入MAC地址"
:disabled="!isEdit && addMode === 'amt' && !amtTestSuccess"
/> />
</n-form-item-gi> </n-form-item-gi>
<n-form-item-gi label="设备状态" path="status"> <n-form-item-gi label="设备状态" path="status">
@ -148,7 +231,14 @@
<template #footer> <template #footer>
<n-space justify="end"> <n-space justify="end">
<n-button @click="modalVisible = false">取消</n-button> <n-button @click="modalVisible = false">取消</n-button>
<n-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</n-button> <n-button
type="primary"
@click="handleSubmit"
:loading="submitLoading"
:disabled="!isEdit && addMode === 'amt' && !amtTestSuccess"
>
确定
</n-button>
</n-space> </n-space>
</template> </template>
</n-modal> </n-modal>
@ -187,8 +277,11 @@ import {
fetchCreateDevice, fetchCreateDevice,
fetchUpdateDevice, fetchUpdateDevice,
fetchDeleteDevice, fetchDeleteDevice,
fetchBatchDeleteDevice fetchBatchDeleteDevice,
fetchTestAmtConnection,
fetchAmtDeviceInfo
} from '@/service/api/device'; } from '@/service/api/device';
import { fetchAllActiveCredentials, type AmtCredential } from '@/service/api/amt';
defineOptions({ defineOptions({
name: 'DeviceList' name: 'DeviceList'
@ -353,6 +446,22 @@ const modalTitle = ref('');
const isEdit = ref(false); const isEdit = ref(false);
const submitLoading = ref(false); const submitLoading = ref(false);
const formRef = ref(); const formRef = ref();
// manualamtAMT
const addMode = ref<'manual' | 'amt'>('manual');
// AMT
const amtFormData = reactive({
ipAddress: '',
username: '',
password: '',
credentialId: null as number | null,
useCredential: false
});
const amtCredentials = ref<AmtCredential[]>([]);
const amtTesting = ref(false);
const amtTestSuccess = ref(false);
const formData = reactive({ const formData = reactive({
id: null as number | null, id: null as number | null,
deviceName: '', deviceName: '',
@ -421,6 +530,8 @@ function handleCheck(keys: number[]) {
function handleAdd() { function handleAdd() {
isEdit.value = false; isEdit.value = false;
modalTitle.value = '新增设备'; modalTitle.value = '新增设备';
addMode.value = 'manual';
amtTestSuccess.value = false;
Object.assign(formData, { Object.assign(formData, {
id: null, id: null,
deviceName: '', deviceName: '',
@ -430,7 +541,15 @@ function handleAdd() {
macAddress: '', macAddress: '',
remark: '' remark: ''
}); });
Object.assign(amtFormData, {
ipAddress: '',
username: '',
password: '',
credentialId: null,
useCredential: false
});
modalVisible.value = true; modalVisible.value = true;
loadAmtCredentials();
} }
// //
@ -506,4 +625,105 @@ async function handleBatchDelete() {
onMounted(() => { onMounted(() => {
loadData(); loadData();
}); });
// AMT
async function loadAmtCredentials() {
try {
const { data } = await fetchAllActiveCredentials();
amtCredentials.value = data || [];
} catch (error) {
console.error('加载 AMT 凭证失败:', error);
}
}
// AMT
async function handleTestAmtConnection() {
//
if (!amtFormData.ipAddress) {
window.$message?.warning('请输入 IP 地址');
return;
}
if (!amtFormData.useCredential && (!amtFormData.username || !amtFormData.password)) {
window.$message?.warning('请输入用户名和密码,或选择已保存的凭证');
return;
}
if (amtFormData.useCredential && !amtFormData.credentialId) {
window.$message?.warning('请选择 AMT 凭证');
return;
}
amtTesting.value = true;
amtTestSuccess.value = false;
try {
const requestData: Api.Device.AmtTestRequest = {
ipAddress: amtFormData.ipAddress
};
if (amtFormData.useCredential && amtFormData.credentialId) {
requestData.credentialId = amtFormData.credentialId;
} else {
requestData.username = amtFormData.username;
requestData.password = amtFormData.password;
}
const { data } = await fetchTestAmtConnection(requestData);
if (data) {
amtTestSuccess.value = true;
window.$message?.success('AMT 连接测试成功');
//
await handleGetAmtDeviceInfo();
} else {
window.$message?.error('AMT 连接测试失败');
}
} catch (error: any) {
window.$message?.error(error?.message || 'AMT 连接测试失败');
} finally {
amtTesting.value = false;
}
}
// AMT
async function handleGetAmtDeviceInfo() {
try {
const requestData: Api.Device.AmtTestRequest = {
ipAddress: amtFormData.ipAddress
};
if (amtFormData.useCredential && amtFormData.credentialId) {
requestData.credentialId = amtFormData.credentialId;
} else {
requestData.username = amtFormData.username;
requestData.password = amtFormData.password;
}
const { data } = await fetchAmtDeviceInfo(requestData);
//
formData.deviceName = data.deviceName;
formData.deviceCode = data.deviceCode;
formData.ipAddress = data.ipAddress;
formData.macAddress = data.macAddress;
window.$message?.success('设备信息获取成功');
} catch (error: any) {
window.$message?.error(error?.message || '获取设备信息失败');
}
}
// 使
function handleCredentialToggle(value: boolean) {
if (value) {
amtFormData.username = '';
amtFormData.password = '';
} else {
amtFormData.credentialId = null;
}
amtTestSuccess.value = false;
}
</script> </script>

View File

@ -1,16 +1,43 @@
@echo off @echo off
chcp 65001 >nul
echo ======================================== echo ========================================
echo 测试 AMT API 接口 echo 测试 AMT API 是否正常
echo ======================================== echo ========================================
echo. echo.
echo 测试获取凭证列表... echo [测试 1] 检查后端是否运行...
curl -X GET "http://localhost:8080/api/amt/credential/list?page=1&size=10" -H "Content-Type: application/json" curl -s http://localhost:8080/device/statistics >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
echo 后端未运行或无法连接!
echo 请先启动后端服务。
pause
exit /b 1
)
echo 后端正常运行 ✓
echo.
echo [测试 2] 检查模拟模式状态...
curl -s http://localhost:8080/device/amt/mockStatus
echo.
echo.
echo [测试 3] 测试 AMT 连接 API使用模拟数据...
echo.
echo 发送测试请求...
curl -X POST http://localhost:8080/device/amt/test ^
-H "Content-Type: application/json" ^
-d "{\"ipAddress\":\"192.168.1.100\",\"username\":\"admin\",\"password\":\"admin\"}"
echo. echo.
echo. echo.
echo ======================================== echo ========================================
echo 测试完成 echo 测试完成
echo ======================================== echo ========================================
echo.
echo 如果看到成功响应,说明 API 正常工作。
echo 如果看到错误,请检查:
echo 1. 后端是否正常启动
echo 2. 端口 8080 是否被占用
echo 3. 查看后端控制台日志
echo.
pause pause

43
test_amt_connection.bat Normal file
View File

@ -0,0 +1,43 @@
@echo off
echo ========================================
echo AMT 连接测试工具
echo ========================================
echo.
set /p AMT_IP="请输入 AMT 设备 IP 地址: "
set /p AMT_USER="请输入 AMT 用户名 (默认: admin): "
if "%AMT_USER%"=="" set AMT_USER=admin
echo.
echo 正在测试连接到 %AMT_IP%...
echo.
REM 测试端口 16992 是否开放
echo [1/4] 测试端口 16992 (HTTP)...
powershell -Command "Test-NetConnection -ComputerName %AMT_IP% -Port 16992 -InformationLevel Detailed"
echo.
echo [2/4] 测试端口 16993 (HTTPS)...
powershell -Command "Test-NetConnection -ComputerName %AMT_IP% -Port 16993 -InformationLevel Detailed"
echo.
echo [3/4] 尝试 HTTP 连接...
curl -v -u %AMT_USER% http://%AMT_IP%:16992/wsman -H "Content-Type: application/soap+xml;charset=UTF-8" -d "<?xml version=\"1.0\" encoding=\"UTF-8\"?><s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:wsmid=\"http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd\"><s:Header/><s:Body><wsmid:Identify/></s:Body></s:Envelope>"
echo.
echo [4/4] 尝试 HTTPS 连接...
curl -v -k -u %AMT_USER% https://%AMT_IP%:16993/wsman -H "Content-Type: application/soap+xml;charset=UTF-8" -d "<?xml version=\"1.0\" encoding=\"UTF-8\"?><s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:wsmid=\"http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd\"><s:Header/><s:Body><wsmid:Identify/></s:Body></s:Envelope>"
echo.
echo ========================================
echo 测试完成
echo ========================================
echo.
echo 提示:
echo - 如果端口不通,请检查防火墙设置
echo - 如果返回 401请检查用户名和密码
echo - 如果返回 404AMT 可能未启用
echo - HTTP 端口: 16992, HTTPS 端口: 16993
echo.
pause