# 项目管理模块 - HTTP回调设计方案(备选方案) > **文档状态**: 备选方案存档 > **当前实现**: 接口注入方式(参见 `2-rmdc-project-management-DDS.md` 第3.4节) > **适用场景**: 未来微服务化部署时可考虑迁移到此方案 --- ## 1. 概述 本文档描述项目与工单模块之间基于 **HTTP 回调** 的状态同步机制。当系统需要分布式部署时,可考虑从当前的"接口注入"方式迁移到此方案。 ### 1.1 设计原则 项目模块(`rmdc-project-management`)调用工单模块(`rmdc-work-procedure`)创建和管理工单,工单状态变更需要同步更新项目的生命周期状态。采用 **HTTP 回调机制 + 领域事件** 的设计模式实现状态同步。 > **核心思想**:工单状态变更时,由工单模块主动 HTTP 回调项目模块,项目模块根据事件类型更新自身的生命周期状态。 --- ## 2. 架构设计 ### 2.1 时序图 ```mermaid 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 创建工单时注册回调 ```go // 项目模块创建工单时,注册回调配置 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 回调请求结构 ```go // 工单模块发起的回调请求 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 项目模块接收回调 ```go // 项目模块 - 接收工单回调并更新项目状态 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 事件到生命周期状态的映射 ```go // 事件到生命周期状态的映射 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. 回调失败重试表 ```go // 工单模块 - 回调失败时存储待重试 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()` 调用