This commit is contained in:
2026-04-21 22:36:48 +08:00
commit 488c695fdf
748 changed files with 266838 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
# 组合包处理器说明
## 🚀 动态组合包机制
从现在开始,组合包支持**动态处理机制**,大大简化了组合包的开发和维护工作。
## 📋 工作原理
### 1. 自动识别
- 所有以 `COMB` 开头的API编码会被自动识别为组合包
- 系统会自动调用通用组合包处理器处理请求
### 2. 处理流程
1. **优先级检查**:首先检查是否有注册的自定义处理器
2. **通用处理**如果没有自定义处理器且API编码以COMB开头使用通用处理器
3. **数据库驱动**:根据数据库中的组合包配置自动调用相应的子产品处理器
## 🛠️ 使用方式
### 普通组合包(无自定义逻辑)
**只需要在数据库配置,无需编写任何代码!**
1.`products` 表中创建组合包产品:
```sql
INSERT INTO products (code, name, is_package, ...)
VALUES ('COMB1234', '新组合包', true, ...);
```
2. 在 `product_package_items` 表中配置子产品:
```sql
INSERT INTO product_package_items (package_id, product_id, sort_order)
VALUES
('组合包产品ID', '子产品1ID', 1),
('组合包产品ID', '子产品2ID', 2);
```
3. **直接调用**无需任何额外编码API立即可用
### 自定义组合包(有特殊逻辑)
如果需要对组合包结果进行后处理,才需要编写代码:
1. **创建处理器文件**`combXXXX_processor.go`
2. **注册处理器**:在 `api_request_service.go` 中注册
3. **实现自定义逻辑**:在处理器中实现特殊业务逻辑
## 📁 现有组合包示例
### COMB86PM自定义处理器
```go
// 有自定义逻辑重命名子产品ApiCode
for _, resp := range combinedResult.Responses {
if resp.ApiCode == "FLXGBC21" {
resp.ApiCode = "FLXG54F5"
}
}
```
### COMB298Y通用处理器
- **无需编码**:已删除专门的处理器文件
- **自动处理**:通过数据库配置自动工作
## ✅ 优势
1. **零配置**:普通组合包只需数据库配置,无需编码
2. **灵活性**:特殊需求仍可通过自定义处理器实现
3. **维护性**:减少重复代码,统一处理逻辑
4. **扩展性**:新增组合包极其简单,配置即用
## 🔧 开发建议
1. **优先使用通用处理器**:除非有特殊业务逻辑,否则不要编写自定义处理器
2. **命名规范**:组合包编码必须以 `COMB` 开头
3. **数据库配置**:确保组合包在数据库中正确配置了 `is_package=true` 和子产品关系
4. **排序控制**:通过 `sort_order` 字段控制子产品在响应中的顺序

View File

@@ -0,0 +1,36 @@
package comb
import (
"context"
"encoding/json"
"errors"
"hyapi-server/internal/domains/api/dto"
"hyapi-server/internal/domains/api/services/processors"
)
// ProcessCOMB86PMRequest COMB86PM API处理方法
func ProcessCOMB86PMRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.COMB86PMReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
// 调用组合包服务处理请求
// Options会自动传递给所有子处理器
combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMB86PM")
if err != nil {
return nil, err
}
// 如果有ApiCode为FLXG54F5的子产品改名为FLXG54F6
for _, resp := range combinedResult.Responses {
if resp.ApiCode == "FLXGBC21" {
resp.ApiCode = "FLXG54F5"
}
}
return json.Marshal(combinedResult)
}

View File

