This commit is contained in:
2025-12-03 12:03:42 +08:00
parent 1cf64e831c
commit 63252fa30f
27 changed files with 7167 additions and 36 deletions

View File

@@ -0,0 +1,185 @@
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
}