Files
ProjectAGiPrompt/8-CMII-RMDC/4-rmdc-project-management/5-rmdc-project-http-callback-design.md
2026-01-21 16:15:49 +08:00

8.7 KiB
Raw Blame History

项目管理模块 - HTTP回调设计方案备选方案

文档状态: 备选方案存档
当前实现: 接口注入方式(参见 2-rmdc-project-management-DDS.md 第3.4节)
适用场景: 未来微服务化部署时可考虑迁移到此方案


1. 概述

本文档描述项目与工单模块之间基于 HTTP 回调 的状态同步机制。当系统需要分布式部署时,可考虑从当前的"接口注入"方式迁移到此方案。

1.1 设计原则

项目模块(rmdc-project-management)调用工单模块(rmdc-work-procedure)创建和管理工单,工单状态变更需要同步更新项目的生命周期状态。采用 HTTP 回调机制 + 领域事件 的设计模式实现状态同步。

核心思想:工单状态变更时,由工单模块主动 HTTP 回调项目模块,项目模块根据事件类型更新自身的生命周期状态。


2. 架构设计

2.1 时序图

sequenceDiagram
    participant PM as rmdc-project-management
    participant WP as rmdc-work-procedure
    participant DB as Database
    
    Note over PM,WP: 回调机制:工单状态变更 → 回调业务模块 → 更新项目状态
    
    PM->>WP: 1. 创建工单 (携带回调配置)
    WP->>DB: 2. 存储工单 + 回调配置
    WP-->>PM: 3. 返回 workflow_id
    
    Note over WP: 工单状态变更事件发生...
    
    WP->>WP: 4. 状态机转换 (pending_review → approved)
    WP->>PM: 5. HTTP回调 /api/project/workflow-callback
    PM->>PM: 6. 解析事件,更新项目 lifecycle_status
    PM-->>WP: 7. 确认回调成功
    WP->>DB: 8. 标记回调已完成

2.2 为什么不算循环依赖

虽然调用关系形成了"环",但这是 松耦合的事件通知模式,不是真正的循环依赖:

依赖类型 说明 是否存在 问题程度
编译依赖 模块A的代码import模块B 不存在 -
启动依赖 模块A启动必须依赖模块B先启动 ⚠️ 单向
运行时调用 模块A在运行时调用模块B的API 双向 需设计
逻辑依赖 模块A的业务逻辑依赖模块B的存在 ⚠️ 单向

关键点工单模块不import项目模块任何代码只知道一个回调URL字符串两个模块在代码层面完全解耦。


3. 回调配置设计

3.1 创建工单时注册回调

// 项目模块创建工单时,注册回调配置
type CreateWorkflowRequest struct {
    ModuleCode   string `json:"module_code"`   // "project_management"
    WorkflowType string `json:"workflow_type"` // "project_detail" / "project_modify"
    // ... 其他字段 ...
    
    // 回调配置
    CallbackConfig *CallbackConfig `json:"callback_config"`
}

type CallbackConfig struct {
    // 回调URL工单状态变更时调用
    CallbackURL string `json:"callback_url"` // "/api/project/workflow-callback"
    
    // 关注的事件列表(只回调这些事件)
    SubscribedEvents []string `json:"subscribed_events"` // ["submit", "approve", "return", "revoke"]
    
    // 业务上下文(回调时原样返回)
    BusinessContext map[string]interface{} `json:"business_context"` // {"project_id": "xxx"}
}

3.2 回调请求结构

// 工单模块发起的回调请求
type WorkflowCallbackRequest struct {
    WorkflowID      string                 `json:"workflow_id"`
    WorkflowType    string                 `json:"workflow_type"`
    Event           string                 `json:"event"`           // "approve", "return", "submit" 等
    FromStatus      string                 `json:"from_status"`
    ToStatus        string                 `json:"to_status"`
    OperatorID      int64                  `json:"operator_id"`
    OperatorName    string                 `json:"operator_name"`
    BusinessContext map[string]interface{} `json:"business_context"`
    Timestamp       time.Time              `json:"timestamp"`
}

4. 回调处理实现

4.1 项目模块接收回调

