新增MES模块,包含供应商、客户、车辆和地磅数据记录管理功能,支持免密接口和数据同步。更新相关控制器、实体、服务和数据库配置,优化权限管理和数据字典支持,确保系统的灵活性和可扩展性。

This commit is contained in:
geht
2026-04-30 15:28:20 +08:00
parent 142a0bdaba
commit b03cbeff9b
121 changed files with 10540 additions and 424 deletions

View File

@@ -0,0 +1,186 @@
# 桌面端与后端实时交互、断线续连功能整理yy-admin ↔ jeecg-boot
## 1. 范围与目标
本文整理当前项目中“桌面端yy-admin与后端jeecg-boot”的实时交互链路重点覆盖
- 实时消息通道STOMP/WebSocket
- 用户变更同步(后端→桌面)
- 本地变更回传(桌面→后端)
- 断线检测、自动重连、离线补偿Outbox
---
## 2. 总体架构(当前实现)
### 2.1 后端 → 桌面实时触发REST 拉取落地)
1) jeecg 用户变更时,后端广播到 STOMP 主题 `/topic/sync/jeecg-users`
2) 桌面端统一 STOMP 客户端订阅该主题,收到消息后发布本地事件 `RemoteCommandReceivedEvent`
3) `JeecgUserSyncCoordinator` 识别 `SCADA_USER_CHANGED/SCADA_USERS_CHANGED` 后,不直接写库,而是“入 Outbox”。
4) Outbox 消费器执行 `TryBackgroundSyncJeecgUsersAsync`,通过 SCADA 用户列表接口拉取并写入本地镜像表 `jeecg_sys_user`
> 这条链路是“消息触发 + REST 拉全量/增量”的组合,避免只靠实时包体做复杂幂等。
### 2.2 桌面 → 后端(本地变更回传)
1) 桌面用户 CRUD`SysUserService`)成功后调用 `IUserSyncOutbox` 入队。
2) Outbox 在线时实时发送、离线时持久化。
3) 回传通过 `/sys/sync/batch` 批量提交,后端按 `aggregateType/eventType` 路由处理,并写幂等日志表避免重复消费。
---
## 3. 关键代码位置
## 3.1 后端jeecg-boot
- STOMP 配置与心跳:
`jeecg-module-device-sync/src/main/java/org/jeecg/modules/device/sync/config/WebSocketConfig.java`
- 设备消息与应用层 PING/PONG
`jeecg-module-device-sync/src/main/java/org/jeecg/modules/device/sync/controller/DeviceWebSocketController.java`
- 批量同步入口(桌面回传):
`jeecg-module-device-sync/src/main/java/org/jeecg/modules/device/sync/controller/SyncController.java`
- 用户变更触发 STOMP 广播:
`jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserController.java`
### 3.2 桌面端yy-admin-master
- STOMP 统一连接、心跳、看门狗重连、网络恢复重连:
`YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs`
- STOMP 信号识别并入镜像拉取 Outbox
`YY.Admin.Services/Service/Jeecg/JeecgUserSyncCoordinator.cs`
- 镜像拉取入队:
`YY.Admin/Infrastructure/Sync/JeecgUserMirrorPullOutbox.cs`
- Outbox 核心(持久化、消费、失败重试、上线刷队):
`YY.Admin/Infrastructure/Sync/OutboxProcessor.cs`
- 网络探活与状态事件:
`YY.Admin/Infrastructure/Network/NetworkMonitor.cs`
- 本地用户变更入队接口与实现:
`YY.Admin.Core/Core/Services/IUserSyncOutbox.cs`
`YY.Admin/Infrastructure/Sync/UserSyncOutbox.cs`
- 本地用户 CRUD 完成后回传入队:
`YY.Admin.Services/Service/User/SysUserService.cs`
- 模块启动装配(网络监测 / Outbox / STOMP
`YY.Admin/Module/SyncModule.cs`
- 主窗口启动同步协调器:
`YY.Admin/ViewModels/MainWindowViewModel.cs`
---
## 4. 实时链路详细说明
### 4.1 STOMP 连接与订阅(桌面端)
`StompWebSocketService` 当前行为:
- 连接地址:由 `JeecgIntegration:BaseUrl` 推导为 `ws(s)://<host>/ws/device`(纯 WebSocket
- CONNECT 心跳声明:`heart-beat:10000,10000`
- 默认订阅:
- `/topic/sync/jeecg-users`(用户变更信号)
- `/topic/device/{deviceId}/pong`(应用层保活回应)
- 认证模式额外订阅:
- `/user/{deviceId}/queue/command`
### 4.2 后端广播jeecg 用户变更)
`SysUserController` 在新增/编辑/删除/状态变更等场景调用 `notifyScadaUserChanged(...)`,并通过 `SimpMessagingTemplate``/topic/sync/jeecg-users` 广播 JSON 负载,关键 cmd 为:
- `SCADA_USER_CHANGED`
### 4.3 桌面端信号消费策略
`JeecgUserSyncCoordinator` 不直接依赖推送包体做写库,而是:
- 解析收到的 JSON支持 message/commandJson 嵌套)
- 判断 `cmd` 是否为 `SCADA_USER_CHANGED` / `SCADA_USERS_CHANGED`
- 命中后入队 `JeecgUserMirror` 类型 Outbox
- 由 Outbox 消费阶段调用 `TryBackgroundSyncJeecgUsersAsync` 做实际拉取与落库
该策略提升了一致性与容错性(尤其在断线/消息乱序时)。
---
## 5. 断线检测与自动续连
## 5.1 连接失败重试
`ConnectUnifiedDeviceChannelAsync` 内置重试退避(秒):`0, 2, 5, 10, 30`
### 5.2 协议层心跳 + 应用层 PING
- 协议层:每 10s 发送 STOMP `\n` 心跳帧。
- 应用层:每 10s 发送 `{"cmd":"PING_DEVICE"...}``/app/device/ping`
- 后端 `handlePing` 回复到 `/topic/device/{deviceId}/pong`
### 5.3 看门狗“假在线”检测
- 客户端记录 `_lastReceivedTick`
- 若 30s 内没有任何帧(含心跳或 MESSAGE则判定连接僵死主动重连。
### 5.4 网络恢复触发重连
- `NetworkMonitor` 每 10s 探活(优先 SCADA 免登录接口,失败降级 health
- 状态变更发布 `NetworkStatusChangedEvent`
- STOMP 服务在 `isOnline=true` 且 socket 非 Open 时主动重连。
---
## 6. 离线补偿与幂等
## 6.1 Outbox 持久化
`OutboxProcessor.EnqueueAsync` 先落 SQLite Outbox`OutboxMessage`),再按在线状态决定是否立即入内存通道消费。
### 6.2 消费与失败重试
- 消费失败进入 `MarkFailedAsync`
- `RetryCount + 1`
- 指数退避 `2^n` 秒(上限按实现控制)
- 最多 5 次,超过标记 `Status=2`
- 在线恢复时 `FlushPendingAsync` 会补刷所有待发送消息。
### 6.3 双通道消费分流
`OutboxProcessor` 对消息分两类处理:
1) `JeecgUserMirror`:调用 `_mirrorPullHandler.ExecutePullAsync()`(即拉取镜像)。
2) 其他业务消息(如 `SYS_USER`):走 `_httpSyncClient.SendBatchAsync()``/sys/sync/batch`
### 6.4 后端幂等
`SyncController.batch` 通过 `messageId` 检查幂等日志(`sync_idempotent_log`)避免重复消费;未处理过才路由执行。
---
## 7. 配置项(当前关键项)
配置文件:`YY.Admin.Services/Configuration/appsettings.json``JeecgIntegration` 节点。
建议重点关注:
- `Enabled`
- `BaseUrl`
- `AnonymousMode`
- `UserListPath`(当前是 `/sys/user/scada/queryUser`
- `SyncAllUsersOnJeecgLogin`
- `BackgroundSyncIntervalMinutes`
> 实时通道默认使用 `/ws/device` 统一 STOMP 端点;`WebSocketPath` 仍保留在配置中,但当前同步主链路已切到统一设备通道实现。
---
## 8. 启动时序(简版)
1) `SyncModule.OnInitialized` 启动:
- `NetworkMonitor.StartAsync`
- `OutboxProcessor.StartConsumerAsync`
- `ISignalRService.ConnectUnifiedDeviceChannelAsync`
2) 主窗口 `MainWindowViewModel` 构造中调用 `JeecgUserSyncCoordinator.Start()`
- 订阅 `RemoteCommandReceivedEvent`
- 延迟 3 秒入队一次 `EventBoot` 拉取
3) 后续由“实时消息触发 + 定时/后台同步 + Outbox 补偿”共同维持一致性。
---
## 9. 现状结论
1) 当前实现已形成“统一 STOMP 通道 + 心跳 + 看门狗 + 网络恢复重连 + Outbox 补偿 + 后端幂等”的完整闭环。
2) 用户同步策略是“实时信号触发拉取”而非“直接信号落库”,在工业网络抖动场景更稳健。
3) 本地 CRUD 回传与后端推送下行均已纳入 Outbox/幂等体系,具备断线续连后的最终一致性基础。