@@ -0,0 +1,178 @@
package comb
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
"sync"
"hyapi-server/internal/domains/api/services/processors"
"hyapi-server/internal/domains/product/entities"
"hyapi-server/internal/domains/product/services"
)
// CombService 组合包服务
type CombService struct {
productManagementService *services.ProductManagementService
processorRegistry map[string]processors.ProcessorFunc
}
// NewCombService 创建组合包服务
func NewCombService(productManagementService *services.ProductManagementService) *CombService {
return &CombService{
productManagementService: productManagementService,
processorRegistry: make(map[string]processors.ProcessorFunc),
}
}
// RegisterProcessor 注册处理器
func (cs *CombService) RegisterProcessor(apiCode string, processor processors.ProcessorFunc) {
cs.processorRegistry[apiCode] = processor
}
// GetProcessor 获取处理器(用于内部调用)
func (cs *CombService) GetProcessor(apiCode string) (processors.ProcessorFunc, bool) {
processor, exists := cs.processorRegistry[apiCode]
return processor, exists
}
// ProcessCombRequest 处理组合包请求 - 实现 CombServiceInterface
func (cs *CombService) ProcessCombRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies, packageCode string) (*processors.CombinedResult, error) {
// 1. 根据组合包code获取产品信息
packageProduct, err := cs.productManagementService.GetProductByCode(ctx, packageCode)
if err != nil {
return nil, fmt.Errorf("获取组合包信息失败: %s", err.Error())
}
if !packageProduct.IsPackage {
return nil, fmt.Errorf("产品 %s 不是组合包", packageCode)
}
// 2. 获取组合包的所有子产品
packageItems, err := cs.productManagementService.GetPackageItems(ctx, packageProduct.ID)
if err != nil {
return nil, fmt.Errorf("获取组合包子产品失败: %s", err.Error())
}
if len(packageItems) == 0 {
return nil, fmt.Errorf("组合包 %s 没有配置子产品", packageCode)
}
// 3. 并发调用所有子产品的处理器
results := cs.processSubProducts(ctx, params, deps, packageItems)
// 4. 组合结果
return cs.combineResults(results)
}
// processSubProducts 并发处理子产品
func (cs *CombService) processSubProducts(
ctx context.Context,
params []byte,
deps *processors.ProcessorDependencies,
packageItems []*entities.ProductPackageItem,
) []*processors.SubProductResult {
results := make([]*processors.SubProductResult, 0, len(packageItems))
var wg sync.WaitGroup
var mu sync.Mutex
// 并发处理每个子产品
for _, item := range packageItems {
wg.Add(1)
go func(item *entities.ProductPackageItem) {
defer wg.Done()
result := cs.processSingleSubProduct(ctx, params, deps, item)
mu.Lock()
results = append(results, result)
mu.Unlock()
}(item)
}
wg.Wait()
// 按SortOrder排序
sort.Slice(results, func(i, j int) bool {
return results[i].SortOrder < results[j].SortOrder
})
return results
}
// processSingleSubProduct 处理单个子产品
func (cs *CombService) processSingleSubProduct(
ctx context.Context,
params []byte,
deps *processors.ProcessorDependencies,
item *entities.ProductPackageItem,
) *processors.SubProductResult {
result := &processors.SubProductResult{
ApiCode: item.Product.Code,
SortOrder: item.SortOrder,
Success: false,
}
// 查找对应的处理器
processor, exists := cs.processorRegistry[item.Product.Code]
if !exists {
result.Error = fmt.Sprintf("未找到处理器: %s", item.Product.Code)
return result
}
// 调用处理器
respBytes, err := processor(ctx, params, deps)
if err != nil {
result.Error = err.Error()
return result
}
// 解析响应
var responseData interface{}
if err := json.Unmarshal(respBytes, &responseData); err != nil {
result.Error = fmt.Sprintf("解析响应失败: %s", err.Error())
return result
}
result.Success = true
result.Data = responseData
return result
}
// combineResults 组合所有子产品的结果
// 只要至少有一个子产品成功,就返回成功结果(部分成功也算成功)
// 只有当所有子产品都失败时,才返回错误
func (cs *CombService) combineResults(results []*processors.SubProductResult) (*processors.CombinedResult, error) {
// 检查是否至少有一个成功的子产品
hasSuccess := false
for _, result := range results {
if result.Success {
hasSuccess = true
break
}
}
// 构建组合结果
combinedResult := &processors.CombinedResult{
Responses: results,
}
// 如果所有子产品都失败,返回错误
if !hasSuccess && len(results) > 0 {
// 构建错误信息,包含所有失败的原因
errorMessages := make([]string, 0, len(results))
for _, result := range results {
if result.Error != "" {
errorMessages = append(errorMessages, fmt.Sprintf("%s: %s", result.ApiCode, result.Error))
}
}
errorMsg := fmt.Sprintf("组合包所有子产品调用失败: %s", strings.Join(errorMessages, "; "))
return nil, fmt.Errorf(errorMsg)
}
// 至少有一个成功,返回成功结果
return combinedResult, nil
}

View File

