Files
tyapi-server/internal/shared/pdf/pdf_generator_refactored.go
2025-12-03 12:03:42 +08:00

186 lines
5.5 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 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)
logoPaths := []string{
filepath.Join(baseDir, "天远数据.png"), // 相对当前文件
"天远数据.png", // 当前目录
filepath.Join(baseDir, "..", "天远数据.png"), // 上一级目录
filepath.Join(baseDir, "..", "..", "天远数据.png"), // 上两级目录
}
for _, logoPath := range logoPaths {
if _, err := os.Stat(logoPath); err == nil {
absPath, err := filepath.Abs(logoPath)
if err == nil {
g.logoPath = absPath
g.logger.Info("找到logo文件", zap.String("logo_path", absPath))
return
}
}
}
g.logger.Warn("未找到logo文件", zap.Strings("尝试的路径", logoPaths))
}
// 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 {
g.logger.Error("PDF生成过程中发生panic",
zap.String("product_id", product.ID),
zap.String("product_name", product.Name),
zap.Any("panic_value", r),
)
// 将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 {
g.logger.Error("PDF输出失败", zap.Error(err))
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
}