package pdf import ( "bytes" "context" "fmt" "os" "path/filepath" "runtime" "tyapi-server/internal/domains/product/entities" "github.com/jung-kurt/gofpdf/v2" "github.com/shopspring/decimal" "go.uber.org/zap" ) // PDFGeneratorRefactored 重构后的PDF生成器 type PDFGeneratorRefactored struct { logger *zap.Logger fontManager *FontManager textProcessor *TextProcessor markdownProc *MarkdownProcessor tableParser *TableParser tableRenderer *TableRenderer jsonProcessor *JSONProcessor logoPath string watermarkText string } // NewPDFGeneratorRefactored 创建重构后的PDF生成器 func NewPDFGeneratorRefactored(logger *zap.Logger) *PDFGeneratorRefactored { // 初始化各个模块 textProcessor := NewTextProcessor() fontManager := NewFontManager(logger) markdownProc := NewMarkdownProcessor(textProcessor) tableParser := NewTableParser(logger, fontManager) tableRenderer := NewTableRenderer(logger, fontManager, textProcessor) jsonProcessor := NewJSONProcessor() gen := &PDFGeneratorRefactored{ logger: logger, fontManager: fontManager, textProcessor: textProcessor, markdownProc: markdownProc, tableParser: tableParser, tableRenderer: tableRenderer, jsonProcessor: jsonProcessor, watermarkText: "海南海宇大数据有限公司", } // 查找logo文件 gen.findLogo() return gen } // findLogo 查找logo文件 func (g *PDFGeneratorRefactored) findLogo() { // 获取当前文件所在目录 _, filename, _, _ := runtime.Caller(0) baseDir := filepath.Dir(filename) // 优先使用相对路径(Linux风格,使用正斜杠) logoPaths := []string{ "internal/shared/pdf/天远数据.png", // 相对于项目根目录(最常用) "./internal/shared/pdf/天远数据.png", // 当前目录下的相对路径 filepath.Join(baseDir, "天远数据.png"), // 相对当前文件 } // 尝试相对路径 for _, logoPath := range logoPaths { if _, err := os.Stat(logoPath); err == nil { g.logoPath = logoPath return } } // 尝试服务器绝对路径(后备方案) if runtime.GOOS == "linux" { serverPaths := []string{ "/www/tyapi-server/internal/shared/pdf/天远数据.png", "/app/internal/shared/pdf/天远数据.png", } for _, logoPath := range serverPaths { if _, err := os.Stat(logoPath); err == nil { g.logoPath = logoPath return } } } // 只记录关键错误 g.logger.Warn("未找到logo文件") } // GenerateProductPDF 为产品生成PDF文档(接受响应类型,内部转换) func (g *PDFGeneratorRefactored) GenerateProductPDF(ctx context.Context, productID string, productName, productCode, description, content string, price float64, doc *entities.ProductDocumentation) ([]byte, error) { // 构建临时的 Product entity(仅用于PDF生成) product := &entities.Product{ ID: productID, Name: productName, Code: productCode, Description: description, Content: content, } // 如果有价格信息,设置价格 if price > 0 { product.Price = decimal.NewFromFloat(price) } return g.generatePDF(product, doc) } // GenerateProductPDFFromEntity 从entity类型生成PDF(推荐使用) func (g *PDFGeneratorRefactored) GenerateProductPDFFromEntity(ctx context.Context, product *entities.Product, doc *entities.ProductDocumentation) ([]byte, error) { return g.generatePDF(product, doc) } // generatePDF 内部PDF生成方法 func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *entities.ProductDocumentation) (result []byte, err error) { defer func() { if r := recover(); r != nil { // 将panic转换为error,而不是重新抛出 if e, ok := r.(error); ok { err = fmt.Errorf("PDF生成panic: %w", e) } else { err = fmt.Errorf("PDF生成panic: %v", r) } result = nil } }() g.logger.Info("开始生成PDF", zap.String("product_id", product.ID), zap.String("product_name", product.Name), zap.Bool("has_doc", doc != nil), ) // 创建PDF文档 (A4大小,gofpdf v2 默认支持UTF-8) pdf := gofpdf.New("P", "mm", "A4", "") // 优化边距,减少空白 pdf.SetMargins(15, 25, 15) // 加载黑体字体(用于所有内容,除了水印) chineseFontAvailable := g.fontManager.LoadChineseFont(pdf) // 加载水印字体(使用宋体或其他非黑体字体) g.fontManager.LoadWatermarkFont(pdf) // 设置文档信息 pdf.SetTitle("Product Documentation", true) pdf.SetAuthor("TYAPI Server", true) pdf.SetCreator("TYAPI Server", true) g.logger.Info("PDF文档基本信息设置完成") // 创建页面构建器 pageBuilder := NewPageBuilder(g.logger, g.fontManager, g.textProcessor, g.markdownProc, g.tableParser, g.tableRenderer, g.jsonProcessor, g.logoPath, g.watermarkText) // 添加第一页(产品信息) g.logger.Info("开始添加第一页") pageBuilder.AddFirstPage(pdf, product, doc, chineseFontAvailable) g.logger.Info("第一页添加完成") // 如果有关联的文档,添加接口文档页面 if doc != nil { g.logger.Info("开始添加文档页面") pageBuilder.AddDocumentationPages(pdf, doc, chineseFontAvailable) g.logger.Info("文档页面添加完成") } else { g.logger.Info("没有文档信息,跳过文档页面") } // 生成PDF字节流 g.logger.Info("开始生成PDF字节流") var buf bytes.Buffer err = pdf.Output(&buf) if err != nil { // 错误已返回,不记录日志 return nil, fmt.Errorf("生成PDF失败: %w", err) } pdfBytes := buf.Bytes() g.logger.Info("PDF生成成功", zap.String("product_id", product.ID), zap.Int("pdf_size", len(pdfBytes)), ) return pdfBytes, nil }