2025-12-19 17:05:09 +08:00
|
|
|
|
package component_report
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"archive/zip"
|
|
|
|
|
|
"context"
|
2025-12-22 18:32:34 +08:00
|
|
|
|
"crypto/md5"
|
|
|
|
|
|
"encoding/hex"
|
2025-12-19 17:05:09 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"io"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strings"
|
2025-12-22 18:32:34 +08:00
|
|
|
|
"time"
|
2025-12-19 17:05:09 +08:00
|
|
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ZipGenerator ZIP文件生成器
|
|
|
|
|
|
type ZipGenerator struct {
|
|
|
|
|
|
logger *zap.Logger
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 缓存配置
|
|
|
|
|
|
CacheEnabled bool
|
|
|
|
|
|
CacheDir string
|
|
|
|
|
|
CacheTTL time.Duration
|
2025-12-19 17:05:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewZipGenerator 创建ZIP文件生成器
|
|
|
|
|
|
func NewZipGenerator(logger *zap.Logger) *ZipGenerator {
|
|
|
|
|
|
return &ZipGenerator{
|
2025-12-22 18:32:34 +08:00
|
|
|
|
logger: logger,
|
|
|
|
|
|
CacheEnabled: true,
|
|
|
|
|
|
CacheDir: "storage/component-reports/cache",
|
|
|
|
|
|
CacheTTL: 24 * time.Hour, // 默认缓存24小时
|
2025-12-19 17:05:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// NewZipGeneratorWithCache 创建带有自定义缓存配置的ZIP文件生成器
|
|
|
|
|
|
func NewZipGeneratorWithCache(logger *zap.Logger, cacheEnabled bool, cacheDir string, cacheTTL time.Duration) *ZipGenerator {
|
|
|
|
|
|
return &ZipGenerator{
|
|
|
|
|
|
logger: logger,
|
|
|
|
|
|
CacheEnabled: cacheEnabled,
|
|
|
|
|
|
CacheDir: cacheDir,
|
|
|
|
|
|
CacheTTL: cacheTTL,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GenerateZipFile 生成ZIP文件,包含 example.json 和根据子产品编码匹配的UI组件文件
|
2025-12-19 17:05:09 +08:00
|
|
|
|
// productID: 产品ID
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// subProductCodes: 子产品编码列表(用于过滤和下载匹配的UI组件)
|
2025-12-19 17:05:09 +08:00
|
|
|
|
// exampleJSONGenerator: 示例JSON生成器
|
|
|
|
|
|
// outputPath: 输出ZIP文件路径(如果为空,则使用默认路径)
|
|
|
|
|
|
func (g *ZipGenerator) GenerateZipFile(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
productID string,
|
|
|
|
|
|
subProductCodes []string,
|
|
|
|
|
|
exampleJSONGenerator *ExampleJSONGenerator,
|
|
|
|
|
|
outputPath string,
|
|
|
|
|
|
) (string, error) {
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 生成缓存键
|
|
|
|
|
|
cacheKey := g.generateCacheKey(productID, subProductCodes)
|
|
|
|
|
|
|
|
|
|
|
|
// 检查缓存
|
|
|
|
|
|
if g.CacheEnabled {
|
|
|
|
|
|
cachedPath, err := g.getCachedFile(cacheKey)
|
|
|
|
|
|
if err == nil && cachedPath != "" {
|
|
|
|
|
|
// g.logger.Debug("使用缓存的ZIP文件",
|
|
|
|
|
|
// zap.String("product_id", productID),
|
|
|
|
|
|
// zap.String("cache_path", cachedPath))
|
|
|
|
|
|
|
|
|
|
|
|
// 如果指定了输出路径,复制缓存文件到目标位置
|
|
|
|
|
|
if outputPath != "" && outputPath != cachedPath {
|
|
|
|
|
|
if err := g.copyFile(cachedPath, outputPath); err != nil {
|
|
|
|
|
|
g.logger.Error("复制缓存文件失败", zap.Error(err))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return outputPath, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return cachedPath, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 17:05:09 +08:00
|
|
|
|
// 1. 生成 example.json 内容
|
|
|
|
|
|
exampleJSON, err := exampleJSONGenerator.GenerateExampleJSON(ctx, productID, subProductCodes)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("生成example.json失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 确定输出路径
|
|
|
|
|
|
if outputPath == "" {
|
|
|
|
|
|
// 使用默认路径:storage/component-reports/{productID}.zip
|
|
|
|
|
|
outputDir := "storage/component-reports"
|
|
|
|
|
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("创建输出目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
outputPath = filepath.Join(outputDir, fmt.Sprintf("%s_example.json.zip", productID))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 创建ZIP文件
|
|
|
|
|
|
zipFile, err := os.Create(outputPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("创建ZIP文件失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer zipFile.Close()
|
|
|
|
|
|
|
|
|
|
|
|
zipWriter := zip.NewWriter(zipFile)
|
|
|
|
|
|
defer zipWriter.Close()
|
|
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 4. 将生成的内容添加到 Pure_Component/public 目录下的 example.json
|
|
|
|
|
|
exampleWriter, err := zipWriter.Create("Pure_Component/public/example.json")
|
2025-12-19 17:05:09 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("创建example.json文件失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_, err = exampleWriter.Write(exampleJSON)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("写入example.json失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 5. 添加整个 Pure_Component 目录,但只包含子产品编码匹配的UI组件文件
|
|
|
|
|
|
srcBasePath := filepath.Join("resources", "Pure_Component")
|
|
|
|
|
|
uiBasePath := filepath.Join(srcBasePath, "src", "ui")
|
2025-12-19 17:05:09 +08:00
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 根据子产品编码收集所有匹配的组件名称(文件夹名或文件名)
|
2025-12-19 17:05:09 +08:00
|
|
|
|
matchedNames := make(map[string]bool)
|
2025-12-22 18:32:34 +08:00
|
|
|
|
for _, subProductCode := range subProductCodes {
|
|
|
|
|
|
path, _, err := exampleJSONGenerator.MatchSubProductCodeToPath(ctx, subProductCode)
|
2025-12-19 17:05:09 +08:00
|
|
|
|
if err == nil && path != "" {
|
|
|
|
|
|
// 获取组件名称(文件夹名或文件名)
|
|
|
|
|
|
componentName := filepath.Base(path)
|
|
|
|
|
|
matchedNames[componentName] = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 遍历整个 Pure_Component 目录
|
2025-12-19 17:05:09 +08:00
|
|
|
|
err = filepath.Walk(srcBasePath, func(path string, info os.FileInfo, err error) error {
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 计算相对于 Pure_Component 的路径
|
2025-12-19 17:05:09 +08:00
|
|
|
|
relPath, err := filepath.Rel(srcBasePath, path)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 转换为ZIP路径格式,保持在Pure_Component目录下
|
|
|
|
|
|
zipPath := filepath.ToSlash(filepath.Join("Pure_Component", relPath))
|
2025-12-19 17:05:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查是否在 ui 目录下
|
|
|
|
|
|
uiRelPath, err := filepath.Rel(uiBasePath, path)
|
|
|
|
|
|
isInUIDir := err == nil && !strings.HasPrefix(uiRelPath, "..")
|
|
|
|
|
|
|
|
|
|
|
|
if isInUIDir {
|
|
|
|
|
|
// 如果是 ui 目录本身,直接添加
|
|
|
|
|
|
if uiRelPath == "." || uiRelPath == "" {
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
|
|
_, err = zipWriter.Create(zipPath + "/")
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件/文件夹名称
|
|
|
|
|
|
fileName := info.Name()
|
|
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 检查是否应该保留:匹配到的组件文件夹/文件
|
2025-12-19 17:05:09 +08:00
|
|
|
|
shouldInclude := false
|
|
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 检查是否是匹配的组件(检查组件名称)
|
|
|
|
|
|
if matchedNames[fileName] {
|
2025-12-19 17:05:09 +08:00
|
|
|
|
shouldInclude = true
|
|
|
|
|
|
} else {
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 检查是否在匹配的组件文件夹内
|
|
|
|
|
|
// 获取相对于 ui 的路径的第一部分(组件文件夹名)
|
|
|
|
|
|
parts := strings.Split(filepath.ToSlash(uiRelPath), "/")
|
|
|
|
|
|
if len(parts) > 0 && parts[0] != "" && parts[0] != "." {
|
|
|
|
|
|
if matchedNames[parts[0]] {
|
|
|
|
|
|
shouldInclude = true
|
2025-12-19 17:05:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !shouldInclude {
|
|
|
|
|
|
// 跳过不匹配的文件/文件夹
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
|
|
return filepath.SkipDir
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是目录,创建目录项
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
|
|
_, err = zipWriter.Create(zipPath + "/")
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加文件
|
|
|
|
|
|
return g.AddFileToZip(zipWriter, path, zipPath)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
2025-12-22 18:32:34 +08:00
|
|
|
|
g.logger.Warn("添加Pure_Component目录失败", zap.Error(err))
|
2025-12-19 17:05:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
g.logger.Info("成功生成ZIP文件",
|
|
|
|
|
|
zap.String("product_id", productID),
|
|
|
|
|
|
zap.String("output_path", outputPath),
|
|
|
|
|
|
zap.Int("example_json_size", len(exampleJSON)),
|
|
|
|
|
|
zap.Int("sub_product_count", len(subProductCodes)),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-22 18:32:34 +08:00
|
|
|
|
// 缓存文件
|
|
|
|
|
|
if g.CacheEnabled {
|
|
|
|
|
|
if err := g.cacheFile(outputPath, cacheKey); err != nil {
|
|
|
|
|
|
g.logger.Warn("缓存ZIP文件失败", zap.Error(err))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
g.logger.Debug("ZIP文件已缓存", zap.String("cache_key", cacheKey))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 17:05:09 +08:00
|
|
|
|
return outputPath, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddFileToZip 添加文件到ZIP
|
|
|
|
|
|
func (g *ZipGenerator) AddFileToZip(zipWriter *zip.Writer, filePath string, zipPath string) error {
|
|
|
|
|
|
file, err := os.Open(filePath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("打开文件失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
|
|
writer, err := zipWriter.Create(zipPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("创建ZIP文件项失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_, err = io.Copy(writer, file)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("复制文件内容失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddFolderToZip 递归添加文件夹到ZIP
|
|
|
|
|
|
func (g *ZipGenerator) AddFolderToZip(zipWriter *zip.Writer, folderPath string, basePath string) error {
|
|
|
|
|
|
return filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算相对路径
|
|
|
|
|
|
relPath, err := filepath.Rel(basePath, path)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为ZIP路径格式(使用正斜杠)
|
|
|
|
|
|
zipPath := filepath.ToSlash(relPath)
|
|
|
|
|
|
|
|
|
|
|
|
return g.AddFileToZip(zipWriter, path, zipPath)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddFileToZipWithTarget 将单个文件添加到ZIP的指定目标路径
|
|
|
|
|
|
func (g *ZipGenerator) AddFileToZipWithTarget(zipWriter *zip.Writer, filePath string, targetPath string) error {
|
|
|
|
|
|
file, err := os.Open(filePath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("打开文件失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
|
|
writer, err := zipWriter.Create(filepath.ToSlash(targetPath))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("创建ZIP文件项失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_, err = io.Copy(writer, file)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("复制文件内容失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddFolderToZipWithPrefix 递归添加文件夹到ZIP,并在ZIP内添加路径前缀
|
|
|
|
|
|
func (g *ZipGenerator) AddFolderToZipWithPrefix(zipWriter *zip.Writer, folderPath string, basePath string, prefix string) error {
|
|
|
|
|
|
return filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
relPath, err := filepath.Rel(basePath, path)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
zipPath := filepath.ToSlash(filepath.Join(prefix, relPath))
|
|
|
|
|
|
return g.AddFileToZip(zipWriter, path, zipPath)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-12-22 18:32:34 +08:00
|
|
|
|
|
|
|
|
|
|
// GenerateFilteredComponentZip 生成筛选后的组件ZIP文件
|
|
|
|
|
|
// productID: 产品ID
|
|
|
|
|
|
// subProductCodes: 子产品编号列表(用于筛选组件)
|
|
|
|
|
|
// outputPath: 输出ZIP文件路径(如果为空,则使用默认路径)
|
|
|
|
|
|
func (g *ZipGenerator) GenerateFilteredComponentZip(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
productID string,
|
|
|
|
|
|
subProductCodes []string,
|
|
|
|
|
|
outputPath string,
|
|
|
|
|
|
) (string, error) {
|
|
|
|
|
|
// 1. 确定基础路径
|
|
|
|
|
|
basePath := filepath.Join("resources", "Pure_Component")
|
|
|
|
|
|
uiBasePath := filepath.Join(basePath, "src", "ui")
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 确定输出路径
|
|
|
|
|
|
if outputPath == "" {
|
|
|
|
|
|
// 使用默认路径:storage/component-reports/{productID}_filtered.zip
|
|
|
|
|
|
outputDir := "storage/component-reports"
|
|
|
|
|
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("创建输出目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
outputPath = filepath.Join(outputDir, fmt.Sprintf("%s_filtered.zip", productID))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 创建ZIP文件
|
|
|
|
|
|
zipFile, err := os.Create(outputPath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("创建ZIP文件失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer zipFile.Close()
|
|
|
|
|
|
|
|
|
|
|
|
zipWriter := zip.NewWriter(zipFile)
|
|
|
|
|
|
defer zipWriter.Close()
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 收集所有匹配的组件名称(文件夹名或文件名)
|
|
|
|
|
|
matchedNames := make(map[string]bool)
|
|
|
|
|
|
for _, productCode := range subProductCodes {
|
|
|
|
|
|
// 简化匹配逻辑,直接使用产品代码作为组件名
|
|
|
|
|
|
matchedNames[productCode] = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 递归添加整个 Pure_Component 目录,但筛选 ui 目录下的内容
|
|
|
|
|
|
err = filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算相对于基础路径的相对路径
|
|
|
|
|
|
relPath, err := filepath.Rel(basePath, path)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为ZIP路径格式,保持在Pure_Component目录下
|
|
|
|
|
|
zipPath := filepath.ToSlash(filepath.Join("Pure_Component", relPath))
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否在 ui 目录下
|
|
|
|
|
|
uiRelPath, err := filepath.Rel(uiBasePath, path)
|
|
|
|
|
|
isInUIDir := err == nil && !strings.HasPrefix(uiRelPath, "..")
|
|
|
|
|
|
|
|
|
|
|
|
if isInUIDir {
|
|
|
|
|
|
// 如果是 ui 目录本身,直接添加
|
|
|
|
|
|
if uiRelPath == "." || uiRelPath == "" {
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
|
|
_, err = zipWriter.Create(zipPath + "/")
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件/文件夹名称
|
|
|
|
|
|
fileName := info.Name()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否应该保留:匹配到的组件文件夹/文件
|
|
|
|
|
|
shouldInclude := false
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是匹配的组件(检查组件名称)
|
|
|
|
|
|
if matchedNames[fileName] {
|
|
|
|
|
|
shouldInclude = true
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 检查是否在匹配的组件文件夹内
|
|
|
|
|
|
// 获取相对于 ui 的路径的第一部分(组件文件夹名)
|
|
|
|
|
|
parts := strings.Split(filepath.ToSlash(uiRelPath), "/")
|
|
|
|
|
|
if len(parts) > 0 && parts[0] != "" && parts[0] != "." {
|
|
|
|
|
|
if matchedNames[parts[0]] {
|
|
|
|
|
|
shouldInclude = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !shouldInclude {
|
|
|
|
|
|
// 跳过不匹配的文件/文件夹
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
|
|
return filepath.SkipDir
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是目录,创建目录项
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
|
|
_, err = zipWriter.Create(zipPath + "/")
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加文件
|
|
|
|
|
|
return g.AddFileToZip(zipWriter, path, zipPath)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
g.logger.Warn("添加Pure_Component目录失败", zap.Error(err))
|
|
|
|
|
|
return "", fmt.Errorf("添加Pure_Component目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
g.logger.Info("成功生成筛选后的组件ZIP文件",
|
|
|
|
|
|
zap.String("product_id", productID),
|
|
|
|
|
|
zap.String("output_path", outputPath),
|
|
|
|
|
|
zap.Int("matched_components_count", len(matchedNames)),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return outputPath, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// generateCacheKey 生成缓存键
|
|
|
|
|
|
func (g *ZipGenerator) 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[:])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// getCachedFile 获取缓存文件
|
|
|
|
|
|
func (g *ZipGenerator) getCachedFile(cacheKey string) (string, error) {
|
|
|
|
|
|
// 确保缓存目录存在
|
|
|
|
|
|
if err := os.MkdirAll(g.CacheDir, 0755); err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("创建缓存目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cacheFilePath := filepath.Join(g.CacheDir, cacheKey+".zip")
|
|
|
|
|
|
|
|
|
|
|
|
// 检查文件是否存在
|
|
|
|
|
|
fileInfo, err := os.Stat(cacheFilePath)
|
|
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
|
|
return "", nil // 文件不存在,但不是错误
|
|
|
|
|
|
}
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查文件是否过期
|
|
|
|
|
|
if time.Since(fileInfo.ModTime()) > g.CacheTTL {
|
|
|
|
|
|
// 文件过期,删除
|
|
|
|
|
|
os.Remove(cacheFilePath)
|
|
|
|
|
|
return "", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return cacheFilePath, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// cacheFile 缓存文件
|
|
|
|
|
|
func (g *ZipGenerator) cacheFile(filePath, cacheKey string) error {
|
|
|
|
|
|
// 确保缓存目录存在
|
|
|
|
|
|
if err := os.MkdirAll(g.CacheDir, 0755); err != nil {
|
|
|
|
|
|
return fmt.Errorf("创建缓存目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cacheFilePath := filepath.Join(g.CacheDir, cacheKey+".zip")
|
|
|
|
|
|
|
|
|
|
|
|
// 复制文件到缓存目录
|
|
|
|
|
|
return g.copyFile(filePath, cacheFilePath)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// copyFile 复制文件
|
|
|
|
|
|
func (g *ZipGenerator) copyFile(src, dst string) error {
|
|
|
|
|
|
sourceFile, err := os.Open(src)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer sourceFile.Close()
|
|
|
|
|
|
|
|
|
|
|
|
destFile, err := os.Create(dst)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer destFile.Close()
|
|
|
|
|
|
|
|
|
|
|
|
_, err = io.Copy(destFile, sourceFile)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|