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

23 KiB
Raw Blame History

运营调度领域技术方案

领域编号: 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 服务负责人维护