ether-docs/02-DESIGN/domains/02-OPERATIONS.md

786 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 运营调度领域技术方案
**领域编号**: 4.2
**微服务**: ether-ops
**最后更新**: 2026-02-10
---
## 一、领域概述
### 1.1 领域职责
运营调度领域是 Ether 平台的核心业务领域,负责管理:
- 综合工单管理(创建、分配、处理、关闭)
- 工单流转记录与状态机
- 消息通知系统(模板、规则、渠道)
- 工单统计与分析
### 1.2 核心概念
| 概念 | 说明 | 对应实体 |
|------|------|----------|
| **工单** | 综合业务单据,支持多种类型 | WorkOrder |
| **工单流转** | 工单状态变更记录 | WorkOrderFlow |
| **通知渠道** | 消息发送通道 | NotificationChannel |
| **通知模板** | 消息内容模板 | NotificationTemplate |
| **通知规则** | 触发条件和发送策略 | NotificationRule |
| **通知历史** | 已发送消息记录 | NotificationHistory |
---
## 二、领域模型
### 2.1 聚合根设计
#### WorkOrder综合工单
```java
@Entity
@Table(name = "ops_work_order")
@Data
public class WorkOrder {
@Id
private UUID id;
private UUID projectId;
private String orderNo; // 工单编号: WO2024021000001
// 工单类型
private WorkOrderType orderType; // REPAIR/COMPLAINT/CLEANING/SECURITY/OTHER
private WorkOrderStatus status; // 状态机
private WorkOrderPriority priority; // URGENT/HIGH/MEDIUM/LOW
private WorkOrderSource source; // APP/PHONE/INSPECTION/IOT/SYSTEM
// 基本信息
private String title;
private String description;
// 报修人信息
private UUID reporterId;
private String reporterName;
private String reporterPhone;
private String reporterAddress;
// 关联信息
private UUID spaceNodeId; // 关联空间
private UUID equipmentId; // 关联设备
// 处理人信息
private UUID assigneeId;
private String assigneeName;
private LocalDateTime assignedAt;
private LocalDateTime acceptedAt;
private LocalDateTime startedAt;
private LocalDateTime completedAt;
private LocalDateTime closedAt;
// 费用
private BigDecimal actualCost;
private BigDecimal materialCost;
private BigDecimal laborCost;
// 结果
private String resultDescription;
private Integer satisfactionScore;
private String satisfactionComment;
// 附件
private String images;
private String attachments;
// 扩展属性
private String attributes;
// 审计字段
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private UUID createdBy;
}
```
**业务规则**:
- orderNo 自动生成,格式: WO + yyyyMMdd + 5位序号
- 状态流转必须通过合法的业务操作
- 关闭工单时必须填写处理结果
#### WorkOrderFlow工单流转记录
```java
@Entity
@Table(name = "ops_work_order_flow")
@Data
public class WorkOrderFlow {
@Id
private UUID id;
private UUID workOrderId;
// 流转信息
private WorkOrderStatus fromStatus;
private WorkOrderStatus toStatus;
private String action; // 操作: ASSIGN/ACCEPT/START/COMPLETE/CLOSE
// 操作人
private UUID operatorId;
private String operatorName;
private LocalDateTime operateTime;
// 备注
private String remark;
// 附件
private String images;
}
```
### 2.2 状态机设计
```
┌─────────────┐
│ CREATED │ ← 创建工单
│ (已创建) │
└──────┬──────┘
│ 分配
┌─────────────┐
│ ASSIGNED │ ← 分配给处理人
│ (已分配) │
└──────┬──────┘
│ 接单
┌─────────────┐
│ ACCEPTED │ ← 处理人接单
│ (已接单) │
└──────┬──────┘
│ 开始处理
┌─────────────┐
│ IN_PROGRESS │ ← 开始处理
│ (处理中) │
└──────┬──────┘
│ 完成
┌─────────────┐
│ COMPLETED │ ← 处理完成
│ (已完成) │
└──────┬──────┘
│ 关闭
┌─────────────┐
│ CLOSED │ ← 工单关闭
│ (已关闭) │
└─────────────┘
特殊状态:
- SUSPENDED (已挂起): 任意状态可转入,可恢复
- RETURNED (已退回): ASSIGNED状态可转入需重新分配
```
**状态流转规则**:
| 当前状态 | 允许操作 | 下一状态 | 权限 |
|---------|---------|---------|------|
| CREATED | 分配 | ASSIGNED | 管理员/调度员 |
| ASSIGNED | 接单 | ACCEPTED | 被指派人 |
| ASSIGNED | 退回 | RETURNED | 被指派人 |
| ACCEPTED | 开始 | IN_PROGRESS | 被指派人 |
| IN_PROGRESS | 完成 | COMPLETED | 被指派人 |
| COMPLETED | 关闭 | CLOSED | 管理员/创建人 |
| * | 挂起 | SUSPENDED | 管理员 |
| SUSPENDED | 恢复 | 原状态 | 管理员 |
---
## 三、消息通知系统
### 3.1 聚合根设计
#### NotificationChannel通知渠道
```java
@Entity
@Table(name = "ops_notification_channel")
@Data
public class NotificationChannel {
@Id
private UUID id;
private UUID projectId;
private String name;
private ChannelType type; // SITE_MESSAGE/SMS/EMAIL/PUSH/WECHAT_WORK
// 配置(JSONB)
private String config; // 渠道配置参数
// SITE_MESSAGE: {}
// SMS: {provider, apiKey, apiSecret, templateCode}
// EMAIL: {host, port, username, password}
// PUSH: {appKey, appSecret}
// WECHAT_WORK: {corpId, agentId, secret}
private Boolean enabled;
private Integer priority; // 优先级,数字越小优先级越高
private Integer dailyLimit; // 日发送限制
private Integer sentToday; // 今日已发送
// 审计字段
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
```
#### NotificationTemplate消息模板
```java
@Entity
@Table(name = "ops_notification_template")
@Data
public class NotificationTemplate {
@Id
private UUID id;
private UUID projectId;
private String name;
private String code; // 模板编码,唯一
// 模板内容
private String titleTemplate;
private String contentTemplate;
// 变量定义
private String variables; // ["orderNo", "title", "assigneeName"]
// 适用渠道
private String channels; // ["SITE_MESSAGE", "SMS"]
// 示例
private String example; // 渲染后的示例
// 审计字段
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
// 模板示例:
// titleTemplate: "新工单通知: {{title}}"
// contentTemplate: "您有一个新的{{orderType}}工单待处理,工单号: {{orderNo}},请尽快处理。"
```
#### NotificationRule通知规则
```java
@Entity
@Table(name = "ops_notification_rule")
@Data
public class NotificationRule {
@Id
private UUID id;
private UUID projectId;
private String name;
private String eventType; // 事件类型: WORK_ORDER_CREATED/ASSIGNED/COMPLETED
// 触发条件(JSONB)
private String conditions; // {"orderType": "REPAIR", "priority": "HIGH"}
// 延迟发送
private Integer delayMinutes; // 延迟分钟数0为立即发送
// 通知配置
private String templateCode; // 关联模板
private String receivers; // 接收人: ["CREATOR", "ASSIGNEE", "MANAGER"]
private String channels; // 通知渠道优先级: ["SITE_MESSAGE", "SMS"]
// 免打扰
private String quietHours; // 免打扰时段: "22:00-08:00"
private Boolean enabled;
// 审计字段
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
```
#### NotificationHistory通知历史
```java
@Entity
@Table(name = "ops_notification_history")
@Data
public class NotificationHistory {
@Id
private UUID id;
private UUID projectId;
// 通知内容
private String title;
private String content;
private String eventType;
// 接收人
private UUID receiverId;
private String receiverName;
private String receiverPhone;
private String receiverEmail;
// 渠道信息
private ChannelType channel;
private String channelMsgId; // 渠道消息ID
// 状态
private NotificationStatus status; // PENDING/SENT/FAILED/READ
private String failReason; // 失败原因
private LocalDateTime sendTime;
private LocalDateTime readTime;
// 关联业务
private String businessType; // WORK_ORDER/INSPECTION/FEE
private String businessId;
// 审计字段
private LocalDateTime createdAt;
}
```
### 3.2 通知事件类型
| 事件类型 | 触发时机 | 默认接收人 | 默认渠道 |
|---------|---------|-----------|---------|
| WORK_ORDER_CREATED | 工单创建 | 创建人 | 站内信 |
| WORK_ORDER_ASSIGNED | 工单分配 | 处理人 | 站内信+推送 |
| WORK_ORDER_ACCEPTED | 工单接单 | 创建人 | 站内信 |
| WORK_ORDER_COMPLETED | 工单完成 | 创建人 | 站内信+推送 |
| WORK_ORDER_CLOSED | 工单关闭 | - | - |
| INSPECTION_TODAY | 当天巡检提醒 | 巡检人 | 站内信+推送 |
| INSPECTION_OVERDUE | 巡检逾期 | 巡检人+管理员 | 站内信+短信 |
| FEE_UPCOMING_DUE | 费用即将到期 | 业主 | 站内信+推送 |
| FEE_OVERDUE | 费用逾期 | 业主 | 站内信+短信 |
### 3.3 通知流程
```
1. 业务事件触发
2. 查询匹配的通知规则
3. 渲染消息模板
4. 确定接收人列表
5. 选择通知渠道
6. 检查免打扰设置
7. 发送消息
8. 记录发送历史
9. 更新站内信未读数
```
---
## 四、工单统计
### 4.1 统计维度
```java
// 工单统计服务
@Service
public class WorkOrderStatisticsService {
// 概览统计
public WorkOrderOverviewVO getOverview(UUID projectId, LocalDate startDate, LocalDate endDate) {
// 工单总数
// 待处理数
// 今日新增
// 今日完成
// 平均处理时长
// 满意度评分
}
// 趋势统计
public List<TrendVO> getTrend(UUID projectId, StatisticsType type, LocalDate startDate, LocalDate endDate) {
// 按日/周/月统计工单量
// 创建趋势
// 完成趋势
}
// 类型分布
public List<DistributionVO> getTypeDistribution(UUID projectId, LocalDate startDate, LocalDate endDate) {
// 按工单类型统计
}
// 处理人排行
public List<RankingVO> getAssigneeRanking(UUID projectId, LocalDate startDate, LocalDate endDate) {
// 处理人工作量排行
// 处理人满意度排行
}
// 超时分析
public List<OvertimeVO> getOvertimeAnalysis(UUID projectId, LocalDate startDate, LocalDate endDate) {
// 超时工单列表
// 超时原因分析
}
}
```
### 4.2 统计指标
| 指标 | 说明 | 计算方式 |
|------|------|---------|
| 工单总数 | 指定时间范围内的工单总数 | COUNT(*) |
| 待处理数 | 状态为CREATED/ASSIGNED/ACCEPTED/IN_PROGRESS的工单数 | COUNT(*) WHERE status IN (...) |
| 今日新增 | 今日创建的工单数 | COUNT(*) WHERE created_at >= today |
| 今日完成 | 今日完成的工单数 | COUNT(*) WHERE completed_at >= today |
| 平均处理时长 | 从创建到完成的平均时间 | AVG(completed_at - created_at) |
| 按时完成率 | 在SLA时间内完成的工单比例 | COUNT(on_time) / COUNT(completed) |
| 满意度评分 | 业主评价的平均分 | AVG(satisfaction_score) |
---
## 五、API 接口
### 5.1 WorkOrder API
```java
@RestController
@RequestMapping("/api/v1/ops/work-orders")
@Tag(name = "工单管理")
public class WorkOrderController {
@PostMapping
@Operation(summary = "创建工单")
public Result<WorkOrderVO> create(@RequestBody @Valid WorkOrderCreateRequest request);
@GetMapping("/{id}")
@Operation(summary = "获取工单详情")
public Result<WorkOrderVO> getById(@PathVariable UUID id);
@GetMapping
@Operation(summary = "分页查询工单")
public Result<Page<WorkOrderVO>> page(WorkOrderQueryRequest request);
@PutMapping("/{id}")
@Operation(summary = "更新工单")
public Result<WorkOrderVO> update(@PathVariable UUID id,
@RequestBody @Valid WorkOrderUpdateRequest request);
@DeleteMapping("/{id}")
@Operation(summary = "删除工单")
public Result<Void> delete(@PathVariable UUID id);
// 业务操作
@PostMapping("/{id}/assign")
@Operation(summary = "分配工单")
public Result<WorkOrderVO> assign(@PathVariable UUID id,
@RequestBody @Valid WorkOrderAssignRequest request);
@PostMapping("/{id}/accept")
@Operation(summary = "接单")
public Result<WorkOrderVO> accept(@PathVariable UUID id);
@PostMapping("/{id}/start")
@Operation(summary = "开始处理")
public Result<WorkOrderVO> start(@PathVariable UUID id);
@PostMapping("/{id}/complete")
@Operation(summary = "完成工单")
public Result<WorkOrderVO> complete(@PathVariable UUID id,
@RequestBody @Valid WorkOrderCompleteRequest request);
@PostMapping("/{id}/close")
@Operation(summary = "关闭工单")
public Result<WorkOrderVO> close(@PathVariable UUID id,
@RequestBody @Valid WorkOrderCloseRequest request);
@PostMapping("/{id}/suspend")
@Operation(summary = "挂起工单")
public Result<WorkOrderVO> suspend(@PathVariable UUID id,
@RequestBody WorkOrderSuspendRequest request);
@PostMapping("/{id}/resume")
@Operation(summary = "恢复工单")
public Result<WorkOrderVO> resume(@PathVariable UUID id);
// 流转记录
@GetMapping("/{id}/flows")
@Operation(summary = "获取流转记录")
public Result<List<WorkOrderFlowVO>> getFlows(@PathVariable UUID id);
}
```
### 5.2 Notification API
```java
@RestController
@RequestMapping("/api/v1/ops/notifications")
@Tag(name = "消息通知")
public class NotificationController {
// 渠道管理
@PostMapping("/channels")
@Operation(summary = "创建通知渠道")
public Result<NotificationChannelVO> createChannel(@RequestBody @Valid ChannelCreateRequest request);
@GetMapping("/channels")
@Operation(summary = "查询渠道列表")
public Result<List<NotificationChannelVO>> listChannels();
// 模板管理
@PostMapping("/templates")
@Operation(summary = "创建消息模板")
public Result<NotificationTemplateVO> createTemplate(@RequestBody @Valid TemplateCreateRequest request);
@GetMapping("/templates")
@Operation(summary = "查询模板列表")
public Result<List<NotificationTemplateVO>> listTemplates();
// 规则管理
@PostMapping("/rules")
@Operation(summary = "创建通知规则")
public Result<NotificationRuleVO> createRule(@RequestBody @Valid RuleCreateRequest request);
@GetMapping("/rules")
@Operation(summary = "查询规则列表")
public Result<List<NotificationRuleVO>> listRules();
// 消息历史
@GetMapping("/history")
@Operation(summary = "分页查询消息历史")
public Result<Page<NotificationHistoryVO>> pageHistory(NotificationHistoryQueryRequest request);
// 个人消息
@GetMapping("/my")
@Operation(summary = "获取我的消息列表")
public Result<Page<NotificationVO>> getMyNotifications(@RequestParam(required = false) Boolean unread);
@GetMapping("/my/unread-count")
@Operation(summary = "获取未读消息数量")
public Result<Long> getUnreadCount();
@PostMapping("/{id}/read")
@Operation(summary = "标记已读")
public Result<Void> markAsRead(@PathVariable UUID id);
@PostMapping("/read-all")
@Operation(summary = "全部已读")
public Result<Void> markAllAsRead();
}
```
### 5.3 Statistics API
```java
@RestController
@RequestMapping("/api/v1/ops/statistics")
@Tag(name = "工单统计")
public class WorkOrderStatisticsController {
@GetMapping("/overview")
@Operation(summary = "概览统计")
public Result<WorkOrderOverviewVO> getOverview(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate);
@GetMapping("/trend")
@Operation(summary = "趋势统计")
public Result<List<TrendVO>> getTrend(
@RequestParam StatisticsType type,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate);
@GetMapping("/type-distribution")
@Operation(summary = "类型分布")
public Result<List<DistributionVO>> getTypeDistribution(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate);
@GetMapping("/assignee-ranking")
@Operation(summary = "处理人排行")
public Result<List<RankingVO>> getAssigneeRanking(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate);
}
```
---
## 六、实现状态与差异
### 6.1 实现状态
| 功能模块 | 实现状态 | 备注 |
|---------|---------|------|
| WorkOrder | 🟢 已实现 | 完整状态机、CRUD |
| WorkOrderFlow | 🟢 已实现 | 自动记录流转 |
| NotificationChannel | 🟢 已实现 | 基础CRUD |
| NotificationTemplate | 🟢 已实现 | 基础CRUD |
| NotificationRule | 🟢 已实现 | 基础CRUD |
| NotificationHistory | 🟢 已实现 | 基础CRUD |
| 工单与通知集成 | 🟢 已实现 | 事件驱动 |
| 工单统计 | 🟢 已实现 | 多维度统计 |
| SLA监控 | 🔴 未实现 | 超时预警 |
| 智能派单 | 🔴 未实现 | 自动分配 |
### 6.2 与设计方案的差异
| 设计项 | 设计方案 | 现有实现 | 差异分析 |
|--------|----------|----------|----------|
| **工单状态机** | 完整状态流转 | 已实现所有状态 | ✅ 符合设计 |
| **通知渠道** | 多渠道支持 | 仅站内信实现 | ⚠️ 其他渠道待扩展 |
| **工单统计** | 多维度分析 | 基本实现 | 🟡 可扩展更多维度 |
| **巡检归属** | ether-ops | ether-mdm | 🟡 领域边界模糊 |
### 6.3 待改进项
| 优先级 | 改进项 | 说明 |
|--------|--------|------|
| P2 | 实现SLA监控 | 工单超时预警和自动升级 |
| P2 | 扩展通知渠道 | 短信、邮件、推送渠道 |
| P3 | 智能派单算法 | 基于负载、技能、位置的自动分配 |
| P3 | 添加工单满意度 | 业主评价机制 |
---
## 七、数据库表结构
```sql
-- 工单表
CREATE TABLE ops_work_order (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
order_no VARCHAR(32) NOT NULL,
order_type VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'CREATED',
priority VARCHAR(20) NOT NULL DEFAULT 'MEDIUM',
source VARCHAR(20) NOT NULL,
title VARCHAR(200) NOT NULL,
description TEXT,
reporter_id UUID,
reporter_name VARCHAR(100),
reporter_phone VARCHAR(20),
reporter_address VARCHAR(255),
space_node_id UUID,
equipment_id UUID,
assignee_id UUID,
assignee_name VARCHAR(100),
assigned_at TIMESTAMP,
accepted_at TIMESTAMP,
started_at TIMESTAMP,
completed_at TIMESTAMP,
closed_at TIMESTAMP,
actual_cost NUMERIC(12,2),
material_cost NUMERIC(12,2),
labor_cost NUMERIC(12,2),
result_description TEXT,
satisfaction_score INTEGER,
satisfaction_comment VARCHAR(500),
images TEXT,
attachments TEXT,
attributes JSONB,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID,
updated_by UUID,
UNIQUE(project_id, order_no)
);
-- 工单流转表
CREATE TABLE ops_work_order_flow (
id UUID PRIMARY KEY,
work_order_id UUID NOT NULL,
from_status VARCHAR(20),
to_status VARCHAR(20) NOT NULL,
action VARCHAR(50) NOT NULL,
operator_id UUID,
operator_name VARCHAR(100),
operate_time TIMESTAMP NOT NULL DEFAULT NOW(),
remark VARCHAR(500),
images TEXT
);
-- 通知渠道表
CREATE TABLE ops_notification_channel (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
name VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL,
config JSONB,
enabled BOOLEAN DEFAULT TRUE,
priority INTEGER DEFAULT 0,
daily_limit INTEGER,
sent_today INTEGER DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- 消息模板表
CREATE TABLE ops_notification_template (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
name VARCHAR(100) NOT NULL,
code VARCHAR(50) NOT NULL,
title_template VARCHAR(200),
content_template TEXT NOT NULL,
variables JSONB,
channels JSONB,
example TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(project_id, code)
);
-- 通知规则表
CREATE TABLE ops_notification_rule (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
name VARCHAR(100) NOT NULL,
event_type VARCHAR(50) NOT NULL,
conditions JSONB,
delay_minutes INTEGER DEFAULT 0,
template_code VARCHAR(50) NOT NULL,
receivers JSONB,
channels JSONB,
quiet_hours VARCHAR(20),
enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- 通知历史表
CREATE TABLE ops_notification_history (
id UUID PRIMARY KEY,
project_id UUID NOT NULL,
title VARCHAR(200),
content TEXT,
event_type VARCHAR(50),
receiver_id UUID,
receiver_name VARCHAR(100),
receiver_phone VARCHAR(20),
receiver_email VARCHAR(100),
channel VARCHAR(20) NOT NULL,
channel_msg_id VARCHAR(100),
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
fail_reason VARCHAR(500),
send_time TIMESTAMP,
read_time TIMESTAMP,
business_type VARCHAR(50),
business_id VARCHAR(50),
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- 创建索引
CREATE INDEX idx_work_order_project ON ops_work_order(project_id);
CREATE INDEX idx_work_order_status ON ops_work_order(status);
CREATE INDEX idx_work_order_assignee ON ops_work_order(assignee_id);
CREATE INDEX idx_work_order_created ON ops_work_order(created_at);
CREATE INDEX idx_work_order_flow_order ON ops_work_order_flow(work_order_id);
CREATE INDEX idx_notification_history_receiver ON ops_notification_history(receiver_id);
CREATE INDEX idx_notification_history_status ON ops_notification_history(status);
```
---
**文档维护**: 本领域技术方案由 ether-ops 服务负责人维护