This commit is contained in:
@@ -45,7 +45,6 @@ func (pd *ProductDocumentation) IsValid() bool {
|
|||||||
return pd.DeletedAt.Time.IsZero()
|
return pd.DeletedAt.Time.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// UpdateContent 更新文档内容
|
// UpdateContent 更新文档内容
|
||||||
func (pd *ProductDocumentation) UpdateContent(requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes string) {
|
func (pd *ProductDocumentation) UpdateContent(requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes string) {
|
||||||
pd.RequestURL = requestURL
|
pd.RequestURL = requestURL
|
||||||
@@ -167,7 +166,6 @@ func (pd *ProductDocumentation) UpdateDocumentation(requestURL, requestMethod, b
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// GetDocumentationSummary 获取文档摘要
|
// GetDocumentationSummary 获取文档摘要
|
||||||
func (pd *ProductDocumentation) GetDocumentationSummary() map[string]interface{} {
|
func (pd *ProductDocumentation) GetDocumentationSummary() map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package pdf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/jung-kurt/gofpdf/v2"
|
"github.com/jung-kurt/gofpdf/v2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -227,6 +229,101 @@ func (r *DatabaseTableRenderer) calculateColumnWidths(pdf *gofpdf.Fpdf, tableDat
|
|||||||
return colWidths
|
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 渲染表头
|
// renderHeader 渲染表头
|
||||||
func (r *DatabaseTableRenderer) renderHeader(pdf *gofpdf.Fpdf, headers []string, colWidths []float64, startY float64) float64 {
|
func (r *DatabaseTableRenderer) renderHeader(pdf *gofpdf.Fpdf, headers []string, colWidths []float64, startY float64) float64 {
|
||||||
_, lineHt := pdf.GetFontSize()
|
_, lineHt := pdf.GetFontSize()
|
||||||
@@ -238,7 +335,8 @@ func (r *DatabaseTableRenderer) renderHeader(pdf *gofpdf.Fpdf, headers []string,
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
colW := colWidths[i]
|
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 := float64(len(headerLines)) * lineHt
|
||||||
// 添加上下内边距
|
// 添加上下内边距
|
||||||
headerHeight += lineHt * 0.8 // 上下各0.4倍行高的内边距
|
headerHeight += lineHt * 0.8 // 上下各0.4倍行高的内边距
|
||||||
@@ -263,11 +361,7 @@ func (r *DatabaseTableRenderer) renderHeader(pdf *gofpdf.Fpdf, headers []string,
|
|||||||
colW := colWidths[i]
|
colW := colWidths[i]
|
||||||
|
|
||||||
// 清理表头数据,移除任何残留的HTML标签和样式
|
// 清理表头数据,移除任何残留的HTML标签和样式
|
||||||
header = strings.TrimSpace(header)
|
header = r.cleanTextForPDF(header)
|
||||||
// 移除HTML标签(使用简单的替换)
|
|
||||||
header = strings.ReplaceAll(header, "<br>", " ")
|
|
||||||
header = strings.ReplaceAll(header, "<br/>", " ")
|
|
||||||
header = strings.ReplaceAll(header, "<br />", " ")
|
|
||||||
|
|
||||||
// 绘制表头背景
|
// 绘制表头背景
|
||||||
pdf.Rect(currentX, startY, colW, maxHeaderHeight, "FD")
|
pdf.Rect(currentX, startY, colW, maxHeaderHeight, "FD")
|
||||||
@@ -275,7 +369,8 @@ func (r *DatabaseTableRenderer) renderHeader(pdf *gofpdf.Fpdf, headers []string,
|
|||||||
// 绘制表头文本
|
// 绘制表头文本
|
||||||
if header != "" {
|
if header != "" {
|
||||||
// 计算文本的实际高度(减少内边距,给文本更多空间)
|
// 计算文本的实际高度(减少内边距,给文本更多空间)
|
||||||
headerLines := pdf.SplitText(header, colW-4)
|
// 使用安全的SplitText方法
|
||||||
|
headerLines := r.safeSplitText(pdf, header, colW-4)
|
||||||
textHeight := float64(len(headerLines)) * lineHt
|
textHeight := float64(len(headerLines)) * lineHt
|
||||||
if textHeight < lineHt {
|
if textHeight < lineHt {
|
||||||
textHeight = lineHt
|
textHeight = lineHt
|
||||||
@@ -360,31 +455,17 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co
|
|||||||
cell := row[j]
|
cell := row[j]
|
||||||
cellWidth := colWidths[j] - 4 // 减少内边距到4mm,给文本更多空间
|
cellWidth := colWidths[j] - 4 // 减少内边距到4mm,给文本更多空间
|
||||||
|
|
||||||
|
// 先清理单元格文本(在计算宽度之前)
|
||||||
|
cell = r.cleanTextForPDF(cell)
|
||||||
|
|
||||||
// 计算文本实际宽度,判断是否需要换行
|
// 计算文本实际宽度,判断是否需要换行
|
||||||
textWidth := r.getTextWidth(pdf, cell)
|
textWidth := r.getTextWidth(pdf, cell)
|
||||||
var lines []string
|
var lines []string
|
||||||
|
|
||||||
// 只有当文本宽度超过单元格宽度时才换行
|
// 只有当文本宽度超过单元格宽度时才换行
|
||||||
if textWidth > cellWidth {
|
if textWidth > cellWidth {
|
||||||
// 文本需要换行
|
// 文本需要换行,使用安全的SplitText方法
|
||||||
func() {
|
lines = r.safeSplitText(pdf, cell, cellWidth)
|
||||||
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)
|
|
||||||
}()
|
|
||||||
} else {
|
} else {
|
||||||
// 文本不需要换行,单行显示
|
// 文本不需要换行,单行显示
|
||||||
lines = []string{cell}
|
lines = []string{cell}
|
||||||
@@ -417,11 +498,7 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co
|
|||||||
cell := row[j]
|
cell := row[j]
|
||||||
|
|
||||||
// 清理单元格数据,移除任何残留的HTML标签和样式
|
// 清理单元格数据,移除任何残留的HTML标签和样式
|
||||||
cell = strings.TrimSpace(cell)
|
cell = r.cleanTextForPDF(cell)
|
||||||
// 移除HTML标签(使用简单的正则表达式)
|
|
||||||
cell = strings.ReplaceAll(cell, "<br>", " ")
|
|
||||||
cell = strings.ReplaceAll(cell, "<br/>", " ")
|
|
||||||
cell = strings.ReplaceAll(cell, "<br />", " ")
|
|
||||||
|
|
||||||
// 绘制单元格背景
|
// 绘制单元格背景
|
||||||
if fill {
|
if fill {
|
||||||
@@ -440,21 +517,8 @@ func (r *DatabaseTableRenderer) renderRows(pdf *gofpdf.Fpdf, rows [][]string, co
|
|||||||
var textLines []string
|
var textLines []string
|
||||||
// 只有当文本宽度超过单元格宽度时才换行
|
// 只有当文本宽度超过单元格宽度时才换行
|
||||||
if textWidth > cellWidth {
|
if textWidth > cellWidth {
|
||||||
// 文本需要换行
|
// 文本需要换行,使用安全的SplitText方法
|
||||||
func() {
|
textLines = r.safeSplitText(pdf, cell, cellWidth)
|
||||||
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)
|
|
||||||
}()
|
|
||||||
} else {
|
} else {
|
||||||
// 文本不需要换行,单行显示
|
// 文本不需要换行,单行显示
|
||||||
textLines = []string{cell}
|
textLines = []string{cell}
|
||||||
|
|||||||
Reference in New Issue
Block a user