This commit is contained in:
@@ -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{}{
|
||||
|
||||
@@ -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, "<br>", " ")
|
||||
text = strings.ReplaceAll(text, "<br/>", " ")
|
||||
text = strings.ReplaceAll(text, "<br />", " ")
|
||||
|
||||
// 移除控制字符(除了换行符和制表符)
|
||||
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, "<br>", " ")
|
||||
header = strings.ReplaceAll(header, "<br/>", " ")
|
||||
header = strings.ReplaceAll(header, "<br />", " ")
|
||||
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, "<br>", " ")
|
||||
cell = strings.ReplaceAll(cell, "<br/>", " ")
|
||||
cell = strings.ReplaceAll(cell, "<br />", " ")
|
||||
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}
|
||||
|
||||
Reference in New Issue
Block a user