# 项目管理模块 - 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()` 调用