View File

@@ -0,0 +1,299 @@
# 桌面端后端数据交互通用模板(以车辆管理为例)
## 1. 目的与适用范围
本文将“车辆管理”已落地的同步能力抽象为可复用模板,适用于后续任意业务模块(如供应商、订单、库存、质检等)快速实现:
- 桌面端 CRUD 与后端数据交互
- 正向同步(桌面端 -> 后端)
- 反向同步(后端 -> 桌面端)
- 断线续传(离线可操作,重连自动补偿)
- 本地缓存(保证离线可查、可改)
---
## 2. 总体架构模板
建议统一采用“**信号触发 + REST 拉取 + Outbox 补偿**”架构:
1. 后端业务变更后,广播 STOMP 信号(只发变更事件,不强依赖完整数据包)。
2. 桌面端接收信号后,不直接写本地业务表,而是触发“拉取接口”同步最新数据。
3. 桌面端本地操作时,在线优先调用后端;失败或离线则先写本地缓存 + 记录待同步操作Outbox
4. 网络恢复后自动回放 Outbox成功后清理队列并全量回拉一次做一致性对齐。
**核心原则**
- 在线追求实时,离线保证可用,重连追求最终一致。
- 任何“回传动作”必须幂等。
- 同步链路必须有完整日志(请求、结果、回放、失败原因)。
---
## 3. 后端接口模板(推荐样式)
以车辆管理为例,接口分两类:业务 CRUD 接口 + 同步辅助接口。
### 3.1 业务 CRUD给桌面端直接调用
建议统一路径风格(示例):
- `GET /{module}/{entity}/anon/list?pageNo=1&pageSize=10000&tenantId=1002`
- `GET /{module}/{entity}/anon/queryById?id=xxx&tenantId=1002`
- `POST /{module}/{entity}/anon/add?tenantId=1002`
- `POST /{module}/{entity}/anon/edit?tenantId=1002`
- `DELETE /{module}/{entity}/anon/delete?id=xxx&tenantId=1002`
- `POST /{module}/{entity}/anon/updateStatus?id=xxx&status=1&tenantId=1002`
返回建议统一:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"result": {}
}
```
分页 `result` 建议统一包含:
- `records`
- `total`
- `current`
- `size`
### 3.2 后端推送信号(反向同步触发)
后端每次成功增删改后,广播 STOMP
- Topic`/topic/sync/{entity-topic}`
- Payload 示例:
```json
{
"cmd": "MES_VEHICLE_CHANGED",
"action": "add|edit|delete|status",
"entityId": "xxx",
"timestamp": 1710000000000
}
```
说明:
- `cmd` 必须唯一且稳定,桌面端按它路由处理。
- `action` 用于日志和增量策略判断。
- `entityId` 可选但建议提供。
### 3.3 桌面端正向回传批量接口(可选增强)
若使用统一 Outbox 回传通道,建议后端提供:
- `POST /sys/sync/batch`
请求体示例:
```json
[
{
"messageId": "outbox-id-1",
"aggregateType": "VEHICLE",
"aggregateId": "vehicle-id",
"eventType": "CREATE|UPDATE|DELETE|TOGGLE_STATUS",
"payload": "{...}",
"occurredAt": "2026-01-01T10:00:00Z"
}
]
```
后端必须做 `messageId` 幂等去重。
---
## 4. 桌面端数据层模板
## 4.1 必备组件
每个模块建议固定四层:
1. `EntityService`(例:`VehicleService`
- CRUD 入口
- 本地缓存 + 离线队列
- 重连回放逻辑
2. `SyncCoordinator`(例:`VehicleSyncCoordinator`
- 监听 STOMP 信号
- 识别 `cmd` 并发布本地事件或触发同步
3. `OutboxProcessor`(全局复用)
- 持久化消息
- 在线实时发、离线补发
- 失败重试 + 指数退避
4. `NetworkMonitor`(全局复用)
- 网络状态检测
- 状态变化事件发布
## 4.2 本地缓存与待同步队列
推荐本地文件结构(示例):
- `%LocalAppData%/YY.Admin/sync-cache/{entity}-cache.json`
- `%LocalAppData%/YY.Admin/sync-cache/{entity}-pending-ops.json`
建议数据结构:
- `cache`: 最新本地快照(可直接查询)
- `pendingOps`: 离线期间操作日志,按时间顺序回放
`pendingOps` 字段建议:
- `id`
- `opType`Add/Edit/Delete/UpdateStatus
- `entityId`
- `payload`
- `createdAt`
---
## 5. 同步逻辑模板(标准流程)
### 5.1 查询流程Page/List
1. 在线:先拉远端数据 -> 刷新本地缓存。
2. 远端失败:回退本地缓存。
3.`pendingOps` 叠加到查询结果(保证 UI 看到“离线后的最新本地状态”)。
### 5.2 新增/编辑/删除/状态修改
1. 若在线,优先调用远端接口。
2. 远端成功:更新本地缓存。
3. 远端失败或离线:写入 `pendingOps`,并立即更新本地缓存(保证可用)。
**注意**:新增时若本地临时 ID`local-xxx`)不符合后端主键规则,回放前需清空 ID 让后端生成。
### 5.3 网络恢复(断线续传)
触发条件:`NetworkMonitor` 从离线 -> 在线。
流程:
1. 串行回放 `pendingOps`
2. 回放中任意失败立即停止,等待下次重试。
3. 回放完成后再做一次全量拉取,覆盖本地缓存。
4. 发布 UI 刷新事件(避免用户手动点查询)。
### 5.4 后端 -> 桌面反向同步
流程:
1. STOMP 收到 `cmd`
2. `SyncCoordinator` 解析命令。
3. 触发本地“拉取并刷新缓存”。
4. UI 订阅变更事件刷新列表。
---
## 6. 可复用方法模板
后续新模块可直接复用以下模式(名称替换即可):
- `FetchRemoteListAsync()`
- `RemoteAddAsync() / RemoteEditAsync() / RemoteDeleteAsync() / RemoteUpdateStatusAsync()`
- `ApplyFilters()`
- `ApplyPendingOpsSnapshotUnsafe()`
- `EnqueuePendingOperation()`
- `ReplayPendingOperationsAsync()`
- `ExecutePendingOperationAsync()`
- `LoadPendingOpsFromDisk() / SavePendingOpsToDiskUnsafe()`
- `LoadCacheFromDisk() / SaveCacheToDiskUnsafe()`
- `CloneEntity()`
建议抽一个通用基类(后续可做):
- `OfflineSyncEntityServiceBase<TEntity, TPendingOp>`
- 负责缓存、队列、回放、网络事件订阅
- 业务子类只实现远端接口和过滤逻辑
---
## 7. 日志模板(强制建议)
每个模块建议统一日志前缀,便于检索。
车辆管理示例前缀可复用为模块名替换:
- `[车辆同步]` 服务初始化
- `[车辆列表]` 查询链路
- `[车辆新增]` `[车辆修改]` `[车辆删除]` `[车辆状态]` 本地/远端操作
- `[车辆远端]` 实际 HTTP 请求与结果
- `[车辆入队]` 离线入队
- `[车辆回放]` 重连回放
- `[车辆重连]` 全量对齐
- `[车辆推送]` STOMP 信号处理
- `[车辆网络]` 网络状态变化
每条日志建议最少包含:
- 操作类型
- 业务主键
- 在线/离线状态
- 是否成功
- 失败原因(异常 message
- 队列长度(适用时)
---
## 8. 新模块落地清单(开发步骤)
按以下顺序复制模板最稳:
1. 后端先准备 `anon` CRUD 接口(或授权接口)+ 统一返回结构。
2. 后端在 CRUD 成功后广播 STOMP 变更信号。
3. 桌面端新增 `EntityService`(先把在线 CRUD 跑通)。
4. 增加本地缓存与 `pendingOps` 持久化。
5. 加入网络状态监听与重连回放。
6. 新增 `SyncCoordinator` 处理 STOMP -> 本地刷新。
7. 全链路日志补齐。
8. 按“联调测试矩阵”逐项验证。
---
## 9. 联调测试矩阵(建议)
每个模块至少跑完以下用例:
1. 在线新增 -> 后端可见。
2. 在线编辑 -> 后端可见。
3. 在线删除 -> 后端可见。
4. 在线状态切换 -> 后端可见。
5. 离线新增/编辑/删除 -> 本地立即可见。
6. 重连后自动回放 -> 后端最终一致。
7. 后端直接改数据 -> 桌面端自动刷新。
8. 异常网络抖动 -> 不崩溃,回放可恢复。
---
## 10. 车辆管理对应实现参考(当前项目)
桌面端:
- `YY.Admin.Services/Service/Vehicle/VehicleService.cs`
- `YY.Admin.Services/Service/Vehicle/VehicleSyncCoordinator.cs`
- `YY.Admin.Core/Core/Services/IVehicleService.cs`
后端:
- `jeecg-module-xslmes/.../MesXslVehicleController.java`(业务接口 + 变更推送)
- `jeecg-module-device-sync/.../SyncController.java`(如启用统一回传通道)
基础设施:
- `YY.Admin/Infrastructure/Sync/OutboxProcessor.cs`
- `YY.Admin/Infrastructure/Network/NetworkMonitor.cs`
- `YY.Admin/Infrastructure/Hubs/StompWebSocketService.cs`
---
## 11. 推荐统一规范(后续团队约定)
1. 所有新模块都按“在线优先 + 离线入队 + 重连回放 + 全量对齐”实现。
2. 所有模块都使用统一日志前缀和字段。
3. 后端变更推送统一 `cmd` 命名:`{SYSTEM}_{ENTITY}_CHANGED`
4. 回传消息统一带 `messageId` 幂等键。
5. 同步失败不阻塞主流程,但必须可观测(日志 + 队列长度)。
6. 任意新模块上线前必须跑完“联调测试矩阵”。
---
> 结论:
> 车辆管理已经提供了完整的可复制样板。后续新模块只需替换“实体、接口、过滤字段、命令字”,其余同步骨架可直接复用,从而快速实现稳定的桌面端/后端数据交互能力。