diff --git a/internal/shared/pdf/database_table_reader.go b/internal/shared/pdf/database_table_reader.go index 8de0611..be325ac 100644 --- a/internal/shared/pdf/database_table_reader.go +++ b/internal/shared/pdf/database_table_reader.go @@ -98,10 +98,7 @@ func (r *DatabaseTableReader) ReadTableFromDocumentation(ctx context.Context, do tableData, err := r.parseMarkdownTable(content) if err != nil { - r.logger.Error("解析markdown表格失败", - zap.String("field_type", fieldType), - zap.Error(err), - zap.String("content_preview", r.getContentPreview(content, 500))) + // 错误已返回,不记录日志 return nil, fmt.Errorf("解析markdown表格失败: %w", err) } diff --git a/internal/shared/pdf/database_table_renderer.go b/internal/shared/pdf/database_table_renderer.go index 16b1afb..9aa33b6 100644 --- a/internal/shared/pdf/database_table_renderer.go +++ b/internal/shared/pdf/database_table_renderer.go @@ -270,25 +270,31 @@ func (r *DatabaseTableRenderer) safeSplitText(pdf *gofpdf.Fpdf, text string, wid // 如果文本为空或宽度无效,返回单行 if text == "" || width <= 0 { + if text == "" { + return []string{""} + } + return []string{text} + } + + // 检查文本长度,如果太短可能不需要分割 + if len([]rune(text)) <= 1 { return []string{text} } // 使用匿名函数和recover保护SplitText调用 var lines []string + var panicOccurred bool func() { defer func() { if rec := recover(); rec != nil { - r.logger.Warn("SplitText发生panic,使用估算值", - zap.Any("error", rec), - zap.String("text_preview", func() string { - if len(text) > 50 { - return text[:50] + "..." - } - return text - }()), - zap.Float64("width", width)) + panicOccurred = true + // 静默处理,不记录日志 // 如果panic发生,使用估算值 charCount := len([]rune(text)) + if charCount == 0 { + lines = []string{""} + return + } estimatedLines := math.Max(1, math.Ceil(float64(charCount)/20)) lines = make([]string, int(estimatedLines)) if estimatedLines == 1 { @@ -316,12 +322,27 @@ func (r *DatabaseTableRenderer) safeSplitText(pdf *gofpdf.Fpdf, text string, wid lines = pdf.SplitText(text, width) }() - // 如果lines为nil或空,返回单行 - if lines == nil || len(lines) == 0 { + // 如果panic发生或lines为nil或空,使用后备方案 + if panicOccurred || lines == nil || len(lines) == 0 { + // 如果文本不为空,至少返回一行 + if text != "" { + return []string{text} + } + return []string{""} + } + + // 过滤掉空行(但保留至少一行) + nonEmptyLines := make([]string, 0, len(lines)) + for _, line := range lines { + if strings.TrimSpace(line) != "" { + nonEmptyLines = append(nonEmptyLines, line) + } + } + if len(nonEmptyLines) == 0 { return []string{text} } - return lines + return nonEmptyLines } // renderHeader 渲染表头 @@ -450,6 +471,10 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co validRowIndex++ // 计算这一行的最大高度 + // 确保lineHt有效 + if lineHt <= 0 { + lineHt = 5.0 // 默认行高 + } maxCellHeight := lineHt * 2.0 // 使用合理的最小高度 for j := 0; j < numCols && j < len(row); j++ { cell := row[j] @@ -471,12 +496,32 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co lines = []string{cell} } - cellHeight := float64(len(lines)) * lineHt + // 确保lines不为空且有效 + if len(lines) == 0 || (len(lines) == 1 && lines[0] == "") { + lines = []string{cell} + if lines[0] == "" { + lines[0] = " " // 至少保留一个空格,避免高度为0 + } + } + + // 计算单元格高度,确保不会出现Inf或NaN + lineCount := float64(len(lines)) + if lineCount <= 0 { + lineCount = 1 + } + if lineHt <= 0 { + lineHt = 5.0 // 默认行高 + } + cellHeight := lineCount * lineHt // 添加上下内边距 cellHeight += lineHt * 0.8 // 上下各0.4倍行高的内边距 if cellHeight < lineHt*2.0 { cellHeight = lineHt * 2.0 } + // 检查是否为有效数值 + if math.IsInf(cellHeight, 0) || math.IsNaN(cellHeight) { + cellHeight = lineHt * 2.0 + } if cellHeight > maxCellHeight { maxCellHeight = cellHeight } @@ -545,10 +590,7 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co func() { defer func() { if rec := recover(); rec != nil { - r.logger.Warn("MultiCell渲染失败", - zap.Any("error", rec), - zap.Int("row_index", rowIndex), - zap.Int("col_index", j)) + // 静默处理,不记录日志 } }() // 使用正常的行高,文本已经垂直居中 @@ -561,8 +603,20 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co currentX += colW } + // 检查maxCellHeight是否为有效数值 + if math.IsInf(maxCellHeight, 0) || math.IsNaN(maxCellHeight) { + maxCellHeight = lineHt * 2.0 + } + if maxCellHeight <= 0 { + maxCellHeight = lineHt * 2.0 + } + // 移动到下一行 - pdf.SetXY(15.0, startY+maxCellHeight) + nextY := startY + maxCellHeight + if math.IsInf(nextY, 0) || math.IsNaN(nextY) { + nextY = pdf.GetY() + lineHt*2.0 + } + pdf.SetXY(15.0, nextY) } } diff --git a/internal/shared/pdf/font_manager.go b/internal/shared/pdf/font_manager.go index 660dc98..e5f5360 100644 --- a/internal/shared/pdf/font_manager.go +++ b/internal/shared/pdf/font_manager.go @@ -32,11 +32,6 @@ func NewFontManager(logger *zap.Logger) *FontManager { baseDir: baseDir, } - // 记录基础目录(用于调试) - logger.Debug("字体管理器初始化", - zap.String("base_dir", baseDir), - zap.String("caller_file", filename)) - return fm } @@ -48,27 +43,19 @@ func (fm *FontManager) LoadChineseFont(pdf *gofpdf.Fpdf) bool { fontPaths := fm.getChineseFontPaths() if len(fontPaths) == 0 { - fm.logger.Warn("未找到中文字体文件,PDF中的中文可能显示为空白", - zap.String("base_dir", fm.baseDir), - zap.String("hint", "请确保字体文件存在于 internal/shared/pdf/fonts/ 目录下,或设置 PDF_FONT_DIR 环境变量")) + fm.logger.Warn("未找到中文字体文件") return false } // 尝试加载字体 for _, fontPath := range fontPaths { - fm.logger.Debug("尝试加载字体", zap.String("font_path", fontPath)) if fm.tryAddFont(pdf, fontPath, fm.chineseFontName) { fm.chineseFontLoaded = true - fm.logger.Info("成功加载中文字体", zap.String("font_path", fontPath)) return true - } else { - fm.logger.Debug("字体加载失败", zap.String("font_path", fontPath)) } } - fm.logger.Warn("所有中文字体文件都无法加载,PDF中的中文可能显示为空白", - zap.Int("tried_paths", len(fontPaths)), - zap.Strings("font_paths", fontPaths)) + fm.logger.Warn("无法加载中文字体文件") return false } @@ -80,7 +67,6 @@ func (fm *FontManager) LoadWatermarkFont(pdf *gofpdf.Fpdf) bool { fontPaths := fm.getWatermarkFontPaths() if len(fontPaths) == 0 { - fm.logger.Warn("未找到水印字体文件,将使用主字体") return false } @@ -88,12 +74,10 @@ func (fm *FontManager) LoadWatermarkFont(pdf *gofpdf.Fpdf) bool { for _, fontPath := range fontPaths { if fm.tryAddFont(pdf, fontPath, fm.watermarkFontName) { fm.watermarkFontLoaded = true - fm.logger.Info("成功加载水印字体", zap.String("font_path", fontPath)) return true } } - fm.logger.Warn("所有水印字体文件都无法加载,将使用主字体") return false } @@ -101,10 +85,7 @@ func (fm *FontManager) LoadWatermarkFont(pdf *gofpdf.Fpdf) bool { func (fm *FontManager) tryAddFont(pdf *gofpdf.Fpdf, fontPath, fontName string) bool { defer func() { if r := recover(); r != nil { - fm.logger.Error("添加字体时发生panic", - zap.Any("panic", r), - zap.String("font_path", fontPath), - zap.String("font_name", fontName)) + // 静默处理,不记录日志 } }() @@ -113,18 +94,25 @@ func (fm *FontManager) tryAddFont(pdf *gofpdf.Fpdf, fontPath, fontName string) b return false } - // gofpdf v2使用AddUTF8Font添加支持UTF-8的字体 - pdf.AddUTF8Font(fontName, "", fontPath) // 常规样式 - pdf.AddUTF8Font(fontName, "B", fontPath) // 粗体样式 + // 将相对路径转换为绝对路径(gofpdf在Output时需要绝对路径) + absFontPath, err := filepath.Abs(fontPath) + if err != nil { + absFontPath = fontPath + } + + // 再次检查绝对路径文件是否存在 + if _, err := os.Stat(absFontPath); err != nil { + return false + } + + // gofpdf v2使用AddUTF8Font添加支持UTF-8的字体(使用绝对路径) + pdf.AddUTF8Font(fontName, "", absFontPath) // 常规样式 + pdf.AddUTF8Font(fontName, "B", absFontPath) // 粗体样式 // 验证字体是否可用 pdf.SetFont(fontName, "", 12) testWidth := pdf.GetStringWidth("测试") if testWidth == 0 { - fm.logger.Debug("字体加载后无法使用", - zap.String("font_path", fontPath), - zap.String("font_name", fontName), - zap.Float64("test_width", testWidth)) return false } @@ -158,127 +146,79 @@ func (fm *FontManager) getWatermarkFontPaths() []string { func (fm *FontManager) buildFontPaths(fontNames []string) []string { var fontPaths []string - // 方式1: 使用 pdf/fonts/ 目录(相对于当前文件) + // 方式1: 优先使用相对路径(Linux风格,使用正斜杠)- 最常用 + for _, fontName := range fontNames { + // 相对路径(相对于项目根目录) + relativePaths := []string{ + "internal/shared/pdf/fonts/" + fontName, + "./internal/shared/pdf/fonts/" + fontName, + } + fontPaths = append(fontPaths, relativePaths...) + } + + // 方式2: 使用 pdf/fonts/ 目录(相对于当前文件) for _, fontName := range fontNames { fontPaths = append(fontPaths, filepath.Join(fm.baseDir, "fonts", fontName)) } - // 方式2: 尝试相对于工作目录的路径(适用于编译后的二进制文件) - // 这是最常用的方式,适用于直接运行二进制文件的情况 + // 方式3: 尝试相对于工作目录的路径 if workDir, err := os.Getwd(); err == nil { - fm.logger.Debug("工作目录", zap.String("work_dir", workDir)) for _, fontName := range fontNames { - // 尝试多个可能的相对路径 paths := []string{ filepath.Join(workDir, "internal", "shared", "pdf", "fonts", fontName), - filepath.Join(workDir, "fonts", fontName), - filepath.Join(workDir, fontName), - // 如果工作目录是项目根目录的父目录 filepath.Join(workDir, "tyapi-server", "internal", "shared", "pdf", "fonts", fontName), } fontPaths = append(fontPaths, paths...) } - } else { - fm.logger.Warn("无法获取工作目录", zap.Error(err)) } - // 方式3: 尝试使用可执行文件所在目录 - // 适用于systemd服务或直接运行二进制文件的情况 + // 方式4: 尝试使用可执行文件所在目录 if execPath, err := os.Executable(); err == nil { execDir := filepath.Dir(execPath) - // 解析符号链接,获取真实路径(Linux上很重要) if realPath, err := filepath.EvalSymlinks(execPath); err == nil { execDir = filepath.Dir(realPath) } - fm.logger.Debug("可执行文件目录", - zap.String("exec_dir", execDir), - zap.String("exec_path", execPath)) for _, fontName := range fontNames { paths := []string{ filepath.Join(execDir, "internal", "shared", "pdf", "fonts", fontName), - filepath.Join(execDir, "fonts", fontName), - filepath.Join(execDir, fontName), - // 如果可执行文件在bin目录,尝试上一级目录 filepath.Join(filepath.Dir(execDir), "internal", "shared", "pdf", "fonts", fontName), } fontPaths = append(fontPaths, paths...) } - } else { - fm.logger.Warn("无法获取可执行文件路径", zap.Error(err)) } - // 方式4: 尝试使用环境变量指定的路径(如果设置了) + // 方式5: 尝试使用环境变量指定的路径 if fontDir := os.Getenv("PDF_FONT_DIR"); fontDir != "" { - fm.logger.Info("使用环境变量指定的字体目录", zap.String("font_dir", fontDir)) for _, fontName := range fontNames { fontPaths = append(fontPaths, filepath.Join(fontDir, fontName)) } } - // 方式5: 尝试常见的服务器部署路径(硬编码路径,作为后备方案) - // 支持多种Linux服务器部署场景 - commonServerPaths := []string{ - "/www/tyapi-server/internal/shared/pdf/fonts", // 用户提供的路径 - "/app/internal/shared/pdf/fonts", // Docker常见路径 - "/usr/local/tyapi-server/internal/shared/pdf/fonts", // 标准安装路径 - "/opt/tyapi-server/internal/shared/pdf/fonts", // 可选安装路径 - "/home/ubuntu/tyapi-server/internal/shared/pdf/fonts", // Ubuntu用户目录 - "/root/tyapi-server/internal/shared/pdf/fonts", // root用户目录 - "/var/www/tyapi-server/internal/shared/pdf/fonts", // Web服务器常见路径 - } - for _, basePath := range commonServerPaths { - for _, fontName := range fontNames { - fontPaths = append(fontPaths, filepath.Join(basePath, fontName)) + // 方式6: 服务器绝对路径(作为最后的后备方案) + if runtime.GOOS == "linux" { + commonServerPaths := []string{ + "/www/tyapi-server/internal/shared/pdf/fonts", + "/app/internal/shared/pdf/fonts", + } + for _, basePath := range commonServerPaths { + for _, fontName := range fontNames { + fontPaths = append(fontPaths, filepath.Join(basePath, fontName)) + } } } - // 记录所有尝试的路径(用于调试) - fm.logger.Debug("查找字体文件", - zap.Strings("font_names", fontNames), - zap.Int("total_paths", len(fontPaths)), - zap.Strings("paths", fontPaths)) - - // 过滤出实际存在的字体文件(并检查权限) + // 过滤出实际存在的字体文件 var existingFonts []string for _, fontPath := range fontPaths { - // 检查文件是否存在 fileInfo, err := os.Stat(fontPath) - if err == nil { - // 检查是否为常规文件(不是目录) - if !fileInfo.Mode().IsRegular() { - fm.logger.Debug("路径不是常规文件", - zap.String("font_path", fontPath), - zap.String("mode", fileInfo.Mode().String())) - continue - } - // 检查是否有读取权限(Linux上很重要) - if fileInfo.Mode().Perm()&0444 == 0 { - fm.logger.Warn("字体文件无读取权限", - zap.String("font_path", fontPath), - zap.String("mode", fileInfo.Mode().String())) - // 仍然尝试添加,因为可能通过其他方式有权限 - } + if err == nil && fileInfo.Mode().IsRegular() { existingFonts = append(existingFonts, fontPath) - fm.logger.Debug("找到字体文件", - zap.String("font_path", fontPath), - zap.Int64("size", fileInfo.Size())) - } else { - // 只在Debug级别记录,避免日志过多 - fm.logger.Debug("字体文件不存在", - zap.String("font_path", fontPath), - zap.Error(err)) } } + // 只记录关键错误 if len(existingFonts) == 0 { - fm.logger.Warn("未找到任何字体文件", - zap.Strings("font_names", fontNames), - zap.String("base_dir", fm.baseDir), - zap.Int("total_paths_checked", len(fontPaths))) - } else { - fm.logger.Info("找到字体文件", - zap.Int("count", len(existingFonts)), - zap.Strings("paths", existingFonts)) + fm.logger.Warn("未找到字体文件", zap.Strings("font_names", fontNames)) } return existingFonts diff --git a/internal/shared/pdf/page_builder.go b/internal/shared/pdf/page_builder.go index 24f6abf..3c5f6fa 100644 --- a/internal/shared/pdf/page_builder.go +++ b/internal/shared/pdf/page_builder.go @@ -637,14 +637,10 @@ func (pb *PageBuilder) addHeader(pdf *gofpdf.Fpdf, chineseFontAvailable bool) { // 绘制logo(如果存在) if pb.logoPath != "" { if _, err := os.Stat(pb.logoPath); err == nil { - // gofpdf的ImageOptions方法(调整位置和大小,左边距是15mm) pdf.ImageOptions(pb.logoPath, 15, 5, 15, 15, false, gofpdf.ImageOptions{}, 0, "") - pb.logger.Info("已添加logo", zap.String("path", pb.logoPath)) } else { - pb.logger.Warn("logo文件不存在", zap.String("path", pb.logoPath), zap.Error(err)) + pb.logger.Warn("logo文件不存在", zap.String("path", pb.logoPath)) } - } else { - pb.logger.Warn("logo路径为空") } // 绘制"天远数据"文字(使用中文字体如果可用) diff --git a/internal/shared/pdf/pdf_generator.go b/internal/shared/pdf/pdf_generator.go index f4861dd..ca96c85 100644 --- a/internal/shared/pdf/pdf_generator.go +++ b/internal/shared/pdf/pdf_generator.go @@ -68,9 +68,30 @@ func (g *PDFGenerator) getChineseFontPaths() []string { fontPaths = []string{ "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", + "/usr/share/fonts/truetype/wqy/wqy-microhei.ttf", + "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttf", "/usr/share/fonts/truetype/arphic/uming.ttc", "/usr/share/fonts/truetype/arphic/ukai.ttc", + "/usr/share/fonts/truetype/arphic/uming.ttf", + "/usr/share/fonts/truetype/arphic/ukai.ttf", "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", + "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/truetype/noto/NotoSansCJK-Bold.ttc", + "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.otf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", + "/usr/local/share/fonts/simhei.ttf", + "/usr/local/share/fonts/simsun.ttf", + "/usr/local/share/fonts/simkai.ttf", + "/opt/fonts/simhei.ttf", + "/opt/fonts/simsun.ttf", + "/opt/fonts/simkai.ttf", + "/home/.fonts/simhei.ttf", + "/home/.fonts/simsun.ttf", + "/home/.fonts/simkai.ttf", + "/root/.fonts/simhei.ttf", + "/root/.fonts/simsun.ttf", + "/root/.fonts/simkai.ttf", } } else if runtime.GOOS == "darwin" { // macOS系统字体路径 @@ -99,23 +120,37 @@ func (g *PDFGenerator) findLogo() { _, filename, _, _ := runtime.Caller(0) baseDir := filepath.Dir(filename) + // 优先使用相对路径(Linux风格,使用正斜杠) logoPaths := []string{ - filepath.Join(baseDir, "天远数据.png"), // 相对当前文件 - "天远数据.png", // 当前目录 - filepath.Join(baseDir, "..", "天远数据.png"), // 上一级目录 - filepath.Join(baseDir, "..", "..", "天远数据.png"), // 上两级目录 + "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 - g.logger.Info("找到logo文件", zap.String("logo_path", logoPath)) return } } - g.logger.Warn("未找到logo文件", zap.Strings("尝试的路径", logoPaths)) + // 尝试服务器绝对路径(后备方案) + 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文档(接受响应类型,内部转换) diff --git a/internal/shared/pdf/pdf_generator_refactored.go b/internal/shared/pdf/pdf_generator_refactored.go index 78bbae4..ea9ccdf 100644 --- a/internal/shared/pdf/pdf_generator_refactored.go +++ b/internal/shared/pdf/pdf_generator_refactored.go @@ -61,23 +61,37 @@ 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"), // 相对当前文件 - "天远数据.png", // 当前目录 - filepath.Join(baseDir, "..", "天远数据.png"), // 上一级目录 - filepath.Join(baseDir, "..", "..", "天远数据.png"), // 上两级目录 } + // 尝试相对路径 for _, logoPath := range logoPaths { if _, err := os.Stat(logoPath); err == nil { - // 直接使用相对路径,不转换为绝对路径 g.logoPath = logoPath - g.logger.Info("找到logo文件", zap.String("logo_path", logoPath)) return } } - g.logger.Warn("未找到logo文件", zap.Strings("尝试的路径", logoPaths)) + // 尝试服务器绝对路径(后备方案) + 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文档(接受响应类型,内部转换) @@ -108,11 +122,6 @@ func (g *PDFGeneratorRefactored) GenerateProductPDFFromEntity(ctx context.Contex 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) @@ -169,7 +178,7 @@ func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *ent 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) } @@ -181,3 +190,4 @@ func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *ent return pdfBytes, nil } + diff --git a/internal/shared/pdf/table_parser.go b/internal/shared/pdf/table_parser.go index 5030313..7659852 100644 --- a/internal/shared/pdf/table_parser.go +++ b/internal/shared/pdf/table_parser.go @@ -68,9 +68,7 @@ func (tp *TableParser) ParseAndRenderTable(ctx context.Context, pdf *gofpdf.Fpdf // 渲染表格到PDF if err := tp.databaseRenderer.RenderTable(pdf, tableData); err != nil { - tp.logger.Error("渲染表格失败", - zap.String("field_type", fieldType), - zap.Error(err)) + // 错误已返回,不记录日志 return fmt.Errorf("渲染表格失败: %w", err) }