feat(ops): update cleaner status and assignment logic

This commit is contained in:
lzh
2026-01-19 14:56:31 +08:00
parent 73bc3b299f
commit b71558ae07
169 changed files with 57972 additions and 162 deletions

View File

@@ -0,0 +1,174 @@
# Ops 业务运营模块架构文档
> **文档定位**Ops 模块的深度架构设计与演进文档
> **维护策略**按架构演进版本记录保留历史设计决策ADR
> **最后更新**2026-01-14
> **文档版本**v2.1.0
---
## 📖 文档说明
### Ops 模块简介
**OpsOperations业务运营模块**是 AIoT Platform 的核心业务模块,承载万物梁行的四大业务线:
| 业务线 | 代码模块 | 业务范围 |
|--------|----------|----------|
| **保洁** | `environment-biz` | 排班、巡检、耗材、智能调度 |
| **安保** | `security-biz` | 巡更、报警、岗位管理 |
| **工程** | `facilities-biz` | 维保、巡检、备件管理 |
| **客服** | `service-biz` | 投诉、建议、满意度 |
**核心能力**
- 工单引擎(基于状态机)
- 智能调度系统(自动派单、优先级队列)
- IoT 设备集成(工牌、信标、传感器)
- 绩效评价系统
### 文档结构
本文档分为 10 个部分,从架构总览到具体实现,全方位记录 Ops 模块的设计与演进。
```
Part 1: [架构总览](./part1-架构总览.md) - 业务背景、系统架构、核心概念
Part 2: [架构演进史](./part2-架构演进史.md) - 从阶段1到阶段3的演进历程
Part 3: [核心架构设计](./part3-核心架构设计.md) - 服务职责、分层设计、技术方案
Part 4: [关键技术方案](./part4-关键技术方案.md) - 状态机、队列、派单、事件驱动、P0打断
Part 5: [架构重构实践](./part5-架构重构实践.md) - 问题识别、重构方案、实施过程
Part 6: [性能与可靠性](./part6-性能与可靠性.md) - 优化、并发、可靠性保障
Part 7: [扩展性设计](./part7-扩展性设计.md) - 如何新增业务类型、扩展策略
Part 8: [测试指南](./part8-测试指南.md) - 单元测试、集成测试、性能测试
Part 9: [技术决策记录ADR](./part9-技术决策记录.md) - 记录重要技术决策
Part 10: [附录](./part10-附录.md) - 类图、时序图、术语表、变更日志
```
### 适用人群
- **Ops 模块开发者**:了解架构设计,参与功能开发
- **架构师**:理解设计决策,参与架构评审
- **技术负责人**:掌握技术债务,规划重构方向
- **新成员**:快速了解 Ops 模块的全貌
### 版本管理
| 版本 | 日期 | 主要内容 |
|------|------|----------|
| v1.0 | 2025-12 | 阶段2完成工单引擎、状态机、事件溯源 |
| v2.0 | 2026-01 | 阶段3规划智能调度系统 |
| v2.1 | 2026-01-06 | 架构重构方案(重新定义组件职责) |
---
## 🗂️ 快速导航
### 我想知道...
**业务相关**
- [Ops 模块有哪些业务线?](./part1-架构总览.md#14-四大业务线)
- [保洁工单的完整流程是什么?](./part1-架构总览.md#13-核心业务流程)
- [如何处理紧急任务打断?](./part4-关键技术方案.md#45-p0-紧急任务打断)
**架构相关**
- [Ops 模块的整体架构是什么?](./part1-架构总览.md#12-系统架构)
- [服务职责如何划分?](./part3-核心架构设计.md#31-服务职责划分)
- [为什么要重构 DispatchEngine](./part5-架构重构实践.md#51-问题识别)
**技术实现**
- [状态机如何实现?](./part4-关键技术方案.md#41-状态机实现详解)
- [Redis + MySQL 混合队列如何设计?](./part3-核心架构设计.md#33-redis--mysql-混合队列)
- [事件驱动架构如何实现?](./part4-关键技术方案.md#44-事件驱动架构)
**开发相关**
- [如何新增一个业务类型?](./part7-扩展性设计.md#71-新增业务条线流程)
- [如何扩展派单策略?](./part7-扩展性设计.md#72-自定义派单策略)
- [如何编写单元测试?](./part8-测试指南.md#81-单元测试规范)
- [有哪些重大的技术决策?](./part9-技术决策记录.md)
- [如何查询术语定义?](./part10-附录.md#101-核心术语表)
---
## 📚 核心概念
### 工单Work Order
工单是 Ops 模块的核心业务对象,代表一个需要处理的任务。
**工单类型**
- `CLEAN` - 保洁工单
- `REPAIR` - 维修工单
- `SECURITY` - 安保工单
- `SERVICE` - 客服工单
**工单状态**
- `PENDING` - 待接单
- `ASSIGNED` - 已分配
- `ARRIVED` - 已到岗
- `PAUSED` - 已暂停
- `COMPLETED` - 已完成
- `CANCELLED` - 已取消
### 优先级Priority
**优先级等级**
- `P0` - 紧急(可打断当前任务)
- `P1` - 重要(优先处理)
- `P2` - 普通(正常排队)
**关键特性**
- P0 任务会打断保洁员当前正在执行的任务
- P0 任务完成后,自动恢复被中断的任务
### 队列Queue
每个保洁员/维修工都有一个优先级队列,存储待处理的任务。
**队列状态**
- `WAITING` - 等待中
- `PROCESSING` - 处理中
- `PAUSED` - 暂停中
- `REMOVED` - 已移除
**技术实现**Redis Sorted Set + MySQL 混合队列
### 派单引擎Dispatch Engine
根据策略自动推荐最合适的执行人员。
**派单策略**
- 区域优先策略(就近分配)
- 负载均衡策略(考虑当前任务数)
- 技能匹配策略(维修工单需要相应技能)
### 状态机State Machine
管理工单的状态转换,保证状态一致性。
**核心能力**
- 状态转换规则验证
- 事件记录(事件溯源)
- 监听器/事件处理器通知
---
## 🔗 相关文档
- [AIoT Platform 技术全景文档](../technical-overview/README.md)
- [部署指南](../deployment-guide.md)
- [工单调度系统技术实现汇报文档](../工单调度系统技术实现汇报文档.md)
- [阶段2实现总结](../phase2-implementation-summary.md)
---
## 📧 贡献指南
本文档是 Ops 模块的官方架构文档,欢迎团队成员贡献:
1. **新增内容**:在对应章节添加,更新目录和版本号
2. **修正错误**:标注修正位置和原因
3. **补充示例**:添加代码示例和图表
4. **记录决策**:使用 ADR 格式记录技术决策
---
**下一章**[Part 1: 架构总览](./part1-架构总览.md)

View File

@@ -0,0 +1,429 @@
# Part 1: 架构总<E69E84><E680BB>
## 1.1 业务背景
### 1.1.1 物业管理行业现状
传统物业管理面临的核心挑战:
| 问题 | 具体表现 | 影响 |
|------|----------|------|
| **调度低效** | 依赖人工派单,电话沟通 | 响应慢,客户体验差 |
| **资源浪费** | 保洁员/维修工闲置或过载 | 人力成本高,效率低 |
| **信息滞后** | 无法实时掌握现场情况 | 问题发现晚,处理慢 |
| **质量不稳定** | 依赖人工经验,缺乏标准 | 服务质量参差不齐 |
| **数据孤岛** | 各业务系统独立 | 难以统筹决策,数据价值低 |
### 1.1.2 万物梁行的数字化目标
**短期目标**
-<><E5AE9E>工单智能调度提升响应速度
- 实时监控人员状态,优化资源分配
- 记录服务数据,支撑绩效评价
**中期目标**
- 四大业务线一体化运营
- 数据驱动决策,优化运营策略
- 提升客户满意度,降低运营成本
**长期目标**
- 打造行业领先的智能运营平台
- 输出数字化解决方案
- 支持规模化复制
---
## 1.2 系统架构
### 1.2.1 模块结构
```
viewsh-module-ops/
├── viewsh-module-ops-api/ # [契约层] DTO、Feign 接口
├── viewsh-module-ops-biz/ # [核心层] 工单引擎、公共逻辑
├── viewsh-module-environment-biz/ # [业务层] 保洁条线
├── viewsh-module-security-biz/ # [业务层] 安保条线
├── viewsh-module-facilities-biz/ # [业务层] 工程条线
├── viewsh-module-service-biz/ # [业务层] 客服条线
└── viewsh-module-ops-server/ # [宿主层] 微服务入口
```
### 1.2.2 分层架构
```
┌─────────────────────────────────────────────────────────────┐
│ 宿主层 (Server) │
│ Ops Server Application │
│ - REST API │
│ - 配置管理 │
└──────────────────────────┬──────────────────────────────────┘
┌─────────────────┼─────────────────┐
│ │ │
┌────────▼────────┐ ┌──────▼──────┐ ┌───────▼────────┐
│ 保洁业务层 │ │ 安保业务层 │ │ 工程业务层 │
│ (Environment) │ │ (Security) │ │(Facilities) │
│ - 排班 │ │ - 巡更 │ │ - 维保 │
│ - 巡检 │ │ - 报警 │ │ - 巡检 │
│ - 耗材 │ │ - 岗位 │ │ - 备件 │
└────────┬────────┘ └──────┬──────┘ └───────┬────────┘
│ │ │
└─────────────────┼─────────────────┘
┌─────────────────▼─────────────────┐
│ 核心业务层 (Ops Biz) │
│ - 工单引擎 │
│ - 状态机 │
│ - 队列管理 │
│ - 派单引擎 │
└─────────────────┬─────────────────┘
┌─────────────────▼─────────────────┐
│ 契约层 (Ops API) │
│ - DTO 定义 │
│ - 枚举定义 │
│ - 服务接口 │
└─────────────────────────────────────┘
```
**设计原则**
- **依赖倒置**:高层不依赖低层,都依赖抽象
- **单一职责**:每层专注自己的职责
- **开闭原则**:对扩展开放,对修改关闭
- **接口隔离**:业务线通过接口扩展核心引擎
### 1.2.3 技术栈
| 层次 | 技术选型 |
|------|----------|
| 应用框架 | Spring Boot 3.5.9 |
| ORM 框架 | MyBatis Plus 3.5+ |
| 数据库 | MySQL 8.0+ |
| 缓存 | Redis 6.x/7.x |
| 消息队列 | RocketMQ 5.x |
| 定时任务 | Spring @Scheduled / XXL-Job |
| 单元测试 | JUnit 5 + Mockito |
---
## 1.3 核心业务流程
### 1.3.1 保洁工单完整流程
```
┌──────────────┐
│ 系统定时任务 │ 每5分钟生成日常保洁工单
│ 或巡检员创建 │ 手动创建临时工单
└──────┬───────┘
┌──────────────┐
│ 智能派单引擎 │ 推荐最合适的保洁员
└──────┬───────┘
┌──────────────┐
│ 工单入队 │ 加入优先级队列
└──────┬───────┘
┌──────────────┐
│ 工牌语音播报 │ "2楼电梯厅有水渍"
└──────┬───────┘
┌──────────────┐
│ 保洁员接单 │ 确认接单,状态变更为 ARRIVED
└──────┬───────┘
┌──────────────┐
│ 到岗打卡 │ 自动记录位置和时间
└──────┬───────┘
┌──────────────┐
│ 作业进行中 │ 暂停:临时离开
│ │ 恢复:回来继续作业
└──────┬───────┘
┌──────────────┐
│ 完成工单 │ 上传照片,记录作业时长
└──────┬───────┘
┌──────────────┐
│ 自动派发 │ 自动派发下一个任务
│ 下一个任务 │ 或恢复被中断的任务
└──────────────┘
```
### 1.3.2 P0 紧急任务打断流程
```
┌──────────────┐
│ 巡检员创建P0 │ 2楼电梯厅有严重水渍紧急处理
│ 紧急工单 │
└──────┬───────┘
┌──────────────┐
│ 查找附近 │ 500米范围内
│ 保洁员 │ 状态为 IDLE 或 BUSY
└──────┬───────┘
┌──────────────┐
│ 目标保洁员 │ 张师傅正在执行P2任务
│ 当前状态 │ BUSY
└──────┬───────┘
┌──────────────┐
│ 打断当前任务 │ 暂停P2任务
│ │ - 工单状态ARRIVED → PAUSED
│ │ - 队列状态PROCESSING → PAUSED
└──────┬───────┘
┌──────────────┐
│ 紧急插队 │ P0任务加入队列首位
│ │ 立即派单
└──────┬───────┘
┌──────────────┐
│ 高分贝警报 │ 工牌最大音量 + 震动
│ + 语音播报 │ "紧急插队2楼电梯厅"
└──────┬───────┘
┌──────────────┐
│ 张师傅接单 │ 立即处理P0任务
└──────┬───────┘
┌──────────────┐
│ 完成P0任务 │
└──────┬───────┘
┌──────────────┐
│ 自动恢复 │ 恢复被中断的P2任务
│ P2任务 │ 工牌语音播报:"恢复任务"
└──────────────┘
```
---
## 1.4 四大业务线
### 1.4.1 保洁条线(环境管理)
**业务范围**
- 日常保洁(定时生成工单)
- 临时保洁(巡检员手动创建)
- 排班管理(保洁员班次)
- 巡检管理(巡检计划、巡检记录)
- 耗材管理(消耗品、库存)
**核心功能**
- ✅ 智能调度系统(自动派单、优先级队列、紧急插队)
- ✅ 保洁员状态管理IDLE、BUSY、PAUSED、OFFLINE
- ✅ 工牌语音播报(自动去重)
- ✅ 绩效统计(响应时间、作业时长、质量评分)
**技术<E68A80><E69CAF><EFBFBD>现**
- 代码模块:`viewsh-module-environment-biz`
- 服务类:`CleanOrderService``CleanerStatusService`
- 派单策略:`CleanerAreaAssignStrategy``CleanerPriorityScheduleStrategy`
### 1.4.2 安保条线
**业务范围**
- 巡更管理(路线规划、点位打卡)
- 报警管理(异常上报、报警联动)
- 岗位管理(岗位值守、交接班)
- 人员管理(保安档案、考勤)
**核心功能**
- 巡更轨迹追踪GPS + 信标)
- 异常报警联动(自动创建工单)
- 实时监控(巡更进度可视化)
**技术实现**
- 代码模块:`viewsh-module-security-biz`
- 计划实现2026 Q2
### 1.4.3 工程条线(设施管理)
**业务范围**
- 维保管理(设备维保计划)
- 巡检管理(设备巡检)
- 备件管理(库存、领用)
- 故障维修(报修、派单、维修、验收)
**核心功能**
- 设备档案管理
- 维保计划自动生成
- 备件库存预警
- 故障统计分析
**技术实现**
- 代码模块:`viewsh-module-facilities-biz`
- 计划实现2026 Q3
### 1.4.4 客服条线
**业务范围**
- 投诉处理(客户投诉、分类、处理)
- 建议管理(客户建议、采纳)
- 满意度调查(服务评价、统计分析)
**核心功能**
- 多渠道接入(小程序、电话、公众号)
- 智能分类AI 分类)
- 满意度评价
- 服务数据分析
**技术实现**
- 代码模块:`viewsh-module-service-biz`
- 计划实现2026 Q4
---
## 1.5 核心概念
### 1.5.1 工单Work Order
工单是 Ops 模块的核心业务对象,代表一个需要处理的任务。
**工单类型WorkOrderTypeEnum**
```java
CLEAN("CLEAN", "保洁") // 保洁工单
REPAIR("REPAIR", "维修") // 维修工单
SECURITY("SECURITY", "安保") // 安保工单
SERVICE("SERVICE", "客服") // 客服工单
```
**工单状态WorkOrderStatusEnum**
```java
PENDING("待接单") // 工单已创建,等待分配
ASSIGNED("已分配") // 已分配给执行人
ARRIVED("已到岗") // 执行人已到岗
PAUSED("已暂停") // 任务暂停临时离开或被P0打断
COMPLETED("已完成") // 任务完成
CANCELLED("已取消") // 工单取消
```
**工单优先级PriorityEnum**
```java
P0("P0", "紧急") // 可打断当前任务,立即处理
P1("P1", "重要") // 优先处理
P2("P2", "普通") // 正常排队
```
### 1.5.2 保洁员状态CleanerStatus
**状态定义CleanerStatusEnum**
```java
IDLE("空闲") // 可接新单
BUSY("忙碌") // 正在执行工单
PAUSED("暂停") // 临时离开
OFFLINE("离线") // 工牌离线
```
**状态转换规则**
```
OFFLINE → IDLE: 工牌上线,心跳恢复
IDLE → BUSY: 接单成功
BUSY → PAUSED: 任务暂停手动或被P0打断
PAUSED → BUSY: 任务恢复
BUSY → IDLE: 完成所有任务
IDLE/OFFLINE: 工牌离线超时30分钟无心跳
```
### 1.5.3 队列状态OrderQueueStatus
**状态定义OrderQueueStatusEnum**
```java
WAITING("等待中") // 工单已进入队列,等待派单
PROCESSING("处理中") // 任务已下发,正在处理
PAUSED("暂停中") // 任务被暂停
REMOVED("已移除") // 任务已完成或已取消
```
### 1.5.4 服务职责划分
**DispatchEngine派单引擎**
- 职责:根据策略推荐最合适的执行人员
- 不负责:状态管理、设备通知、业务逻辑
**OrderLifecycleManager生命周期管理器**
- 职责:统一管理工单状态和队列状态的同步变更
- 核心方法:`pauseOrder()`, `resumeOrder()`, `interruptOrder()`
**OrderStateMachine状态机**
- 职责:验证状态转换规则、记录事件
- 不触发:任何业务逻辑
**OrderEventPublisher事件发布器**
- 职责:发布工单状态变更事件
- 实现业务解耦
---
## 1.6 当前实施状态
### 已完成 ✅
**阶段1基础数据层**2025.12
- ✅ 枚举类10个
- ✅ DO 实体类10个
- ✅ Mapper 接口10个
- ✅ 数据库表结构
**阶段2工单引擎**2026.01
- ✅ 状态机实现
- ✅ 事件溯源机制
- ✅ 监听器模式
- ✅ 工单 CRUD
- ✅ 工单暂停/恢复
- ✅ REST API11个
- ✅ 单元测试26个
**阶段3智能调度部分**
- ✅ 队列状态管理
- ✅ 派单引擎实现
- ✅ P0 紧急任务打断
- ✅ 保洁员状态管理
**架构重构**2026.01.06
- ✅ 重新定义组件职责
- ✅ 引入 OrderLifecycleManager
- ✅ 创建 OrderEventPublisher
- ✅ 事件处理器替代监听器
### 进行中 ⏳
**阶段3智能调度剩余工作**
- ⏳ 工牌通知服务集成
- ⏳ 消息队列集成MQ
- ⏳ 定时任务(自动生成工单、绩效计算)
### 待实施 📋
**阶段4绩效评价系统**
- ⏳ 响应时间统计
- ⏳ 质量评分
- ⏳ 绩效计算
**阶段5其他业务线**
- ⏳ 安保条线2026 Q2
- ⏳ 工程条线2026 Q3
- ⏳ 客服条线2026 Q4
---
**下一章**[Part 2: 架构演进史](./part2-架构演进史.md)

View File

@@ -0,0 +1,22 @@
# Part 10: 附录
## 10.1 核心术语表
| 术语 | 定义 | 对应代码 |
| :--- | :--- | :--- |
| **派单引擎** | 负责“谁来接单、怎么派单”的决策组件 | `DispatchEngine` |
| **调度上下文** | 包含区域、优先级、技能等派单因子的容器 | `DispatchContext` |
| **打断决策** | 评估新工单是否具有打断当前任务权力的逻辑结果 | `InterruptDecision` |
| **执行人状态** | 执行人在系统中的实时生命周期(空闲、忙碌、离线) | `CleanerStatus` |
## 10.2 架构变更日志
### v2.1.0 (2026-01-06)
- **重构**:引入 `OrderLifecycleManager`,彻底分离状态管理与业务逻辑。
- **新增**:实现 P0 紧急任务自动打断与完成后自动恢复机制。
### v1.0.0 (2025-12-15)
- **基础**:完成工单 CRUD、基础状态机及保洁业务初步接入。
---
**返回目录**[README.md](./README.md)

View File

@@ -0,0 +1,570 @@
# Part 2: 架构演进史
本文档记录 Ops 业务运营模块从零开始到当前的完整演进历程,包括每个阶段的目标、成果、经验教训和技术债务。
---
## 2.1 阶段1基础数据层2025.12
### 2.1.1 实施时间
2025年12月 - 2026年1月初
### 2.1.2 阶段目标
搭建工单管理系统的数据基础,创建所有必要的数据模型。
### 2.1.3 核心成果
**枚举类10个**
| 枚举类 | 说明 |
|--------|------|
| `WorkOrderTypeEnum` | 工单类型CLEAN、REPAIR、SECURITY、SERVICE |
| `WorkOrderStatusEnum` | 工单状态PENDING、ASSIGNED、ARRIVED、PAUSED、COMPLETED、CANCELLED |
| `PriorityEnum` | 优先级P0、P1、P2 |
| `CleanerStatusEnum` | 保洁员状态IDLE、BUSY、PAUSED、OFFLINE |
| `DispatchModeEnum` | 派单模式AUTO、MANUAL |
| `OperatorTypeEnum` | 操作人类型SYSTEM、ADMIN、CLEANER、INSPECTOR |
| `BadgeDeviceTypeEnum` | 设备类型BADGE、BEACON |
| `AreaTypeEnum` | 区域类型PARK、BUILDING、FLOOR、FUNCTION |
| `FunctionTypeEnum` | 功能类型LOBBY、ELEVATOR、CORRIDOR、RESTROOM |
| `EventDomainEnum` | 事件领域DISPATCH、QUALITY、SYSTEM |
**DO 实体类10个**
| 实体类 | 说明 |
|--------|------|
| `OpsOrderDO` | 工单主表 |
| `OpsOrderCleanExtDO` | 保洁工单扩展表 |
| `OpsOrderEventDO` | 工单事件记录表 |
| `OpsOrderDispatchDO` | 派单决策记录表 |
| `OpsOrderQueueDO` | 工单队列表 |
| `CleanerStatusDO` | 保洁员状态表 |
| `CleanerPerformanceMonthlyDO` | 月度绩效汇总表 |
| `BadgeDeviceDO` | 工牌设备表 |
| `CleanEventLogDO` | 保洁事件日志表 |
| `BusAreaDO` | 业务区域表 |
**Mapper 接口10个**
- `OpsOrderMapper`
- `OpsOrderEventMapper`
- `CleanerStatusMapper`
- `BadgeDeviceMapper`
- `OpsOrderCleanExtMapper`
- `OpsOrderDispatchMapper`
- `OpsOrderQueueMapper`
- `CleanerPerformanceMonthlyMapper`
- `CleanEventLogMapper`
- `BusAreaMapper`
**SQL 表结构**
- 文件:`sql/mysql/aiot_ops_work_order.sql`
- 创建了所有工单相关的数据库表
### 2.1.4 遗留问题
- 无业务逻辑实现
- 无服务层和 Controller
- 无单元测试
---
## 2.2 阶段2工单引擎2026.01
### 2.2.1 实施时间
2026年1月 - 2026年1月中旬
### 2.2.2 阶段目标
实现工单管理系统的核心业务逻辑包括状态机、事件溯源、REST API。
### 2.2.3 核心成果
#### DTO 数据传输对象11个
**工单基础 DTOops-biz 模块)**
- `OpsOrderCreateReqDTO` - 创建工单请求
- `OpsOrderUpdateReqDTO` - 更新工单请求
- `OpsOrderPageReqDTO` - 分页查询请求
- `OpsOrderAssignReqDTO` - 分配工单请求
- `OpsOrderCompleteReqDTO` - 完成工单请求
- `OpsOrderRespDTO` - 工单基本信息响应
- `OpsOrderDetailRespDTO` - 工单详细信息响应
- `OpsOrderEventRespDTO` - 工单事件记录响应
**保洁业务 DTOenvironment-biz 模块)**
- `CleanOrderAutoCreateReqDTO` - 保洁工单自动创建请求
- `CleanOrderPauseReqDTO` - 保洁工单暂停请求
- `CleanOrderResumeReqDTO` - 保洁工单恢复请求
#### 状态机核心实现
**`OrderStateMachine`** - 状态机引擎
```java
@Service
public class OrderStateMachine {
// 状态转换规则
private static final Map<WorkOrderStatusEnum, Set<WorkOrderStatusEnum>> TRANSITIONS = Map.of(
PENDING, Set.of(ASSIGNED, CANCELLED),
ASSIGNED, Set.of(ARRIVED, CANCELLED),
ARRIVED, Set.of(PAUSED, COMPLETED),
PAUSED, Set.of(ARRIVED, CANCELLED),
COMPLETED, Collections.emptySet(),
CANCELLED, Collections.emptySet()
);
@Transactional
public void transition(OpsOrderDO order, WorkOrderStatusEnum newStatus,
OperatorTypeEnum operatorType, Long operatorId, String remark);
}
```
**核心功能**
- ✅ 状态转换规则验证
- ✅ 自动记录事件到 `ops_order_event`
- ✅ 通知所有注册的监听器
- ✅ 支持业务扩展(通过监听器模式)
**状态流转图**
```
PENDING (待接单)
ASSIGNED (已分配)
ARRIVED (已到岗)
↓ ↘
PAUSED (已暂停) COMPLETED (已完成) [终态]
ARRIVED (已恢复)
任何状态 → CANCELLED (已取消) [终态]
```
#### 事件与监听器
**`OrderStateChangedEvent`** - 状态变更事件
```java
@Data
@AllArgsConstructor
public class OrderStateChangedEvent {
private OpsOrderDO order;
private WorkOrderStatusEnum fromStatus;
private WorkOrderStatusEnum toStatus;
private OperatorTypeEnum operatorType;
private Long operatorId;
private String remark;
}
```
**`OrderStateChangeListener`** - 监听器接口
```java
@FunctionalInterface
public interface OrderStateChangeListener {
void onStateChanged(OrderStateChangedEvent event);
}
```
#### 工单服务层
**`OpsOrderService` 接口** - 10个核心方法
| 方法 | 说明 |
|------|------|
| `createOrder()` | 创建工单 |
| `updateOrder()` | 更新工单 |
| `deleteOrder()` | 删除工单 |
| `getOrder()` | 获取工单详情 |
| `getOrderPage()` | 分页查询工单 |
| `assignOrder()` | 分配工单给执行人 |
| `acceptOrder()` | 执行人接单 |
| `completeOrder()` | 完成工单 |
| `pauseOrder()` | 暂停工单 |
| `resumeOrder()` | 恢复工单 |
| `cancelOrder()` | 取消工单 |
#### REST API11个
| HTTP方法 | 路径 | 功能 | 权限 |
|---------|------|------|------|
| POST | `/ops/order/create` | 创建工单 | `ops:order:create` |
| PUT | `/ops/order/update` | 更新工单 | `ops:order:update` |
| DELETE | `/ops/order/delete` | 删除工单 | `ops:order:delete` |
| GET | `/ops/order/get` | 获取工单详情 | `ops:order:query` |
| GET | `/ops/order/page` | 分页查询工单 | `ops:order:query` |
| POST | `/ops/order/assign` | 分配工单 | `ops:order:assign` |
| POST | `/ops/order/accept` | 接单 | `ops:order:accept` |
| POST | `/ops/order/complete` | 完成工单 | `ops:order:complete` |
| POST | `/ops/order/pause` | 暂停工单 | `ops:order:pause` |
| POST | `/ops/order/resume` | 恢复工单 | `ops:order:resume` |
| POST | `/ops/order/cancel` | 取消工单 | `ops:order:cancel` |
#### 单元测试26个
**`OrderStateMachineTest`** - 10个测试用例
- 状态转换测试
- 非法转换测试
- 终态测试
**`OpsOrderServiceTest`** - 16个测试用例
- CRUD 操作测试
- 业务规则测试
- 异常场景测试
**测试结果**
```bash
Tests run: 26, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
```
**测试覆盖率**
- 状态机核心逻辑100%
- 工单服务业务规则95%+
- 异常场景覆盖:完整
- 边界条件测试:完整
### 2.2.4 架构设计亮点
**1. 模块化分层设计**
```
viewsh-module-ops/
├── viewsh-module-ops-api/ # 契约层
├── viewsh-module-ops-biz/ # 核心业务层
│ ├── dal/ # 数据访问层
│ ├── service/ # 服务层
│ │ ├── order/ # 工单服务
│ │ ├── fsm/ # 状态机
│ │ └── event/ # 事件服务
├── viewsh-module-environment-biz/ # 保洁业务线
│ ├── service/
│ │ ├── cleanorder/ # 保洁工单服务
│ │ └── listener/ # 保洁状态监听器
└── viewsh-module-ops-server/ # 宿主层
└── controller/ # REST API
```
**2. 状态机模式**
- 集中管理状态转换规则
- 避免状态散落在各个业务方法
- 易于维护和扩展新状态
- 自动记录状态变更历史
**3. 监听器模式**
- 解耦核心引擎与业务逻辑
- 业务线可独立扩展
- 支持多监听器协同工作
- 符合开闭原则
**4. 事件溯源**
- 所有状态转换记录到 `ops_order_event`
- 支持完整的审计追溯
- 可用于状态重建和数据分析
### 2.2.5 技术债务
| 优先级 | 问题 | 改进方案 | 预估工作量 |
|-------|------|---------|-----------|
| P0 | 分页查询未实现 | 实现 selectPage | 2小时 |
| P1 | 异常处理过于简单 | 定义业务异常类体系 | 4小时 |
| P1 | 缺少参数校验单元测试 | 增加参数校验测试用例 | 3小时 |
| P2 | 日志打印不够详细 | 增加关键操作日志 | 2小时 |
| P2 | 缺少性能监控 | 集成 Micrometer 统计 | 4小时 |
---
## 2.3 阶段3智能调度系统2026.01 - 规划中)
### 2.3.1 实施计划
2026年1月 - 2026年2月
### 2.3.2 阶段目标
实现自动派单、优先级队列、紧急插队功能。
### 2.3.3 核心功能
**1. 保洁员状态管理**
**状态定义**
- `IDLE` - 空闲(可接新单)
- `BUSY` - 忙碌(正在执行工单)
- `PAUSED` - 暂停(临时离开)
- `OFFLINE` - 离线(工牌离线)
**核心方法**
- `updateStatus()` - 更新状态
- `handleHeartbeat()` - 处理工牌心跳
- `listAvailableCleaners()` - 查询可接单的保洁员
- `findNearbyCleaners()` - 查询附近的保洁员
**2. 优先级队列管理**
**Redis + MySQL 混合队列**
- Redis Sorted Set实时队列高性能
- MySQL持久化存储数据不丢失
**队列状态**
- `WAITING` - 等待中
- `PROCESSING` - 处理中
- `PAUSED` - 暂停中
- `REMOVED` - 已移除
**核心方法**
- `enqueue()` - 入队
- `startExecution()` - 开始执行WAITING → PROCESSING
- `pauseTask()` - 暂停任务PROCESSING → PAUSED
- `resumeTask()` - 恢复任务PAUSED → PROCESSING
**3. 自动派单引擎**
**派单策略**
- 区域优先策略(就近分配)
- 负载均衡策略(考虑当前任务数)
- 技能匹配策略(维修工单需要相应技能)
**核心方法**
- `recommendAssignee()` - 推荐执行人员
- `autoDispatch()` - 自动派单
- `urgentDispatch()` - 紧急派单(可能打断)
**4. P0 紧急任务打断**
**打断条件**
- 目标保洁员状态为 BUSY
- 新工单优先级为 P0
**打断流程**
1. 查找附近的保洁员
2. 如果保洁员正在执行任务BUSY
3. 暂停当前任务(工单状态 + 队列状态同步变更)
4. 派发 P0 工单
5. P0 完成后,自动恢复被中断的任务
**5. IoT 设备集成**
**工牌设备集成**
- 订阅设备状态变更事件
- 接收设备心跳消息
- 发送语音播报指令
**语音播报优化**
- 自动去重(同一工单不重复播报)
- P0 紧急任务:高分贝警报 + 震动
- 普通/P1 任务:普通语音 + 震动
### 2.3.4 实施进度
**已完成 ✅**
- 队列状态管理WAITING、PROCESSING、PAUSED、REMOVED
- 派单引擎实现DispatchEngine
- 保洁员状态管理
- P0 紧急任务打断机制
- 语音播报去重服务
**进行中 ⏳**
- 工牌通知服务集成
- 消息队列集成MQ
- 定时任务(自动生成工单、绩效计算)
**待实施 📋**
- 绩效评价系统
- 单元测试(派单逻辑、队列管理、紧急插队)
---
## 2.4 架构重构历程2026.01.06
### 2.4.1 背景
在阶段2和阶段3的实施过程中发现了一些架构问题需要进行重构。
### 2.4.2 问题识别
**问题1DispatchEngine 职责不清晰**
- 接口定义了 13 个方法,但很多返回 null
- 派单引擎与队列服务的职责边界模糊
- `autoDispatch()` 实际只是调用 `orderQueueService.startExecution()`
**问题2通用方法抽离不彻底**
- `OpsOrderService` 只有基础的 CRUD
- 暂停/恢复逻辑直接调用状态机,没有与队列联动
- 打断逻辑只在保洁业务层实现,其他业务线无法复用
**问题3Listener 设计不明确**
- 监听器注册是全局的,无法区分业务类型
- 监听器之间没有优先级定义
- `CleanOrderStateChangeListener` 直接依赖注入 `CleanOrderService`,产生循环依赖风险
**问题4状态与队列状态同步问题**
- 工单状态 (`WorkOrderStatusEnum`) 和队列状态 (`OrderQueueStatusEnum`) 是两个独立的状态机
- 两者之间的同步需要业务层手动保证
### 2.4.3 重构方案设计
**核心原则**
1. 单一职责:每个组件只负责一个明确的职责
2. 开闭原则:通过接口和策略模式支持扩展
3. 依赖倒置:业务层依赖通用层的抽象接口
4. 清晰分层:通用能力下沉,业务能力上浮
**重新定义组件职责**
**DispatchEngine派单引擎- 重新定位**
- 职责:只负责"决策",推荐最合适的执行人员
- 不负责:状态管理、设备通知、业务逻辑
**OrderLifecycleManager工单生命周期管理- 新增**
- 职责:统一管理工单状态和队列状态的同步变更
- 核心方法:`pauseOrder()`, `resumeOrder()`, `interruptOrder()`, `completeOrder()`
**OrderStateMachine状态机- 优化**
- 职责:验证状态转换规则、记录事件
- 不触发:任何业务逻辑(移除监听器机制)
**OrderEventPublisher事件发布器- 新增**
- 职责:发布工单状态变更事件
- 实现业务解耦
### 2.4.4 重构实施
**Phase 1: 基础重构**
| 任务 | 说明 | 状态 |
|------|------|------|
| 重构 DispatchEngine 接口和实现 | 纯决策层 | ✅ 完成 |
| 创建 OrderLifecycleManager | 生命周期管理 | ✅ 完成 |
| 重构 OrderStateMachine移除监听器 | 纯状态管理 | ✅ 完成 |
| 创建 OrderEventPublisher | 事件发布器 | ✅ 完成 |
| 迁移暂停/恢复逻辑到 OrderLifecycleManager | 统一状态同步 | ✅ 完成 |
**Phase 2: 业务层改造**
| 任务 | 说明 | 状态 |
|------|------|------|
| 创建事件处理器替代监听器 | CleanOrderEventHandler | ✅ 完成 |
| 重构 CleanOrderService | 使用 OrderLifecycleManager | ✅ 完成 |
| 更新派单策略实现 | CleanerAreaPriorityStrategy | ✅ 完成 |
| 单元测试编写 | 测试用例 | 📋 待进行 |
**Phase 3: 优化与验证**
| 任务 | 说明 | 状态 |
|------|------|------|
| 性能优化 | - | ⏳ 待进行 |
| 集成测试 | - | ⏳ 待进行 |
| 文档更新 | - | ⏳ 待进行 |
### 2.4.5 收获与反思
**成功经验**
1. **职责分离带来清晰度**
- DispatchEngine 专注于决策
- OrderLifecycleManager 统一管理状态同步
- 各组件职责清晰,易于理解和维护
2. **事件驱动架构提升扩展性**
- 业务方通过订阅事件处理自己的逻辑
- 新增业务类型只需实现事件处理器
- 核心引擎不需要修改
3. **兼容性保证平滑迁移**
- 保留旧实现作为兼容层
- 业务流程不中断
- 逐步迁移到新架构
**踩坑记录**
1. **循环依赖问题**
- 问题DispatchEngine 依赖 CleanOrderServiceCleanOrderService 依赖 DispatchEngine
- 解决:使用 `@Lazy` 延迟注入
2. **状态同步复杂性**
- 问题:工单状态和队列状态需要保持一致
- 解决:引入 OrderLifecycleManager 统一管理
3. **监听器优先级**
- 问题:监听器之间没有优先级定义
- 解决:改为事件驱动,由业务方决定处理顺序
**改进建议**
1. **从设计阶段就明确职责**
- 在编码前充分讨论组件职责
- 使用 UML 图展示组件关系
- 定期 Review 架构设计
2. **持续重构**
- 不要等技术债务积累太多
- 定期(每季度)进行架构 Review
- 及时重构不合理的设计
3. **完善测试**
- 单元测试覆盖核心逻辑
- 集成测试覆盖端到端流程
- 性能测试验证优化效果
---
## 2.5 版本对比
### 2.5.1 v1.0阶段2完成
**特点**
- 状态机 + 监听器模式
- 基础工单 CRUD
- REST API
- 单元测试
**优点**
- 功能完整
- 测试覆盖率高
- 监听器模式支持扩展
**缺点**
- DispatchEngine 职责不清晰
- 通用方法抽离不彻底
- 状态同步需要手动保证
### 2.5.2 v2.0(架构重构)
**特点**
- 纯决策层的派单引擎
- 统一的声明周期管理
- 事件驱动架构
- 兼容层保证平滑迁移
**优点**
- 职责清晰
- 易于扩展
- 易于测试
- 向后兼容
**缺点**
- 组件数量增加
- 学习曲线稍陡
---
## 2.6 关键里程碑
| 日期 | 里程碑 | 说明 |
|------|--------|------|
| 2025-12 | 阶段1完成 | 基础数据层 |
| 2026-01 | 阶段2完成 | 工单引擎、状态机、REST API |
| 2026-01-06 | 架构重构方案 | 重新定义组件职责 |
| 2026-01 | 阶段3规划 | 智能调度系统(部分完成) |
| 2026 Q2 | 安保条线 | 计划实施 |
| 2026 Q3 | 工程条线 | 计划实施 |
| 2026 Q4 | 客服条线 | 计划实施 |
---
**下一章**[Part 3: 核心架构设计](./part3-核心架构设计.md)

View File

@@ -0,0 +1,785 @@
# Part 3: 核心架构设计
本文档详细说明 Ops 模块的核心架构设计包括服务职责划分、分层设计、Redis + MySQL 混合队列等关键技术方案。
---
## 3.1 服务职责划分
### 3.1.1 DispatchEngine派单引擎- 纯决策层
**职责定义**
DispatchEngine 是一个纯决策层组件只负责根据策略推荐最<E88D90><E69C80><EFBFBD>适的执行人员不涉及状态管理、设备通知或业务逻辑。
**核心接口**
```java
public interface DispatchEngine {
/**
* 推荐执行人员(核心方法)
* @param context 派单上下文(区域、优先级、技能要求等)
* @return 推荐的执行人员ID如果没有合适的返回null
*/
AssigneeRecommendation recommendAssignee(DispatchContext context);
/**
* 批量推荐(用于选择最优人员)
*/
List<AssigneeRecommendation> recommendAssignees(DispatchContext context, int limit);
/**
* 判断是否可以打断当前任务
*/
InterruptDecision evaluateInterrupt(Long currentAssigneeId, DispatchContext urgentContext);
/**
* 注册派单策略
*/
void registerStrategy(DispatchStrategy strategy);
/**
* 注册业务类型与策略的映射
*/
void registerBusinessTypeStrategy(String businessType, String strategyName);
}
```
**推荐结果AssigneeRecommendation**
```java
@Data
@Builder
public class AssigneeRecommendation {
private Long assigneeId; // 执行人员ID
private String assigneeName; // 执行人员姓名
private Integer score; // 匹配分数 0-100
private String reason; // 推荐理由
private Double distance; // 距离(米)
private Integer currentLoad; // 当前任务数
}
```
**打断决策InterruptDecision**
```java
@Data
@Builder
public class InterruptDecision {
private boolean canInterrupt; // 是否可以打断
private String reason; // 决策理由
private Integer currentPriority; // 当前任务优先级
private Integer urgentPriority; // 紧急任务优先级
}
```
**设计原则**
1. **单一职责**:只负责决策,不管状态管理和业务逻辑
2. **策略模式**:通过不同的策略实现不同的派单算法
3. **可扩展性**:注册新策略,无需修改核心代码
### 3.1.2 OrderLifecycleManager生命周期管理- 状态同步器
**职责定义**
OrderLifecycleManager 负责统一管理工单状态和队列状态的同步变更,确保两者的一致性。
**核心接口**
```java
public interface OrderLifecycleManager {
/**
* 暂停工单(同步更新队列状态和工单状态)
*/
void pauseOrder(Long orderId, Long operatorId, String reason);
/**
* 恢复工单(同步更新队列状态和工单状态)
*/
void resumeOrder(Long orderId, Long operatorId);
/**
* 打断工单P0紧急任务场景
* 同步更新工单状态ARRIVED → PAUSED+ 队列状态PROCESSING → PAUSED
*/
void interruptOrder(Long orderId, Long urgentOrderId, Long operatorId);
/**
* 完成工单(清理队列记录)
*/
void completeOrder(Long orderId, Long operatorId);
/**
* 取消工单
*/
void cancelOrder(Long orderId, Long operatorId, String reason);
}
```
**核心实现**
```java
@Service
@Slf4j
public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
@Resource
private OrderStateMachine orderStateMachine;
@Resource
private OrderQueueService orderQueueService;
@Resource
private OrderEventPublisher eventPublisher;
@Override
@Transactional(rollbackFor = Exception.class)
public void pauseOrder(Long orderId, Long operatorId, String reason) {
// 1. 查询工单和队列
OpsOrderDO order = getOrderByOrderId(orderId);
OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(orderId);
// 2. 验证状态
validateCanPause(order, queueDTO);
// 3. 同步转换状态
// 工单状态ARRIVED → PAUSED
orderStateMachine.transition(order, PAUSED,
OperatorTypeEnum.CLEANER, operatorId, reason);
// 队列状态PROCESSING → PAUSED
if (queueDTO != null) {
orderQueueService.pauseTask(queueDTO.getId());
}
// 4. 发布事件
eventPublisher.publishStateChanged(OrderStateChangedEvent.builder()
.orderId(orderId)
.orderType(order.getOrderType())
.oldStatus(ARRIVED)
.newStatus(PAUSED)
.operatorId(operatorId)
.build());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void interruptOrder(Long orderId, Long urgentOrderId, Long operatorId) {
// 1. 查询工单和队列
OpsOrderDO order = getOrderByOrderId(orderId);
OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(orderId);
// 2. 验证状态(只有 ARRIVED 可以被打断)
if (!canInterrupt(order.getStatus())) {
throw new IllegalStateException("工单状态不允许被打断: " + order.getStatus());
}
// 3. 同步转换状态
orderStateMachine.transition(order, PAUSED,
OperatorTypeEnum.SYSTEM, operatorId, "被P0任务打断: " + urgentOrderId);
if (queueDTO != null) {
orderQueueService.pauseTask(queueDTO.getId());
orderQueueService.updateEventMessage(queueDTO.getId(),
"被P0任务打断紧急工单ID: " + urgentOrderId);
}
// 4. 发布事件
eventPublisher.publishStateChanged(OrderStateChangedEvent.builder()
.orderId(orderId)
.orderType(order.getOrderType())
.oldStatus(ARRIVED)
.newStatus(PAUSED)
.operatorId(operatorId)
.addPayload("urgentOrderId", urgentOrderId)
.addPayload("interruptReason", "P0_TASK_INTERRUPT")
.build());
}
// ... 其他方法实现
}
```
**关键特性**
1. **事务保证**:所有状态变更在事务中完成,保证原子性
2. **状态同步**:工单状态和队列状态同步更新
3. **事件发布**:状态变更完成后发布领域事件
### 3.1.3 OrderStateMachine状态机- 纯状态管理
**职责定义**
OrderStateMachine 负责验证状态转换规则和记录事件,不触发任何业务逻辑。
**核心接口**
```java
public interface OrderStateMachine {
/**
* 执行状态转换(不触发任何业务逻辑)
* @return 状态转换结果
*/
TransitionResult transition(OpsOrderDO order,
WorkOrderStatusEnum newStatus,
OperatorTypeEnum operatorType,
Long operatorId,
String remark);
/**
* 检查状态转换是否合法
*/
boolean canTransition(WorkOrderStatusEnum from, WorkOrderStatusEnum to);
/**
* 获取允许的转换目标
*/
Set<WorkOrderStatusEnum> getAllowedTransitions(WorkOrderStatusEnum from);
}
```
**状态转换规则**
```java
private static final Map<WorkOrderStatusEnum, Set<WorkOrderStatusEnum>> TRANSITIONS = Map.of(
PENDING, Set.of(ASSIGNED, CANCELLED),
ASSIGNED, Set.of(ARRIVED, CANCELLED),
ARRIVED, Set.of(PAUSED, COMPLETED),
PAUSED, Set.of(ARRIVED, CANCELLED),
COMPLETED, Collections.emptySet(),
CANCELLED, Collections.emptySet()
);
```
**设计原则**
1. **纯状态管理**:只管状态转换,不管业务逻辑
2. **事件溯源**:记录所有状态变更到事件表
3. **规则验证**:验证状态转换的合法性
### 3.1.4 OrderEventPublisher事件发布器- 业务解耦器
**职责定义**
OrderEventPublisher 负责发布工单相关的领域事件,实现业务解耦。
**核心接口**
```java
public interface OrderEventPublisher {
/**
* 发布状态变更事件
*/
void publishStateChanged(OrderStateChangedEvent event);
/**
* 发布工单创建事件
*/
void publishOrderCreated(OrderCreatedEvent event);
/**
* 发布工单完成事件
*/
void publishOrderCompleted(OrderCompletedEvent event);
}
```
**领域事件定义**
```java
@Data
@Builder
public class OrderStateChangedEvent {
private Long orderId;
private String orderType; // 工单类型 CLEAN/REPAIR/SECURITY
private WorkOrderStatusEnum oldStatus;
private WorkOrderStatusEnum newStatus;
private OperatorTypeEnum operatorType;
private Long operatorId;
private LocalDateTime eventTime;
private Map<String, Object> payload; // 扩展信息
}
```
**使用示例**
```java
// 保洁工单事件处理器
@Component
public class CleanOrderEventHandler {
@EventListener
public void onStateChanged(OrderStateChangedEvent event) {
// 只处理保洁类型的工单
if (!"CLEAN".equals(event.getOrderType())) {
return;
}
switch (event.getNewStatus()) {
case ARRIVED:
handleArrived(event);
break;
case PAUSED:
handlePaused(event);
break;
case COMPLETED:
handleCompleted(event);
break;
}
}
}
```
---
## 3.2 分层架构设计
### 3.2.1 模块结构图
```
viewsh-module-ops/
├── viewsh-module-ops-api/ # [契约层]
│ ├── enums/ # 枚举类
│ │ ├── WorkOrderTypeEnum.java # 工单类型
│ │ ├── WorkOrderStatusEnum.java # 工单状态
│ │ ├── PriorityEnum.java # 优先级
│ │ ├── OrderQueueStatusEnum.java # 队列状态
│ │ └── ...
│ ├── dto/ # 数据传输对象
│ │ ├── order/
│ │ ├── queue/
│ │ └── ...
│ ├── event/ # 领域事件定义
│ │ ├── OrderStateChangedEvent.java
│ │ ├── OrderCreatedEvent.java
│ │ └── OrderCompletedEvent.java
│ └── api/ # 服务接口
│ ├── dispatch/DispatchEngineService.java # 派单引擎接口
│ └<><E29494>─ queue/OrderQueueService.java # 队列服务接口
├── viewsh-module-ops-biz/ # [通用业务层]
│ ├── core/ # 核心组件
│ │ ├── statemachine/ # 状态机
│ │ │ ├── OrderStateMachine.java # 状态机接口
│ │ │ └── OrderStateMachineImpl.java # 状态机实现
│ │ ├── lifecycle/ # 生命周期管理
│ │ │ ├── OrderLifecycleManager.java # 生命周期管理接口
│ │ │ └── OrderLifecycleManagerImpl.java # 生命周期管理实现
│ │ ├── dispatch/ # 派单引擎
│ │ │ ├── DispatchEngine.java # 派单引擎接口
│ │ │ ├── DispatchEngineImpl.java # 派单引擎实现
│ │ │ ├── DispatchStrategy.java # 派单策略接口
│ │ │ └── DispatchContext.java # 派单上下文
│ │ ├── event/ # 事件处理
│ │ │ ├── OrderEventPublisher.java # 事件发布器接口
│ │ │ └── OrderEventPublisherImpl.java # 事件发布器实现
│ │ └── queue/ # 队列管理
│ │ ├── OrderQueueService.java # 队列服务接口
│ │ └── OrderQueueServiceImpl.java # 队列服务实现
│ │
│ ├── service/ # 业务服务
│ │ ├── OrderService.java # 通用工单服务接口
│ │ └── OrderServiceImpl.java # 通用工单服务实现
│ │
│ └── dal/ # 数据访问层
│ ├── dataobject/ # DO 实体
│ └── mysql/ # Mapper 接口
├── viewsh-module-environment-biz/ # [保洁业务层]
│ ├── service/ # 业务服务
│ │ ├── cleanorder/
│ │ │ ├── CleanOrderService.java # 保洁工单服务接口
│ │ │ └── CleanOrderServiceImpl.java # 保洁工单服务实现
│ │ ├── cleaner/
│ │ │ └── CleanerStatusService.java # 保洁员状态服务
│ │ └── dispatch/
│ │ └── CleanerAreaAssignStrategy.java # 保洁派单策略
│ │
│ └── handler/ # 事件处理器
│ └── CleanOrderEventHandler.java # 保洁工单事件处理器
├── viewsh-module-security-biz/ # [安保业务层]
│ └── ...(待实现)
├── viewsh-module-facilities-biz/ # [工程业务层]
│ └── ...(待实现)
├── viewsh-module-service-biz/ # [客服业务层]
│ └── ...(待实现)
└── viewsh-module-ops-server/ # [宿主层]
└── controller/ # REST API
└── admin/
└── OpsOrderController.java
```
### 3.2.2 层级依赖关系
```
┌─────────────────────────────────────────────────────────┐
│ 宿主层 (Server) │
│ - REST API 聚合 │
│ - 全局配置管理 │
└──────────────────────────┬──────────────────────────────┘
┌──────────────────┴──────────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ 业务线层 (Biz) │ │ 通用业务层 │
│ - 业务逻辑 │ │ (Ops Biz) │
│ - 业务规则 │ ─────────▶ │ - 核心引擎 │
│ - 扩展字段 │ 依赖 │ - 公共逻辑 │
└──────────────────┘ └────────┬─────────┘
┌──────────────────────────────────┴──────────┐
│ │
▼ │
┌──────────────────────────────────────────────┴──────┐
│ 契约层 (API) │
│ - DTO 定义 │
│ - 枚举定义 │
│ - 服务接口 │
│ - 领域事件定义 │
└──────────────────────────────────────────────────┘
```
**依赖规则**
1. **纵向依赖**Server → Biz → API
2. **横向依赖**:业务线 → 通用业务层 → 契约层
3. **禁止依赖**API 层不依赖任何业务模块
### 3.2.3 服务边界定义
**契约层API**
- 定义DTO、枚举、服务接口、领域事件
- 不依赖:任何业务模块
- 被依赖:所有业务模块
**通用业务层Biz**
- 定义:工单引擎、状态机、队列、派单引擎
- 依赖API 层
- 被依赖:业务线层
- 职责:提供通用的工单管理能力
**业务线层**
- 定义:业务特定逻辑、派单策略、事件处理器
- 依赖API 层、通用业务层
- 职责:实现特定业务类型的功能
---
## 3.3 Redis + MySQL 混合队列设计
### 3.3.1 为什么选择混合队列
| 对比项 | 纯 Redis | 纯 MySQL | Redis + MySQL |
|--------|----------|----------|---------------|
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 持久化 | ❌ 需额外配置 | ✅ 天然支持 | ✅ MySQL 持久化 |
| 优先级支持 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 数据一致性 | ⚠️ 可能丢失 | ✅ 强一致性 | ✅ 最终一致性 |
| 复杂查询 | ⚠️ 有限 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 成本 | 中 | 低 | 中 |
**结论**Redis + MySQL 混合队列兼顾性能、可靠性、功能。
### 3.3.2 架构设计
```
┌─────────────────────────────────────────────────────────────┐
│ 应用层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 入队操作 │ │ 出队操作 │ │ 查询操作 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼──────────────────┼──────────────────┼─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Redis Queue Layer │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Sorted Set: ops:order:queue:{cleanerId} │ │
│ │ Score: 优先级分数 + 时间戳 │ │
│ │ Member: JSON字符串 {queueId, orderId, priority...} │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Hash: ops:order:queue:info:{queueId} │ │
│ │ 存储完整的队列信息(用于查询单个记录) │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Lock: ops:order:queue:lock:{cleanerId} │ │
│ │ 分布式锁(防止并发操作) │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ ▲
│ 异步同步 │ 查询历史
▼ │
┌─────────────────────────────────────────────────────────────┐
│ MySQL Persistent Layer │
│ ┌───────────────────────────────────────────────────┐ │
│ │ ops_order_queue 表 │ │
│ │ - 持久化存储所有队列记录 │ │
│ │ - 支持复杂查询、统计分析 │ │
│ │ - 数据备份和恢复 │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 3.3.3 Redis 数据结构
**Sorted Set实时队列**
**Key 格式**`ops:order:queue:{cleanerId}`
**Score 计算公式**
```
score = priorityScore + timestamp
priorityScore:
P0 = 0
P1 = 1000000
P2 = 2000000
timestamp: 当前时间戳(毫秒)
结果:优先级高的排在前面,同优先级按时间排序
```
**Member 格式**
```json
{
"queueId": 123,
"orderId": 456,
"priority": 1,
"status": "WAITING",
"enqueueTime": 1234567890
}
```
**操作命令**
```bash
# 入队
ZADD ops:order:queue:1001 score '{"queueId":123,...}'
# 出队(获取最高优先级任务)
ZPOPMIN ops:order:queue:1001
# 查询待办数量
ZCARD ops:order:queue:1001
# 查询前N个任务
ZRANGE ops:order:queue:1001 0 N WITHSCORES
```
**Hash队列详细信息**
**Key 格式**`ops:order:queue:info:{queueId}`
**用途**:快速查询单个队列记录的详细信息
### 3.3.4 数据同步策略
**写入流程(异步双写)**
```java
public Long enqueue(OrderQueueDTO dto) {
// 1. 先写 MySQL保证数据不丢失
Long queueId = mysqlMapper.insert(dto);
// 2. 再写 Redis异步失败不影响主流程
CompletableFuture.runAsync(() -> {
try {
redisQueueService.enqueue(dto);
} catch (Exception e) {
log.error("Redis 队列写入失败,依赖定时同步任务补偿", e);
}
});
return queueId;
}
```
**读取流程(优先 Redis**
```java
public List<OrderQueueDTO> getTasksByUserId(Long userId) {
// 1. 先查 Redis实时数据
List<OrderQueueDTO> redisTasks = redisQueueService.getTasks(userId);
if (!CollectionUtils.isEmpty(redisTasks)) {
return redisTasks;
}
// 2. Redis 没有数据,查 MySQL 并同步到 Redis
List<OrderQueueDTO> mysqlTasks = mysqlMapper.selectByUserId(userId);
redisQueueService.batchEnqueue(mysqlTasks);
return mysqlTasks;
}
```
**定时同步(数据一致性保障)**
```java
@Scheduled(cron = "0 */5 * * * ?")
public void syncMySQLToRedis() {
// 查询最近1小时变更的数据
List<OrderQueueDTO> changedTasks = mysqlMapper.selectChangedAfter(DateUtils.addHours(-1));
// 同步到 Redis
redisQueueService.batchEnqueue(changedTasks);
log.info("定时同步完成,同步{}条记录", changedTasks.size());
}
```
---
## 3.4 设计模式应用
### 3.4.1 状态机模式
**定义**
允许对象在内部状态改变时改变其行为,对象看起来就像改变了它的类一样。
**应用场景**
- 工单状态转换
- 状态转换规则验证
- 事件记录
**优点**
- 集中管理状态转换规则
- 避免状态散落在各个业务方法
- 易于维护和扩展
### 3.4.2 观察者模式
**定义**
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
**应用场景**
- 状态变更事件监听
- 业务线扩展(保洁、安保、工程、客服)
**实现方式**
- 旧版本监听器Listener
- 新版本事件处理器EventHandler+ Spring @EventListener
**优点**
- 解耦核心引擎与业务逻辑
- 业务线可独立扩展
- 支持多监听器协同工作
### 3.4.3 策略模式
**定义**
定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。
**应用场景**
- 派单策略(区域优先、负载均衡、技能匹配)
- 不同业务类型使用不同的派单策略
**实现方式**
```java
public interface DispatchStrategy {
AssigneeRecommendation recommend(DispatchContext context);
}
@Component("cleanerAreaStrategy")
public class CleanerAreaAssignStrategy implements DispatchStrategy {
// 保洁派单策略实现
}
@Component("repairSkillStrategy")
public class RepairSkillAssignStrategy implements DispatchStrategy {
// 维修派单策略实现
}
```
**优点**
- 算法与客户端分离
- 避免多重条件判断
- 易于扩展新策略
### 3.4.4 模板方法模式
**定义**
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
**应用场景**
- 工单创建流程(创建 → 分配 → 通知)
- 派单流程(推荐 → 入队 → 派发 → 通知)
**实现方式**
```java
public abstract class AbstractDispatchService {
public final void dispatch(Long orderId) {
// 1. 推荐执行人员
AssigneeRecommendation recommendation = recommendAssignee(orderId);
// 2. 入队
Long queueId = enqueue(orderId, recommendation.getAssigneeId());
// 3. 派发(子类实现)
doDispatch(queueId, recommendation);
// 4. 通知(子类实现)
notifyAssignee(queueId, recommendation);
}
protected abstract void doDispatch(Long queueId, AssigneeRecommendation recommendation);
protected abstract void notifyAssignee(Long queueId, AssigneeRecommendation recommendation);
}
```
---
## 3.5 调用链设计
### 3.5.1 工单创建流程
```
CleanOrderService.createAutoCleanOrder()
├─> DispatchEngine.recommendAssignee() 【通用】推荐保洁员
│ └─> CleanerAreaAssignStrategy 【保洁】执行推荐逻辑
├─> OrderService.createOrder() 【通用】创建工单
│ └─> OrderStateMachine.transition() 【通用】状态PENDING
│ └─> OrderEventPublisher.publish() 【通用】发布创建事件
├─> OrderQueueService.enqueue() 【通用】入队
└─> CleanOrderEventHandler.onCreate() 【保洁】处理创建事件
└─> 根据保洁员状态决定是否立即派单
```
### 3.5.2 工单打断流程
```
CleanOrderService.upgradePriorityToP0()
├─> DispatchEngine.evaluateInterrupt() 【通用】判断是否可打断
├─> OrderLifecycleManager.interruptOrder() 【通用】打断工单
│ ├─> OrderStateMachine.transition() 【通用】状态ARRIVED → PAUSED
│ ├─> OrderQueueService.pauseTask() 【通用】队列PROCESSING → PAUSED
│ └─> OrderEventPublisher.publish() 【通用】发布状态变更事件
├─> CleanOrderEventHandler.onInterrupt() 【保洁】处理打断事件
│ ├─> 更新保洁员状态
│ ├─> 记录扩展信息
│ └─> 派发P0工单
```
---
**下一章**[Part 4: 关键技术方案](./part4-关键技术方案.md)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,977 @@
# Part 5: 架构重构实践
本文档详细记录 Ops 模块架构重构的完整实践过程,为未来的架构演进提供参考。
---
## 5.1 问题识别
在阶段2和阶段3的实施过程中我们发现了一些架构问题需要进行重构。
### 5.1.1 DispatchEngine 职责不清晰
**问题表现**
DispatchEngine 接口定义了 13 个方法,但很多方法返回 null 或空实现:
```java
public interface DispatchEngine {
AssigneeRecommendation recommendAssignee(DispatchContext context);
// 以下方法职责不清晰
void autoDispatch(Long orderId); // 实际只是调用 orderQueueService.startExecution()
void handleOrderCompleted(Long orderId); // 业务逻辑,不应该在引擎中
void notifyAssignee(Long assigneeId); // 设备通知,不应该在引擎中
// 还有 10 个类似的方法...
}
```
**问题分析**
1. **职责边界模糊**:派单引擎混合了决策、业务逻辑、设备通知等多种职责
2. **命名误导**`autoDispatch` 看起来是自动派单,实际只是更新队列状态
3. **扩展困难**:新增业务类型时,需要修改 DispatchEngine 核心代码
**影响**
- 开发人员困惑:不知道哪些功能应该放在 DispatchEngine 中
- 测试困难:职责过多导致单元测试复杂
- 维护成本高:修改一处可能影响多个功能
### 5.1.2 通用方法抽离不彻底
**问题表现**
`OpsOrderService` 只有基础的 CRUD 操作,暂停/恢复等通用功能散落在各处:
```java
// OpsOrderService 中只有基础操作
public interface OpsOrderService {
Long createOrder(OrderCreateReqDTO reqDTO);
void updateOrder(OrderUpdateReqDTO reqDTO);
void deleteOrder(Long orderId);
OpsOrderRespDTO getOrder(Long orderId);
}
// 暂停/恢复逻辑直接在 CleanOrderService 中实现
@Service
public class CleanOrderServiceImpl {
public void pauseOrder(Long orderId) {
// 1. 更新工单状态
orderStateMachine.transition(order, PAUSED, ...);
// 2. 更新队列状态 - 手动保证同步
OrderQueueDTO queue = orderQueueService.getByOpsOrderId(orderId);
orderQueueService.pauseTask(queue.getId());
// 问题:状态同步逻辑需要业务层手动处理,容易遗漏
}
}
```
**问题分析**
1. **重复代码**:每个业务类型都要实现一遍暂停/恢复逻辑
2. **一致性风险**:手动同步工单状态和队列状态,容易出现不一致
3. **缺少统一入口**:没有统一的生命周期管理
**影响**
- 代码重复率高
- 容易出现状态不一致的 bug
- 新增业务类型时需要重复实现
### 5.1.3 Listener 设计不明确
**问题表现**
监听器注册是全局的,无法区分业务类型,且存在循环依赖风险:
```java
@Component
public class CleanOrderStateChangeListener implements OrderStateChangeListener {
@Resource
private CleanOrderService cleanOrderService; // 依赖业务服务
@Override
public void onStateChanged(OrderStateChangedEvent event) {
// 问题1无法区分业务类型需要手动判断
if (!"CLEAN".equals(event.getOrderType())) {
return;
}
// 问题2直接调用业务服务可能产生循环依赖
cleanOrderService.handleStateChanged(event);
}
}
// CleanOrderService 同时依赖 DispatchEngine
@Service
public class CleanOrderServiceImpl {
@Resource
private DispatchEngine dispatchEngine; // 可能产生循环依赖
}
```
**问题分析**
1. **监听器注册全局化**:所有监听器都会收到所有事件,需要手动过滤
2. **缺少优先级定义**:多个监听器执行顺序不确定
3. **循环依赖风险**Listener → Service → Engine → Listener
**影响**
- 性能浪费(全局广播)
- 循环依赖导致启动失败
- 难以控制执行顺序
### 5.1.4 状态与队列状态同步问题
**问题表现**
工单状态(`WorkOrderStatusEnum`)和队列状态(`OrderQueueStatusEnum`)是两个独立的状态机:
```java
// 工单状态
PENDING ASSIGNED ARRIVED PAUSED COMPLETED
// 队列状态
WAITING PROCESSING PAUSED REMOVED
```
**同步困难**
```java
// 暂停工单时,需要同时更新两个状态
public void pauseOrder(Long orderId) {
// 步骤1更新工单状态
orderStateMachine.transition(order, WorkOrderStatusEnum.PAUSED, ...);
// 步骤2查询队列
OrderQueueDTO queue = orderQueueService.getByOpsOrderId(orderId);
// 步骤3更新队列状态
orderQueueService.updateStatus(queue.getId(), OrderQueueStatusEnum.PAUSED);
// 问题如果步骤3失败会导致状态不一致
}
```
**问题分析**
1. **双写一致性**:需要手动保证两个状态的同步
2. **事务边界不清晰**:状态更新可能跨越多个服务
3. **缺少统一协调**:没有统一的地方管理状态同步
**影响**
- 容易出现状态不一致
- 调试困难
- 数据修复成本高
---
## 5.2 重构方案设计
### 5.2.1 核心设计原则
**1. 单一职责原则SRP**
- 每个组件只负责一个明确的职责
- DispatchEngine 只负责决策
- OrderStateMachine 只负责状态管理
- OrderLifecycleManager 负责状态同步
**2. 开闭原则OCP**
- 通过接口和策略模式支持扩展
- 新增业务类型不需要修改核心代码
**3. 依赖倒置原则DIP**
- 业务层依赖通用层的抽象接口
- 通用层不依赖具体业务实现
**4. 清晰分层**
- 通用能力下沉(状态机、队列、派单引擎)
- 业务能力上浮(业务规则、事件处理)
### 5.2.2 组件职责重新定义
**DispatchEngine派单引擎- 纯决策层**
```java
public interface DispatchEngine {
/**
* 推荐执行人员(核心职责)
*/
AssigneeRecommendation recommendAssignee(DispatchContext context);
/**
* 判断是否可以打断当前任务
*/
InterruptDecision evaluateInterrupt(Long currentAssigneeId, DispatchContext urgentContext);
/**
* 注册派单策略
*/
void registerStrategy(String businessType, AssignStrategy strategy);
}
```
**职责**
- ✅ 根据上下文推荐最合适的执行人员
- ✅ 评估是否可以打断当前任务
- ✅ 管理派单策略
- ❌ 不负责状态管理
- ❌ 不负责设备通知
- ❌ 不负责业务逻辑
**OrderLifecycleManager生命周期管理- 新增组件**
```java
public interface OrderLifecycleManager {
/**
* 暂停工单(同步更新工单状态和队列状态)
*/
void pauseOrder(Long orderId, Long operatorId, String reason);
/**
* 恢复工单
*/
void resumeOrder(Long orderId, Long operatorId);
/**
* 打断工单P0紧急任务场景
*/
void interruptOrder(Long orderId, Long urgentOrderId, Long operatorId);
/**
* 完成工单
*/
void completeOrder(Long orderId, Long operatorId);
}
```
**职责**
- ✅ 统一管理工单状态和队列状态的同步变更
- ✅ 保证状态一致性(事务)
- ✅ 发布状态变更事件
- ❌ 不包含业务特定逻辑
**OrderStateMachine状态机- 优化**
```java
public interface OrderStateMachine {
/**
* 执行状态转换(不触发业务逻辑)
*/
TransitionResult transition(OpsOrderDO order,
WorkOrderStatusEnum newStatus,
OperatorTypeEnum operatorType,
Long operatorId,
String remark);
/**
* 检查状态转换是否合法
*/
boolean canTransition(WorkOrderStatusEnum from, WorkOrderStatusEnum to);
}
```
**职责**
- ✅ 验证状态转换规则
- ✅ 记录状态变更事件
- ❌ 不再包含监听器机制(移除)
- ❌ 不触发任何业务逻辑
**OrderEventPublisher事件发布器- 新增组件**
```java
public interface OrderEventPublisher {
/**
* 发布状态变更事件
*/
void publishStateChanged(OrderStateChangedEvent event);
/**
* 发布工单创建事件
*/
void publishOrderCreated(OrderCreatedEvent event);
}
```
**职责**
- ✅ 发布工单相关的领域事件
- ✅ 实现业务解耦
### 5.2.3 新架构设计图
**重构前**
```
┌─────────────────────────────────────┐
│ DispatchEngine │
职责混乱13个方法
│ - 推荐人员 │
│ - 自动派单 │
│ - 设备通知 │
│ - 业务逻辑 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ OrderStateMachine │
│ (包含监听器,触发业务逻辑) │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ CleanOrderService │
│ (手动同步状态,容易出错) │
└─────────────────────────────────────┘
```
**重构后**
```
┌──────────────────┐ ┌──────────────────┐
│ DispatchEngine │ │ OrderLifecycle │
│ (纯决策层) │ │ Manager │
│ - 推荐执行人 │ │ (状态同步器) │
│ - 评估打断 │ │ - 暂停/恢复 │
└────────┬─────────┘ │ - 打断/完成 │
│ └────────┬─────────┘
│ │
▼ ▼
┌────────────────────────────────────┐
│ OrderStateMachine │
│ (纯状态管理,不触发业务逻辑) │
└────────┬───────────────────────────┘
┌────────────────────────────────────┐
│ OrderEventPublisher │
│ (事件发布器) │
└────────┬───────────────────────────┘
┌────────────────────────────────────┐
│ CleanOrderEventHandler │
│ (保洁业务事件处理器) │
└────────────────────────────────────┘
```
### 5.2.4 重构前后对比
| 方面 | 重构前 | 重构后 |
|------|--------|--------|
| 职责划分 | 模糊,混合多种职责 | 清晰,单一职责 |
| 状态同步 | 业务层手动同步 | OrderLifecycleManager 统一管理 |
| 业务扩展 | 修改核心代码 | 实现事件处理器 |
| 循环依赖 | 存在风险 | 通过事件解耦 |
| 测试难度 | 困难,职责过多 | 容易,职责单一 |
---
## 5.3 重构实施过程
### 5.3.1 Phase 1: 基础重构
**任务 1重构 DispatchEngine 接口**
**Before**
```java
public interface DispatchEngine {
AssigneeRecommendation recommendAssignee(DispatchContext context);
void autoDispatch(Long orderId);
void handleOrderCompleted(Long orderId);
void notifyAssignee(Long assigneeId);
// ... 还有 9 个方法
}
```
**After**
```java
public interface DispatchEngine {
/**
* 推荐执行人员(核心方法)
*/
AssigneeRecommendation recommendAssignee(DispatchContext context);
/**
* 评估是否可以打断
*/
InterruptDecision evaluateInterrupt(Long currentAssigneeId, DispatchContext urgentContext);
/**
* 注册派单策略
*/
void registerStrategy(String businessType, AssignStrategy strategy);
}
```
**任务 2创建 OrderLifecycleManager**
```java
@Service
@Slf4j
public class OrderLifecycleManagerImpl implements OrderLifecycleManager {
@Resource
private OrderStateMachine orderStateMachine;
@Resource
private OrderQueueService orderQueueService;
@Resource
private OrderEventPublisher eventPublisher;
@Override
@Transactional(rollbackFor = Exception.class)
public void pauseOrder(Long orderId, Long operatorId, String reason) {
// 1. 查询工单和队列
OpsOrderDO order = getOrderByOrderId(orderId);
OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(orderId);
// 2. 验证状态
validateCanPause(order, queueDTO);
// 3. 同步转换状态(事务保证原子性)
orderStateMachine.transition(order, PAUSED, operatorType, operatorId, reason);
if (queueDTO != null) {
orderQueueService.pauseTask(queueDTO.getId());
}
// 4. 发布事件
eventPublisher.publishStateChanged(OrderStateChangedEvent.builder()
.orderId(orderId)
.orderType(order.getOrderType())
.oldStatus(ARRIVED)
.newStatus(PAUSED)
.operatorId(operatorId)
.build());
}
// ... 其他方法实现
}
```
**任务 3重构 OrderStateMachine移除监听器**
**Before**
```java
@Service
public class OrderStateMachine {
private List<OrderStateChangeListener> listeners = new ArrayList<>();
public void registerListener(OrderStateChangeListener listener) {
listeners.add(listener);
}
@Transactional
public void transition(OpsOrderDO order, WorkOrderStatusEnum newStatus, ...) {
// 更新状态
order.setStatus(newStatus.name());
orderMapper.updateById(order);
// 触发监听器
for (OrderStateChangeListener listener : listeners) {
listener.onStateChanged(new OrderStateChangedEvent(...));
}
}
}
```
**After**
```java
@Component
public class OrderStateMachine {
@Resource
private OrderEventPublisher eventPublisher; // 改为使用事件发布器
@Transactional
public void transition(OpsOrderDO order, WorkOrderStatusEnum newStatus, ...) {
// 1. 验证状态转换
validateTransition(currentStatus, newStatus);
// 2. 更新状态
order.setStatus(newStatus.name());
orderMapper.updateById(order);
// 3. 记录事件
eventService.recordEvent(orderId, oldStatus, newStatus, ...);
// 4. 发布事件(替代监听器)
eventPublisher.publishStateChanged(OrderStateChangedEvent.builder()
.orderId(order.getId())
.oldStatus(oldStatus)
.newStatus(newStatus)
.build());
}
}
```
**任务 4创建 OrderEventPublisher**
```java
@Service
@Slf4j
public class OrderEventPublisherImpl implements OrderEventPublisher {
@Resource
private ApplicationEventPublisher applicationEventPublisher;
@Override
@Async // 异步发布,不阻塞主流程
public void publishStateChanged(OrderStateChangedEvent event) {
try {
applicationEventPublisher.publishEvent(event);
log.info("状态变更事件已发布: orderId={}, {} -> {}",
event.getOrderId(), event.getOldStatus(), event.getNewStatus());
} catch (Exception e) {
log.error("状态变更事件发布失败: orderId={}", event.getOrderId(), e);
}
}
}
```
### 5.3.2 Phase 2: 业务层改造
**任务 1创建事件处理器替代监听器**
**BeforeListener**
```java
@Component
public class CleanOrderStateChangeListener implements OrderStateChangeListener {
@Resource
private CleanOrderService cleanOrderService;
@Override
public void onStateChanged(OrderStateChangedEvent event) {
if (!"CLEAN".equals(event.getOrderType())) {
return;
}
cleanOrderService.handleStateChanged(event);
}
}
```
**AfterEventHandler**
```java
@Component
public class CleanOrderEventHandler {
@Resource
private CleanerStatusService cleanerStatusService;
@Resource
private BadgeNotificationService badgeNotificationService;
@EventListener
public void onStateChanged(OrderStateChangedEvent event) {
// 只处理保洁类型的工单
if (!"CLEAN".equals(event.getOrderType())) {
return;
}
switch (event.getNewStatus()) {
case ARRIVED:
handleArrived(event);
break;
case PAUSED:
handlePaused(event);
break;
case COMPLETED:
handleCompleted(event);
break;
}
}
private void handleArrived(OrderStateChangedEvent event) {
// 更新保洁员状态为 BUSY
cleanerStatusService.updateStatus(event.getOperatorId(), CleanerStatusEnum.BUSY);
// 发送工牌通知
badgeNotificationService.notifyWorkStart(event.getOperatorId(), event.getOrderId());
}
private void handleCompleted(OrderStateChangedEvent event) {
// 更新保洁员状态为 IDLE
cleanerStatusService.updateStatus(event.getOperatorId(), CleanerStatusEnum.IDLE);
// 自动派发下一个任务
dispatchEngine.autoDispatchNext(event.getOrderId(), event.getOperatorId());
}
}
```
**任务 2重构 CleanOrderService**
**Before**
```java
@Service
public class CleanOrderServiceImpl {
public void pauseOrder(Long orderId) {
// 手动同步状态
orderStateMachine.transition(order, PAUSED, ...);
OrderQueueDTO queue = orderQueueService.getByOpsOrderId(orderId);
orderQueueService.pauseTask(queue.getId());
}
}
```
**After**
```java
@Service
public class CleanOrderServiceImpl {
@Resource
private OrderLifecycleManager orderLifecycleManager; // 使用生命周期管理器
public void pauseOrder(Long orderId, String reason) {
// 委托给生命周期管理器,保证状态同步
orderLifecycleManager.pauseOrder(orderId, SecurityUtils.getLoginUserId(), reason);
// 业务特定逻辑
updateCleanerStatus(orderId, CleanerStatusEnum.PAUSED);
}
}
```
### 5.3.3 Phase 3: 优化与验证
**任务 1性能优化**
**优化点 1事件异步发布**
```java
@Service
public class OrderEventPublisherImpl {
@Async("eventExecutor") // 使用独立线程池
public void publishStateChanged(OrderStateChangedEvent event) {
applicationEventPublisher.publishEvent(event);
}
}
@Configuration
public class AsyncConfig {
@Bean("eventExecutor")
public Executor eventExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("event-");
executor.initialize();
return executor;
}
}
```
**优化点 2批量状态更新**
```java
public void batchPauseOrders(List<Long> orderIds, String reason) {
// 批量更新,减少数据库交互
orderLifecycleManager.batchPause(orderIds, operatorId, reason);
}
```
**任务 2集成测试**
```java
@SpringBootTest
class OrderLifecycleManagerIntegrationTest {
@Resource
private OrderLifecycleManager orderLifecycleManager;
@Resource
private OpsOrderMapper orderMapper;
@Resource
private OpsOrderQueueMapper queueMapper;
@Test
void testPauseOrder_ShouldSyncBothStatuses() {
// Given
Long orderId = createTestOrder();
// When
orderLifecycleManager.pauseOrder(orderId, 1L, "测试暂停");
// Then
OpsOrderDO order = orderMapper.selectById(orderId);
assertEquals("PAUSED", order.getStatus());
OpsOrderQueueDO queue = queueMapper.selectByOpsOrderId(orderId);
assertEquals("PAUSED", queue.getQueueStatus());
}
}
```
**任务 3文档更新**
更新以下文档:
- [x] part2-架构演进史.md - 添加重构历程
- [x] part3-核心架构设计.md - 更新服务职责
- [ ] part5-架构重构实践.md - 本文档
- [ ] API 文档 - 更新接口说明
---
## 5.4 重构收获与反思
### 5.4.1 成功经验
**1. 职责分离带来清晰度**
重构后,每个组件的职责非常明确:
- DispatchEngine只做决策不管执行
- OrderLifecycleManager只管状态同步不管业务逻辑
- OrderEventPublisher只管事件发布不管事件处理
- EventHandler只处理特定业务的事件
**收益**
- 新人快速理解代码
- 单元测试简单明确
- 问题定位更快
**2. 事件驱动架构提升扩展性**
通过事件驱动,新增业务类型只需:
```java
// 1. 实现事件处理器
@Component
public class RepairOrderEventHandler {
@EventListener
public void onStateChanged(OrderStateChangedEvent event) {
if (!"REPAIR".equals(event.getOrderType())) {
return;
}
// 处理维修工单的特定逻辑
}
}
// 2. 无需修改核心引擎代码
```
**收益**
- 核心引擎稳定,无需修改
- 业务团队独立开发
- 新功能上线快
**3. 兼容性保证平滑迁移**
保留旧实现作为兼容层:
```java
@Deprecated
public class OldDispatchEngine {
// 保留旧接口,内部委托给新实现
public void autoDispatch(Long orderId) {
orderLifecycleManager.dispatch(orderId);
}
}
```
**收益**
- 业务流程不中断
- 逐步迁移,风险可控
- 回滚方案简单
### 5.4.2 踩坑记录
**坑 1循环依赖问题**
**问题**
```java
@Service
public class DispatchEngineImpl {
@Resource
private CleanOrderService cleanOrderService; // DispatchEngine -> CleanOrderService
}
@Service
public class CleanOrderServiceImpl {
@Resource
private DispatchEngine dispatchEngine; // CleanOrderService -> DispatchEngine
}
// 启动报错Circular dependency detected
```
**解决方案**
```java
@Service
public class DispatchEngineImpl {
@Lazy // 使用延迟注入
@Resource
private CleanOrderService cleanOrderService;
}
```
**更好的解决方案**
```java
// 通过事件解耦,避免直接依赖
@Component
public class CleanOrderEventHandler {
@EventListener
public void onOrderDispatched(OrderDispatchedEvent event) {
// 处理派单事件
}
}
```
**坑 2状态同步的事务边界**
**问题**
```java
@Transactional
public void pauseOrder(Long orderId) {
// 更新工单状态(在事务内)
orderStateMachine.transition(order, PAUSED, ...);
// 更新队列状态(调用另一个服务,可能在另一个事务)
orderQueueService.pauseTask(queueId); // 如果失败,工单状态已提交
}
```
**解决方案**
```java
@Transactional(rollbackFor = Exception.class) // 确保所有异常都回滚
public void pauseOrder(Long orderId) {
// 所有数据库操作在同一个事务中
orderStateMachine.transition(order, PAUSED, ...);
orderQueueService.pauseTask(queueId);
// 事件发布在事务提交后(使用 @TransactionalEventListener
eventPublisher.publishStateChanged(...);
}
```
**坑 3事件处理器的执行顺序**
**问题**
```java
@Component
public class CleanOrderEventHandler {
@EventListener
public void onStateChanged(OrderStateChangedEvent event) {
// 处理器A更新保洁员状态
}
}
@Component
public class CleanOrderNotificationHandler {
@EventListener
public void onStateChanged(OrderStateChangedEvent event) {
// 处理器B发送通知
// 问题可能在处理器A之前执行
}
}
```
**解决方案**
```java
@Component
public class CleanOrderEventHandler {
@EventListener
@Order(1) // 优先级1先执行
public void onStateChanged(OrderStateChangedEvent event) {
// 更新保洁员状态
}
}
@Component
public class CleanOrderNotificationHandler {
@EventListener
@Order(2) // 优先级2后执行
public void onStateChanged(OrderStateChangedEvent event) {
// 发送通知
}
}
```
### 5.4.3 改进建议
**1. 从设计阶段就明确职责**
在编码前:
- 使用 UML 类图展示组件关系
- 定义清晰的接口契约
- 明确依赖方向
**2. 持续重构,不要积累技术债务**
建立机制:
- 每季度进行架构 Review
- 及时重构不合理的设计
- 不要等到问题严重才重构
**3. 完善测试,保证重构质量**
测试策略:
- 单元测试覆盖核心逻辑
- 集成测试覆盖端到端流程
- 重构前后对比测试
---
## 5.5 兼容性处理
### 5.5.1 保证平滑迁移
**兼容层设计**
```java
@Deprecated
@Service("oldDispatchEngine")
public class DispatchEngineAdapter implements OldDispatchEngine {
@Resource
private DispatchEngine newDispatchEngine;
@Resource
private OrderLifecycleManager orderLifecycleManager;
@Override
public void autoDispatch(Long orderId) {
// 委托给新实现
orderLifecycleManager.dispatch(orderId);
}
@Override
public void notifyAssignee(Long assigneeId) {
// 废弃方法,记录警告
log.warn("notifyAssignee() is deprecated, please use EventHandler instead");
}
}
```
### 5.5.2 逐步切换策略
**阶段 1保留旧接口**
- 新旧实现并存
- 旧接口内部调用新实现
- 添加 @Deprecated 注解
**阶段 2迁移业务代码**
- 逐个模块迁移到新接口
- 测试验证功能正常
**阶段 3清理旧代码**
- 确认所有业务已迁移
- 删除旧接口和兼容层
### 5.5.3 版本兼容性
**版本规划**
- v2.0: 新架构发布,旧接口标记为 Deprecated
- v2.1: 旧接口保留,持续迁移
- v3.0: 删除旧接口
---
**下一章**[Part 6: 性能与可靠性](./part6-性能与可靠性.md)

View File

@@ -0,0 +1,301 @@
# Part 6: 性能与可靠性
本文档详细说明 Ops 模块的性能优化策略和可靠性保障措施。
---
## 6.1 性能优化
### 6.1.1 Redis 缓存策略
**热点数据缓存**
```java
@Service
public class CleanerStatusService {
@Cacheable(value = "cleaner:status", key = "#userId", unless = "#result == null")
public CleanerStatusDO getStatus(Long userId) {
return cleanerStatusMapper.selectByUserId(userId);
}
@CacheEvict(value = "cleaner:status", key = "#userId")
public void updateStatus(Long userId, CleanerStatusEnum status) {
cleanerStatusMapper.updateStatus(userId, status);
}
}
```
**缓存更新策略**
- **Cache Aside**: 先更新数据库,再删除缓存
- **过期时间**: 5-30 分钟,避免数据过期
- **空值缓存**: 防止缓存穿透
### 6.1.2 数据库优化
**索引设计**
```sql
-- 工单表索引
CREATE INDEX idx_status ON ops_order(status);
CREATE INDEX idx_assignee_id ON ops_order(assignee_id);
CREATE INDEX idx_priority_status ON ops_order(priority, status);
CREATE INDEX idx_create_time ON ops_order(create_time);
-- 队列表索引
CREATE UNIQUE INDEX uk_user_order ON ops_order_queue(user_id, ops_order_id);
CREATE INDEX idx_status ON ops_order_queue(status);
CREATE INDEX idx_priority ON ops_order_queue(priority);
```
**查询优化**
```java
// ❌ N+1 查询
List<OpsOrderDO> orders = orderMapper.selectList(wrapper);
for (OpsOrderDO order : orders) {
UserDO user = userMapper.selectById(order.getAssigneeId()); // N次查询
}
// ✅ 批量查询
List<OpsOrderDO> orders = orderMapper.selectList(wrapper);
Set<Long> userIds = orders.stream().map(OpsOrderDO::getAssigneeId).collect(Collectors.toSet());
List<UserDO> users = userMapper.selectBatchIds(userIds); // 1次查询
Map<Long, UserDO> userMap = users.stream().collect(Collectors.toMap(UserDO::getId, u -> u));
```
### 6.1.3 异步处理
**事件异步发布**
```java
@Service
public class OrderEventPublisherImpl {
@Async("eventExecutor")
public void publishStateChanged(OrderStateChangedEvent event) {
applicationEventPublisher.publishEvent(event);
}
}
@Configuration
public class AsyncConfig {
@Bean("eventExecutor")
public Executor eventExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("event-");
executor.initialize();
return executor;
}
}
```
### 6.1.4 批量操作优化
```java
// 批量入队
public void batchEnqueue(List<OrderQueueDTO> queueList) {
// MySQL 批量插入
orderQueueMapper.insertBatch(queueList);
// Redis 批量写入
CompletableFuture.runAsync(() -> {
redisQueueService.batchEnqueue(queueList);
});
}
```
---
## 6.2 并发控制
### 6.2.1 分布式锁
**Redis 分布式锁**
```java
@Service
public class OrderQueueService {
public void enqueue(Long orderId, Long userId) {
String lockKey = "queue:lock:" + userId;
// 获取分布式锁
Boolean lock = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(lock)) {
try {
// 执行入队操作
doEnqueue(orderId, userId);
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
} else {
throw new ServiceException("系统繁忙,请稍后重试");
}
}
}
```
### 6.2.2 乐观锁
```java
@TableName("ops_order")
public class OpsOrderDO {
private Long id;
private String status;
@Version // 乐观锁字段
private Integer version;
}
// 更新时自动检查版本号
orderMapper.updateById(order); // 如果 version 不匹配,更新失败
```
---
## 6.3 可靠性保障
### 6.3.1 Redis + MySQL 双写策略
**写入流程**
```java
@Transactional(rollbackFor = Exception.class)
public Long enqueue(OrderQueueDTO dto) {
// 1. 先写 MySQL保证数据不丢失
Long queueId = orderQueueMapper.insert(dto);
// 2. 异步写 Redis失败不影响主流程
CompletableFuture.runAsync(() -> {
try {
redisQueueService.enqueue(dto);
} catch (Exception e) {
log.error("Redis 队列写入失败,依赖定时同步任务补偿", e);
}
});
return queueId;
}
```
### 6.3.2 定时同步任务
```java
@Scheduled(cron = "0 */5 * * * ?") // 每5分钟同步一次
public void syncMySQLToRedis() {
// 查询最近变更的数据
List<OrderQueueDTO> changedTasks =
orderQueueMapper.selectChangedAfter(DateUtils.addHours(-1));
// 同步到 Redis
redisQueueService.batchEnqueue(changedTasks);
log.info("定时同步完成,同步{}条记录", changedTasks.size());
}
```
### 6.3.3 故障恢复机制
**Redis 宕机降级**
```java
public List<OrderQueueDTO> getTasksByUserId(Long userId) {
try {
// 1. 优先从 Redis 获取
List<OrderQueueDTO> redisTasks = redisQueueService.getTasksByUserId(userId);
if (redisTasks != null && !redisTasks.isEmpty()) {
return redisTasks;
}
} catch (Exception e) {
log.error("Redis 查询失败,降级到 MySQL", e);
}
// 2. Redis 失败,从 MySQL 获取
return orderQueueMapper.selectListByUserId(userId);
}
```
---
## 6.4 监控与告警
### 6.4.1 关键指标监控
**系统指标**
- QPS每秒请求数
- 响应时间P50、P95、P99
- 错误率
- 线程池使用率
**业务指标**
- 工单创建速率
- 派单成功率
- 平均响应时长
- 队列积压数量
### 6.4.2 告警规则
| 告警项 | 触发条件 | 级别 | 处理方式 |
|--------|---------|------|----------|
| P0 工单超时 | 超时未接单 > 3 分钟 | P0 | 钉钉 + 短信 |
| 队列积压 | 某区域积压 > 10 个 | P1 | 钉钉通知 |
| 派单失败率 | 失败率 > 5% | P1 | 钉钉通知 |
| API 错误率 | 错误率 > 1% | P2 | 邮件通知 |
---
## 6.5 容灾设计
### 6.5.1 服务降级
```java
@Service
public class DispatchEngineService {
@Resource
private DispatchEngine dispatchEngine;
public DispatchResult dispatch(DispatchContext context) {
try {
// 正常派单流程
return dispatchEngine.dispatch(context);
} catch (Exception e) {
log.error("自动派单失败,降级为手动派单", e);
// 降级方案:创建待分配工单
return createPendingOrder(context);
}
}
}
```
### 6.5.2 数据备份
**MySQL 备份**
```bash
# 每日备份
mysqldump -h localhost -u root -p aiot_ops > backup_$(date +%Y%m%d).sql
```
**Redis 持久化**
```bash
# redis.conf
save 900 1 # 900秒内至少1个key变化执行BGSAVE
save 300 10 # 300秒内至少10个key变化
save 60 10000 # 60秒内至少10000个key变化
appendonly yes # 开启 AOF
appendfsync everysec # 每秒同步
```
---
**下一章**[Part 7: 扩展性设计](./part7-扩展性设计.md)

View File

@@ -0,0 +1,51 @@
# Part 7: 扩展性设计
本章节指导开发者如何基于 Ops 核心引擎扩展新的业务条线(如安保、工程、客服)或自定义派单逻辑。
## 7.1 新增业务条线流程
新增一个业务条线(如安保 `SECURITY`)需遵循以下步骤:
### 7.1.1 定义业务标识
`ops-api` 模块的 `WorkOrderTypeEnum` 中新增业务枚举值:
```java
SECURITY("SECURITY", "安保")
```
### 7.1.2 创建业务扩展表
根据业务特有字段创建扩展表(参考 `ops_order_clean_ext`),并通过 `ops_order_id` 与主表关联。
- **示例**`ops_order_security_ext` 存储巡更路线 ID、巡更点位数量等。
### 7.1.3 实现业务逻辑编排
在新的业务模块(如 `security-biz`)中创建 `SecurityOrderService`,调用通用的 `OpsOrderService` 完成基础 CRUD并在业务层处理特有逻辑。
## 7.2 自定义派单策略
调度引擎采用策略模式,支持为不同业务配置不同的推荐算法。
### 7.2.1 实现分配策略 (AssignStrategy)
实现 `AssignStrategy` 接口,定义具体的执行人推荐逻辑:
- **工程维修**优先匹配具备相关技能证书SkillId的员工。
- **安保巡更**:优先匹配当前排班在该路线附近的保安。
### 7.2.2 注册策略
在实现类上标注 `@Component`,引擎会自动通过 `registerAssignStrategy` 完成注册。
## 7.3 基于事件的行为扩展
推荐使用领域事件OrderStateChangedEvent实现核心引擎与业务副作用的解耦。
### 7.3.1 编写 EventHandler
创建业务专属的处理器(如 `SecurityOrderEventHandler`
- **场景**:当安保工单状态变为 `COMPLETED` 时,联动触发周边监控摄像头的预置位回归。
- **实现**
```java
@EventListener
public void onSecurityOrderCompleted(OrderStateChangedEvent event) {
if (!"SECURITY".equals(event.getOrderType())) return;
// 执行安保特有的副作用逻辑
}
```
---
**下一章**[Part 8: 测试指南](./part8-测试指南.md)

View File

@@ -0,0 +1,29 @@
# Part 8: 测试指南
为保证 Ops 模块在高速演进过程中的稳定性,所有核心逻辑必须通过自动化测试覆盖。
## 8.1 单元测试规范
### 8.1.1 依赖模拟 (Mocking)
使用 `JUnit 5``Mockito`。推荐继承项目通用的测试基类或使用 `MockitoExtension`
- **核心原则**Mock 掉 Mapper 层和外部 RPC 调用,专注验证 Service 层的状态转换逻辑。
### 8.1.2 状态机测试
针对 `OrderStateMachine` 的每一次修改,必须确保:
1. **合法路径测试**:验证允许的状态转换(如 `ARRIVED` -> `COMPLETED`)能成功。
2. **非法路径测试**:验证不允许的转换(如 `PENDING` -> `COMPLETED`)必须抛出异常。
## 8.2 集成测试规范
### 8.2.1 混合队列验证
由于采用 Redis + MySQL 双写架构,集成测试需重点验证:
- 事务提交后Redis 队列中的分数Score与优先级Priority是否匹配。
- `QueueSyncHandler` 是否正确处理了缓存失效后的补偿逻辑。
## 8.3 常用测试工具
- **Podam**:用于快速生成填充了随机数据的 DO/DTO 对象。
- **BaseDbAndRedisUnitTest**:支持 H2 内存数据库和内置 Redis 的集成测试基类。
---
**下一章**[Part 9: 技术决策记录 (ADR)](./part9-技术决策记录.md)

View File

@@ -0,0 +1,21 @@
# Part 9: 技术决策记录 (ADR)
记录 Ops 模块重大架构决策的背景、方案及权衡。
## ADR-001: 采用 Redis + MySQL 混合队列架构
- **背景**:工单派单对实时性要求极高,纯数据库查询在数据量大时存在性能瓶颈;而纯缓存方案在宕机时存在丢失关键业务数据的风险。
- **决策**:使用 Redis Sorted Set 承载实时排序与出队,使用 MySQL 存储持久化流水。
- **后果**:提升了派单响应速度(毫秒级),但增加了双写一致性维护的复杂度。
## ADR-002: 引入 OrderLifecycleManager 生命周期层
- **背景**:初期版本中,工单状态机与 Redis 队列的同步散落在各业务 Service 中,导致代码重复且易出现状态不一致。
- **决策**:抽象统一的生命周期管理器,采用责任链模式编排“状态转换 -> 队列同步 -> 事件发布”流程。
- **后果**:业务层代码量减少 40%,核心链路逻辑更加清晰。
## ADR-003: 异步事件驱动架构 (EDA)
- **背景**:核心派单逻辑中掺杂了大量的语音播报、短信通知等副作用逻辑,严重影响主流程吞吐量。
- **决策**:引入领域事件,状态机变更后同步发布 Spring Event副作用逻辑通过 `@Async` 监听器异步处理。
- **后果**:实现了核心引擎与业务插件的完全解耦。
---
**下一章**[Part 10: 附录](./part10-附录.md)