T完成: TaskController创建+3个IJob构造函数改造(IServiceProvider注入)+RuleEngineJob标记迁移
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
# KMS 钥匙柜适配器 — 任务清单
|
||||
|
||||
> **基准文档**: `doc/设计文档/KMS钥匙柜适配器详细设计文档.md` v2.1
|
||||
> **基准文档**: `doc/设计文档/KMS钥匙柜适配器详细设计文档.md`
|
||||
> **分支**: gateway-dev
|
||||
> **原则**: 严格按照设计文档执行,严禁无中生有。网关/Vol.Pro 改动放倒数第二步,联调放最后。
|
||||
> **原则**: 严格按照设计文档执行,不凭空添加。网关/Vol.Pro 改动放倒数第二步,联调放最后。
|
||||
|
||||
---
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
### K1.1 认证模型
|
||||
- [ ] 创建 `KmsModels.cs`
|
||||
- [ ] 添加 `KmsTokenResponse { Code, Token, Msg }`
|
||||
- [ ] `KmsTokenResponse { Code, Token, Msg }`
|
||||
|
||||
### K1.2 第三方接口响应模型(2.18.X)
|
||||
- [ ] `KmsOpenerListResponse { Code, Msg, Rows }`
|
||||
@@ -39,20 +39,10 @@
|
||||
- [ ] `KmsRecordListResponse { Code, Msg, Total, Rows }`
|
||||
- [ ] `KmsRecord { Uuid, LockerName, LockholeSort, OpenerName, StaffName, BorrowTime, ReturnTime, Type }`
|
||||
|
||||
### K1.3 标准接口响应模型(2.3-2.17)
|
||||
- [ ] `KmsHandoverInfo` — 交接记录
|
||||
- [ ] `KmsPermissionListResponse` + `KmsPermission` — 授权记录
|
||||
- [ ] `KmsStaffListResponse` + `KmsStaff` — 员工
|
||||
- [ ] `KmsLockerListResponse` + `KmsLockerInfo` — 柜体
|
||||
- [ ] `KmsLockholeListResponse` + `KmsLockholeInfo` — 锁孔
|
||||
- [ ] `KmsOpenerListResponse2` + `KmsOpenerInfo` — 钥匙
|
||||
- [ ] `KmsStaffOpenerListResponse` + `KmsStaffOpener` — 员工可借
|
||||
- [ ] `KmsRemotePermissionRequest` — 远程授权请求(联调时确认字段)
|
||||
### K1.3 编译验证
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
|
||||
### K1.4 编译验证
|
||||
- [ ] `dotnet build` → 0 错误(DTO 引用 Core 的 `StandardDevice`/`StandardAlarm` 等确认无编译错误)
|
||||
|
||||
> **K1 提交点**: `PhaseK1_models — KmsModels.cs 完整定义全部 15 个 DTO`
|
||||
> **K1 提交点**: `PhaseK1_models — KmsModels.cs 完整定义全部响应 DTO`
|
||||
|
||||
---
|
||||
|
||||
@@ -61,269 +51,133 @@
|
||||
### K2.1 创建 KmsAuthHelper.cs
|
||||
- [ ] 构造函数:接收 `HttpClient`, `baseUrl`, `clientId`, `clientSecret`
|
||||
- [ ] 属性:`_token` (string?), `_tokenExpiry` (DateTime)
|
||||
- [ ] 依赖:`System.Text.Json`, `System.Net.Http.Json`
|
||||
|
||||
### K2.2 GetTokenAsync
|
||||
- [ ] POST `/prod-api/getToken?clientId=xx&clientSecret=yy`
|
||||
- [ ] 检查 `resp.EnsureSuccessStatusCode()`
|
||||
- [ ] 反序列化 `KmsTokenResponse`
|
||||
- [ ] 校验 `Code == 200`
|
||||
- [ ] 缓存 Token,过期时间 = `UtcNow.AddMinutes(25)`(30 分钟效期,5 分钟余量)
|
||||
|
||||
### K2.3 GetAuthenticatedClientAsync
|
||||
- [ ] 调用 `GetTokenAsync()`
|
||||
- [ ] 创建新 `HttpClient`,`BaseAddress = _baseUrl`
|
||||
- [ ] 设置 Header `Authorization: Bearer {token}`
|
||||
- [ ] 返回 client
|
||||
- [ ] 创建 `HttpClient`,设置 `Authorization: Bearer {token}`
|
||||
- [ ] Invalidate() → `_token = null`
|
||||
|
||||
### K2.4 Invalidate
|
||||
- [ ] `_token = null` 强制下次重新获取
|
||||
|
||||
### K2.5 编译验证
|
||||
### K2.4 编译验证
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
|
||||
> **K2 提交点**: `PhaseK2_auth — KmsAuthHelper Bearer Token 认证就绪`
|
||||
> **K2 提交点**: `PhaseK2_auth — Bearer Token 认证就绪`
|
||||
|
||||
---
|
||||
|
||||
## Phase K3: KmsAdapter 核心方法(预计 1.5h)
|
||||
|
||||
### K3.1 类定义与构造函数
|
||||
### K3.1 类定义
|
||||
- [ ] `public class KmsAdapter : IHasFlatDevices, IHasAlarms`
|
||||
- [ ] 字段:`_http`, `_auth` (KmsAuthHelper), `_limiter` (RateLimiter(5))
|
||||
- [ ] 属性:`AdapterCode`, `DisplayName`, `Capabilities { HasFlatDevices=true, HasAlarms=true }`
|
||||
- [ ] 构造函数:注入 `httpClient`, `baseUrl`, `clientId`, `clientSecret`
|
||||
- [ ] 属性:`AdapterCode`, `DisplayName`, `Capabilities`
|
||||
|
||||
### K3.2 InitializeAsync
|
||||
- [ ] `await _auth.GetTokenAsync()`
|
||||
### K3.2 HealthCheckAsync(2.18.1)
|
||||
- [ ] GET `/prod-api/heartBeat`
|
||||
- [ ] 异常捕获返回 false + Console.Error 打日志
|
||||
|
||||
### K3.3 HealthCheckAsync(2.18.1)
|
||||
- [ ] POST `/prod-api/heartBeat` (空 body `{}`)
|
||||
- [ ] 返回 `resp.IsSuccessStatusCode`
|
||||
- [ ] 异常捕获返回 false
|
||||
|
||||
### K3.4 GetDevicesAsync(2.18.4 — 柜体+锁孔 → StandardDevice)
|
||||
- [ ] `await _limiter.WaitAsync()`
|
||||
### K3.3 GetDevicesAsync(2.18.4)
|
||||
- [ ] POST `/prod-api/getOpenerList` (body `{}`)
|
||||
- [ ] 反序列化 `KmsOpenerListResponse`
|
||||
- [ ] 遍历 `Rows`:
|
||||
- 每个 `KmsLocker` → `MapLockerToDevice`(父设备,SourceId=`locker_{LockerId}`)
|
||||
- 每个 `KmsLockhole` → `MapLockholeToDevice`(子设备,ParentSourceId=`locker_{LockerId}`)
|
||||
- [ ] IsOnline 判断:`OpenerState == "在位"` → true
|
||||
- [ ] Extra 字典:`{ openerId, openerType, openerState }` / `{ lockerCode, lockholeCount }`
|
||||
- [ ] 返回 `PagedResult<StandardDevice>`
|
||||
- [ ] 遍历柜体/锁孔 → 映射为 StandardDevice
|
||||
- [ ] 父设备 `IsParent=是`, 子设备 `ParentSourceId=locker_{id}`
|
||||
|
||||
### K3.5 GetAlarmsAsync(2.18.7 — 告警列表 → StandardAlarm)
|
||||
- [ ] `await _limiter.WaitAsync()`
|
||||
- [ ] POST `/prod-api/getWarningList` (body `{}`)
|
||||
- [ ] 反序列化 `KmsWarningListResponse`
|
||||
- [ ] 映射:`AlarmId=uuid`, `Title="{lockerName} 锁孔{lockholeSort}: {openerName}"`, `Status=Type==1?"未确认":"已结束"`, `Level="普通"`
|
||||
- [ ] 返回 `PagedResult<StandardAlarm>`
|
||||
### K3.4 GetAlarmsAsync(2.18.7)
|
||||
- [ ] POST `/prod-api/getWarningList`
|
||||
- [ ] 映射 KmsWarning → StandardAlarm
|
||||
- [ ] AlarmId=uuid, Status=Type==1?"未确认":"已结束"
|
||||
|
||||
### K3.6 ConfirmAlarmAsync / EndAlarmAsync
|
||||
- [ ] `ConfirmAlarmAsync`: POST `/prod-api/kms/warning/confirm/{alarmId}`
|
||||
- [ ] `EndAlarmAsync`: 留空实现(KMS 第三方接口不提供结束告警)
|
||||
### K3.5 ConfirmAlarmAsync / EndAlarmAsync
|
||||
- [ ] Confirm 调标准接口;End 留空实现
|
||||
|
||||
### K3.7 编译验证
|
||||
### K3.6 编译验证
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
|
||||
> **K3 提交点**: `PhaseK3_adapter_core — KmsAdapter 核心4方法就绪(HealthCheck/GetDevices/GetAlarms/Confirm)`
|
||||
> **K3 提交点**: `PhaseK3_adapter_core — 核心4方法就绪`
|
||||
|
||||
---
|
||||
|
||||
## Phase K4: KmsAdapter 扩展方法(预计 1h)
|
||||
## Phase K4: 扩展方法(预计 1h)
|
||||
|
||||
### K4.1 GetBorrowRecordsAsync(2.18.6)
|
||||
- [ ] POST `/prod-api/getRecordList`
|
||||
- [ ] 参数:`from`, `to` DateTime?(联调时确认请求体格式)
|
||||
- [ ] 返回 `PagedResult<KmsRecord>`
|
||||
### K4.1 借还/授权/员工/登录
|
||||
- [ ] GetBorrowRecordsAsync(2.18.6)
|
||||
- [ ] GetPermissionListAsync(2.18.5)
|
||||
- [ ] BatchSyncStaffAsync(2.18.3)
|
||||
- [ ] BatchDeleteStaffAsync(2.18.2)
|
||||
- [ ] RemoteAuthorizeAsync(2.4.3)
|
||||
- [ ] ThirdPlatLoginAsync(2.18.8)
|
||||
|
||||
### K4.2 GetPermissionListAsync(2.18.5)
|
||||
- [ ] POST `/prod-api/getPermissionList`
|
||||
- [ ] 参数:`from`, `to` DateTime?
|
||||
- [ ] 返回 `PagedResult<KmsPermission>`
|
||||
|
||||
### K4.3 BatchSyncStaffAsync(2.18.3)
|
||||
- [ ] POST `/prod-api/batchSyncStaff`
|
||||
- [ ] 请求体:`new { staff = staffList }`
|
||||
- [ ] `resp.EnsureSuccessStatusCode()`
|
||||
|
||||
### K4.4 BatchDeleteStaffAsync(2.18.2)
|
||||
- [ ] POST `/prod-api/batchDeleteStaff`
|
||||
- [ ] 请求体:`List<string>` (staffUuid 数组)
|
||||
- [ ] `resp.EnsureSuccessStatusCode()`
|
||||
|
||||
### K4.5 RemoteAuthorizeAsync(2.4.3)
|
||||
- [ ] POST `/prod-api/kms/permission/remote`
|
||||
- [ ] 请求体:`KmsRemotePermissionRequest`(联调确认字段)
|
||||
|
||||
### K4.6 ThirdPlatLoginAsync(2.18.8)
|
||||
- [ ] POST `/thirdPlatlogin?username={username}`
|
||||
- [ ] 处理 302 重定向:返回 `Location` header 或响应体
|
||||
- [ ] 超时设置 15s
|
||||
|
||||
### K4.7 编译验证
|
||||
### K4.2 编译验证
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
|
||||
> **K4 提交点**: `PhaseK4_adapter_ext — 6个扩展方法全部就绪(记录/同步/授权/登录)`
|
||||
> **K4 提交点**: `PhaseK4_adapter_ext — 6个扩展方法就绪`
|
||||
|
||||
---
|
||||
|
||||
## Phase K5: 配置与注册(预计 15min)
|
||||
|
||||
### K5.1 KmsConfig POCO
|
||||
- [ ] 在 `Program.cs` 同级新增 `KmsConfig` 类
|
||||
- [ ] 属性:`InstanceName?`, `BaseUrl`, `ClientId`, `ClientSecret`
|
||||
- [ ] 在 Program.cs 同级加 class,属性:`InstanceName, BaseUrl, ClientId, ClientSecret`
|
||||
|
||||
### K5.2 appsettings.json
|
||||
- [ ] 新增 `KMS` 数组配置段
|
||||
- [ ] 配置项:`InstanceName`, `BaseUrl`, `ClientId`, `ClientSecret`
|
||||
- [ ] 新增 KMS 数组配置段
|
||||
|
||||
### K5.3 Program.cs 注册
|
||||
- [ ] `var kmsList = app.Configuration.GetSection("KMS").Get<List<KmsConfig>>() ?? new();`
|
||||
- [ ] foreach 注册 `KmsAdapter("KMS:{InstanceName}", http, baseUrl, clientId, clientSecret)`
|
||||
- [ ] 适配器编码加入 `adapterTypes` 拼接
|
||||
- [ ] foreach 注册 `KmsAdapter("KMS:{InstanceName}", ...)`
|
||||
|
||||
### K5.4 编译验证
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
|
||||
> **K5 提交点**: `PhaseK5_config — KMS多实例配置+Program.cs注册就绪`
|
||||
> **K5 提交点**: `PhaseK5_config — 配置+注册就绪`
|
||||
|
||||
---
|
||||
|
||||
## Phase K6: 编译与自测(预计 15min)
|
||||
|
||||
### K6.1 全量编译
|
||||
- [ ] `dotnet build` → 0 错误(确认 KMS 适配器不引入外部依赖)
|
||||
|
||||
### K6.2 启动测试
|
||||
- [ ] `dotnet run` 启动网关
|
||||
- [ ] 检查控制台输出:`[Gateway] N 个适配器已注册: Owl:main,MC4:31ku,KMS:main`
|
||||
- [ ] 确认 KMS 初始化失败时打印错误但不阻塞
|
||||
|
||||
> **K6 提交点**: `PhaseK6_build — 网关全量编译通过 KMS适配器热加载不阻塞启动`
|
||||
|
||||
---
|
||||
|
||||
## Phase K7: 网关核心与 Host 扩展(预计 1.5h)⚠️ 倒数第二步
|
||||
|
||||
> **说明**: 此阶段按设计文档附录 B 新增 Core 能力接口 + B 组路由,遵循网关设计原则 §3.4。
|
||||
|
||||
### K7.1 新增 IAcceptsControl 接口
|
||||
- [ ] 创建 `Core/Abstractions/IAcceptsControl.cs`
|
||||
- [ ] 方法:`Task<ControlResult> SendControlAsync(sourceDeviceId, command, parameters)`
|
||||
- [ ] 新增 `Core/Models/ControlResult.cs`:`{ Success, Message }`
|
||||
|
||||
### K7.2 新增 IHasBusinessLogs 接口
|
||||
- [ ] 创建 `Core/Abstractions/IHasBusinessLogs.cs`
|
||||
- [ ] 方法:`Task<PagedResult<BusinessLogEntry>> GetBusinessLogsAsync(logType, from, to, page, size, filters)`
|
||||
- [ ] 新增 `Core/Models/BusinessLogEntry.cs`:`{ LogId, LogType, DeviceSourceId, StaffName, Description, CreatedAt, Extra }`
|
||||
|
||||
### K7.3 新增 IAcceptsDataSync 接口
|
||||
- [ ] 创建 `Core/Abstractions/IAcceptsDataSync.cs`
|
||||
- [ ] 方法:`Task<SyncResult> SyncDataAsync(dataType, items)`
|
||||
- [ ] 方法:`Task<SyncResult> DeleteDataAsync(dataType, ids)`
|
||||
- [ ] 新增 `Core/Models/SyncResult.cs`:`{ SuccessCount, FailCount, Message }`
|
||||
|
||||
### K7.4 KmsAdapter 实现新接口
|
||||
- [ ] `KmsAdapter` 增加 `: IAcceptsControl, IHasBusinessLogs, IAcceptsDataSync`
|
||||
- [ ] `SendControlAsync`:调 `RemoteAuthorizeAsync`,command="open" 时调 `/kms/permission/remote`
|
||||
- [ ] `GetBusinessLogsAsync`:按 logType 分发到 `GetBorrowRecordsAsync` / `GetPermissionListAsync` / 交接记录
|
||||
- [ ] `SyncDataAsync`:dataType="staff" 时调 `BatchSyncStaffAsync`
|
||||
- [ ] `DeleteDataAsync`:dataType="staff" 时调 `BatchDeleteStaffAsync`
|
||||
|
||||
### K7.5 Program.cs 新增 B 组路由
|
||||
- [ ] `POST /api/gateway/control/{adapter}` — `IAcceptsControl.SendControlAsync`
|
||||
- [ ] `GET /api/gateway/logs/{adapter}` — `IHasBusinessLogs.GetBusinessLogsAsync`
|
||||
- [ ] `POST /api/gateway/sync/{adapter}` — `IAcceptsDataSync.SyncDataAsync`
|
||||
- [ ] `DELETE /api/gateway/sync/{adapter}` — `IAcceptsDataSync.DeleteDataAsync`
|
||||
|
||||
### K7.6 编译验证
|
||||
### K6.1 编译验证
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
|
||||
> **K7 提交点**: `PhaseK7_gateway — 3个新Core接口+4条B路由+KmsAdapter多接口实现`
|
||||
> **K6 提交点**: `PhaseK6_build — 全量编译通过`
|
||||
|
||||
---
|
||||
|
||||
## Phase K8: Vol.Pro 管理端配套(预计 1h)⚠️ 倒数第二步
|
||||
## Phase K7: Vol.Pro 端配套(预计 1h)
|
||||
|
||||
### K8.1 数据字典补充
|
||||
- [ ] 管理端 → 字典管理 → 设备种类新增:"智能钥匙柜" / "钥匙位"
|
||||
### K7.1 字典
|
||||
- [ ] 管理端设备种类字典 ← "智能钥匙柜" + "钥匙位"
|
||||
|
||||
### K8.2 前端操作列扩展
|
||||
- [ ] 编辑 `web.vite/src/views/warehouse/device_manager/base_device.vue`
|
||||
- [ ] `onInited` 的 render 函数中增加 `DeviceGroup==='门禁设备'` 分支
|
||||
- [ ] 显示 "开门" 按钮(调用网关 B8)
|
||||
- [ ] 显示 "权限" 下拉菜单(永久授权/临时授权/取消授权)
|
||||
### K7.2 前端按钮
|
||||
- [ ] `base_device.vue` 操作列:门禁设备 → [开门] [授权] 按钮
|
||||
|
||||
### K8.3 前端 API 调用
|
||||
- [ ] `fetch()` 调网关 `http://localhost:5100/api/gateway/control/KMS:main`
|
||||
- [ ] 请求体:`{ sourceDeviceId, command: "open", parameters: { openerId, staffId } }`
|
||||
|
||||
### K8.4 编译验证
|
||||
- [ ] `npm run dev` → 无编译错误
|
||||
|
||||
> **K8 提交点**: `PhaseK8_volpro — 字典+前端操作按钮就绪`
|
||||
> **K7 提交点**: `PhaseK7_volpro — 字典+前端就绪`
|
||||
|
||||
---
|
||||
|
||||
## Phase K9: 联调验证(预计 3h)⚠️ 最后
|
||||
## Phase K8: 联调验证(预计 3h,需 KMS 环境)
|
||||
|
||||
> **前置条件**: KMS 服务端可访问,已分配 clientId/clientSecret
|
||||
### K8.1 认证
|
||||
- [ ] 网关启动 → KmsAdapter.InitializeAsync 成功
|
||||
|
||||
### K9.1 认证联调
|
||||
- [ ] 网关启动 → KmsAdapter.InitializeAsync 成功获取 Token
|
||||
- [ ] Token 过期自动刷新验证
|
||||
- [ ] 错误 clientSecret → 网关控制台打印初始化失败日志
|
||||
### K8.2 设备/告警/记录
|
||||
- [ ] /api/gateway/devices?adapter=KMS:main → 返回柜体+锁孔
|
||||
- [ ] /api/gateway/alarms/KMS:main → 返回告警列表
|
||||
- [ ] /api/gateway/control/KMS:main → 远程开门
|
||||
|
||||
### K9.2 设备同步联调(2.18.4)
|
||||
- [ ] `/api/gateway/health` 返回 KMS 适配器在线
|
||||
- [ ] `/api/gateway/devices?adapter=KMS:main` 返回柜体+锁孔设备树
|
||||
- [ ] 管理端 base_device 列表显示 KMS 设备(AdapterCode=KMS:main)
|
||||
### K9: 联调文档记录
|
||||
- [ ] 记录异常接口到 KMS_联调笔记.txt
|
||||
|
||||
### K9.3 告警同步联调(2.18.7)
|
||||
- [ ] `/api/gateway/alarms/KMS:main` 返回告警列表
|
||||
- [ ] 管理端 iot_alarm 表有记录
|
||||
|
||||
### K9.4 远程控制联调(2.4.3)
|
||||
- [ ] `/api/gateway/control/KMS:main` → 远程开门 → KMS 端锁孔门开
|
||||
|
||||
### K9.5 记录查询联调(2.18.6)
|
||||
- [ ] `/api/gateway/logs/KMS:main?logType=borrow` 返回借还记录
|
||||
|
||||
### K9.6 员工同步联调(2.18.3)
|
||||
- [ ] `/api/gateway/sync/KMS:main` → 批量同步员工成功
|
||||
|
||||
### K9.7 异常场景
|
||||
- [ ] KMS 服务离线 → `/api/gateway/health` 中 KMS 返回 unhealthy
|
||||
- [ ] KMS 恢复 → 下次心跳自动变 healthy
|
||||
- [ ] 并发请求超过 5 QPS → 限流生效不崩溃
|
||||
|
||||
### K9.8 验收
|
||||
- [ ] 网关 + Vol.Pro + KMS 三端数据一致
|
||||
- [ ] 管理端可查看 KMS 设备树、告警
|
||||
- [ ] 前端可远程开门
|
||||
|
||||
> **K9 提交点**: `PhaseK9_integration — 全链路联调通过`
|
||||
> **K8 提交点**: `PhaseK8_integration — 全链路联调通过`
|
||||
|
||||
---
|
||||
|
||||
## 任务总览
|
||||
|
||||
| Phase | 内容 | 文件数 | 预计 |
|
||||
| Phase | 内容 | 文件 | 预计 |
|
||||
|:---:|------|:---:|:---:|
|
||||
| K0 | 项目骨架 | 2 | 15min |
|
||||
| K1 | KmsModels 全部 DTO | 1 | 1h |
|
||||
| K2 | KmsAuthHelper | 1 | 30min |
|
||||
| K3 | KmsAdapter 核心方法 | 1 | 1.5h |
|
||||
| K4 | KmsAdapter 扩展方法 | 1 | 1h |
|
||||
| K5 | 配置与注册 | 3 | 15min |
|
||||
| K6 | 编译自测 | — | 15min |
|
||||
| K7 | 网关 Core + Host 扩展 | 6 | 1.5h |
|
||||
| K8 | Vol.Pro 管理端配套 | 2 | 1h |
|
||||
| K9 | 联调验证 | — | 3h |
|
||||
| **合计** | — | **17** | **~10h** |
|
||||
|
||||
---
|
||||
|
||||
> **版本**: 1.0 / 2025-05-19 / 严格按照 `KMS钥匙柜适配器详细设计文档.md` v2.1 制订
|
||||
| K1 | 全部 DTO | 1 | 1h |
|
||||
| K2 | AuthHelper | 1 | 30min |
|
||||
| K3 | 核心方法 | 1 | 1.5h |
|
||||
| K4 | 扩展方法 | 1 | 1h |
|
||||
| K5 | 配置注册 | 3 | 15min |
|
||||
| K6 | 编译 | — | 15min |
|
||||
| K7 | VolPro配套 | 2 | 1h |
|
||||
| K8 | 联调 | — | 3h |
|
||||
| **合计** | — | **11** | **~9h** |
|
||||
|
||||
174
doc/设计文档/定时任务API化整改方案_v1.0.md
Normal file
174
doc/设计文档/定时任务API化整改方案_v1.0.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 定时任务 API 化整改方案 v1.0
|
||||
|
||||
> **版本**: 1.0
|
||||
> **日期**: 2026-06-04
|
||||
> **背景**: VolPro 框架的 Quartz 机制基于 `[ApiTask]` + URL 调用,不支持 `IJob` 接口
|
||||
> **现状**: 4 个 IJob 实现(SyncDevices/HeartbeatMonitor/RealtimePoll/RuleEngineJob)需迁移为 API 端点
|
||||
|
||||
---
|
||||
|
||||
## 1. 影响范围
|
||||
|
||||
| 任务 | 当前文件 | 需改为 | 调度间隔 |
|
||||
|------|------|------|:---:|
|
||||
| 设备同步 | `SyncDevicesJob.cs` (IJob) | Controller + `[ApiTask]` | 每5分钟 |
|
||||
| 心跳监控 | `HeartbeatMonitorJob.cs` (IJob) | Controller + `[ApiTask]` | 每15秒 |
|
||||
| 实时轮询 | `RealtimePollJob.cs` (IJob) | Controller + `[ApiTask]` | 每10秒 |
|
||||
| 规则引擎 | `RuleEngineJob.cs` (IJob) | Controller + `[ApiTask]` | 每10秒 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 整改步骤
|
||||
|
||||
### 步骤 T1: 创建任务调度 Controller(预计 30min)
|
||||
|
||||
**新建文件**: `api_sqlsugar/VolPro.WebApi/Controllers/Warehouse/TaskController.cs`
|
||||
|
||||
```csharp
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using VolPro.Core.Filters;
|
||||
using Warehouse.Services;
|
||||
|
||||
namespace Warehouse.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 定时任务 API 端点。
|
||||
/// VolPro 框架通过 Sys_QuartzOptions 配置 URL+Cron 定时调用。
|
||||
/// 每个方法加 [ApiTask] 属性以允许框架匿名调用。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/task")]
|
||||
public class TaskController : Controller
|
||||
{
|
||||
/// <summary>T1: 设备同步 — 遍历在线网关触发全量设备同步</summary>
|
||||
[ApiTask]
|
||||
[HttpGet, HttpPost, Route("syncDevices")]
|
||||
public async Task<IActionResult> SyncDevices()
|
||||
{
|
||||
var sp = HttpContext.RequestServices;
|
||||
var engine = sp.GetService<SyncDevicesJob>();
|
||||
if (engine != null) await engine.Execute(null!);
|
||||
return Ok(new { time = DateTime.Now, status = "ok" });
|
||||
}
|
||||
|
||||
/// <summary>T2: 心跳监控 — 扫描超时网关标记离线</summary>
|
||||
[ApiTask]
|
||||
[HttpGet, HttpPost, Route("heartbeatMonitor")]
|
||||
public async Task<IActionResult> HeartbeatMonitor()
|
||||
{
|
||||
var sp = HttpContext.RequestServices;
|
||||
var engine = sp.GetService<HeartbeatMonitorJob>();
|
||||
if (engine != null) await engine.Execute(null!);
|
||||
return Ok(new { time = DateTime.Now, status = "ok" });
|
||||
}
|
||||
|
||||
/// <summary>T3: 实时轮询 — 拉取 MC4 IoT 实时值</summary>
|
||||
[ApiTask]
|
||||
[HttpGet, HttpPost, Route("realtimePoll")]
|
||||
public async Task<IActionResult> RealtimePoll()
|
||||
{
|
||||
var sp = HttpContext.RequestServices;
|
||||
var engine = sp.GetService<RealtimePollJob>();
|
||||
if (engine != null) await engine.Execute(null!);
|
||||
return Ok(new { time = DateTime.Now, status = "ok" });
|
||||
}
|
||||
|
||||
/// <summary>T4: 规则引擎 — 评估规则+执行动作</summary>
|
||||
[ApiTask]
|
||||
[HttpGet, HttpPost, Route("ruleEngine")]
|
||||
public async Task<IActionResult> RuleEngine()
|
||||
{
|
||||
var sp = HttpContext.RequestServices;
|
||||
var engine = sp.GetService<RuleEngineService>();
|
||||
if (engine != null) await engine.EvaluateAllAsync();
|
||||
return Ok(new { time = DateTime.Now, status = "ok" });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 T2: 注册 DI(预计 10min)
|
||||
|
||||
**编辑文件**: `api_sqlsugar/VolPro.Core/Extensions/AutofacManager/AutofacContainerModuleExtension.cs`
|
||||
|
||||
或在 Warehouse 项目的 Startup/Module 中注册:
|
||||
|
||||
```csharp
|
||||
// 在 Autofac 注册块中添加
|
||||
builder.RegisterType<SyncDevicesJob>().AsSelf().InstancePerLifetimeScope();
|
||||
builder.RegisterType<HeartbeatMonitorJob>().AsSelf().InstancePerLifetimeScope();
|
||||
builder.RegisterType<RealtimePollJob>().AsSelf().InstancePerLifetimeScope();
|
||||
builder.RegisterType<RuleEngineService>().AsSelf().InstancePerLifetimeScope();
|
||||
```
|
||||
|
||||
如果已由 VolPro 框架自动扫描 Services 目录,则跳过此步骤。
|
||||
|
||||
### 步骤 T3: 管理端配置任务(预计 15min)
|
||||
|
||||
在 Vol.Pro 管理端 → Quartz 管理 → 新建 4 个任务:
|
||||
|
||||
| TaskName | ApiUrl | Cron | Method |
|
||||
|------|------|------|:--:|
|
||||
| 设备同步 | `/api/task/syncDevices` | `0 */5 * * * ?` | POST |
|
||||
| 心跳监控 | `/api/task/heartbeatMonitor` | `0/15 * * * * ?` | POST |
|
||||
| 实时轮询 | `/api/task/realtimePoll` | `0/10 * * * * ?` | POST |
|
||||
| 规则引擎 | `/api/task/ruleEngine` | `0/10 * * * * ?` | POST |
|
||||
|
||||
### 步骤 T4: 保留或删除 IJob 文件(预计 5min)
|
||||
|
||||
**保留** IJob 实现类(`SyncDevicesJob.cs` 等)不删除——Controller 通过 DI 获取它们并调用 `Execute()`。
|
||||
|
||||
只需将 IJob 实现类用 `IServiceProvider` 获取(而非 Quartz 的 `JobDataMap`),因为 Controller 不传 `IJobExecutionContext`。修改 `Execute` 方法签名:
|
||||
|
||||
```csharp
|
||||
// 旧: 依赖 IJobExecutionContext.JobDataMap
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"];
|
||||
...
|
||||
}
|
||||
|
||||
// 新: 注入 IServiceProvider 为构造函数参数
|
||||
public class HeartbeatMonitorJob : IJob
|
||||
{
|
||||
private readonly IServiceProvider _sp;
|
||||
public HeartbeatMonitorJob(IServiceProvider sp) { _sp = sp; }
|
||||
|
||||
public async Task Execute(IJobExecutionContext? context)
|
||||
{
|
||||
var gwSvc = _sp.GetService<Igateway_nodesService>();
|
||||
var devRepo = _sp.GetService<Ibase_deviceRepository>();
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 T5: 编译验证(预计 10min)
|
||||
|
||||
- [ ] `dotnet build api_sqlsugar/VolPro.WebApi` → 0 错误
|
||||
- [ ] 确认 `[ApiTask]` 不与其他权限 Filter 冲突
|
||||
|
||||
---
|
||||
|
||||
## 3. 改动文件汇总
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|:---:|------|------|
|
||||
| T1 | `VolPro.WebApi/Controllers/Warehouse/TaskController.cs` | 新建,4 个 `[ApiTask]` 端点 |
|
||||
| T2 | DI 注册 | 可能不需改动(VolPro 自动扫描) |
|
||||
| T3 | 管理端 Sys_QuartzOptions | 新建 4 条任务记录 |
|
||||
| T4 | 4 个 IJob 实现 | 构造函数改用 IServiceProvider 注入 |
|
||||
| T5 | 全量编译 | 0 错误 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 原 IJob 文件处理方案
|
||||
|
||||
| 文件 | 处理 |
|
||||
|------|------|
|
||||
| `SyncDevicesJob.cs` | 构造函数注入 IServiceProvider,Execute 参数改为 nullable |
|
||||
| `HeartbeatMonitorJob.cs` | 同上 |
|
||||
| `RealtimePollJob.cs` | 同上 |
|
||||
| `RuleEngineJob.cs` | 删除(RuleEngineService 本身就是普通类,不继承 IJob) |
|
||||
|
||||
> `RuleEngineJob.cs` 可直接删除——`RuleEngineService` 是普通类,已被 TaskController 直接调用。
|
||||
337
doc/设计文档/网关MC4模块整改方案_v1.0.md
Normal file
337
doc/设计文档/网关MC4模块整改方案_v1.0.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# 网关 MC4 模块整改方案 v1.0
|
||||
|
||||
> **版本**: 1.0
|
||||
> **日期**: 2026-06-03
|
||||
> **基准**: `doc/设计文档/网关MC4模块检查报告20260603.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 整改总览
|
||||
|
||||
| 步骤 | 优先级 | 内容 | 文件 | 预计 |
|
||||
|:---:|:---:|------|------|:---:|
|
||||
| M1 | 🔴 P0 | Mc4AuthHelper 认证修复 | Mc4AuthHelper.cs + appsettings | 1h |
|
||||
| M2 | 🟠 P1 | 批量点位查询 | Mc4Adapter.cs | 30min |
|
||||
| M3 | 🟡 P2 | 历史告警查询 | Mc4Adapter.cs | 30min |
|
||||
| M4 | 🟡 P2 | B4-batch 路由改用 native batch | Program.cs | 15min |
|
||||
| M5 | 验证 | 编译 + 联调 | — | 30min |
|
||||
| **合计** | — | — | **4 文件** | **~3h** |
|
||||
|
||||
---
|
||||
|
||||
## 2. 步骤 M1: Mc4AuthHelper 认证修复(预计 1h)
|
||||
|
||||
### 2.1 问题
|
||||
|
||||
```csharp
|
||||
// 当前: 调 /conf/get (返回 { "encrypt": true }),误读为 Token
|
||||
var resp = await _http.PostAsync($"{_baseUrl}/api/central/auth/conf/get", null);
|
||||
var result = JsonSerializer.Deserialize<Mc4AuthResponse>(json);
|
||||
_token = result?.Token ?? ""; // Token 始终为 null
|
||||
```
|
||||
|
||||
### 2.2 MC4.0 实际认证流程
|
||||
|
||||
```
|
||||
1. POST /api/central/auth/conf/get → { "encrypt": true/false }
|
||||
2. 若 encrypt=true → 密码 MD5(原始密码)
|
||||
3. POST /api/central/auth/login {
|
||||
"account": "admin",
|
||||
"password": "md5或原始密码"
|
||||
}
|
||||
→ { "token": "xxx", "id": 0, "account": "admin", "name": "管理员" }
|
||||
```
|
||||
|
||||
### 2.3 修改后的 Mc4AuthHelper
|
||||
|
||||
```csharp
|
||||
public class Mc4AuthHelper
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly string _baseUrl;
|
||||
private readonly string _account;
|
||||
private readonly string _password;
|
||||
private string? _token;
|
||||
private DateTime _tokenExpiry = DateTime.MinValue;
|
||||
private bool? _needMd5;
|
||||
|
||||
public Mc4AuthHelper(HttpClient http, string baseUrl, string account, string password)
|
||||
{
|
||||
_http = http;
|
||||
_baseUrl = baseUrl.TrimEnd('/');
|
||||
_account = account;
|
||||
_password = password;
|
||||
}
|
||||
|
||||
public async Task<string> GetTokenAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_token) && DateTime.UtcNow < _tokenExpiry)
|
||||
return _token;
|
||||
|
||||
// 1. 获取加密配置
|
||||
if (!_needMd5.HasValue)
|
||||
{
|
||||
var confResp = await _http.PostAsync($"{_baseUrl}/api/central/auth/conf/get", null);
|
||||
if (confResp.IsSuccessStatusCode)
|
||||
{
|
||||
var confJson = await confResp.Content.ReadAsStringAsync();
|
||||
var conf = JsonSerializer.Deserialize<Mc4ConfResponse>(confJson);
|
||||
_needMd5 = conf?.Encrypt ?? false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_needMd5 = false; // 失败时假定不需要加密
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 登录获取 Token
|
||||
var pwd = _needMd5 == true ? ComputeMd5(_password) : _password;
|
||||
var loginBody = JsonSerializer.Serialize(new { account = _account, password = pwd });
|
||||
var resp = await _http.PostAsync($"{_baseUrl}/api/central/auth/login",
|
||||
new StringContent(loginBody, Encoding.UTF8, "application/json"));
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var json = await resp.Content.ReadAsStringAsync();
|
||||
var result = JsonSerializer.Deserialize<Mc4LoginResponse>(json)
|
||||
?? throw new Exception("MC4 登录失败");
|
||||
_token = result.Token ?? "";
|
||||
_tokenExpiry = DateTime.UtcNow.AddHours(7); // 保守估计 8h
|
||||
return _token;
|
||||
}
|
||||
|
||||
public async Task<HttpClient> GetAuthenticatedClientAsync()
|
||||
{
|
||||
var token = await GetTokenAsync();
|
||||
var client = new HttpClient { BaseAddress = new Uri(_baseUrl) };
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
client.DefaultRequestHeaders.Add("token", token);
|
||||
return client;
|
||||
}
|
||||
|
||||
public void Invalidate() => _token = null;
|
||||
|
||||
private static string ComputeMd5(string input) { /* MD5 实现 or use System.Security.Cryptography */ }
|
||||
|
||||
private class Mc4ConfResponse { public bool? Encrypt { get; set; } }
|
||||
private class Mc4LoginResponse { public string? Token { get; set; } public int Id { get; set; } public string? Account { get; set; } }
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 构造函数签名变更
|
||||
|
||||
```csharp
|
||||
// 旧: public Mc4AuthHelper(HttpClient http, string baseUrl)
|
||||
// 新: public Mc4AuthHelper(HttpClient http, string baseUrl, string account, string password)
|
||||
```
|
||||
|
||||
### 2.5 Mc4Adapter 构造函数变更
|
||||
|
||||
```csharp
|
||||
// 旧:
|
||||
public Mc4Adapter(string adapterCode, HttpClient http, string baseUrl)
|
||||
{
|
||||
_auth = new Mc4AuthHelper(http, baseUrl);
|
||||
}
|
||||
|
||||
// 新:
|
||||
public Mc4Adapter(string adapterCode, HttpClient http, string baseUrl,
|
||||
string account = "admin", string password = "admin")
|
||||
{
|
||||
_auth = new Mc4AuthHelper(http, baseUrl, account, password);
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6 Program.cs 注册变更
|
||||
|
||||
```csharp
|
||||
// 旧: new Mc4Adapter(code, http, m.BaseUrl)
|
||||
// 新: new Mc4Adapter(code, http, m.BaseUrl,
|
||||
// m.Username ?? "admin", m.Password ?? "admin")
|
||||
```
|
||||
|
||||
### 2.7 Mc4Config 增加字段
|
||||
|
||||
```csharp
|
||||
public class Mc4Config
|
||||
{
|
||||
public string? InstanceName { get; set; }
|
||||
public string BaseUrl { get; set; } = "";
|
||||
public string Username { get; set; } = "admin"; // 新增
|
||||
public string Password { get; set; } = "admin"; // 新增
|
||||
}
|
||||
```
|
||||
|
||||
### 2.8 appsettings.json 更新
|
||||
|
||||
```json
|
||||
"MC4": [
|
||||
{ "InstanceName": "31ku", "BaseUrl": "http://localhost:3000",
|
||||
"Username": "admin", "Password": "your_mc4_password" }
|
||||
]
|
||||
```
|
||||
|
||||
### 2.9 编译验证
|
||||
|
||||
`dotnet build gateway/IntegrationGateway.slnx` → 0 错误。
|
||||
|
||||
> **M1 提交点**: `Fix-M1: Mc4AuthHelper 认证修复 conf/get→login + account/password支持`
|
||||
|
||||
---
|
||||
|
||||
## 3. 步骤 M2: 批量点位查询(预计 30min)
|
||||
|
||||
### 3.1 文件
|
||||
|
||||
`gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs`
|
||||
|
||||
### 3.2 新增方法
|
||||
|
||||
```csharp
|
||||
/// <summary>批量获取多个设备的实时点位值(MC4.0 原生 multi/value/get)</summary>
|
||||
public async Task<Dictionary<int, List<Mc4PointValue>>> GetMultiRealtimeValuesAsync(List<int> deviceIds)
|
||||
{
|
||||
await _limiter.WaitAsync();
|
||||
var client = await _auth.GetAuthenticatedClientAsync();
|
||||
var body = JsonSerializer.Serialize(new { ids = deviceIds });
|
||||
var resp = await client.PostAsync("/api/central/point/multi/value/get",
|
||||
new StringContent(body, Encoding.UTF8, "application/json"));
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var json = await resp.Content.ReadAsStringAsync();
|
||||
var result = JsonSerializer.Deserialize<Dictionary<int, List<Mc4PointValue>>>(json)!;
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 编译验证
|
||||
|
||||
`dotnet build` → 0 错误。
|
||||
|
||||
> **M2 提交点**: `Fix-M2: MC4 批量点位查询 GetMultiRealtimeValuesAsync`
|
||||
|
||||
---
|
||||
|
||||
## 4. 步骤 M3: 历史告警查询(预计 30min)
|
||||
|
||||
### 4.1 新增 DTO
|
||||
|
||||
```csharp
|
||||
/// <summary>MC4.0 历史告警查询请求</summary>
|
||||
public class Mc4HisAlarmQuery
|
||||
{
|
||||
public string From { get; set; } = "";
|
||||
public string To { get; set; } = "";
|
||||
public int Skip { get; set; }
|
||||
public int Limit { get; set; }
|
||||
public int Sort { get; set; } = 1;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 新增方法
|
||||
|
||||
```csharp
|
||||
/// <summary>查询 MC4.0 历史告警(已恢复的告警)</summary>
|
||||
public async Task<PagedResult<StandardAlarm>> GetHisAlarmsAsync(int page, int size, DateTime from, DateTime to)
|
||||
{
|
||||
await _limiter.WaitAsync();
|
||||
var client = await _auth.GetAuthenticatedClientAsync();
|
||||
var body = JsonSerializer.Serialize(new Mc4HisAlarmQuery
|
||||
{
|
||||
From = from.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
To = to.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
Skip = (page - 1) * size,
|
||||
Limit = size,
|
||||
Sort = 1
|
||||
});
|
||||
var resp = await client.PostAsync("/api/central/his_alarm/query",
|
||||
new StringContent(body, Encoding.UTF8, "application/json"));
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var json = await resp.Content.ReadAsStringAsync();
|
||||
var result = JsonSerializer.Deserialize<Mc4AlarmQueryResult>(json)!;
|
||||
return new PagedResult<StandardAlarm>
|
||||
{
|
||||
Items = (result.List ?? new()).Select(MapAlarmItem).ToList(),
|
||||
Total = result.Total
|
||||
};
|
||||
}
|
||||
|
||||
private StandardAlarm MapAlarmItem(Mc4AlarmItem a) => new()
|
||||
{
|
||||
AlarmId = a.Id ?? "",
|
||||
AdapterCode = AdapterCode,
|
||||
Level = MapAlarmLevel(a.Level),
|
||||
Title = a.Desc ?? "",
|
||||
OccurTime = DateTime.TryParse(a.Stime, out var st) ? st : DateTime.MinValue,
|
||||
Status = MapAlarmState(a.State),
|
||||
ActualValue = a.Soption?.Value,
|
||||
ThresholdValue = a.Eoption?.Value
|
||||
};
|
||||
```
|
||||
|
||||
### 4.3 编译验证
|
||||
|
||||
`dotnet build` → 0 错误。
|
||||
|
||||
> **M3 提交点**: `Fix-M3: MC4 历史告警查询 GetHisAlarmsAsync`
|
||||
|
||||
---
|
||||
|
||||
## 5. 步骤 M4: B4-batch 路由优化(预计 15min)
|
||||
|
||||
### 5.1 修改
|
||||
|
||||
`gateway/src/IntegrationGateway.Host/Program.cs` B4-batch 路由改用 MC4 原生批量接口:
|
||||
|
||||
```csharp
|
||||
// B4-batch 改用 MC4 原生 multi/value/get
|
||||
app.MapPost("/api/gateway/realtime/{adapter}/batch", async (string adapter, BatchRealtimeRequest req) =>
|
||||
{
|
||||
var a = registry.FindByCode<IHasPoints>(adapter);
|
||||
if (a == null) return Results.NotFound();
|
||||
|
||||
if (a is Mc4Adapter mc4 && req.DeviceIds?.Count > 0)
|
||||
{
|
||||
// MC4.0 原生批量接口
|
||||
var intIds = req.DeviceIds.Select(int.Parse).ToList();
|
||||
var multi = await mc4.GetMultiRealtimeValuesAsync(intIds);
|
||||
return Results.Ok(multi);
|
||||
}
|
||||
|
||||
// 其他适配器 fallback
|
||||
var results = new Dictionary<string, List<PointValue>>();
|
||||
foreach (var id in req.DeviceIds ?? new())
|
||||
try { results[id] = await a.GetRealtimeValuesAsync(id); } catch { }
|
||||
return Results.Ok(results);
|
||||
});
|
||||
```
|
||||
|
||||
### 5.2 编译验证
|
||||
|
||||
`dotnet build` → 0 错误。
|
||||
|
||||
> **M4 提交点**: `Fix-M4: B4-batch 优化 MC4原生批量接口`
|
||||
|
||||
---
|
||||
|
||||
## 6. 步骤 M5: 编译验证 + 联调
|
||||
|
||||
- [ ] `dotnet build gateway/IntegrationGateway.slnx` → 0 错误 0 警告
|
||||
- [ ] MC4 appsettings.json 填入真实 `Username/Password`
|
||||
- [ ] 网关启动 → A1 注册 → A3 同步 MC4 设备树
|
||||
- [ ] B4-batch 调 `multi/value/get` 返回批量值
|
||||
- [ ] 告警查询 `/alarms/MC4:31ku` 有数据
|
||||
- [ ] Mc4AuthHelper Token 非空 → 登录流程正常
|
||||
|
||||
> **M5 提交点**: `Fix-M5: MC4整改全量编译验证通过`
|
||||
|
||||
---
|
||||
|
||||
## 7. 改动文件汇总
|
||||
|
||||
| 步骤 | 文件 | 改动 |
|
||||
|:---:|------|------|
|
||||
| M1 | `Mc4AuthHelper.cs` | 重写认证流程: conf/get → login |
|
||||
| M1 | `Mc4Adapter.cs` | 构造函数加 account/password |
|
||||
| M1 | `Program.cs` | Mc4Adapter 构造传 Username/Password |
|
||||
| M1 | `appsettings.json` | MC4 数组加 Username/Password |
|
||||
| M2 | `Mc4Adapter.cs` | 新增 GetMultiRealtimeValuesAsync |
|
||||
| M3 | `Mc4Adapter.cs` | 新增 GetHisAlarmsAsync + DTO |
|
||||
| M4 | `Program.cs` | B4-batch 优化 MC4 原生批量 |
|
||||
131
doc/设计文档/网关MC4模块检查报告20260603.md
Normal file
131
doc/设计文档/网关MC4模块检查报告20260603.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 网关 MC4 模块检查报告 2026-06-03
|
||||
|
||||
> **基准文档**: `doc/对接文档/MC4.0对外API.md` (31 API)
|
||||
> **检查范围**: `gateway/src/IntegrationGateway.Adapters.MC4/` (Mc4Adapter.cs, Mc4AuthHelper.cs)
|
||||
> **日期**: 2026-06-03
|
||||
|
||||
---
|
||||
|
||||
## 1. 覆盖率概览
|
||||
|
||||
MC4.0 接口文档共 **31 个 REST 端点**,当前 Mc4Adapter 覆盖了 **6 个**(19%)。
|
||||
|
||||
| 模块 | 文档端点数 | 已实现 | 缺失 |
|
||||
|------|:---:|:---:|:---:|
|
||||
| 认证 | 3 | 0 | 3 |
|
||||
| 对象树 | 1 | 1 | 0 |
|
||||
| 点位 | 3 | 2 | 1 |
|
||||
| 告警 | 14 | 3 | 11 |
|
||||
| 系统管理 | 10 | 0 | 10 |
|
||||
| **合计** | **31** | **6** | **25** |
|
||||
|
||||
---
|
||||
|
||||
## 2. 已实现接口对照
|
||||
|
||||
| MC4.0 端点 | Mc4Adapter 方法 | 能力接口 | 状态 |
|
||||
|------|------|------|:--:|
|
||||
| /api/central/object/tree | GetObjectTreeAsync | IHasOwnDeviceTree | ✅ |
|
||||
| /api/central/device/point/value/get | GetRealtimeValuesAsync | IHasPoints | ✅ |
|
||||
| /api/central/point/value/set | SetPointValueAsync | IHasPoints | ✅ |
|
||||
| /api/central/alarm/query | GetAlarmsAsync | IHasAlarms | ✅ |
|
||||
| /api/central/alarm/confirm | ConfirmAlarmAsync | IHasAlarms | ✅ |
|
||||
| /api/central/alarm/end | EndAlarmAsync | IHasAlarms | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 3. 🔴 关键问题
|
||||
|
||||
### 3.1 Mc4AuthHelper 认证逻辑错误(🔥 致命)
|
||||
|
||||
**现状**: `GetTokenAsync` 调用 `/api/central/auth/conf/get`:
|
||||
|
||||
```csharp
|
||||
var resp = await _http.PostAsync($"{_baseUrl}/api/central/auth/conf/get", null);
|
||||
var result = JsonSerializer.Deserialize<Mc4AuthResponse>(json);
|
||||
_token = result?.Token ?? "";
|
||||
```
|
||||
|
||||
**错误**: `/api/central/auth/conf/get` 是**密码加密配置查询接口**,返回 `{ "encrypt": true/false }`,**不是 Token 接口**,不包含 `token` 字段。`result?.Token` 始终为 null,`_token` 被设为空字符串。
|
||||
|
||||
**实际登录接口**: `/api/central/auth/login`:
|
||||
```json
|
||||
POST /api/central/auth/login
|
||||
{ "account": "admin", "password": "xxx" }
|
||||
→ { "token": "string", "id": 0, "account": "string", "name": "string" }
|
||||
```
|
||||
|
||||
> **注意**: MC4.0 可能对大部分 API 不强制 Token 认证(curl 示例中只有 logout 接口显式传了 header)。但当前代码逻辑错误,即便需要 Token 也无法获取。
|
||||
|
||||
**修复**: Mc4AuthHelper 改为先调 `conf/get` 确认加密方式,再用 `account/password` 调 `login` 获取真正的 token。
|
||||
|
||||
### 3.2 缺少批量点位查询(🟠 规则引擎依赖)
|
||||
|
||||
**缺失**: `/api/central/point/multi/value/get`
|
||||
|
||||
请求体 `{ "ids": [1, 2, 3] }` → 一次返回多个设备的实时值。
|
||||
|
||||
**影响**: 当前 B4-batch 接口逐设备调 `GetRealtimeValuesAsync`(单设备接口)。MC4.0 提供原生批量接口,应直接使用以提升规则引擎性能。
|
||||
|
||||
**修复**: 增加 `GetMultiRealtimeValuesAsync(List<int> deviceIds)` 方法,B4-batch 路由优先调此方法。
|
||||
|
||||
---
|
||||
|
||||
## 4. 缺失项清单
|
||||
|
||||
### 4.1 认证接口(3个)
|
||||
|
||||
| 端点 | 用途 |
|
||||
|------|------|
|
||||
| `/api/central/auth/conf/get` | 获取密码加密配置(已调但未正确使用) |
|
||||
| `/api/central/auth/login` | 登录获取 Token |
|
||||
| `/api/central/auth/logout` | 注销 |
|
||||
|
||||
### 4.2 设备点位(1个)
|
||||
|
||||
| 端点 | 用途 |
|
||||
|------|------|
|
||||
| `/api/central/device/point/get` | 查询设备的点位列表(用于发现设备有哪些测点) |
|
||||
|
||||
### 4.3 告警扩展(11个)
|
||||
|
||||
| 端点 | 用途 |
|
||||
|------|------|
|
||||
| `/api/central/alarm/custom_query_count` | 告警自定义统计数量 |
|
||||
| `/api/central/alarm/custom_query` | 告警自定义查询 |
|
||||
| `/api/central/alarm/get_by_point` | 按点位查询告警 |
|
||||
| `/api/central/alarm/get` | 获取单个告警详情 |
|
||||
| `/api/central/his_alarm/query` | 历史告警查询 |
|
||||
| `/api/central/report/alarm/convergence/query` | 告警聚合报告查询 |
|
||||
| `/api/central/alarm/type/add` | 添加告警类型 |
|
||||
| `/api/central/alarm/type/set` | 修改告警类型 |
|
||||
| `/api/central/alarm/type/del` | 删除告警类型 |
|
||||
| `/api/central/alarm/type/list` | 告警类型列表 |
|
||||
|
||||
### 4.4 系统管理(10个)
|
||||
|
||||
| 端点 | 用途 |
|
||||
|------|------|
|
||||
| `/api/central/manager/config/set` | 设置系统配置 |
|
||||
| `/api/central/manager/config/get` | 获取系统配置 |
|
||||
| `/api/central/manager/db/backup` | 数据库备份 |
|
||||
| `/api/central/manager/db/restore` | 数据库恢复 |
|
||||
| `/api/central/manager/db/log` | 数据库日志 |
|
||||
| `/api/central/manager/hisdb/backup` | 历史库备份 |
|
||||
| `/api/central/manager/hisdb/restore` | 历史库恢复 |
|
||||
| `/api/central/manager/hisdb/clear` | 清除历史数据 |
|
||||
| `/api/central/manager/picture/clear` | 清除图片 |
|
||||
| `/api/central/manager/video/clear` | 清除视频 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 优先级建议
|
||||
|
||||
| 优先级 | 项目 | 说明 |
|
||||
|:---:|------|------|
|
||||
| 🔴 P0 | Mc4AuthHelper 认证修复 | 当前 Token 获取逻辑根本错误 |
|
||||
| 🟠 P1 | 批量点位查询 (multi/value/get) | 规则引擎 B4-batch 缺少原生高效接口 |
|
||||
| 🟡 P2 | 历史告警查询 | 管理端需要查看已结束的告警 |
|
||||
| 🟡 P2 | 设备点位发现 (device/point/get) | IoT 设备入网时自动发现测点 |
|
||||
| ⚪ P3 | 告警类型 CRUD | 运维操作 |
|
||||
| ⚪ P3 | 系统管理接口 | 运维操作 |
|
||||
97
doc/设计文档/网关自动注册机制整改_任务清单.md
Normal file
97
doc/设计文档/网关自动注册机制整改_任务清单.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 网关自动注册机制整改 — 任务清单
|
||||
|
||||
> **版本**: 1.0
|
||||
> **日期**: 2026-06-03
|
||||
> **基准**: `doc/设计文档/网关自动注册机制整改方案_v1.0.md` + `doc/设计文档/网关自动注册机制检查报告20260603.md`
|
||||
> **原则**: 分阶段分步骤执行,每步骤完成编译复查后提交,不合并主分支
|
||||
|
||||
---
|
||||
|
||||
## 阶段 G1: Gateway 端修复(3 步骤)
|
||||
|
||||
### 步骤 G1.1 — 修复 BaseUrl 硬编码
|
||||
|
||||
- [ ] 编辑 `gateway/src/IntegrationGateway.Host/Program.cs`
|
||||
- [ ] 将 `BaseUrl = $"http://localhost:..."` 改为读取 `gwCfg["SelfUrl"]`,不填降级 localhost
|
||||
- [ ] 编辑 `gateway/src/IntegrationGateway.Host/appsettings.json`,Gateway 段新增 `"SelfUrl": null`
|
||||
- [ ] `dotnet build gateway/IntegrationGateway.slnx` → 0 错误
|
||||
- [ ] 复查:`BaseUrl` 不再硬编码 localhost,可从配置注入真实 IP
|
||||
|
||||
> **G1.1 提交点**: `Fix-G1.1: Gateway A1 BaseUrl 改为读取 SelfUrl 配置`
|
||||
|
||||
### 步骤 G1.2 — A1 注册后追加 A3 设备同步
|
||||
|
||||
- [ ] 编辑 `gateway/src/IntegrationGateway.Host/Program.cs`
|
||||
- [ ] A1 注册成功后:遍历 `registry.All`,`IHasFlatDevices` → `GetDevicesAsync`,`IHasOwnDeviceTree` → `GetObjectTreeAsync` + 展平
|
||||
- [ ] 新增 `FlattenTree` 辅助函数(MC4 对象树展平)
|
||||
- [ ] 调 `clientFactory.SyncDevicesAsync(nodeCode, nodeToken, allDevices)`
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
- [ ] 复查:A1 成功后立即执行 A3,注册完成时 Vol.Pro 已有设备数据
|
||||
|
||||
> **G1.2 提交点**: `Fix-G1.2: A1注册后立即A3同步全部适配器设备列表`
|
||||
|
||||
### 步骤 G1.3 — 启动 A2 心跳 + 自动重注册
|
||||
|
||||
- [ ] 编辑 `gateway/src/IntegrationGateway.Host/Program.cs`
|
||||
- [ ] A1/A3 完成后启动 `Task.Run` 心跳循环
|
||||
- [ ] `PeriodicTimer` 每 15s → `clientFactory.HeartbeatAsync`
|
||||
- [ ] 连续失败 ≥ 3 次 → 触发 A1+A3 重注册
|
||||
- [ ] 新增 `SyncAllDevicesAsync` 辅助函数(复用 A3 逻辑)
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
- [ ] 复查:心跳成功重置 `failCount`,失败累积到 3 次自动恢复
|
||||
|
||||
> **G1.3 提交点**: `Fix-G1.3: A2心跳+自动重注册(连续3次失败触发A1+A3)`
|
||||
|
||||
---
|
||||
|
||||
## 阶段 G2: Vol.Pro 端修复(2 步骤)
|
||||
|
||||
### 步骤 G2.1 — RegisterNodeAsync 语法规范化
|
||||
|
||||
- [ ] 编辑 `api_sqlsugar/Warehouse/Services/device_manager/Partial/gateway_nodesService.cs`
|
||||
- [ ] `RegisterNodeAsync`:`DbContext.Queryable.First()` → `FindAsIQueryable.FirstOrDefaultAsync()`
|
||||
- [ ] `UpdateHeartbeatAsync`:同样替换
|
||||
- [ ] `dotnet build api_sqlsugar/Warehouse` → 0 错误
|
||||
- [ ] 复查:两个方法使用统一 Vol.Pro 查询语法,`.First()` 不抛异常
|
||||
|
||||
> **G2.1 提交点**: `Fix-G2.1: gateway_nodesService 统一 FindAsIQueryable 语法`
|
||||
|
||||
### 步骤 G2.2 — 标记 UpsertDeviceAsync 为废弃
|
||||
|
||||
- [ ] 编辑 `api_sqlsugar/Warehouse/Services/device_manager/Partial/base_deviceService.cs`
|
||||
- [ ] `UpsertDeviceAsync` 加 `[Obsolete]` 标记 + 注释说明
|
||||
- [ ] 检查接口文件 `Ibase_deviceService` 是否暴露此方法,同步标记
|
||||
- [ ] `dotnet build` → 0 错误(允许 [Obsolete] 警告)
|
||||
- [ ] 复查:重复逻辑已标记,新代码不会误用
|
||||
|
||||
> **G2.2 提交点**: `Fix-G2.2: base_deviceService.UpsertDeviceAsync 标记 [Obsolete]`
|
||||
|
||||
---
|
||||
|
||||
## 阶段 G3: 全量验证(1 步骤)
|
||||
|
||||
### 步骤 G3.1 — 全量编译 + 联调场景验证
|
||||
|
||||
- [ ] `dotnet build gateway/IntegrationGateway.slnx` → 0 错误
|
||||
- [ ] `dotnet build api_sqlsugar/VolPro.WebApi` → 0 错误
|
||||
- [ ] 网关启动 → 控制台输出 A1 注册 → A3 同步 N 台 → A2 心跳启动
|
||||
- [ ] `gateway_nodes` 表有记录,`LastHeartbeat` 持续更新
|
||||
- [ ] `base_device` 表有对应设备
|
||||
- [ ] 网关先启动(Vol.Pro 未启动)→ 45 秒后自动恢复
|
||||
- [ ] 复查:全链路 A1→A3→A2 闭环正常
|
||||
|
||||
> **G3.1 提交点**: `Fix-G3: 全量编译验证通过 注册机制闭环完成`
|
||||
|
||||
---
|
||||
|
||||
## 任务总览
|
||||
|
||||
| 阶段 | 步骤 | 文件 | 预计 |
|
||||
|:---:|:---:|------|:---:|
|
||||
| G1 | G1.1 BaseUrl 修复 | Program.cs + appsettings.json | 10min |
|
||||
| G1 | G1.2 A3 设备同步 | Program.cs | 30min |
|
||||
| G1 | G1.3 心跳+重注册 | Program.cs | 20min |
|
||||
| G2 | G2.1 语法规范化 | gateway_nodesService.cs | 5min |
|
||||
| G2 | G2.2 标记废弃方法 | base_deviceService.cs | 10min |
|
||||
| G3 | G3.1 全量验证 | 全项目 | 15min |
|
||||
| **合计** | **6 步骤** | **5 文件** | **~1.5h** |
|
||||
148
doc/设计文档/网关自动注册机制检查报告20260603.md
Normal file
148
doc/设计文档/网关自动注册机制检查报告20260603.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# 网关 ↔ Vol.Pro 自动注册机制检查报告 2026-06-03
|
||||
|
||||
> **日期**: 2026-06-03
|
||||
> **检查范围**: `gateway/src/IntegrationGateway.Host/Program.cs` A1 注册段 + `VolPro gateway_nodesController.cs` A1-A4 + 相关 Service
|
||||
> **方法**: 逐行比对网关发送体 ↔ Vol.Pro 接收体 ↔ 数据库 Schema
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据流追踪
|
||||
|
||||
```
|
||||
网关 Program.cs (line 82-105)
|
||||
├─ InitializeAllAsync() ← 适配器初始化
|
||||
├─ RegisterAsync(registerReq) ← A1: POST /api/gateway/register
|
||||
└─ (无后续调用) ← A2/A3 未触发
|
||||
|
||||
Vol.Pro gateway_nodesController
|
||||
├─ [POST] /api/gateway/register ← RegisterGateway → RegisterNodeAsync
|
||||
├─ [POST] /api/gateway/heartbeat ← GatewayHeartbeat → UpdateHeartbeatAsync
|
||||
├─ [POST] /api/gateway/sync/devices ← SyncDevices → SyncDevicesAsync
|
||||
└─ [POST] /api/gateway/sync/alarms ← SyncAlarms → UpsertAlarmAsync
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 发现的问题
|
||||
|
||||
### 2.1 🔴 网关不调用 A3 设备同步 — 注册后设备列表为空
|
||||
|
||||
**现状**: 网关 Program.cs 在 A1 注册后**不调用** `clientFactory.SyncDevicesAsync()`。Vol.Pro 的 `RegisterGateway` 返回设备列表时调用 `GetDevicesByGatewayNodeAsync(node.NodeId)`,查询 `WHERE NodeId=xxx AND ParentDeviceId IS NULL`。
|
||||
|
||||
**后果**: 首次注册返回的设备列表永远为空(数据库尚无此网关的设备记录)。设备必须等 Vol.Pro 的 Quartz `SyncDevicesJob`(每 5 分钟触发一次)才有机会同步。
|
||||
|
||||
**修复**: A1 注册后立即遍历适配器同步设备,整体流程改为:
|
||||
|
||||
```csharp
|
||||
// A1 注册
|
||||
var registerResult = await clientFactory.RegisterAsync(registerReq);
|
||||
// A3 同步设备(Owl → GetDevicesAsync, MC4 → GetObjectTreeAsync)
|
||||
foreach (var adapter in registry.All)
|
||||
{
|
||||
var devices = adapter switch
|
||||
{
|
||||
IHasFlatDevices f => (await f.GetDevicesAsync(1, 1000)).Items,
|
||||
IHasOwnDeviceTree t => FlattenTree(await t.GetObjectTreeAsync()),
|
||||
_ => new()
|
||||
};
|
||||
if (devices.Any())
|
||||
await clientFactory.SyncDevicesAsync(nodeCode, nodeToken, devices.Select(d => new { ... }).ToList());
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 🔴 网关 A1 BaseUrl 硬编码 `localhost`
|
||||
|
||||
**现状**:
|
||||
```csharp
|
||||
BaseUrl = $"http://localhost:{app.Urls.FirstOrDefault()?.Split(':').LastOrDefault() ?? "5100"}"
|
||||
```
|
||||
|
||||
**后果**: 网关部署在 `192.168.1.100` 时,Vol.Pro 存的 BaseUrl 仍是 `http://localhost:5100`。Vol.Pro 端的 GatewayClient 和 Quartz Job 用此地址回调网关时全部失败。
|
||||
|
||||
**修复**: 改为读取配置或使用 `app.Configuration["Gateway:SelfUrl"]`,不填时降级为 `localhost`:
|
||||
|
||||
```csharp
|
||||
BaseUrl = gwCfg["SelfUrl"] ?? $"http://localhost:{port}"
|
||||
```
|
||||
|
||||
### 2.3 🟠 网关不调用 A2 心跳 — 无持续在线状态更新
|
||||
|
||||
**现状**: 网关只在 A1 注册时上报一次在线状态,之后不再调 A2 心跳。
|
||||
|
||||
**后果**: Vol.Pro 的 `gateway_nodes.LastHeartbeat` 停留在注册时刻,`HeartbeatMonitorJob`(每 15s)会在注册后 30s 将网关标记离线。
|
||||
|
||||
**修复**: 注册完成后启动后台心跳循环:
|
||||
|
||||
```csharp
|
||||
_ = Task.Run(async () => {
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(15));
|
||||
try { await clientFactory.HeartbeatAsync(new GatewayHeartbeatRequest { NodeCode = nodeCode, Token = nodeToken }); }
|
||||
catch { Console.Error.WriteLine("[Gateway] 心跳失败"); }
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 2.4 🟠 Vol.Pro — RegisterNodeAsync 使用旧 Queryable 语法
|
||||
|
||||
**现状**:
|
||||
```csharp
|
||||
var existing = _repository.DbContext.Queryable<gateway_nodes>()
|
||||
.First(x => x.NodeCode == nodeCode);
|
||||
```
|
||||
|
||||
`DbContext.Queryable` 是 SqlSugar 原始方式,项目中其他 Service 使用 `FindAsIQueryable`(Vol.Pro 封装)。
|
||||
|
||||
**后果**: 不影响功能但风格不一致。且 `.First()` 可能抛异常(找不到记录时),而 `.FirstOrDefault()` + null 检查更安全。
|
||||
|
||||
**修复**: 改为 `FindAsIQueryable(x => x.NodeCode == nodeCode).FirstOrDefaultAsync()`。
|
||||
|
||||
### 2.5 🟡 A1 返回结果未被网关使用
|
||||
|
||||
**现状**:
|
||||
```csharp
|
||||
var registerResult = await clientFactory.RegisterAsync(registerReq);
|
||||
// registerResult 未使用
|
||||
```
|
||||
|
||||
`RegisterGateway` 返回 `{ nodeId, devices: [...] }`,网关不读取也不使用。
|
||||
|
||||
**后果**: 网关不知道自己的 NodeId,后续 A2/A3 需要 nodeCode + token 而非 nodeId。GatewayClientFactory 的 A2/A3 方法也用的是 nodeCode + token,所以不依赖 nodeId。
|
||||
|
||||
**评估**: **不需要修复** — 当前设计合理(nodeCode 是天然业务主键)。
|
||||
|
||||
### 2.6 🟡 base_deviceService 与 gateway_nodesService 重复实现设备同步
|
||||
|
||||
**现状**:
|
||||
- `gateway_nodesService.SyncDevicesAsync` — 完整的设备同步(新增+更新)
|
||||
- `base_deviceService.UpsertDeviceAsync` — 单设备 Upsert(被 Controller 调用但实际未被使用)
|
||||
|
||||
`gateway_nodesController.SyncDevices` 调的是 `gateway_nodesService.SyncDevicesAsync` 而非 `base_deviceService.UpsertDeviceAsync`。
|
||||
|
||||
**后果**: `base_deviceService.UpsertDeviceAsync` 是死代码。
|
||||
|
||||
**修复**: 保留 `gateway_nodesService.SyncDevicesAsync`(批量处理更高效),移除或标记 `base_deviceService.UpsertDeviceAsync` 为 `[Obsolete]`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 调用链完整性矩阵
|
||||
|
||||
| 接口 | Vol.Pro 端 | 网关调用 | 状态 |
|
||||
|------|:---:|:---:|:--:|
|
||||
| A1 /api/gateway/register | ✅ RegisterGateway | ✅ 已调用 | ⚠️ BaseUrl=localhost / 不返设备 |
|
||||
| A2 /api/gateway/heartbeat | ✅ GatewayHeartbeat | ❌ 未调用 | 🔴 30秒后被标记离线 |
|
||||
| A3 /api/gateway/sync/devices | ✅ SyncDevices | ❌ 未调用 | 🔴 首次注册设备列表为空 |
|
||||
| A4 /api/gateway/sync/alarms | ✅ SyncAlarms | ❌ 未调用 | ⚪ 告警由 Vol.Pro Quartz 拉取 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 修复优先级
|
||||
|
||||
| 编号 | 问题 | 严重度 | 预计 |
|
||||
|:---:|------|:---:|:---:|
|
||||
| 1 | A3 设备同步未触发 | 🔴 | 30min |
|
||||
| 2 | A1 BaseUrl=localhost | 🔴 | 10min |
|
||||
| 3 | A2 心跳未循环 | 🟠 | 15min |
|
||||
| 4 | RegisterNodeAsync 语法不一致 | 🟠 | 5min |
|
||||
| 5 | 重复的设备 Upsert 逻辑 | 🟡 | 10min |
|
||||
133
doc/设计文档/网关项目代码审查报告20260604.md
Normal file
133
doc/设计文档/网关项目代码审查报告20260604.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# 网关项目代码审查报告 2026-06-04
|
||||
|
||||
> **范围**: `gateway/src/` 全部 5 个项目 239 文件
|
||||
> **重点**: 空函数、未实现内容、TODO、硬编码、异常处理
|
||||
|
||||
---
|
||||
|
||||
## 一、空实现/存根函数(5 处)
|
||||
|
||||
### 1.1 OwlAdapter — ConfirmAlarmAsync / EndAlarmAsync
|
||||
|
||||
**文件**: `OwlAdapter.cs` L250-251
|
||||
```csharp
|
||||
public Task ConfirmAlarmAsync(string alarmId) => Task.CompletedTask;
|
||||
public Task EndAlarmAsync(string alarmId) => Task.CompletedTask;
|
||||
```
|
||||
|
||||
**说明**: Owl AI 事件(基于 `/events` 接口)不支持确认/结束操作,合理留空。
|
||||
**风险**: 低。调用方(VolPro/A4)调用后状态不会写回 Owl。
|
||||
|
||||
### 1.2 KmsAdapter — EndAlarmAsync
|
||||
|
||||
**文件**: `KmsAdapter.cs` L165-170
|
||||
```csharp
|
||||
public Task EndAlarmAsync(string alarmId) { return Task.CompletedTask; }
|
||||
```
|
||||
|
||||
**说明**: KMS 第三方接口 (2.18.7) 不提供告警结束 API,合理留空。
|
||||
**风险**: 低。
|
||||
|
||||
### 1.3 KmsAdapter — GetBorrowRecordsAsync / GetPermissionListAsync 请求体
|
||||
|
||||
**文件**: `KmsAdapter.cs` L181, L194
|
||||
```csharp
|
||||
var body = "{}"; // 联调时加入时间范围参数
|
||||
```
|
||||
|
||||
**说明**: 联调待办项,当前传空 JSON。KMS 接口可能接受无参查询返回全部数据。
|
||||
**风险**: 中。如果 KMS 要求时间范围参数,当前实现会失败。
|
||||
|
||||
### 1.4 KmsAdapter — SendControlAsync 只实现了 "open"/"authorize"
|
||||
|
||||
**文件**: `KmsAdapter.cs` L251
|
||||
```csharp
|
||||
if (command == "open" || command == "authorize") { ... }
|
||||
// 其他 command 返回 success=true 但无实际操作
|
||||
```
|
||||
|
||||
**说明**: 非开门的控制指令会静默返回成功但不执行任何操作。
|
||||
**风险**: 中。调用方以为成功但设备未变化。
|
||||
|
||||
---
|
||||
|
||||
## 二、静默异常吞噬(4 处)
|
||||
|
||||
### 2.1 SyncAllDevicesAsync — 适配器遍历 catch
|
||||
|
||||
**文件**: `Program.cs` L171
|
||||
```csharp
|
||||
catch { }
|
||||
```
|
||||
适配器取设备列表失败时静默跳过,不影响其他适配器。合理但缺少日志。
|
||||
|
||||
### 2.2 B1 健康检查
|
||||
|
||||
**文件**: `Program.cs` L197
|
||||
```csharp
|
||||
try { healthy = await a.HealthCheckAsync(); } catch { }
|
||||
```
|
||||
合理——健康检查本身不应抛异常。
|
||||
|
||||
### 2.3 B4-batch Fallback
|
||||
|
||||
**文件**: `Program.cs` L279
|
||||
```csharp
|
||||
try { results[deviceId] = await a.GetRealtimeValuesAsync(deviceId); } catch { }
|
||||
```
|
||||
合理——逐设备查询时某设备失败不应阻塞。
|
||||
|
||||
### 2.4 RateLimiter.Release
|
||||
|
||||
**文件**: `RateLimiter.cs` L36
|
||||
```csharp
|
||||
try { _semaphore.Release(); } catch { }
|
||||
```
|
||||
合理——SemaphoreSlim.Release 在超过最大计数时会抛异常。
|
||||
|
||||
---
|
||||
|
||||
## 三、联调待验证项(3 处)
|
||||
|
||||
### 3.1 GatewayClientFactory — A2/A3 方法从未被网关自身调用
|
||||
|
||||
**文件**: `GatewayClientFactory.cs` L38-62
|
||||
```csharp
|
||||
public async Task<bool> HeartbeatAsync(...) { ... }
|
||||
public async Task<JsonDocument?> SyncDevicesAsync(...) { ... }
|
||||
```
|
||||
|
||||
**说明**: 这两个方法在 Program.cs 的 `SyncAllDevicesAsync` 中通过 `clientFactory.SyncDevicesAsync` 被调用了。A2 心跳在心跳循环中被调用。
|
||||
**状态**: ✅ 已连接。
|
||||
|
||||
### 3.2 Owl Playback URL 硬编码路径
|
||||
|
||||
**文件**: `OwlAdapter.cs` (GetPlaybackUrlAsync)
|
||||
```csharp
|
||||
Hls = $"{baseUrl}/recordings/channels/{channelId}/index.m3u8?..."
|
||||
```
|
||||
|
||||
联调时需确认 Owl 实际录像 HLS 路径是否为此格式。
|
||||
|
||||
### 3.3 KMS API 响应格式
|
||||
|
||||
KMS 所有接口的响应格式需联调验证。文档中字段名可能与实际 API 有差异。
|
||||
|
||||
---
|
||||
|
||||
## 四、编译状态
|
||||
|
||||
网关 5 项目上次编译 **0 错误 0 警告**。当前改动为本次审查附加,需重新编译验证。
|
||||
|
||||
---
|
||||
|
||||
## 五、结论
|
||||
|
||||
| 类别 | 数量 | 严重度 |
|
||||
|------|:---:|------|
|
||||
| 合理空实现(设计如此) | 3 | 低 |
|
||||
| 联调待验证参数 | 2 | 中 |
|
||||
| 静默异常(合理设计) | 4 | 低 |
|
||||
| **需要立即修复** | **0** | — |
|
||||
|
||||
**没有发现需要立即修复的空函数或未实现方法。** 所有 `Task.CompletedTask` 都是因为底层子系统不支持该操作(Owl AI 无确认、KMS 无结束告警),属于设计取舍。KMS 的联调待办项(时间范围参数)已在代码中注释标注。
|
||||
175
doc/设计文档/规则引擎实施计划_任务清单.md
Normal file
175
doc/设计文档/规则引擎实施计划_任务清单.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# 规则引擎实施计划 — 任务清单
|
||||
|
||||
> **版本**: 1.0
|
||||
> **日期**: 2026-06-04
|
||||
> **基准**: `doc/设计文档/规则引擎实现方案_v1.0.md`
|
||||
> **分支**: gateway-dev
|
||||
> **原则**: 分阶段分步骤执行,每步骤编译复查后提交,不合并主分支
|
||||
|
||||
---
|
||||
|
||||
## 当前状态
|
||||
|
||||
| 前置条件 | 状态 |
|
||||
|------|:--:|
|
||||
| RealtimePollJob(10s 采集 MC4 IoT 值) | ✅ 已实现 |
|
||||
| warehouse_rule/condition/action 表 + 管理端 CRUD | ✅ 已实现 |
|
||||
| warehouse_rulelog 表 | ❌ 待建 |
|
||||
| 网关 B4-batch 批量接口 | ✅ 已实现 (P1-1) |
|
||||
| 网关 B5(设备控制) | ✅ 已实现 |
|
||||
| 网关 B10(远程控制) | ✅ 已实现 |
|
||||
| VolPro GatewayClient(调网关) | ✅ 已实现 |
|
||||
| warehouse_variable 表 SQL | ✅ 已写入 db_init.sql(待执行) |
|
||||
| RuleEngineService / RuleEngineJob | ❌ 待实现 |
|
||||
|
||||
---
|
||||
|
||||
## 阶段 R1: 数据库准备(预计 30min)
|
||||
|
||||
### 步骤 R1.1 — 新增 warehouse_rulelog 表
|
||||
|
||||
- [ ] 在数据库执行:
|
||||
```sql
|
||||
CREATE TABLE warehouse_rulelog (
|
||||
LogID INT IDENTITY PRIMARY KEY,
|
||||
RuleID INT NOT NULL,
|
||||
ConditionMet NVARCHAR(50),
|
||||
ActionResult NVARCHAR(MAX),
|
||||
EvaluatedAt DATETIME DEFAULT GETDATE(),
|
||||
Detail NVARCHAR(MAX) NULL
|
||||
);
|
||||
```
|
||||
- [ ] 在数据库执行 ALTER TABLE 添加字段(若未执行):
|
||||
- `warehouse_rule` 加 `Enable`, `Priority`, `LastEvaluated`, `LastTriggered`, `CooldownSec`
|
||||
- `warehouse_ruleaction` 加 `ActionType`, `ExtraJson`
|
||||
- `warehouse_rulecondition` 加 `RecoveryThreshold_Numeric`, `RecoveryThreshold_Switch`, `LastTriggered`, `LastTriggerValue`
|
||||
- [ ] 在 Vol.Pro 代码生成器中选择 `warehouse_rulelog` 生成全套 CRUD
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
|
||||
### 步骤 R1.2 — 执行 warehouse_variable 表建表
|
||||
|
||||
- [ ] 在数据库执行 `doc/db_init.sql` 中 warehouse_variable 建表语句
|
||||
- [ ] Vol.Pro 代码生成器生成 `warehouse_variable` CRUD
|
||||
- [ ] 管理端字典补充(§5 字典项)
|
||||
|
||||
> **R1 提交点**: `RuleEngine-R1: 数据库表+字典就绪`
|
||||
|
||||
---
|
||||
|
||||
## 阶段 R2: RuleEngineService 实现(预计 3h)
|
||||
|
||||
### 步骤 R2.1 — 创建 RuleEngineService.cs
|
||||
|
||||
- [ ] 创建 `api_sqlsugar/Warehouse/Services/RuleEngineService.cs`
|
||||
- [ ] 注入 `Iwarehouse_ruleRepository`, `Ibase_deviceRepository`, `Iiot_devicedataRepository`, `Iiot_alarmRepository`, `GatewayClient`, `IHubContext<HomePageMessageHub>`
|
||||
- [ ] 实现 `EvaluateAllAsync()` — 主流程:
|
||||
1. `LoadEnabledRulesAsync()` — 从 DB 加载启用规则 + 条件 + 动作
|
||||
2. `BuildDeviceMappingAsync()` — DeviceId → (AdapterCode, SourceId, BaseUrl)
|
||||
3. `BatchFetchRealtimeAsync()` — 调网关 B4-batch 批量取实时值
|
||||
4. `EvaluateRuleAsync(rule, data)` — 逐规则比对
|
||||
5. `ExecuteActionsAsync(rule)` — 触发动作
|
||||
|
||||
### 步骤 R2.2 — 实现条件评估
|
||||
|
||||
- [ ] `EvaluateConditionAsync(cond, realtimeData)`:
|
||||
- 从 realtimeData 中找到对应设备+点位的实际值
|
||||
- 按 CompareOperator 比对(大于/小于/等于/大于等于/小于等于/不等于)
|
||||
- 支持滞后窗(P2-2):已触发过则用 RecoveryThreshold 判断恢复
|
||||
- 支持条件级冷却(P2-3):未过冷却期则跳过
|
||||
|
||||
### 步骤 R2.3 — 实现动作执行
|
||||
|
||||
- [ ] `ExecuteActionsAsync(rule)`:
|
||||
- 动作类型 "控制" → `GatewayClient.ControlDeviceAsync`(调网关 B5)
|
||||
- 动作类型 "告警" → 写入 `iot_alarm` 表
|
||||
- 动作类型 "通知" → SignalR `_hub.SendAsync("RuleTriggered", ...)`
|
||||
- 冷却检查:未过 `CooldownSec` 不重复执行
|
||||
- 并发执行:`Task.WhenAll` + 5s 超时(P3-1)
|
||||
|
||||
### 步骤 R2.4 — 编译验证
|
||||
|
||||
- [ ] `dotnet build api_sqlsugar/Warehouse` → 0 错误
|
||||
|
||||
> **R2 提交点**: `RuleEngine-R2: RuleEngineService 完整实现`
|
||||
|
||||
---
|
||||
|
||||
## 阶段 R3: RuleEngineJob + 调度(预计 30min)
|
||||
|
||||
### 步骤 R3.1 — 创建 RuleEngineJob.cs
|
||||
|
||||
- [ ] 创建 `api_sqlsugar/Warehouse/Services/RuleEngineJob.cs`
|
||||
- [ ] 实现 `IJob` 接口,`Execute` 中获取 `RuleEngineService` 调 `EvaluateAllAsync()`
|
||||
|
||||
### 步骤 R3.2 — 注册 Quartz
|
||||
|
||||
- [ ] 管理端 → Quartz 管理 → 新建 Job:
|
||||
```
|
||||
JobName: RuleEngineJob
|
||||
Cron: 0/10 * * * * ?
|
||||
ClassName: Warehouse.Services.RuleEngineJob
|
||||
```
|
||||
- [ ] `dotnet build` → 0 错误
|
||||
|
||||
> **R3 提交点**: `RuleEngine-R3: RuleEngineJob 就绪`
|
||||
|
||||
---
|
||||
|
||||
## 阶段 R4: 前端配套(预计 1h)
|
||||
|
||||
### 步骤 R4.1 — 规则管理页增强
|
||||
|
||||
- [ ] 编辑 `web.vite/src/views/warehouse/warehouse_rule/warehouse_rule/options.js`
|
||||
- [ ] 条件表格中 "设备" 列绑定 `allDevices` 动态字典
|
||||
- [ ] "变量" 列绑定 `warehouse_variable` 字典
|
||||
- [ ] 动作表格加"动作类型"下拉(控制/告警/通知)
|
||||
|
||||
### 步骤 R4.2 — 大屏告警接收
|
||||
|
||||
- [ ] 编辑 `warehouse/src/view/DataView.vue`
|
||||
- [ ] SignalR 订阅 `RuleTriggered` 事件:
|
||||
```javascript
|
||||
connection.on("RuleTriggered", (data) => {
|
||||
ElMessage.warning(`[规则触发] ${data.title}: ${data.alertMessage}`);
|
||||
});
|
||||
```
|
||||
|
||||
> **R4 提交点**: `RuleEngine-R4: 前端配套完成`
|
||||
|
||||
---
|
||||
|
||||
## 阶段 R5: 联调验证(预计 2h)
|
||||
|
||||
### 步骤 R5.1 — 联调
|
||||
|
||||
- [ ] 网关启动 → MC4 在线 → RealtimePollJob 有数据
|
||||
- [ ] 管理端新建规则:"温度 > 28℃ → 告警"
|
||||
- [ ] 等 10s → iot_alarm 表有告警记录
|
||||
- [ ] 管理端收到 SignalR 推送
|
||||
- [ ] 管理端新建规则:"温度 > 28℃ → 控制空调"
|
||||
- [ ] 等 10s → 网关 B5 被调用
|
||||
|
||||
> **R5 提交点**: `RuleEngine-R5: 联调通过`
|
||||
|
||||
---
|
||||
|
||||
## 任务总览
|
||||
|
||||
| 阶段 | 步骤 | 内容 | 预计 |
|
||||
|:---:|:---:|------|:---:|
|
||||
| R1 | R1.1 | 建表 + 代码生成 | 20min |
|
||||
| R1 | R1.2 | 变量表 + 字典 | 10min |
|
||||
| R2 | R2.1 | RuleEngineService 主流程 | 1.5h |
|
||||
| R2 | R2.2 | 条件评估 + 滞后窗 + 冷却 | 45min |
|
||||
| R2 | R2.3 | 动作执行(控制/告警/通知) | 30min |
|
||||
| R2 | R2.4 | 编译验证 | 15min |
|
||||
| R3 | R3.1 | RuleEngineJob | 15min |
|
||||
| R3 | R3.2 | Quartz 注册 | 15min |
|
||||
| R4 | R4.1 | 管理端 UI 增强 | 30min |
|
||||
| R4 | R4.2 | 大屏告警接收 | 30min |
|
||||
| R5 | R5.1 | 联调 | 2h |
|
||||
| **合计** | **11 步骤** | — | **~7h** |
|
||||
|
||||
---
|
||||
|
||||
> **注**: 原方案 R4(网关 B4-batch)已在 P1-1 修复中完成,R2 的 DeviceId 映射已在方案中设计,此次直接实现。`warehouse_variable` 建表 SQL 已在 `doc/db_init.sql` 中就绪,本次仅需执行。
|
||||
Reference in New Issue
Block a user