package main import ( "context" "fmt" "os" "path/filepath" "regexp" "sort" "strings" "github.com/shopspring/decimal" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" "gorm.io/gorm/schema" ) // ProcessorInfo 处理器信息 type ProcessorInfo struct { ProductCode string // 产品编号(从文件名提取) ProductName string // 产品名称(从数据库查询) Category string // 分类(从数据库查询) Price string // 价格(从数据库查询) DataSource string // 数据源(根据service确定) DataSourceCode string // 数据源编号(CallAPI的第一个参数) CostPrice string // 成本价(留空) } // Product 产品实体(简化版) type Product struct { Code string `gorm:"column:code"` Name string `gorm:"column:name"` CategoryID string `gorm:"column:category_id"` Category *ProductCategory `gorm:"foreignKey:CategoryID"` Price decimal.Decimal `gorm:"column:price"` } // ProductCategory 产品分类实体(简化版) type ProductCategory struct { ID string `gorm:"column:id"` Name string `gorm:"column:name"` } func main() { // 获取处理器目录 processorsDir := filepath.Join("internal", "domains", "api", "services", "processors") if len(os.Args) > 1 { processorsDir = os.Args[1] } // 扫描所有处理器文件 processors, err := scanProcessors(processorsDir) if err != nil { fmt.Fprintf(os.Stderr, "扫描处理器失败: %v\n", err) os.Exit(1) } // 连接数据库 db, err := connectDB() if err != nil { fmt.Fprintf(os.Stderr, "连接数据库失败: %v\n", err) os.Exit(1) } // 检查数据库产品数量和处理器数量 ctx := context.Background() checkProductAndProcessorCounts(ctx, db, processors) // 查询产品信息并填充 fmt.Println("正在查询数据库...") successCount := 0 for i := range processors { product, err := queryProduct(ctx, db, processors[i].ProductCode) if err == nil && product != nil && product.Name != "" { processors[i].ProductName = product.Name if product.Category != nil && product.Category.Name != "" { processors[i].Category = product.Category.Name } // 格式化价格,保留两位小数 if !product.Price.IsZero() { processors[i].Price = product.Price.String() } successCount++ } else { // 如果查询失败,保留空值(不输出错误,避免日志过多) processors[i].ProductName = "" processors[i].Category = "" processors[i].Price = "" } } fmt.Printf("成功查询到 %d/%d 个产品的信息\n", successCount, len(processors)) // 按数据源排序 sortProcessorsByDataSource(processors) // 输出表格 printTable(processors) // 同时输出到文件 writeToFiles(processors) } // sortProcessorsByDataSource 按数据源排序 func sortProcessorsByDataSource(processors []ProcessorInfo) { // 定义数据源的排序优先级 dataSourceOrder := map[string]int{ "安徽智查": 1, "羽山数据": 2, "西部数据": 3, "四川星维": 4, "天眼查": 5, "阿里云": 6, "木子数据": 7, "内部处理": 8, "未知": 9, } sort.Slice(processors, func(i, j int) bool { orderI, existsI := dataSourceOrder[processors[i].DataSource] orderJ, existsJ := dataSourceOrder[processors[j].DataSource] // 如果数据源不存在,放在最后 if !existsI { orderI = 999 } if !existsJ { orderJ = 999 } // 首先按数据源排序 if orderI != orderJ { return orderI < orderJ } // 如果数据源相同,按产品编号排序 return processors[i].ProductCode < processors[j].ProductCode }) } // writeToFiles 将表格写入文件 func writeToFiles(processors []ProcessorInfo) { // 写入 CSV 文件 csvFile, err := os.Create("processors_table.csv") if err == nil { defer csvFile.Close() csvFile.WriteString("\xEF\xBB\xBF") // UTF-8 BOM csvFile.WriteString("产品编号,产品名称,分类,价格,数据源,数据源编号,成本价\n") for _, p := range processors { productName := strings.ReplaceAll(p.ProductName, ",", ",") category := strings.ReplaceAll(p.Category, ",", ",") csvFile.WriteString(fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s\n", p.ProductCode, productName, category, p.Price, p.DataSource, p.DataSourceCode, p.CostPrice)) } fmt.Println("\n✅ CSV 文件已保存到: processors_table.csv") } // 写入 Markdown 文件 mdFile, err := os.Create("processors_table.md") if err == nil { defer mdFile.Close() mdFile.WriteString("# 处理器产品信息表\n\n") mdFile.WriteString("| 产品编号 | 产品名称 | 分类 | 价格 | 数据源 | 数据源编号 | 成本价 |\n") mdFile.WriteString("|---------|---------|------|------|--------|-----------|--------|\n") for _, p := range processors { productName := p.ProductName if productName == "" { productName = "-" } category := p.Category if category == "" { category = "-" } price := p.Price if price == "" { price = "-" } dataSource := p.DataSource if dataSource == "" { dataSource = "-" } dataSourceCode := p.DataSourceCode if dataSourceCode == "" { dataSourceCode = "-" } costPrice := p.CostPrice if costPrice == "" { costPrice = "-" } mdFile.WriteString(fmt.Sprintf("| %s | %s | %s | %s | %s | %s | %s |\n", p.ProductCode, productName, category, price, dataSource, dataSourceCode, costPrice)) } fmt.Println("✅ Markdown 文件已保存到: processors_table.md") } } // scanProcessors 扫描处理器文件 func scanProcessors(dir string) ([]ProcessorInfo, error) { var processors []ProcessorInfo err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // 跳过非 .go 文件 if !strings.HasSuffix(path, "_processor.go") { return nil } // 跳过 comb 和 test 目录 if strings.Contains(path, string(filepath.Separator)+"comb"+string(filepath.Separator)) { return nil } if strings.Contains(path, string(filepath.Separator)+"test"+string(filepath.Separator)) { return nil } // 读取文件内容 content, err := os.ReadFile(path) if err != nil { return err } // 提取产品编号(从文件名) filename := filepath.Base(path) productCode := extractProductCode(filename) // 解析文件内容,提取数据源和数据源编号 dataSource, dataSourceCode := extractDataSourceInfo(string(content)) processors = append(processors, ProcessorInfo{ ProductCode: productCode, ProductName: "", Category: "", Price: "", DataSource: dataSource, DataSourceCode: dataSourceCode, CostPrice: "", }) return nil }) return processors, err } // extractProductCode 从文件名提取产品编号 func extractProductCode(filename string) string { // 例如: dwbg6a2c_processor.go -> DWBG6A2C name := strings.TrimSuffix(filename, "_processor.go") return strings.ToUpper(name) } // extractDataSourceInfo 从代码中提取数据源和数据源编号 func extractDataSourceInfo(content string) (string, string) { // 先尝试匹配特殊的 CallAPI 方法(如 G05HZ01CallAPI) specialCallAPIPattern := regexp.MustCompile(`deps\.(\w+Service)\.(\w+CallAPI)\([^,]+,\s*"([^"]+)"`) specialMatches := specialCallAPIPattern.FindAllStringSubmatch(content, -1) if len(specialMatches) > 0 { serviceName := specialMatches[0][1] dataSourceCode := specialMatches[0][3] return getDataSourceName(serviceName), dataSourceCode } // 先尝试匹配 AlicloudService 的特殊情况(第一个参数是字符串路径,没有 ctx) // 匹配: deps.AlicloudService.CallAPI("path", ...) alicloudPattern := regexp.MustCompile(`deps\.AlicloudService\.CallAPI\(\s*"([^"]+)"`) alicloudMatches := alicloudPattern.FindAllStringSubmatch(content, -1) if len(alicloudMatches) > 0 { path := alicloudMatches[0][1] parts := strings.Split(path, "/") dataSourceCode := path if len(parts) > 0 { dataSourceCode = parts[len(parts)-1] } return "阿里云", dataSourceCode } // 匹配普通的 CallAPI 调用 // 匹配模式: deps.ServiceName.CallAPI(ctx, "CODE", ...) 或 deps.ServiceName.CallAPI(ctx, projectID, ...) callAPIPattern := regexp.MustCompile(`deps\.(\w+Service)\.CallAPI\([^,]*,\s*([^,)]+)`) matches := callAPIPattern.FindAllStringSubmatch(content, -1) if len(matches) == 0 { // 检查是否有直接的 HTTP 请求(如 COMENT01) if strings.Contains(content, "http.NewRequest") || strings.Contains(content, "http.Client") { return "内部处理", "" } // 检查是否调用了其他处理器(如 QYGL3F8E) if strings.Contains(content, "Process") && strings.Contains(content, "Request") { return "内部处理", "" } return "未知", "" } // 取第一个匹配的服务 serviceName := matches[0][1] codeOrVar := strings.TrimSpace(matches[0][2]) var dataSourceCode string // 如果是字符串字面量(带引号),直接提取 if strings.HasPrefix(codeOrVar, `"`) && strings.HasSuffix(codeOrVar, `"`) { dataSourceCode = strings.Trim(codeOrVar, `"`) // 对于 AlicloudService,提取路径的最后部分作为数据源编号 if serviceName == "AlicloudService" { parts := strings.Split(dataSourceCode, "/") if len(parts) > 0 { dataSourceCode = parts[len(parts)-1] } } } else { // 如果是变量(如 projectID),尝试查找变量定义 // 对于 XingweiService,查找 projectID 变量 if strings.Contains(codeOrVar, "projectID") || codeOrVar == "projectID" { projectIDPattern := regexp.MustCompile(`projectID\s*:=\s*"([^"]+)"`) projectIDMatches := projectIDPattern.FindAllStringSubmatch(content, -1) if len(projectIDMatches) > 0 { // 取最后一个匹配的 projectID(通常是最接近 CallAPI 的那个) dataSourceCode = projectIDMatches[len(projectIDMatches)-1][1] } else { dataSourceCode = "变量未找到" } } else { // 尝试查找变量定义(通用模式) varPattern := regexp.MustCompile(regexp.QuoteMeta(codeOrVar) + `\s*:=\s*"([^"]+)"`) varMatches := varPattern.FindAllStringSubmatch(content, -1) if len(varMatches) > 0 { dataSourceCode = varMatches[len(varMatches)-1][1] } else { dataSourceCode = codeOrVar } } } // 根据服务名确定数据源 dataSource := getDataSourceName(serviceName) return dataSource, dataSourceCode } // getDataSourceName 根据服务名获取数据源名称 func getDataSourceName(serviceName string) string { switch serviceName { case "ZhichaService": return "安徽智查" case "YushanService": return "羽山数据" case "WestDexService": return "西部数据" case "XingweiService": return "四川星维" case "TianYanChaService": return "天眼查" case "AlicloudService": return "阿里云" case "MuziService": return "木子数据" default: return "未知" } } // connectDB 连接数据库 func connectDB() (*gorm.DB, error) { // 数据库连接配置 dsn := "host=1.117.67.95 user=tyapi_user password=Pg9mX4kL8nW2rT5y dbname=tyapi port=25010 sslmode=disable TimeZone=Asia/Shanghai" // 配置GORM,使用单数表名(与项目配置一致) gormConfig := &gorm.Config{ NamingStrategy: schema.NamingStrategy{ SingularTable: true, // 使用单数表名 }, Logger: logger.Default.LogMode(logger.Silent), // 禁用日志输出,减少噪音 } db, err := gorm.Open(postgres.Open(dsn), gormConfig) if err != nil { return nil, fmt.Errorf("连接数据库失败: %w", err) } // 测试连接 sqlDB, err := db.DB() if err != nil { return nil, fmt.Errorf("获取数据库实例失败: %w", err) } if err := sqlDB.Ping(); err != nil { return nil, fmt.Errorf("数据库连接测试失败: %w", err) } fmt.Println("数据库连接成功") return db, nil } // checkProductAndProcessorCounts 检查数据库产品数量和处理器数量 func checkProductAndProcessorCounts(ctx context.Context, db *gorm.DB, processors []ProcessorInfo) { // 统计处理器数量(排除 comb) processorCount := len(processors) fmt.Printf("\n=== 统计信息 ===\n") fmt.Printf("处理器数量(排除 comb): %d\n", processorCount) // 统计数据库中的产品数量(排除组合包) var productCount int64 // 先尝试单数表名 err := db.WithContext(ctx). Table("product"). Where("is_package = ? AND deleted_at IS NULL", false). Count(&productCount).Error // 如果单数表名查询失败,尝试复数表名 if err != nil { if strings.Contains(err.Error(), "does not exist") { err = db.WithContext(ctx). Table("products"). Where("is_package = ? AND deleted_at IS NULL", false). Count(&productCount).Error } } if err == nil { fmt.Printf("数据库产品数量(排除组合包): %d\n", productCount) } else { fmt.Printf("数据库产品数量查询失败: %v\n", err) } // 统计有匹配产品的处理器数量 matchedCount := 0 for _, p := range processors { var count int64 err := db.WithContext(ctx). Table("product"). Where("code = ? AND deleted_at IS NULL", p.ProductCode). Count(&count).Error if err != nil && strings.Contains(err.Error(), "does not exist") { err = db.WithContext(ctx). Table("products"). Where("code = ? AND deleted_at IS NULL", p.ProductCode). Count(&count).Error } if err == nil && count > 0 { matchedCount++ } } fmt.Printf("有匹配产品的处理器数量: %d/%d\n", matchedCount, processorCount) fmt.Printf("无匹配产品的处理器数量: %d/%d\n", processorCount-matchedCount, processorCount) fmt.Println() } // queryProduct 查询产品信息 func queryProduct(ctx context.Context, db *gorm.DB, code string) (*Product, error) { var product Product var err error // 尝试不同的表名查询(先尝试单数表名,因为用户确认表名是 product) tableNames := []string{"product", "products"} for _, tableName := range tableNames { err = db.WithContext(ctx). Table(tableName). Select("code, name, category_id, price"). Where("code = ? AND deleted_at IS NULL", code). First(&product).Error if err == nil { // 查询成功,查询分类信息 if product.CategoryID != "" { var category ProductCategory // 尝试不同的分类表名(先尝试单数表名) categoryTableNames := []string{"product_category", "product_categories"} for _, catTableName := range categoryTableNames { err = db.WithContext(ctx). Table(catTableName). Select("id, name"). Where("id = ? AND deleted_at IS NULL", product.CategoryID). First(&category).Error if err == nil { product.Category = &category break } // 如果是表不存在的错误,继续尝试下一个表名 if err != nil && strings.Contains(err.Error(), "does not exist") { continue } // 如果是记录不存在的错误,也继续尝试(可能是表名不对) if err == gorm.ErrRecordNotFound { continue } } // 即使分类查询失败,也返回产品信息(分类可以为空) } return &product, nil } // 检查错误类型 errStr := err.Error() // 如果是表不存在的错误,继续尝试下一个表名 if strings.Contains(errStr, "does not exist") { continue } // 如果是记录不存在的错误,也继续尝试(可能是表名不对) if err == gorm.ErrRecordNotFound { continue } // 其他错误直接返回 return nil, err } // 所有表名都查询失败 return nil, gorm.ErrRecordNotFound } // printTable 打印表格 func printTable(processors []ProcessorInfo) { // 打印表头 fmt.Printf("%-15s %-30s %-20s %-15s %-15s %-20s %-15s\n", "产品编号", "产品名称", "分类", "价格", "数据源", "数据源编号", "成本价") fmt.Println(strings.Repeat("-", 130)) // 打印数据 for _, p := range processors { fmt.Printf("%-15s %-30s %-20s %-15s %-15s %-20s %-15s\n", p.ProductCode, p.ProductName, p.Category, p.Price, p.DataSource, p.DataSourceCode, p.CostPrice) } // 打印 CSV 格式 fmt.Println("\n=== CSV 格式 ===") fmt.Println("产品编号,产品名称,分类,价格,数据源,数据源编号,成本价") for _, p := range processors { // 转义 CSV 中的特殊字符 productName := strings.ReplaceAll(p.ProductName, ",", ",") category := strings.ReplaceAll(p.Category, ",", ",") fmt.Printf("%s,%s,%s,%s,%s,%s,%s\n", p.ProductCode, productName, category, p.Price, p.DataSource, p.DataSourceCode, p.CostPrice) } // 打印 Markdown 表格格式 fmt.Println("\n=== Markdown 表格格式 ===") fmt.Println("| 产品编号 | 产品名称 | 分类 | 价格 | 数据源 | 数据源编号 | 成本价 |") fmt.Println("|---------|---------|------|------|--------|-----------|--------|") for _, p := range processors { productName := p.ProductName if productName == "" { productName = "-" } category := p.Category if category == "" { category = "-" } price := p.Price if price == "" { price = "-" } dataSource := p.DataSource if dataSource == "" { dataSource = "-" } dataSourceCode := p.DataSourceCode if dataSourceCode == "" { dataSourceCode = "-" } costPrice := p.CostPrice if costPrice == "" { costPrice = "-" } fmt.Printf("| %s | %s | %s | %s | %s | %s | %s |\n", p.ProductCode, productName, category, price, dataSource, dataSourceCode, costPrice) } }