Files
tyapi-server/scripts/analyze_processors.go
2025-11-13 20:43:35 +08:00

604 lines
17 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}