// 项目模块 - 接收工单回调并更新项目状态
func (h *ProjectHandler) WorkflowCallback(c *gin.Context) {
    var req WorkflowCallbackRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        response.Error(c, errcode.InvalidParams)
        return
    }
    
    // 幂等性检查
    idempotencyKey := fmt.Sprintf("%s:%s:%d", req.WorkflowID, req.Event, req.Timestamp.Unix())
    if h.cache.Exists(idempotencyKey) {
        response.Success(c, nil) // 幂等返回成功
        return
    }
    
    projectID := req.BusinessContext["project_id"].(string)
    
    // 根据工单事件映射项目生命周期状态
    newLifecycleStatus := h.mapWorkflowEventToLifecycle(req.Event, req.ToStatus, req.WorkflowType)
    
    if newLifecycleStatus != "" {
        err := h.projectService.UpdateLifecycleStatus(c, projectID, newLifecycleStatus)
        if err != nil {
            response.Error(c, errcode.InternalError)
            return
        }
    }
    
    // 标记已处理设置24小时过期
    h.cache.Set(idempotencyKey, "1", 24*time.Hour)
    
    response.Success(c, nil)
}

4.2 事件到生命周期状态的映射

// 事件到生命周期状态的映射
func (h *ProjectHandler) mapWorkflowEventToLifecycle(event, toStatus, workflowType string) string {
    // 填写工单
    if workflowType == "project_detail" {
        switch event {
        case "create":  // 创建工单
            return "DRAFTING"
        case "submit":  // 提交审核
            return "REVIEWING"
        case "return":  // 审核打回
            return "DRAFTING"
        case "approve": // 审核通过
            return "RELEASED"
        case "revoke":  // 撤销工单
            return "INIT"
        }
    }
    
    // 修改工单
    if workflowType == "project_modify" {
        switch event {
        case "create":  // 发起修改
            return "MODIFYING"
        case "submit":  // 提交审核
            return "REVIEWING"
        case "return":  // 审核打回
            return "MODIFYING"
        case "approve": // 审核通过
            return "RELEASED"
        case "revoke":  // 撤销工单
            return "RELEASED" // 撤销后恢复为 RELEASED
        }
    }
    
    return ""
}

5. 风险处理

风险 解决方案
回调再次调用工单API导致死循环 回调处理中只更新项目状态不调用工单API
回调失败导致状态不一致 工单模块实现重试机制3次重试 + 指数退避)
回调请求超时阻塞工单流程 设置短超时5秒或完全异步化
重复回调导致重复处理 使用 workflow_id + event + timestamp 作为幂等键

6. 回调失败重试表

// 工单模块 - 回调失败时存储待重试
type CallbackRetry struct {
    ID          int64     `gorm:"primaryKey;autoIncrement" json:"id"`
    WorkflowID  string    `gorm:"type:varchar(64);index" json:"workflow_id"`
    CallbackURL string    `gorm:"type:varchar(256)" json:"callback_url"`
    Payload     string    `gorm:"type:text" json:"payload"`         // JSON
    RetryCount  int       `gorm:"default:0" json:"retry_count"`     // 已重试次数
    MaxRetries  int       `gorm:"default:3" json:"max_retries"`     // 最大重试次数
    NextRetryAt time.Time `json:"next_retry_at"`                    // 下次重试时间
    Status      string    `gorm:"type:varchar(16);default:'pending'" json:"status"` // pending/success/failed
    LastError   string    `gorm:"type:text" json:"last_error"`      // 最后一次错误信息
    CreatedAt   time.Time `json:"created_at"`
    UpdatedAt   time.Time `json:"updated_at"`
}

7. 与接口注入方式的对比

特性 HTTP回调本方案 接口注入(当前实现)
模块解耦 完全解耦仅URL字符串 ⚠️ 接口级解耦
分布式支持 支持 不支持
性能 ⚠️ 网络开销 进程内调用
复杂度 ⚠️ 需要重试/幂等处理 简单直接
事务一致性 ⚠️ 最终一致性 强一致性
适用场景 微服务架构 模块化单体架构

8. 迁移指南

如果未来需要从"接口注入"迁移到"HTTP回调",需要:

  1. 工单模块

    • 实现 HTTP 客户端发起回调
    • 添加回调配置存储
    • 实现重试机制和 callback_retries
  2. 项目模块

    • 实现 /api/project/workflow-callback 接口
    • 添加幂等性检查Redis/缓存)
    • 删除 ProjectLifecycleUpdater 接口注入逻辑
  3. rmdc-core

    • 删除 ProjectWorkflowCallback 适配器
    • 删除 workflowSvc.SetProjectLifecycleUpdater() 调用