addui
This commit is contained in:
204
internal/shared/component_report/README.md
Normal file
204
internal/shared/component_report/README.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# 组件报告生成服务
|
||||
|
||||
这个服务用于生成产品示例报告的 `example.json` 文件,并打包成 ZIP 文件供下载。
|
||||
|
||||
## 功能概述
|
||||
|
||||
1. **生成 example.json 文件**:根据组合包子产品的响应示例数据生成符合格式要求的 JSON 文件
|
||||
2. **打包 ZIP 文件**:将生成的 `example.json` 文件打包成 ZIP 格式
|
||||
3. **HTTP 接口**:提供 HTTP 接口用于生成和下载文件
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
component_report/
|
||||
├── example_json_generator.go # 示例JSON生成器
|
||||
├── zip_generator.go # ZIP文件生成器
|
||||
├── handler.go # HTTP处理器
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 直接使用生成器
|
||||
|
||||
```go
|
||||
// 创建生成器
|
||||
exampleJSONGenerator := component_report.NewExampleJSONGenerator(
|
||||
productRepo,
|
||||
docRepo,
|
||||
apiConfigRepo,
|
||||
logger,
|
||||
)
|
||||
|
||||
// 生成 example.json
|
||||
jsonData, err := exampleJSONGenerator.GenerateExampleJSON(
|
||||
ctx,
|
||||
productID, // 产品ID(可以是组合包或单品)
|
||||
subProductCodes, // 子产品编号列表(可选,如果为空则处理所有子产品)
|
||||
)
|
||||
```
|
||||
|
||||
### 2. 生成 ZIP 文件
|
||||
|
||||
```go
|
||||
// 创建ZIP生成器
|
||||
zipGenerator := component_report.NewZipGenerator(logger)
|
||||
|
||||
// 生成ZIP文件
|
||||
zipPath, err := zipGenerator.GenerateZipFile(
|
||||
ctx,
|
||||
productID,
|
||||
subProductCodes,
|
||||
exampleJSONGenerator,
|
||||
outputPath, // 输出路径(可选,如果为空则使用默认路径)
|
||||
)
|
||||
```
|
||||
|
||||
### 3. 使用 HTTP 接口
|
||||
|
||||
#### 生成 example.json
|
||||
|
||||
```http
|
||||
POST /api/v1/component-report/generate-example-json
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"product_id": "产品ID",
|
||||
"sub_product_codes": ["子产品编号1", "子产品编号2"] // 可选
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"product_id": "产品ID",
|
||||
"json_content": "生成的JSON内容",
|
||||
"json_size": 1234
|
||||
}
|
||||
```
|
||||
|
||||
#### 生成 ZIP 文件
|
||||
|
||||
```http
|
||||
POST /api/v1/component-report/generate-zip
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"product_id": "产品ID",
|
||||
"sub_product_codes": ["子产品编号1", "子产品编号2"], // 可选
|
||||
"output_path": "自定义输出路径" // 可选
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "ZIP文件生成成功",
|
||||
"zip_path": "storage/component-reports/xxx_example.json.zip",
|
||||
"file_size": 12345,
|
||||
"file_name": "xxx_example.json.zip"
|
||||
}
|
||||
```
|
||||
|
||||
#### 生成并下载 ZIP 文件
|
||||
|
||||
```http
|
||||
POST /api/v1/component-report/generate-and-download
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"product_id": "产品ID",
|
||||
"sub_product_codes": ["子产品编号1", "子产品编号2"] // 可选
|
||||
}
|
||||
```
|
||||
|
||||
响应:直接返回 ZIP 文件流
|
||||
|
||||
#### 下载已生成的 ZIP 文件
|
||||
|
||||
```http
|
||||
GET /api/v1/component-report/download-zip/:product_id
|
||||
```
|
||||
|
||||
响应:直接返回 ZIP 文件流
|
||||
|
||||
## example.json 格式
|
||||
|
||||
生成的 `example.json` 文件格式如下:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"feature": {
|
||||
"featureName": "产品名称",
|
||||
"sort": 1
|
||||
},
|
||||
"data": {
|
||||
"apiID": "产品编号",
|
||||
"data": {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": { ... }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"feature": {
|
||||
"featureName": "另一个产品名称",
|
||||
"sort": 2
|
||||
},
|
||||
"data": {
|
||||
"apiID": "另一个产品编号",
|
||||
"data": { ... }
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 响应示例数据提取优先级
|
||||
|
||||
1. **产品文档的 `response_example` 字段**(JSON格式)
|
||||
2. **产品文档的 `response_example` 字段**(Markdown代码块中的JSON)
|
||||
3. **产品API配置的 `response_example` 字段**
|
||||
4. **默认空对象** `{}`(如果都没有)
|
||||
|
||||
## ZIP 文件结构
|
||||
|
||||
生成的 ZIP 文件结构:
|
||||
|
||||
```
|
||||
component-report.zip
|
||||
└── public/
|
||||
└── example.json
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保 `storage/component-reports` 目录存在且有写权限
|
||||
2. 如果产品是组合包,会遍历所有子产品(或指定的子产品)生成响应示例
|
||||
3. 如果某个子产品没有响应示例数据,会使用空对象 `{}` 作为默认值
|
||||
4. ZIP 文件会保存在 `storage/component-reports` 目录下,文件名为 `{productID}_example.json.zip`
|
||||
|
||||
## 集成到路由
|
||||
|
||||
如果需要使用 HTTP 接口,需要在路由中注册:
|
||||
|
||||
```go
|
||||
// 创建处理器
|
||||
componentReportHandler := component_report.NewComponentReportHandler(
|
||||
productRepo,
|
||||
docRepo,
|
||||
apiConfigRepo,
|
||||
logger,
|
||||
)
|
||||
|
||||
// 注册路由
|
||||
router.POST("/api/v1/component-report/generate-example-json", componentReportHandler.GenerateExampleJSON)
|
||||
router.POST("/api/v1/component-report/generate-zip", componentReportHandler.GenerateZip)
|
||||
router.POST("/api/v1/component-report/generate-and-download", componentReportHandler.GenerateAndDownloadZip)
|
||||
router.GET("/api/v1/component-report/download-zip/:product_id", componentReportHandler.DownloadZip)
|
||||
```
|
||||
286
internal/shared/component_report/example_json_generator.go
Normal file
286
internal/shared/component_report/example_json_generator.go
Normal file
@@ -0,0 +1,286 @@
|
||||
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
|
||||
}
|
||||
1343
internal/shared/component_report/handler.go
Normal file
1343
internal/shared/component_report/handler.go
Normal file
File diff suppressed because it is too large
Load Diff
265
internal/shared/component_report/zip_generator.go
Normal file
265
internal/shared/component_report/zip_generator.go
Normal file
@@ -0,0 +1,265 @@
|
||||
package component_report
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ZipGenerator ZIP文件生成器
|
||||
type ZipGenerator struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewZipGenerator 创建ZIP文件生成器
|
||||
func NewZipGenerator(logger *zap.Logger) *ZipGenerator {
|
||||
return &ZipGenerator{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateZipFile 生成ZIP文件,包含 example.json 和匹配的组件文件
|
||||
// productID: 产品ID
|
||||
// subProductCodes: 子产品编号列表(如果为空,则处理所有子产品)
|
||||
// exampleJSONGenerator: 示例JSON生成器
|
||||
// outputPath: 输出ZIP文件路径(如果为空,则使用默认路径)
|
||||
func (g *ZipGenerator) GenerateZipFile(
|
||||
ctx context.Context,
|
||||
productID string,
|
||||
subProductCodes []string,
|
||||
exampleJSONGenerator *ExampleJSONGenerator,
|
||||
outputPath string,
|
||||
) (string, error) {
|
||||
// 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()
|
||||
|
||||
// 4. 添加 example.json 到 public 目录
|
||||
exampleWriter, err := zipWriter.Create("public/example.json")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建example.json文件失败: %w", err)
|
||||
}
|
||||
|
||||
_, err = exampleWriter.Write(exampleJSON)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("写入example.json失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 添加整个 src 目录,但过滤 ui 目录下的文件
|
||||
srcBasePath := filepath.Join("resources", "Pure Component", "src")
|
||||
uiBasePath := filepath.Join(srcBasePath, "ui")
|
||||
|
||||
// 收集所有匹配的组件名称(文件夹名或文件名)
|
||||
matchedNames := make(map[string]bool)
|
||||
for _, productCode := range subProductCodes {
|
||||
path, _, err := exampleJSONGenerator.MatchProductCodeToPath(ctx, productCode)
|
||||
if err == nil && path != "" {
|
||||
// 获取组件名称(文件夹名或文件名)
|
||||
componentName := filepath.Base(path)
|
||||
matchedNames[componentName] = true
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历整个 src 目录
|
||||
err = filepath.Walk(srcBasePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 计算相对于 src 的路径
|
||||
relPath, err := filepath.Rel(srcBasePath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 转换为ZIP路径格式
|
||||
zipPath := filepath.ToSlash(filepath.Join("src", 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()
|
||||
|
||||
// 检查是否应该保留:
|
||||
// 1. CBehaviorRiskScan.vue 文件(无论在哪里)
|
||||
// 2. 匹配到的组件文件夹/文件
|
||||
shouldInclude := false
|
||||
|
||||
// 检查是否是 CBehaviorRiskScan.vue
|
||||
if fileName == "CBehaviorRiskScan.vue" {
|
||||
shouldInclude = true
|
||||
} else {
|
||||
// 检查是否是匹配的组件(检查组件名称)
|
||||
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("添加src目录失败", zap.Error(err))
|
||||
}
|
||||
|
||||
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)),
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user