Files
tyapi-server/internal/shared/component_report/example_json_generator.go

287 lines
7.9 KiB
Go
Raw Normal View History

2025-12-19 17:05:09 +08:00
package component_report
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"go.uber.org/zap"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
)
// ExampleJSONGenerator 示例JSON生成器
type ExampleJSONGenerator struct {
productRepo repositories.ProductRepository
docRepo repositories.ProductDocumentationRepository
apiConfigRepo repositories.ProductApiConfigRepository
logger *zap.Logger
}
// NewExampleJSONGenerator 创建示例JSON生成器
func NewExampleJSONGenerator(
productRepo repositories.ProductRepository,
docRepo repositories.ProductDocumentationRepository,
apiConfigRepo repositories.ProductApiConfigRepository,
logger *zap.Logger,
) *ExampleJSONGenerator {
return &ExampleJSONGenerator{
productRepo: productRepo,
docRepo: docRepo,
apiConfigRepo: apiConfigRepo,
logger: logger,
}
}
// ExampleJSONItem example.json 中的单个项
type ExampleJSONItem struct {
Feature struct {
FeatureName string `json:"featureName"`
Sort int `json:"sort"`
} `json:"feature"`
Data struct {
APIID string `json:"apiID"`
Data interface{} `json:"data"`
} `json:"data"`
}
// GenerateExampleJSON 生成 example.json 文件内容
// productID: 产品ID可以是组合包或单品
// subProductCodes: 子产品编号列表(如果为空,则处理所有子产品)
func (g *ExampleJSONGenerator) GenerateExampleJSON(ctx context.Context, productID string, subProductCodes []string) ([]byte, error) {
// 1. 获取产品信息
product, err := g.productRepo.GetByID(ctx, productID)
if err != nil {
return nil, fmt.Errorf("获取产品信息失败: %w", err)
}
// 2. 构建 example.json 数组
var examples []ExampleJSONItem
if product.IsPackage {
// 组合包:遍历子产品
packageItems, err := g.productRepo.GetPackageItems(ctx, productID)
if err != nil {
return nil, fmt.Errorf("获取组合包子产品失败: %w", err)
}
for sort, item := range packageItems {
// 如果指定了子产品编号列表,只处理列表中的产品
if len(subProductCodes) > 0 {
found := false
for _, code := range subProductCodes {
if item.Product != nil && item.Product.Code == code {
found = true
break
}
}
if !found {
continue
}
}
// 获取子产品信息
var subProduct entities.Product
if item.Product != nil {
subProduct = *item.Product
} else {
subProduct, err = g.productRepo.GetByID(ctx, item.ProductID)
if err != nil {
g.logger.Warn("获取子产品信息失败",
zap.String("product_id", item.ProductID),
zap.Error(err),
)
continue
}
}
// 获取响应示例数据
responseData := g.extractResponseExample(ctx, &subProduct)
// 获取产品名称和编号
productName := subProduct.Name
productCode := subProduct.Code
// 构建示例项
example := ExampleJSONItem{
Feature: struct {
FeatureName string `json:"featureName"`
Sort int `json:"sort"`
}{
FeatureName: productName,
Sort: sort + 1,
},
Data: struct {
APIID string `json:"apiID"`
Data interface{} `json:"data"`
}{
APIID: productCode,
Data: responseData,
},
}
examples = append(examples, example)
}
} else {
// 单品
responseData := g.extractResponseExample(ctx, &product)
example := ExampleJSONItem{
Feature: struct {
FeatureName string `json:"featureName"`
Sort int `json:"sort"`
}{
FeatureName: product.Name,
Sort: 1,
},
Data: struct {
APIID string `json:"apiID"`
Data interface{} `json:"data"`
}{
APIID: product.Code,
Data: responseData,
},
}
examples = append(examples, example)
}
// 3. 序列化为JSON
jsonData, err := json.MarshalIndent(examples, "", " ")
if err != nil {
return nil, fmt.Errorf("序列化example.json失败: %w", err)
}
return jsonData, nil
}
// MatchProductCodeToPath 根据产品编码匹配 UI 组件路径返回路径和类型folder/file
func (g *ExampleJSONGenerator) MatchProductCodeToPath(ctx context.Context, productCode string) (string, string, error) {
basePath := filepath.Join("resources", "Pure Component", "src", "ui")
entries, err := os.ReadDir(basePath)
if err != nil {
return "", "", fmt.Errorf("读取组件目录失败: %w", err)
}
for _, entry := range entries {
name := entry.Name()
// 精确匹配
if name == productCode {
path := filepath.Join(basePath, name)
fileType := "folder"
if !entry.IsDir() {
fileType = "file"
}
return path, fileType, nil
}
// 模糊匹配:文件夹名称包含产品编号,或产品编号包含文件夹名称的核心部分
if strings.Contains(name, productCode) || strings.Contains(productCode, extractCoreCode(name)) {
path := filepath.Join(basePath, name)
fileType := "folder"
if !entry.IsDir() {
fileType = "file"
}
return path, fileType, nil
}
}
return "", "", fmt.Errorf("未找到匹配的组件文件: %s", productCode)
}
// extractCoreCode 提取文件名中的核心编码部分
func extractCoreCode(name string) string {
for i, r := range name {
if (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
return name[i:]
}
}
return name
}
// extractResponseExample 提取产品响应示例数据(优先级:文档 > API配置 > 默认值)
func (g *ExampleJSONGenerator) extractResponseExample(ctx context.Context, product *entities.Product) interface{} {
var responseData interface{}
// 1. 优先从产品文档中获取
doc, err := g.docRepo.FindByProductID(ctx, product.ID)
if err == nil && doc != nil && doc.ResponseExample != "" {
// 尝试直接解析为JSON
err := json.Unmarshal([]byte(doc.ResponseExample), &responseData)
if err == nil {
g.logger.Debug("从产品文档中提取响应示例成功",
zap.String("product_id", product.ID),
zap.String("product_code", product.Code),
)
return responseData
}
// 如果解析失败尝试从Markdown代码块中提取JSON
extractedData := extractJSONFromMarkdown(doc.ResponseExample)
if extractedData != nil {
g.logger.Debug("从Markdown代码块中提取响应示例成功",
zap.String("product_id", product.ID),
zap.String("product_code", product.Code),
)
return extractedData
}
}
// 2. 如果文档中没有尝试从产品API配置中获取
apiConfig, err := g.apiConfigRepo.FindByProductID(ctx, product.ID)
if err == nil && apiConfig != nil && apiConfig.ResponseExample != "" {
// API配置的响应示例通常是 JSON 字符串
err := json.Unmarshal([]byte(apiConfig.ResponseExample), &responseData)
if err == nil {
g.logger.Debug("从产品API配置中提取响应示例成功",
zap.String("product_id", product.ID),
zap.String("product_code", product.Code),
)
return responseData
}
}
// 3. 如果都没有,返回默认空对象
g.logger.Warn("未找到响应示例数据,使用默认空对象",
zap.String("product_id", product.ID),
zap.String("product_code", product.Code),
)
return map[string]interface{}{}
}
// extractJSONFromMarkdown 从Markdown代码块中提取JSON
func extractJSONFromMarkdown(markdown string) interface{} {
// 查找 ```json 代码块
re := regexp.MustCompile("(?s)```json\\s*(.*?)\\s*```")
matches := re.FindStringSubmatch(markdown)
if len(matches) > 1 {
var jsonData interface{}
err := json.Unmarshal([]byte(matches[1]), &jsonData)
if err == nil {
return jsonData
}
}
// 也尝试查找 ``` 代码块(可能是其他格式)
re2 := regexp.MustCompile("(?s)```\\s*(.*?)\\s*```")
matches2 := re2.FindStringSubmatch(markdown)
if len(matches2) > 1 {
var jsonData interface{}
err := json.Unmarshal([]byte(matches2[1]), &jsonData)
if err == nil {
return jsonData
}
}
// 如果提取失败,返回 nil由调用者决定默认值
return nil
}