add购买记录功能

This commit is contained in:
2025-12-22 18:32:34 +08:00
parent 65a61d0336
commit 7f8554fa12
314 changed files with 4029 additions and 83496 deletions

View File

@@ -2,12 +2,15 @@ package component_report
import (
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"go.uber.org/zap"
@@ -21,6 +24,10 @@ type ExampleJSONGenerator struct {
docRepo repositories.ProductDocumentationRepository
apiConfigRepo repositories.ProductApiConfigRepository
logger *zap.Logger
// 缓存配置
CacheEnabled bool
CacheDir string
CacheTTL time.Duration
}
// NewExampleJSONGenerator 创建示例JSON生成器
@@ -35,6 +42,30 @@ func NewExampleJSONGenerator(
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,
}
}
@@ -54,6 +85,20 @@ type ExampleJSONItem struct {
// 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 {
@@ -157,12 +202,21 @@ func (g *ExampleJSONGenerator) GenerateExampleJSON(ctx context.Context, productI
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
}
// MatchProductCodeToPath 根据产品编码匹配 UI 组件路径返回路径和类型folder/file
func (g *ExampleJSONGenerator) MatchProductCodeToPath(ctx context.Context, productCode string) (string, string, error) {
basePath := filepath.Join("resources", "Pure Component", "src", "ui")
// 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 {
@@ -172,18 +226,8 @@ func (g *ExampleJSONGenerator) MatchProductCodeToPath(ctx context.Context, produ
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)) {
// 使用改进的相似性匹配算法
if isSimilarCode(subProductCode, name) {
path := filepath.Join(basePath, name)
fileType := "folder"
if !entry.IsDir() {
@@ -193,7 +237,7 @@ func (g *ExampleJSONGenerator) MatchProductCodeToPath(ctx context.Context, produ
}
}
return "", "", fmt.Errorf("未找到匹配的组件文件: %s", productCode)
return "", "", fmt.Errorf("未找到匹配的组件文件: %s", subProductCode)
}
// extractCoreCode 提取文件名中的核心编码部分
@@ -206,6 +250,44 @@ func extractCoreCode(name string) string {
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{}
@@ -216,20 +298,20 @@ func (g *ExampleJSONGenerator) extractResponseExample(ctx context.Context, produ
// 尝试直接解析为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),
)
// 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),
)
// g.logger.Debug("从Markdown代码块中提取响应示例成功",
// zap.String("product_id", product.ID),
// zap.String("product_code", product.Code),
// )
return extractedData
}
}
@@ -240,10 +322,10 @@ func (g *ExampleJSONGenerator) extractResponseExample(ctx context.Context, produ
// 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),
)
// g.logger.Debug("从产品API配置中提取响应示例成功",
// zap.String("product_id", product.ID),
// zap.String("product_code", product.Code),
// )
return responseData
}
}
@@ -284,3 +366,57 @@ func extractJSONFromMarkdown(markdown string) interface{} {
// 如果提取失败,返回 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)
}