@@ -0,0 +1,148 @@
package comb
import (
"context"
"encoding/json"
"errors"
"hyapi-server/internal/domains/api/dto"
"hyapi-server/internal/domains/api/services/processors"
"hyapi-server/internal/shared/logger"
"go.uber.org/zap"
)
// ProcessCOMBHZY2Request 处理 COMBHZY2 组合包请求
func ProcessCOMBHZY2Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
log := logger.GetGlobalLogger()
var req dto.COMBHZY2Req
if err := json.Unmarshal(params, &req); err != nil {
log.Error("COMBHZY2请求参数反序列化失败",
zap.Error(err),
zap.String("params", string(params)),
zap.String("api_code", "COMBHZY2"),
)
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(req); err != nil {
log.Error("COMBHZY2请求参数验证失败",
zap.Error(err),
zap.String("api_code", "COMBHZY2"),
)
return nil, errors.Join(processors.ErrInvalidParam, err)
}
combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMBHZY2")
if err != nil {
log.Error("COMBHZY2组合包服务调用失败",
zap.Error(err),
zap.String("api_code", "COMBHZY2"),
)
return nil, err
}
if combinedResult == nil {
log.Error("COMBHZY2组合包响应为空",
zap.String("api_code", "COMBHZY2"),
)
return nil, errors.New("组合包响应为空")
}
log.Info("COMBHZY2组合包服务调用成功",
zap.Int("子产品数量", len(combinedResult.Responses)),
zap.String("api_code", "COMBHZY2"),
)
sourceCtx, err := buildSourceContextFromCombined(ctx, combinedResult)
if err != nil {
log.Error("COMBHZY2构建源数据上下文失败",
zap.Error(err),
zap.String("api_code", "COMBHZY2"),
)
return nil, err
}
report := buildTargetReport(ctx, sourceCtx)
reportBytes, err := json.Marshal(report)
if err != nil {
log.Error("COMBHZY2报告序列化失败",
zap.Error(err),
zap.String("api_code", "COMBHZY2"),
)
return nil, errors.Join(processors.ErrSystem, err)
}
return reportBytes, nil
}
func buildSourceContextFromCombined(ctx context.Context, result *processors.CombinedResult) (*sourceContext, error) {
log := logger.GetGlobalLogger()
if result == nil {
log.Error("组合包响应为空", zap.String("api_code", "COMBHZY2"))
return nil, errors.New("组合包响应为空")
}
src := sourceFile{Responses: make([]sourceResponse, 0, len(result.Responses))}
successCount := 0
failedCount := 0
for _, resp := range result.Responses {
if !resp.Success {
log.Warn("子产品调用失败,跳过",
zap.String("api_code", resp.ApiCode),
zap.String("error", resp.Error),
zap.String("parent_api_code", "COMBHZY2"),
)
failedCount++
continue
}
if resp.Data == nil {
log.Warn("子产品数据为空,跳过",
zap.String("api_code", resp.ApiCode),
zap.String("parent_api_code", "COMBHZY2"),
)
failedCount++
continue
}
raw, err := json.Marshal(resp.Data)
if err != nil {
log.Error("序列化子产品数据失败",
zap.Error(err),
zap.String("api_code", resp.ApiCode),
zap.String("parent_api_code", "COMBHZY2"),
)
failedCount++
continue
}
src.Responses = append(src.Responses, sourceResponse{
ApiCode: resp.ApiCode,
Data: raw,
Success: resp.Success,
})
successCount++
}
log.Info("组合包子产品处理完成",
zap.Int("成功数量", successCount),
zap.Int("失败数量", failedCount),
zap.Int("总数量", len(result.Responses)),
zap.String("api_code", "COMBHZY2"),
)
if len(src.Responses) == 0 {
log.Error("组合包子产品全部调用失败",
zap.Int("总数量", len(result.Responses)),
zap.String("api_code", "COMBHZY2"),
)
return nil, errors.New("组合包子产品全部调用失败")
}
return buildSourceContext(ctx, src)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
package comb
import (
"context"
"encoding/json"
"errors"
"hyapi-server/internal/domains/api/services/processors"
"hyapi-server/internal/shared/logger"
"go.uber.org/zap"
)
// ProcessCOMBWD01Request 处理 COMBWD01 组合包请求
// 将返回结构从数组改为以 api_code 为 key 的对象结构
func ProcessCOMBWD01Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
log := logger.GetGlobalLogger()
// 调用组合包服务处理请求
combinedResult, err := deps.CombService.ProcessCombRequest(ctx, params, deps, "COMBWD01")
if err != nil {
log.Error("COMBWD01组合包服务调用失败",
zap.Error(err),
zap.String("api_code", "COMBWD01"),
)
return nil, err
}
if combinedResult == nil {
log.Error("COMBWD01组合包响应为空",
zap.String("api_code", "COMBWD01"),
)
return nil, errors.New("组合包响应为空")
}
log.Info("COMBWD01组合包服务调用成功",
zap.Int("子产品数量", len(combinedResult.Responses)),
zap.String("api_code", "COMBWD01"),
)
// 将数组结构转换为对象结构
responsesMap := make(map[string]*ResponseItem)
for _, resp := range combinedResult.Responses {
item := &ResponseItem{
ApiCode: resp.ApiCode,
Success: resp.Success,
}
// 根据成功/失败状态设置 data 和 error 字段
if resp.Success {
// 成功时data 有值(可能为 nilerror 为 null
item.Data = resp.Data
item.Error = nil
} else {
// 失败时data 为 nullerror 有值
item.Data = nil
if resp.Error != "" {
item.Error = resp.Error
} else {
item.Error = "未知错误"
}
}
responsesMap[resp.ApiCode] = item
}
// 构建新的响应结构
result := map[string]interface{}{
"responses": responsesMap,
}
// 序列化并返回
resultBytes, err := json.Marshal(result)
if err != nil {
log.Error("COMBWD01响应序列化失败",
zap.Error(err),
zap.String("api_code", "COMBWD01"),
)
return nil, errors.Join(processors.ErrSystem, err)
}
return resultBytes, nil
}
// ResponseItem 响应项结构
type ResponseItem struct {
ApiCode string `json:"api_code"`
Success bool `json:"success"`
Data interface{} `json:"data"`
Error interface{} `json:"error"`
}