450 lines
14 KiB
Markdown
450 lines
14 KiB
Markdown
|
# API 调用流程域设计分析
|
|||
|
|
|||
|
## 🎯 业务流程分析
|
|||
|
|
|||
|
根据你描述的场景,这是一个典型的**B2B 数据服务 API 调用流程**:
|
|||
|
|
|||
|
```
|
|||
|
用户企业 → 调用我们的产品API → 获取第三方大数据服务 → 计费扣款
|
|||
|
```
|
|||
|
|
|||
|
## 🏗️ 涉及的业务域分析
|
|||
|
|
|||
|
这个流程总共涉及 **6 个核心域**:
|
|||
|
|
|||
|
### 1. 🚪 **网关域 (Gateway Domain)**
|
|||
|
|
|||
|
- **职责**: 统一入口、基础路由、请求预处理
|
|||
|
- **功能**:
|
|||
|
- 接收所有外部请求
|
|||
|
- 基础的请求验证
|
|||
|
- 路由到具体的业务域
|
|||
|
|
|||
|
### 2. 🛡️ **安全域 (Security Domain)**
|
|||
|
|
|||
|
- **职责**: 认证授权、加密解密、白名单管理
|
|||
|
- **功能**:
|
|||
|
- IP 白名单验证
|
|||
|
- 企业 ID 验证
|
|||
|
- 密钥管理
|
|||
|
- 请求参数解密
|
|||
|
|
|||
|
### 3. 👤 **用户域 (User Domain)**
|
|||
|
|
|||
|
- **职责**: 用户和企业信息管理
|
|||
|
- **功能**:
|
|||
|
- 企业认证信息验证
|
|||
|
- 用户权限检查
|
|||
|
- 企业密钥获取
|
|||
|
|
|||
|
### 4. 📦 **产品域 (Product Domain)**
|
|||
|
|
|||
|
- **职责**: 产品访问控制、权限管理
|
|||
|
- **功能**:
|
|||
|
- 产品访问权限验证
|
|||
|
- API 限额检查
|
|||
|
- 产品配置管理
|
|||
|
|
|||
|
### 5. 📊 **数据服务域 (Data Service Domain)**
|
|||
|
|
|||
|
- **职责**: 核心业务逻辑、第三方 API 调用
|
|||
|
- **功能**:
|
|||
|
- 参数处理和转换
|
|||
|
- 调用上游数据公司 API
|
|||
|
- 数据格式转换和响应
|
|||
|
|
|||
|
### 6. 💰 **计费域 (Billing Domain)**
|
|||
|
|
|||
|
- **职责**: 计费扣款、账单管理
|
|||
|
- **功能**:
|
|||
|
- 钱包余额检查
|
|||
|
- 费用计算
|
|||
|
- 扣款操作
|
|||
|
- 计费记录
|
|||
|
|
|||
|
### 7. 📋 **审计域 (Audit Domain)**
|
|||
|
|
|||
|
- **职责**: 请求记录、日志管理
|
|||
|
- **功能**:
|
|||
|
- API 调用记录
|
|||
|
- 操作日志
|
|||
|
- 审计追踪
|
|||
|
|
|||
|
## 🔄 完整流程设计
|
|||
|
|
|||
|
### 架构图
|
|||
|
|
|||
|
```
|
|||
|
┌─────────────┐
|
|||
|
│ 客户端 │
|
|||
|
└─────────────┘
|
|||
|
│
|
|||
|
▼
|
|||
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
│ 网关域 (Gateway) │
|
|||
|
│ • 请求接收 • 基础验证 • 路由分发 • 响应聚合 │
|
|||
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
│
|
|||
|
▼
|
|||
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
│ 数据服务域 (Data Service) │
|
|||
|
│ [主要协调者] │
|
|||
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
│ │ │ │ │ │
|
|||
|
▼ ▼ ▼ ▼ ▼ ▼
|
|||
|
┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐
|
|||
|
│安全域 ││用户域 ││产品域 ││计费域 ││审计域 ││第三方 │
|
|||
|
│Security││User ││Product ││Billing ││Audit ││API │
|
|||
|
└────────┘└────────┘└────────┘└────────┘└────────┘└────────┘
|
|||
|
```
|
|||
|
|
|||
|
### 详细流程设计
|
|||
|
|
|||
|
```go
|
|||
|
// 完整的API调用流程
|
|||
|
func (h *DataServiceHandler) ProcessAPIRequest(ctx context.Context, req *APIRequest) (*APIResponse, error) {
|
|||
|
// 1. 审计域 - 开始记录
|
|||
|
auditID := h.auditService.StartRequest(ctx, req)
|
|||
|
defer h.auditService.EndRequest(ctx, auditID)
|
|||
|
|
|||
|
// 2. 安全域 - IP白名单验证
|
|||
|
if err := h.securityService.ValidateIP(ctx, req.ClientIP); err != nil {
|
|||
|
return nil, errors.Wrap(err, "IP not in whitelist")
|
|||
|
}
|
|||
|
|
|||
|
// 3. 用户域 - 企业ID验证
|
|||
|
enterprise, err := h.userService.GetEnterpriseByID(ctx, req.EnterpriseID)
|
|||
|
if err != nil {
|
|||
|
return nil, errors.Wrap(err, "invalid enterprise ID")
|
|||
|
}
|
|||
|
|
|||
|
// 4. 安全域 - 获取密钥并解密参数
|
|||
|
secretKey, err := h.securityService.GetEnterpriseSecret(ctx, req.EnterpriseID)
|
|||
|
if err != nil {
|
|||
|
return nil, errors.Wrap(err, "failed to get enterprise secret")
|
|||
|
}
|
|||
|
|
|||
|
decryptedParams, err := h.securityService.DecryptParams(ctx, req.EncryptedParams, secretKey)
|
|||
|
if err != nil {
|
|||
|
return nil, errors.Wrap(err, "failed to decrypt parameters")
|
|||
|
}
|
|||
|
|
|||
|
// 5. 产品域 - 产品权限验证
|
|||
|
if err := h.productService.ValidateAccess(ctx, enterprise.ID, req.ProductCode); err != nil {
|
|||
|
return nil, errors.Wrap(err, "no access to product")
|
|||
|
}
|
|||
|
|
|||
|
// 6. 计费域 - 检查余额
|
|||
|
cost, err := h.billingService.CalculateCost(ctx, req.ProductCode, decryptedParams)
|
|||
|
if err != nil {
|
|||
|
return nil, errors.Wrap(err, "failed to calculate cost")
|
|||
|
}
|
|||
|
|
|||
|
if err := h.billingService.CheckBalance(ctx, enterprise.ID, cost); err != nil {
|
|||
|
return nil, errors.Wrap(err, "insufficient balance")
|
|||
|
}
|
|||
|
|
|||
|
// 7. 数据服务域 - 调用第三方API
|
|||
|
upstreamResp, err := h.callUpstreamAPI(ctx, decryptedParams)
|
|||
|
if err != nil {
|
|||
|
return nil, errors.Wrap(err, "upstream API call failed")
|
|||
|
}
|
|||
|
|
|||
|
// 8. 计费域 - 扣费
|
|||
|
if err := h.billingService.ChargeAccount(ctx, enterprise.ID, cost, auditID); err != nil {
|
|||
|
// 记录扣费失败,但不影响响应
|
|||
|
h.logger.Error("charge failed", zap.Error(err), zap.String("audit_id", auditID))
|
|||
|
}
|
|||
|
|
|||
|
// 9. 安全域 - 加密响应
|
|||
|
encryptedResp, err := h.securityService.EncryptResponse(ctx, upstreamResp, secretKey)
|
|||
|
if err != nil {
|
|||
|
return nil, errors.Wrap(err, "failed to encrypt response")
|
|||
|
}
|
|||
|
|
|||
|
return &APIResponse{
|
|||
|
Data: encryptedResp,
|
|||
|
RequestID: auditID,
|
|||
|
}, nil
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 🚪 路由入口设计
|
|||
|
|
|||
|
### 1. 网关层路由配置
|
|||
|
|
|||
|
```yaml
|
|||
|
# gateway.yaml
|
|||
|
routes:
|
|||
|
- path: /api/v1/data/*
|
|||
|
service: data-service
|
|||
|
middlewares:
|
|||
|
- request-id # 生成请求ID
|
|||
|
- rate-limit # 基础限流
|
|||
|
- audit-start # 开始审计
|
|||
|
timeout: 60s
|
|||
|
|
|||
|
- path: /api/v1/admin/*
|
|||
|
service: admin-service
|
|||
|
middlewares: [admin-auth, rate-limit]
|
|||
|
|
|||
|
- path: /api/v1/user/*
|
|||
|
service: user-service
|
|||
|
middlewares: [user-auth, rate-limit]
|
|||
|
```
|
|||
|
|
|||
|
### 2. 网关中间件
|
|||
|
|
|||
|
```go
|
|||
|
// 网关层的请求预处理中间件
|
|||
|
func DataAPIMiddleware() gin.HandlerFunc {
|
|||
|
return func(c *gin.Context) {
|
|||
|
// 1. 生成请求ID
|
|||
|
requestID := uuid.New().String()
|
|||
|
c.Set("request_id", requestID)
|
|||
|
c.Header("X-Request-ID", requestID)
|
|||
|
|
|||
|
// 2. 提取企业ID(从Header或路径参数)
|
|||
|
enterpriseID := extractEnterpriseID(c)
|
|||
|
if enterpriseID == "" {
|
|||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "missing enterprise ID"})
|
|||
|
c.Abort()
|
|||
|
return
|
|||
|
}
|
|||
|
c.Set("enterprise_id", enterpriseID)
|
|||
|
|
|||
|
// 3. 提取客户端IP
|
|||
|
clientIP := c.ClientIP()
|
|||
|
c.Set("client_ip", clientIP)
|
|||
|
|
|||
|
// 4. 提取产品代码
|
|||
|
productCode := c.Param("product_code")
|
|||
|
c.Set("product_code", productCode)
|
|||
|
|
|||
|
c.Next()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func extractEnterpriseID(c *gin.Context) string {
|
|||
|
// 优先从Header获取
|
|||
|
if id := c.GetHeader("X-Enterprise-ID"); id != "" {
|
|||
|
return id
|
|||
|
}
|
|||
|
|
|||
|
// 从路径参数获取
|
|||
|
return c.Param("enterprise_id")
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 3. 数据服务域作为主协调者
|
|||
|
|
|||
|
```go
|
|||
|
// 数据服务域的HTTP处理器
|
|||
|
type DataServiceHandler struct {
|
|||
|
securityService *SecurityService
|
|||
|
userService *UserService
|
|||
|
productService *ProductService
|
|||
|
billingService *BillingService
|
|||
|
auditService *AuditService
|
|||
|
upstreamClient *UpstreamAPIClient
|
|||
|
logger *zap.Logger
|
|||
|
}
|
|||
|
|
|||
|
// API端点定义
|
|||
|
func (h *DataServiceHandler) RegisterRoutes(r *gin.RouterGroup) {
|
|||
|
// 具体的数据产品API
|
|||
|
r.POST("/financial-data", h.GetFinancialData)
|
|||
|
r.POST("/credit-check", h.GetCreditCheck)
|
|||
|
r.POST("/risk-assessment", h.GetRiskAssessment)
|
|||
|
r.POST("/company-info", h.GetCompanyInfo)
|
|||
|
}
|
|||
|
|
|||
|
// 具体业务处理器
|
|||
|
func (h *DataServiceHandler) GetFinancialData(c *gin.Context) {
|
|||
|
ctx := c.Request.Context()
|
|||
|
|
|||
|
// 构建请求对象
|
|||
|
req := &APIRequest{
|
|||
|
RequestID: c.GetString("request_id"),
|
|||
|
EnterpriseID: c.GetString("enterprise_id"),
|
|||
|
ProductCode: "FINANCIAL_DATA",
|
|||
|
ClientIP: c.GetString("client_ip"),
|
|||
|
EncryptedParams: c.PostForm("data"),
|
|||
|
}
|
|||
|
|
|||
|
// 调用业务逻辑
|
|||
|
resp, err := h.ProcessAPIRequest(ctx, req)
|
|||
|
if err != nil {
|
|||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|||
|
"error": err.Error(),
|
|||
|
"request_id": req.RequestID,
|
|||
|
})
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
c.JSON(http.StatusOK, resp)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 🔄 域间通信设计
|
|||
|
|
|||
|
### 1. 事件驱动架构
|
|||
|
|
|||
|
```go
|
|||
|
// 定义领域事件
|
|||
|
type APIRequestStarted struct {
|
|||
|
RequestID string `json:"request_id"`
|
|||
|
EnterpriseID string `json:"enterprise_id"`
|
|||
|
ProductCode string `json:"product_code"`
|
|||
|
ClientIP string `json:"client_ip"`
|
|||
|
Timestamp time.Time `json:"timestamp"`
|
|||
|
}
|
|||
|
|
|||
|
type APIRequestCompleted struct {
|
|||
|
RequestID string `json:"request_id"`
|
|||
|
EnterpriseID string `json:"enterprise_id"`
|
|||
|
ProductCode string `json:"product_code"`
|
|||
|
Success bool `json:"success"`
|
|||
|
Cost float64 `json:"cost"`
|
|||
|
Duration int64 `json:"duration_ms"`
|
|||
|
Timestamp time.Time `json:"timestamp"`
|
|||
|
}
|
|||
|
|
|||
|
type ChargeRequired struct {
|
|||
|
RequestID string `json:"request_id"`
|
|||
|
EnterpriseID string `json:"enterprise_id"`
|
|||
|
Amount float64 `json:"amount"`
|
|||
|
ProductCode string `json:"product_code"`
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 2. 异步处理
|
|||
|
|
|||
|
```go
|
|||
|
// 异步计费处理
|
|||
|
func (s *BillingService) HandleChargeRequired(event ChargeRequired) error {
|
|||
|
// 异步处理计费,避免阻塞主流程
|
|||
|
go func() {
|
|||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|||
|
defer cancel()
|
|||
|
|
|||
|
if err := s.ProcessCharge(ctx, event); err != nil {
|
|||
|
s.logger.Error("async charge failed",
|
|||
|
zap.Error(err),
|
|||
|
zap.String("request_id", event.RequestID))
|
|||
|
|
|||
|
// 发送补偿事件
|
|||
|
s.eventBus.Publish(ChargeFailed{
|
|||
|
RequestID: event.RequestID,
|
|||
|
Error: err.Error(),
|
|||
|
})
|
|||
|
}
|
|||
|
}()
|
|||
|
|
|||
|
return nil
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 📊 性能和可扩展性考虑
|
|||
|
|
|||
|
### 1. 缓存策略
|
|||
|
|
|||
|
```go
|
|||
|
// 多级缓存策略
|
|||
|
type CacheStrategy struct {
|
|||
|
// L1: 本地缓存 (企业信息、产品配置)
|
|||
|
localCache *cache.Cache
|
|||
|
|
|||
|
// L2: Redis缓存 (密钥、白名单)
|
|||
|
redisCache *redis.Client
|
|||
|
|
|||
|
// L3: 数据库
|
|||
|
db *gorm.DB
|
|||
|
}
|
|||
|
|
|||
|
func (cs *CacheStrategy) GetEnterpriseSecret(enterpriseID string) (string, error) {
|
|||
|
// L1缓存查找
|
|||
|
if secret, found := cs.localCache.Get("secret:" + enterpriseID); found {
|
|||
|
return secret.(string), nil
|
|||
|
}
|
|||
|
|
|||
|
// L2缓存查找
|
|||
|
if secret, err := cs.redisCache.Get(context.Background(), "secret:"+enterpriseID).Result(); err == nil {
|
|||
|
cs.localCache.Set("secret:"+enterpriseID, secret, 5*time.Minute)
|
|||
|
return secret, nil
|
|||
|
}
|
|||
|
|
|||
|
// L3数据库查找
|
|||
|
var enterprise Enterprise
|
|||
|
if err := cs.db.Where("id = ?", enterpriseID).First(&enterprise).Error; err != nil {
|
|||
|
return "", err
|
|||
|
}
|
|||
|
|
|||
|
// 写入缓存
|
|||
|
cs.redisCache.Set(context.Background(), "secret:"+enterpriseID, enterprise.SecretKey, time.Hour)
|
|||
|
cs.localCache.Set("secret:"+enterpriseID, enterprise.SecretKey, 5*time.Minute)
|
|||
|
|
|||
|
return enterprise.SecretKey, nil
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 2. 熔断器模式
|
|||
|
|
|||
|
```go
|
|||
|
// 上游API调用熔断器
|
|||
|
func (s *DataService) callUpstreamAPI(ctx context.Context, params map[string]interface{}) (*UpstreamResponse, error) {
|
|||
|
return s.circuitBreaker.Execute(func() (interface{}, error) {
|
|||
|
client := &http.Client{Timeout: 30 * time.Second}
|
|||
|
|
|||
|
// 构建请求
|
|||
|
reqBody, _ := json.Marshal(params)
|
|||
|
req, _ := http.NewRequestWithContext(ctx, "POST", s.upstreamURL, bytes.NewBuffer(reqBody))
|
|||
|
req.Header.Set("Content-Type", "application/json")
|
|||
|
req.Header.Set("Authorization", "Bearer "+s.upstreamToken)
|
|||
|
|
|||
|
// 发送请求
|
|||
|
resp, err := client.Do(req)
|
|||
|
if err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
defer resp.Body.Close()
|
|||
|
|
|||
|
// 解析响应
|
|||
|
var upstreamResp UpstreamResponse
|
|||
|
if err := json.NewDecoder(resp.Body).Decode(&upstreamResp); err != nil {
|
|||
|
return nil, err
|
|||
|
}
|
|||
|
|
|||
|
return &upstreamResp, nil
|
|||
|
})
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 🎯 总结
|
|||
|
|
|||
|
### 涉及的域数量:**7 个域**
|
|||
|
|
|||
|
1. 网关域 - 统一入口
|
|||
|
2. 安全域 - 认证加密
|
|||
|
3. 用户域 - 企业验证
|
|||
|
4. 产品域 - 权限控制
|
|||
|
5. 数据服务域 - 核心业务
|
|||
|
6. 计费域 - 扣费计算
|
|||
|
7. 审计域 - 请求记录
|
|||
|
|
|||
|
### 路由入口设计:
|
|||
|
|
|||
|
- **主入口**: 网关域 (`/api/v1/data/`)
|
|||
|
- **业务协调**: 数据服务域作为主要协调者
|
|||
|
- **域间通信**: 通过 gRPC 调用和事件总线
|
|||
|
|
|||
|
### 设计优势:
|
|||
|
|
|||
|
1. **清晰的职责分离** - 每个域专注自己的业务
|
|||
|
2. **高可扩展性** - 可以独立扩展任何一个域
|
|||
|
3. **易于测试** - 每个域可以独立测试
|
|||
|
4. **容错性强** - 单个域故障不影响整体
|
|||
|
5. **性能优化** - 多级缓存和异步处理
|
|||
|
|
|||
|
这种设计既保证了业务逻辑的清晰性,又确保了系统的高性能和高可用性。
|