package component_report import ( "context" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "os" "path/filepath" "regexp" "strings" "time" "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 // 缓存配置 CacheEnabled bool CacheDir string CacheTTL time.Duration } // 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, CacheEnabled: true, CacheDir: "storage/component-reports/cache", CacheTTL: 24 * time.Hour, // 默认缓存24小时 } } // NewExampleJSONGeneratorWithCache 创建带有自定义缓存配置的示例JSON生成器 func NewExampleJSONGeneratorWithCache( productRepo repositories.ProductRepository, docRepo repositories.ProductDocumentationRepository, apiConfigRepo repositories.ProductApiConfigRepository, logger *zap.Logger, cacheEnabled bool, cacheDir string, cacheTTL time.Duration, ) *ExampleJSONGenerator { return &ExampleJSONGenerator{ productRepo: productRepo, docRepo: docRepo, apiConfigRepo: apiConfigRepo, logger: logger, CacheEnabled: cacheEnabled, CacheDir: cacheDir, CacheTTL: cacheTTL, } } // 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) { // 生成缓存键 cacheKey := g.generateCacheKey(productID, subProductCodes) // 检查缓存 if g.CacheEnabled { cachedData, err := g.getCachedData(cacheKey) if err == nil && cachedData != nil { // g.logger.Debug("使用缓存的example.json数据", // zap.String("product_id", productID), // zap.String("cache_key", cacheKey)) return cachedData, nil } } // 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) } // 缓存数据 if g.CacheEnabled { if err := g.cacheData(cacheKey, jsonData); err != nil { g.logger.Warn("缓存example.json数据失败", zap.Error(err)) } else { g.logger.Debug("example.json数据已缓存", zap.String("cache_key", cacheKey)) } } return jsonData, nil } // MatchSubProductCodeToPath 根据子产品编码匹配 UI 组件路径,返回路径和类型(folder/file) func (g *ExampleJSONGenerator) MatchSubProductCodeToPath(ctx context.Context, subProductCode 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 isSimilarCode(subProductCode, name) { path := filepath.Join(basePath, name) fileType := "folder" if !entry.IsDir() { fileType = "file" } return path, fileType, nil } } return "", "", fmt.Errorf("未找到匹配的组件文件: %s", subProductCode) } // 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 } // extractMainCode 从子产品编码或文件夹名称中提取主要编码部分 // 处理可能的格式差异,如前缀、后缀等 func extractMainCode(code string) string { // 移除常见的前缀,如 C if len(code) > 0 && code[0] == 'C' { return code[1:] } return code } // isSimilarCode 判断两个编码是否相似,考虑多种可能的格式差异 func isSimilarCode(code1, code2 string) bool { // 直接相等 if code1 == code2 { return true } // 移除常见前缀后比较 mainCode1 := extractMainCode(code1) mainCode2 := extractMainCode(code2) if mainCode1 == mainCode2 || mainCode1 == code2 || code1 == mainCode2 { return true } // 包含关系 if strings.Contains(code1, code2) || strings.Contains(code2, code1) { return true } // 移除前缀后的包含关系 if strings.Contains(mainCode1, code2) || strings.Contains(code2, mainCode1) || strings.Contains(code1, mainCode2) || strings.Contains(mainCode2, code1) { return true } return false } // 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 } // generateCacheKey 生成缓存键 func (g *ExampleJSONGenerator) generateCacheKey(productID string, subProductCodes []string) string { // 使用产品ID和子产品编码列表生成MD5哈希 data := productID for _, code := range subProductCodes { data += "|" + code } hash := md5.Sum([]byte(data)) return hex.EncodeToString(hash[:]) + ".json" } // getCachedData 获取缓存数据 func (g *ExampleJSONGenerator) getCachedData(cacheKey string) ([]byte, error) { // 确保缓存目录存在 if err := os.MkdirAll(g.CacheDir, 0755); err != nil { return nil, fmt.Errorf("创建缓存目录失败: %w", err) } cacheFilePath := filepath.Join(g.CacheDir, cacheKey) // 检查文件是否存在 fileInfo, err := os.Stat(cacheFilePath) if os.IsNotExist(err) { return nil, nil // 文件不存在,但不是错误 } if err != nil { return nil, err } // 检查文件是否过期 if time.Since(fileInfo.ModTime()) > g.CacheTTL { // 文件过期,删除 os.Remove(cacheFilePath) return nil, nil } // 读取文件内容 return os.ReadFile(cacheFilePath) } // cacheData 缓存数据 func (g *ExampleJSONGenerator) cacheData(cacheKey string, data []byte) error { // 确保缓存目录存在 if err := os.MkdirAll(g.CacheDir, 0755); err != nil { return fmt.Errorf("创建缓存目录失败: %w", err) } cacheFilePath := filepath.Join(g.CacheDir, cacheKey) // 写入文件 return os.WriteFile(cacheFilePath, data, 0644) }