运营调度领域技术方案
领域编号: 4.2
微服务: ether-ops
最后更新: 2026-02-10
一、领域概述
1.1 领域职责
运营调度领域是 Ether 平台的核心业务领域,负责管理:
- 综合工单管理(创建、分配、处理、关闭)
- 工单流转记录与状态机
- 消息通知系统(模板、规则、渠道)
- 工单统计与分析
1.2 核心概念
| 概念 |
说明 |
对应实体 |
| 工单 |
综合业务单据,支持多种类型 |
WorkOrder |
| 工单流转 |
工单状态变更记录 |
WorkOrderFlow |
| 通知渠道 |
消息发送通道 |
NotificationChannel |
| 通知模板 |
消息内容模板 |
NotificationTemplate |
| 通知规则 |
触发条件和发送策略 |
NotificationRule |
| 通知历史 |
已发送消息记录 |
NotificationHistory |
二、领域模型
2.1 聚合根设计
WorkOrder(综合工单)
@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(工单流转记录)
@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(通知渠道)
@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(消息模板)
@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(通知规则)
@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(通知历史)
@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 统计维度
// 工单统计服务
@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
@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
@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
@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 |
添加工单满意度 |
业主评价机制 |
七、数据库表结构
-- 工单表
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 服务负责人维护