From b08a63fc9949c5b42f0f91a4ea659fc9c9551fe1 Mon Sep 17 00:00:00 2001
From: 18278715334 <18278715334@163.com>
Date: Wed, 3 Dec 2025 18:02:49 +0800
Subject: [PATCH] 18278715334@163.com
---
.../product/entities/product_documentation.go | 2 -
.../shared/pdf/database_table_renderer.go | 156 ++++++++++++------
2 files changed, 110 insertions(+), 48 deletions(-)
diff --git a/internal/domains/product/entities/product_documentation.go b/internal/domains/product/entities/product_documentation.go
index ecf7ef4..b73c743 100644
--- a/internal/domains/product/entities/product_documentation.go
+++ b/internal/domains/product/entities/product_documentation.go
@@ -45,7 +45,6 @@ func (pd *ProductDocumentation) IsValid() bool {
return pd.DeletedAt.Time.IsZero()
}
-
// UpdateContent 更新文档内容
func (pd *ProductDocumentation) UpdateContent(requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes string) {
pd.RequestURL = requestURL
@@ -167,7 +166,6 @@ func (pd *ProductDocumentation) UpdateDocumentation(requestURL, requestMethod, b
return nil
}
-
// GetDocumentationSummary 获取文档摘要
func (pd *ProductDocumentation) GetDocumentationSummary() map[string]interface{} {
return map[string]interface{}{
diff --git a/internal/shared/pdf/database_table_renderer.go b/internal/shared/pdf/database_table_renderer.go
index 9786c0b..16b1afb 100644
--- a/internal/shared/pdf/database_table_renderer.go
+++ b/internal/shared/pdf/database_table_renderer.go
@@ -2,7 +2,9 @@ package pdf
import (
"math"
+ "regexp"
"strings"
+ "unicode"
"github.com/jung-kurt/gofpdf/v2"
"go.uber.org/zap"
@@ -227,6 +229,101 @@ func (r *DatabaseTableRenderer) calculateColumnWidths(pdf *gofpdf.Fpdf, tableDat
return colWidths
}
+// cleanTextForPDF 清理文本,移除可能导致PDF生成问题的字符
+func (r *DatabaseTableRenderer) cleanTextForPDF(text string) string {
+ // 先移除HTML标签
+ text = strings.TrimSpace(text)
+ text = strings.ReplaceAll(text, "
", " ")
+ text = strings.ReplaceAll(text, "
", " ")
+ text = strings.ReplaceAll(text, "
", " ")
+
+ // 移除控制字符(除了换行符和制表符)
+ var cleaned strings.Builder
+ for _, r := range text {
+ // 保留可打印字符、空格、换行符、制表符
+ if unicode.IsPrint(r) || r == '\n' || r == '\t' || r == '\r' {
+ cleaned.WriteRune(r)
+ } else if unicode.IsSpace(r) {
+ // 将其他空白字符转换为空格
+ cleaned.WriteRune(' ')
+ }
+ }
+ text = cleaned.String()
+
+ // 移除多余的空白字符
+ text = regexp.MustCompile(`\s+`).ReplaceAllString(text, " ")
+
+ // 确保文本不为空且长度合理
+ if len([]rune(text)) > 10000 {
+ // 如果文本过长,截断
+ runes := []rune(text)
+ text = string(runes[:10000]) + "..."
+ }
+
+ return strings.TrimSpace(text)
+}
+
+// safeSplitText 安全地调用SplitText,带错误恢复
+func (r *DatabaseTableRenderer) safeSplitText(pdf *gofpdf.Fpdf, text string, width float64) []string {
+ // 先清理文本
+ text = r.cleanTextForPDF(text)
+
+ // 如果文本为空或宽度无效,返回单行
+ if text == "" || width <= 0 {
+ return []string{text}
+ }
+
+ // 使用匿名函数和recover保护SplitText调用
+ var lines []string
+ 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))
+ // 如果panic发生,使用估算值
+ charCount := len([]rune(text))
+ estimatedLines := math.Max(1, math.Ceil(float64(charCount)/20))
+ lines = make([]string, int(estimatedLines))
+ if estimatedLines == 1 {
+ lines[0] = text
+ } else {
+ // 简单分割文本
+ runes := []rune(text)
+ charsPerLine := int(math.Ceil(float64(len(runes)) / estimatedLines))
+ for i := 0; i < int(estimatedLines); i++ {
+ start := i * charsPerLine
+ end := start + charsPerLine
+ if end > len(runes) {
+ end = len(runes)
+ }
+ if start < len(runes) {
+ lines[i] = string(runes[start:end])
+ } else {
+ lines[i] = ""
+ }
+ }
+ }
+ }
+ }()
+ // 尝试调用SplitText
+ lines = pdf.SplitText(text, width)
+ }()
+
+ // 如果lines为nil或空,返回单行
+ if lines == nil || len(lines) == 0 {
+ return []string{text}
+ }
+
+ return lines
+}
+
// renderHeader 渲染表头
func (r *DatabaseTableRenderer) renderHeader(pdf *gofpdf.Fpdf, headers []string, colWidths []float64, startY float64) float64 {
_, lineHt := pdf.GetFontSize()
@@ -238,7 +335,8 @@ func (r *DatabaseTableRenderer) renderHeader(pdf *gofpdf.Fpdf, headers []string,
break
}
colW := colWidths[i]
- headerLines := pdf.SplitText(header, colW-6) // 增加边距,从4增加到6
+ // 使用安全的SplitText方法
+ headerLines := r.safeSplitText(pdf, header, colW-6)
headerHeight := float64(len(headerLines)) * lineHt
// 添加上下内边距
headerHeight += lineHt * 0.8 // 上下各0.4倍行高的内边距
@@ -263,11 +361,7 @@ func (r *DatabaseTableRenderer) renderHeader(pdf *gofpdf.Fpdf, headers []string,
colW := colWidths[i]
// 清理表头数据,移除任何残留的HTML标签和样式
- header = strings.TrimSpace(header)
- // 移除HTML标签(使用简单的替换)
- header = strings.ReplaceAll(header, "
", " ")
- header = strings.ReplaceAll(header, "
", " ")
- header = strings.ReplaceAll(header, "
", " ")
+ header = r.cleanTextForPDF(header)
// 绘制表头背景
pdf.Rect(currentX, startY, colW, maxHeaderHeight, "FD")
@@ -275,7 +369,8 @@ func (r *DatabaseTableRenderer) renderHeader(pdf *gofpdf.Fpdf, headers []string,
// 绘制表头文本
if header != "" {
// 计算文本的实际高度(减少内边距,给文本更多空间)
- headerLines := pdf.SplitText(header, colW-4)
+ // 使用安全的SplitText方法
+ headerLines := r.safeSplitText(pdf, header, colW-4)
textHeight := float64(len(headerLines)) * lineHt
if textHeight < lineHt {
textHeight = lineHt
@@ -360,31 +455,17 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co
cell := row[j]
cellWidth := colWidths[j] - 4 // 减少内边距到4mm,给文本更多空间
+ // 先清理单元格文本(在计算宽度之前)
+ cell = r.cleanTextForPDF(cell)
+
// 计算文本实际宽度,判断是否需要换行
textWidth := r.getTextWidth(pdf, cell)
var lines []string
// 只有当文本宽度超过单元格宽度时才换行
if textWidth > cellWidth {
- // 文本需要换行
- func() {
- defer func() {
- if rec := recover(); rec != nil {
- r.logger.Warn("SplitText失败,使用估算",
- zap.Any("error", rec),
- zap.Int("row_index", rowIndex),
- zap.Int("col_index", j))
- // 使用估算值
- charCount := len([]rune(cell))
- estimatedLines := math.Max(1, math.Ceil(float64(charCount)/20))
- lines = make([]string, int(estimatedLines))
- for i := range lines {
- lines[i] = ""
- }
- }
- }()
- lines = pdf.SplitText(cell, cellWidth)
- }()
+ // 文本需要换行,使用安全的SplitText方法
+ lines = r.safeSplitText(pdf, cell, cellWidth)
} else {
// 文本不需要换行,单行显示
lines = []string{cell}
@@ -417,11 +498,7 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co
cell := row[j]
// 清理单元格数据,移除任何残留的HTML标签和样式
- cell = strings.TrimSpace(cell)
- // 移除HTML标签(使用简单的正则表达式)
- cell = strings.ReplaceAll(cell, "
", " ")
- cell = strings.ReplaceAll(cell, "
", " ")
- cell = strings.ReplaceAll(cell, "
", " ")
+ cell = r.cleanTextForPDF(cell)
// 绘制单元格背景
if fill {
@@ -440,21 +517,8 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co
var textLines []string
// 只有当文本宽度超过单元格宽度时才换行
if textWidth > cellWidth {
- // 文本需要换行
- func() {
- defer func() {
- if rec := recover(); rec != nil {
- // 如果SplitText失败,使用估算
- charCount := len([]rune(cell))
- estimatedLines := math.Max(1, math.Ceil(float64(charCount)/20))
- textLines = make([]string, int(estimatedLines))
- for i := range textLines {
- textLines[i] = ""
- }
- }
- }()
- textLines = pdf.SplitText(cell, cellWidth)
- }()
+ // 文本需要换行,使用安全的SplitText方法
+ textLines = r.safeSplitText(pdf, cell, cellWidth)
} else {
// 文本不需要换行,单行显示
textLines = []string{cell}