fix组合包文档
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"tyapi-server/internal/shared/pdf"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -765,6 +766,60 @@ func (h *ProductHandler) DownloadProductDocumentation(c *gin.Context) {
|
||||
docVersion = "1.0"
|
||||
}
|
||||
|
||||
// 如果是组合包,获取子产品的文档信息
|
||||
var subProductDocs []*entities.ProductDocumentation
|
||||
if product.IsPackage && len(product.PackageItems) > 0 {
|
||||
h.logger.Info("检测到组合包,开始获取子产品文档",
|
||||
zap.String("product_id", productID),
|
||||
zap.Int("sub_product_count", len(product.PackageItems)),
|
||||
)
|
||||
|
||||
// 收集所有子产品的ID
|
||||
subProductIDs := make([]string, 0, len(product.PackageItems))
|
||||
for _, item := range product.PackageItems {
|
||||
subProductIDs = append(subProductIDs, item.ProductID)
|
||||
}
|
||||
|
||||
// 批量获取子产品的文档
|
||||
subDocs, err := h.documentationAppService.GetDocumentationsByProductIDs(c.Request.Context(), subProductIDs)
|
||||
if err != nil {
|
||||
h.logger.Warn("获取组合包子产品文档失败",
|
||||
zap.String("product_id", productID),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
// 转换为entity类型,并按PackageItems的顺序排序
|
||||
docMap := make(map[string]*entities.ProductDocumentation)
|
||||
for i := range subDocs {
|
||||
docMap[subDocs[i].ProductID] = &entities.ProductDocumentation{
|
||||
ID: subDocs[i].ID,
|
||||
ProductID: subDocs[i].ProductID,
|
||||
RequestURL: subDocs[i].RequestURL,
|
||||
RequestMethod: subDocs[i].RequestMethod,
|
||||
BasicInfo: subDocs[i].BasicInfo,
|
||||
RequestParams: subDocs[i].RequestParams,
|
||||
ResponseFields: subDocs[i].ResponseFields,
|
||||
ResponseExample: subDocs[i].ResponseExample,
|
||||
ErrorCodes: subDocs[i].ErrorCodes,
|
||||
Version: subDocs[i].Version,
|
||||
}
|
||||
}
|
||||
|
||||
// 按PackageItems的顺序构建子产品文档列表
|
||||
for _, item := range product.PackageItems {
|
||||
if subDoc, exists := docMap[item.ProductID]; exists {
|
||||
subProductDocs = append(subProductDocs, subDoc)
|
||||
}
|
||||
}
|
||||
|
||||
h.logger.Info("成功获取组合包子产品文档",
|
||||
zap.String("product_id", productID),
|
||||
zap.Int("total_sub_products", len(product.PackageItems)),
|
||||
zap.Int("docs_found", len(subProductDocs)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试从缓存获取PDF
|
||||
var pdfBytes []byte
|
||||
var cacheHit bool
|
||||
@@ -829,18 +884,61 @@ func (h *ProductHandler) DownloadProductDocumentation(c *gin.Context) {
|
||||
}
|
||||
}()
|
||||
|
||||
// 构建Product实体(用于PDF生成)
|
||||
productEntity := &entities.Product{
|
||||
ID: product.ID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
IsPackage: product.IsPackage,
|
||||
Price: decimal.NewFromFloat(product.Price),
|
||||
}
|
||||
|
||||
// 如果是组合包,添加子产品信息
|
||||
if product.IsPackage && len(product.PackageItems) > 0 {
|
||||
productEntity.PackageItems = make([]*entities.ProductPackageItem, len(product.PackageItems))
|
||||
for i, item := range product.PackageItems {
|
||||
productEntity.PackageItems[i] = &entities.ProductPackageItem{
|
||||
ID: item.ID,
|
||||
PackageID: product.ID,
|
||||
ProductID: item.ProductID,
|
||||
SortOrder: item.SortOrder,
|
||||
Product: &entities.Product{
|
||||
ID: item.ProductID,
|
||||
Code: item.ProductCode,
|
||||
Name: item.ProductName,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 直接调用PDF生成器(简化版本,不使用goroutine)
|
||||
h.logger.Info("开始调用PDF生成器")
|
||||
pdfBytes, genErr := h.pdfGenerator.GenerateProductPDF(
|
||||
c.Request.Context(),
|
||||
product.ID,
|
||||
product.Name,
|
||||
product.Code,
|
||||
product.Description,
|
||||
product.Content,
|
||||
product.Price,
|
||||
docEntity,
|
||||
h.logger.Info("开始调用PDF生成器",
|
||||
zap.Bool("is_package", product.IsPackage),
|
||||
zap.Int("sub_product_docs_count", len(subProductDocs)),
|
||||
)
|
||||
|
||||
// 使用重构后的生成器
|
||||
refactoredGen := pdf.NewPDFGeneratorRefactored(h.logger)
|
||||
var genErr error
|
||||
|
||||
if product.IsPackage && len(subProductDocs) > 0 {
|
||||
// 组合包:使用支持子产品文档的方法
|
||||
pdfBytes, genErr = refactoredGen.GenerateProductPDFWithSubProducts(
|
||||
c.Request.Context(),
|
||||
productEntity,
|
||||
docEntity,
|
||||
subProductDocs,
|
||||
)
|
||||
} else {
|
||||
// 普通产品:使用标准方法
|
||||
pdfBytes, genErr = refactoredGen.GenerateProductPDFFromEntity(
|
||||
c.Request.Context(),
|
||||
productEntity,
|
||||
docEntity,
|
||||
)
|
||||
}
|
||||
h.logger.Info("PDF生成器调用返回",
|
||||
zap.String("product_id", productID),
|
||||
zap.Bool("has_error", genErr != nil),
|
||||
|
||||
@@ -305,6 +305,325 @@ func (pb *PageBuilder) AddDocumentationPages(pdf *gofpdf.Fpdf, doc *entities.Pro
|
||||
pb.addAdditionalInfo(pdf, doc, chineseFontAvailable)
|
||||
}
|
||||
|
||||
// AddDocumentationPagesWithoutAdditionalInfo 添加接口文档页面(不包含二维码和说明)
|
||||
// 用于组合包场景,在所有文档渲染完成后统一添加二维码和说明
|
||||
func (pb *PageBuilder) AddDocumentationPagesWithoutAdditionalInfo(pdf *gofpdf.Fpdf, doc *entities.ProductDocumentation, chineseFontAvailable bool) {
|
||||
// 创建自定义的AddPage函数,确保每页都有水印
|
||||
addPageWithWatermark := func() {
|
||||
pdf.AddPage()
|
||||
pb.addHeader(pdf, chineseFontAvailable)
|
||||
pb.addWatermark(pdf, chineseFontAvailable) // 每页都添加水印
|
||||
}
|
||||
|
||||
addPageWithWatermark()
|
||||
|
||||
pdf.SetY(45)
|
||||
pb.fontManager.SetFont(pdf, "B", 18)
|
||||
_, lineHt := pdf.GetFontSize()
|
||||
pdf.CellFormat(0, lineHt, "接口文档", "", 1, "L", false, 0, "")
|
||||
|
||||
// 请求URL
|
||||
pdf.Ln(8)
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pb.fontManager.SetFont(pdf, "B", 12)
|
||||
pdf.CellFormat(0, lineHt, "请求URL:", "", 1, "L", false, 0, "")
|
||||
// URL使用黑体字体(可能包含中文字符)
|
||||
// 先清理URL中的乱码
|
||||
cleanURL := pb.textProcessor.CleanText(doc.RequestURL)
|
||||
pb.fontManager.SetFont(pdf, "", 10) // 使用黑体
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.MultiCell(0, lineHt*1.2, cleanURL, "", "L", false)
|
||||
|
||||
// 请求方法
|
||||
pdf.Ln(5)
|
||||
pb.fontManager.SetFont(pdf, "B", 12)
|
||||
pdf.CellFormat(0, lineHt, fmt.Sprintf("请求方法:%s", doc.RequestMethod), "", 1, "L", false, 0, "")
|
||||
|
||||
// 基本信息
|
||||
if doc.BasicInfo != "" {
|
||||
pdf.Ln(8)
|
||||
pb.addSection(pdf, "基本信息", doc.BasicInfo, chineseFontAvailable)
|
||||
}
|
||||
|
||||
// 请求参数
|
||||
if doc.RequestParams != "" {
|
||||
pdf.Ln(8)
|
||||
// 显示标题
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
_, lineHt = pdf.GetFontSize()
|
||||
pdf.CellFormat(0, lineHt, "请求参数:", "", 1, "L", false, 0, "")
|
||||
|
||||
// 使用新的数据库驱动方式处理请求参数
|
||||
if err := pb.tableParser.ParseAndRenderTable(context.Background(), pdf, doc, "request_params"); err != nil {
|
||||
pb.logger.Warn("渲染请求参数表格失败,回退到文本显示", zap.Error(err))
|
||||
// 如果表格渲染失败,显示为文本
|
||||
text := pb.textProcessor.CleanText(doc.RequestParams)
|
||||
if strings.TrimSpace(text) != "" {
|
||||
pb.fontManager.SetFont(pdf, "", 10)
|
||||
pdf.MultiCell(0, lineHt*1.3, text, "", "L", false)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成JSON示例
|
||||
if jsonExample := pb.jsonProcessor.GenerateJSONExample(doc.RequestParams, pb.tableParser); jsonExample != "" {
|
||||
pdf.Ln(5)
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
pdf.CellFormat(0, lineHt, "请求示例:", "", 1, "L", false, 0, "")
|
||||
// JSON中可能包含中文值,使用黑体字体
|
||||
pb.fontManager.SetFont(pdf, "", 9) // 使用黑体显示JSON(支持中文)
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.MultiCell(0, lineHt*1.3, jsonExample, "", "L", false)
|
||||
}
|
||||
}
|
||||
|
||||
// 响应示例
|
||||
if doc.ResponseExample != "" {
|
||||
pdf.Ln(8)
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
_, lineHt = pdf.GetFontSize()
|
||||
pdf.CellFormat(0, lineHt, "响应示例:", "", 1, "L", false, 0, "")
|
||||
|
||||
// 优先尝试提取和格式化JSON
|
||||
jsonContent := pb.jsonProcessor.ExtractJSON(doc.ResponseExample)
|
||||
if jsonContent != "" {
|
||||
// 格式化JSON
|
||||
formattedJSON, err := pb.jsonProcessor.FormatJSON(jsonContent)
|
||||
if err == nil {
|
||||
jsonContent = formattedJSON
|
||||
}
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pb.fontManager.SetFont(pdf, "", 9) // 使用等宽字体显示JSON(支持中文)
|
||||
pdf.MultiCell(0, lineHt*1.3, jsonContent, "", "L", false)
|
||||
} else {
|
||||
// 如果没有JSON,尝试使用表格方式处理
|
||||
if err := pb.tableParser.ParseAndRenderTable(context.Background(), pdf, doc, "response_example"); err != nil {
|
||||
pb.logger.Warn("渲染响应示例表格失败,回退到文本显示", zap.Error(err))
|
||||
// 如果表格渲染失败,显示为文本
|
||||
text := pb.textProcessor.CleanText(doc.ResponseExample)
|
||||
if strings.TrimSpace(text) != "" {
|
||||
pb.fontManager.SetFont(pdf, "", 10)
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pdf.MultiCell(0, lineHt*1.3, text, "", "L", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回字段说明
|
||||
if doc.ResponseFields != "" {
|
||||
pdf.Ln(8)
|
||||
// 显示标题
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
_, lineHt = pdf.GetFontSize()
|
||||
pdf.CellFormat(0, lineHt, "返回字段说明:", "", 1, "L", false, 0, "")
|
||||
|
||||
// 使用新的数据库驱动方式处理返回字段(支持多个表格,带标题)
|
||||
if err := pb.tableParser.ParseAndRenderTablesWithTitles(context.Background(), pdf, doc, "response_fields"); err != nil {
|
||||
pb.logger.Warn("渲染返回字段表格失败,回退到文本显示",
|
||||
zap.Error(err),
|
||||
zap.String("content_preview", pb.getContentPreview(doc.ResponseFields, 200)))
|
||||
// 如果表格渲染失败,显示为文本
|
||||
text := pb.textProcessor.CleanText(doc.ResponseFields)
|
||||
if strings.TrimSpace(text) != "" {
|
||||
pb.fontManager.SetFont(pdf, "", 10)
|
||||
pdf.MultiCell(0, lineHt*1.3, text, "", "L", false)
|
||||
} else {
|
||||
pb.logger.Warn("返回字段内容为空或只有空白字符")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pb.logger.Debug("返回字段内容为空,跳过渲染")
|
||||
}
|
||||
|
||||
// 错误代码
|
||||
if doc.ErrorCodes != "" {
|
||||
pdf.Ln(8)
|
||||
// 显示标题
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
_, lineHt = pdf.GetFontSize()
|
||||
pdf.CellFormat(0, lineHt, "错误代码:", "", 1, "L", false, 0, "")
|
||||
|
||||
// 使用新的数据库驱动方式处理错误代码
|
||||
if err := pb.tableParser.ParseAndRenderTable(context.Background(), pdf, doc, "error_codes"); err != nil {
|
||||
pb.logger.Warn("渲染错误代码表格失败,回退到文本显示", zap.Error(err))
|
||||
// 如果表格渲染失败,显示为文本
|
||||
text := pb.textProcessor.CleanText(doc.ErrorCodes)
|
||||
if strings.TrimSpace(text) != "" {
|
||||
pb.fontManager.SetFont(pdf, "", 10)
|
||||
pdf.MultiCell(0, lineHt*1.3, text, "", "L", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 注意:这里不添加二维码和说明,由调用方统一添加
|
||||
}
|
||||
|
||||
// AddSubProductDocumentationPages 添加子产品的接口文档页面(用于组合包)
|
||||
func (pb *PageBuilder) AddSubProductDocumentationPages(pdf *gofpdf.Fpdf, subProduct *entities.Product, doc *entities.ProductDocumentation, chineseFontAvailable bool, isLastSubProduct bool) {
|
||||
// 创建自定义的AddPage函数,确保每页都有水印
|
||||
addPageWithWatermark := func() {
|
||||
pdf.AddPage()
|
||||
pb.addHeader(pdf, chineseFontAvailable)
|
||||
pb.addWatermark(pdf, chineseFontAvailable) // 每页都添加水印
|
||||
}
|
||||
|
||||
addPageWithWatermark()
|
||||
|
||||
pdf.SetY(45)
|
||||
pb.fontManager.SetFont(pdf, "B", 18)
|
||||
_, lineHt := pdf.GetFontSize()
|
||||
|
||||
// 显示子产品标题
|
||||
subProductTitle := fmt.Sprintf("子产品接口文档:%s", subProduct.Name)
|
||||
if subProduct.Code != "" {
|
||||
subProductTitle = fmt.Sprintf("子产品接口文档:%s (%s)", subProduct.Name, subProduct.Code)
|
||||
}
|
||||
pdf.CellFormat(0, lineHt, subProductTitle, "", 1, "L", false, 0, "")
|
||||
|
||||
// 请求URL
|
||||
pdf.Ln(8)
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pb.fontManager.SetFont(pdf, "B", 12)
|
||||
pdf.CellFormat(0, lineHt, "请求URL:", "", 1, "L", false, 0, "")
|
||||
// URL使用黑体字体(可能包含中文字符)
|
||||
// 先清理URL中的乱码
|
||||
cleanURL := pb.textProcessor.CleanText(doc.RequestURL)
|
||||
pb.fontManager.SetFont(pdf, "", 10) // 使用黑体
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.MultiCell(0, lineHt*1.2, cleanURL, "", "L", false)
|
||||
|
||||
// 请求方法
|
||||
pdf.Ln(5)
|
||||
pb.fontManager.SetFont(pdf, "B", 12)
|
||||
pdf.CellFormat(0, lineHt, fmt.Sprintf("请求方法:%s", doc.RequestMethod), "", 1, "L", false, 0, "")
|
||||
|
||||
// 基本信息
|
||||
if doc.BasicInfo != "" {
|
||||
pdf.Ln(8)
|
||||
pb.addSection(pdf, "基本信息", doc.BasicInfo, chineseFontAvailable)
|
||||
}
|
||||
|
||||
// 请求参数
|
||||
if doc.RequestParams != "" {
|
||||
pdf.Ln(8)
|
||||
// 显示标题
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
_, lineHt = pdf.GetFontSize()
|
||||
pdf.CellFormat(0, lineHt, "请求参数:", "", 1, "L", false, 0, "")
|
||||
|
||||
// 使用新的数据库驱动方式处理请求参数
|
||||
if err := pb.tableParser.ParseAndRenderTable(context.Background(), pdf, doc, "request_params"); err != nil {
|
||||
pb.logger.Warn("渲染请求参数表格失败,回退到文本显示", zap.Error(err))
|
||||
// 如果表格渲染失败,显示为文本
|
||||
text := pb.textProcessor.CleanText(doc.RequestParams)
|
||||
if strings.TrimSpace(text) != "" {
|
||||
pb.fontManager.SetFont(pdf, "", 10)
|
||||
pdf.MultiCell(0, lineHt*1.3, text, "", "L", false)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成JSON示例
|
||||
if jsonExample := pb.jsonProcessor.GenerateJSONExample(doc.RequestParams, pb.tableParser); jsonExample != "" {
|
||||
pdf.Ln(5)
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
pdf.CellFormat(0, lineHt, "请求示例:", "", 1, "L", false, 0, "")
|
||||
// JSON中可能包含中文值,使用黑体字体
|
||||
pb.fontManager.SetFont(pdf, "", 9) // 使用黑体显示JSON(支持中文)
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.MultiCell(0, lineHt*1.3, jsonExample, "", "L", false)
|
||||
}
|
||||
}
|
||||
|
||||
// 响应示例
|
||||
if doc.ResponseExample != "" {
|
||||
pdf.Ln(8)
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
_, lineHt = pdf.GetFontSize()
|
||||
pdf.CellFormat(0, lineHt, "响应示例:", "", 1, "L", false, 0, "")
|
||||
|
||||
// 优先尝试提取和格式化JSON
|
||||
jsonContent := pb.jsonProcessor.ExtractJSON(doc.ResponseExample)
|
||||
if jsonContent != "" {
|
||||
// 格式化JSON
|
||||
formattedJSON, err := pb.jsonProcessor.FormatJSON(jsonContent)
|
||||
if err == nil {
|
||||
jsonContent = formattedJSON
|
||||
}
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pb.fontManager.SetFont(pdf, "", 9) // 使用等宽字体显示JSON(支持中文)
|
||||
pdf.MultiCell(0, lineHt*1.3, jsonContent, "", "L", false)
|
||||
} else {
|
||||
// 如果没有JSON,尝试使用表格方式处理
|
||||
if err := pb.tableParser.ParseAndRenderTable(context.Background(), pdf, doc, "response_example"); err != nil {
|
||||
pb.logger.Warn("渲染响应示例表格失败,回退到文本显示", zap.Error(err))
|
||||
// 如果表格渲染失败,显示为文本
|
||||
text := pb.textProcessor.CleanText(doc.ResponseExample)
|
||||
if strings.TrimSpace(text) != "" {
|
||||
pb.fontManager.SetFont(pdf, "", 10)
|
||||
pdf.SetTextColor(0, 0, 0) // 确保深黑色
|
||||
pdf.MultiCell(0, lineHt*1.3, text, "", "L", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回字段说明
|
||||
if doc.ResponseFields != "" {
|
||||
pdf.Ln(8)
|
||||
// 显示标题
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
_, lineHt = pdf.GetFontSize()
|
||||
pdf.CellFormat(0, lineHt, "返回字段说明:", "", 1, "L", false, 0, "")
|
||||
|
||||
// 使用新的数据库驱动方式处理返回字段(支持多个表格,带标题)
|
||||
if err := pb.tableParser.ParseAndRenderTablesWithTitles(context.Background(), pdf, doc, "response_fields"); err != nil {
|
||||
pb.logger.Warn("渲染返回字段表格失败,回退到文本显示",
|
||||
zap.Error(err),
|
||||
zap.String("content_preview", pb.getContentPreview(doc.ResponseFields, 200)))
|
||||
// 如果表格渲染失败,显示为文本
|
||||
text := pb.textProcessor.CleanText(doc.ResponseFields)
|
||||
if strings.TrimSpace(text) != "" {
|
||||
pb.fontManager.SetFont(pdf, "", 10)
|
||||
pdf.MultiCell(0, lineHt*1.3, text, "", "L", false)
|
||||
} else {
|
||||
pb.logger.Warn("返回字段内容为空或只有空白字符")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pb.logger.Debug("返回字段内容为空,跳过渲染")
|
||||
}
|
||||
|
||||
// 错误代码
|
||||
if doc.ErrorCodes != "" {
|
||||
pdf.Ln(8)
|
||||
// 显示标题
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pb.fontManager.SetFont(pdf, "B", 14)
|
||||
_, lineHt = pdf.GetFontSize()
|
||||
pdf.CellFormat(0, lineHt, "错误代码:", "", 1, "L", false, 0, "")
|
||||
|
||||
// 使用新的数据库驱动方式处理错误代码
|
||||
if err := pb.tableParser.ParseAndRenderTable(context.Background(), pdf, doc, "error_codes"); err != nil {
|
||||
pb.logger.Warn("渲染错误代码表格失败,回退到文本显示", zap.Error(err))
|
||||
// 如果表格渲染失败,显示为文本
|
||||
text := pb.textProcessor.CleanText(doc.ErrorCodes)
|
||||
if strings.TrimSpace(text) != "" {
|
||||
pb.fontManager.SetFont(pdf, "", 10)
|
||||
pdf.MultiCell(0, lineHt*1.3, text, "", "L", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 注意:这里不添加二维码和说明,由调用方统一添加
|
||||
}
|
||||
|
||||
// addSection 添加章节
|
||||
func (pb *PageBuilder) addSection(pdf *gofpdf.Fpdf, title, content string, chineseFontAvailable bool) {
|
||||
_, lineHt := pdf.GetFontSize()
|
||||
@@ -835,7 +1154,12 @@ func (pb *PageBuilder) safeSplitText(pdf *gofpdf.Fpdf, text string, width float6
|
||||
return lines
|
||||
}
|
||||
|
||||
// addAdditionalInfo 添加说明文字和二维码
|
||||
// AddAdditionalInfo 添加说明文字和二维码(公开方法)
|
||||
func (pb *PageBuilder) AddAdditionalInfo(pdf *gofpdf.Fpdf, doc *entities.ProductDocumentation, chineseFontAvailable bool) {
|
||||
pb.addAdditionalInfo(pdf, doc, chineseFontAvailable)
|
||||
}
|
||||
|
||||
// addAdditionalInfo 添加说明文字和二维码(私有方法)
|
||||
func (pb *PageBuilder) addAdditionalInfo(pdf *gofpdf.Fpdf, doc *entities.ProductDocumentation, chineseFontAvailable bool) {
|
||||
// 检查是否需要换页
|
||||
pageWidth, pageHeight := pdf.GetPageSize()
|
||||
@@ -925,11 +1249,36 @@ func (pb *PageBuilder) addAdditionalInfo(pdf *gofpdf.Fpdf, doc *entities.Product
|
||||
// readExplanationText 读取说明文本文件
|
||||
func (pb *PageBuilder) readExplanationText() string {
|
||||
resourcesPDFDir := GetResourcesPDFDir()
|
||||
if resourcesPDFDir == "" {
|
||||
pb.logger.Error("无法获取resources/pdf目录路径")
|
||||
return ""
|
||||
}
|
||||
|
||||
textFilePath := filepath.Join(resourcesPDFDir, "后勤服务.txt")
|
||||
|
||||
// 记录尝试读取的文件路径
|
||||
pb.logger.Debug("尝试读取说明文本文件", zap.String("path", textFilePath))
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(textFilePath); os.IsNotExist(err) {
|
||||
pb.logger.Warn("说明文本文件不存在", zap.String("path", textFilePath))
|
||||
fileInfo, err := os.Stat(textFilePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
pb.logger.Warn("说明文本文件不存在",
|
||||
zap.String("path", textFilePath),
|
||||
zap.String("resources_dir", resourcesPDFDir),
|
||||
)
|
||||
} else {
|
||||
pb.logger.Error("检查说明文本文件时出错",
|
||||
zap.String("path", textFilePath),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
if fileInfo.Size() == 0 {
|
||||
pb.logger.Warn("说明文本文件为空", zap.String("path", textFilePath))
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -946,10 +1295,21 @@ func (pb *PageBuilder) readExplanationText() string {
|
||||
// 转换为字符串
|
||||
text := string(content)
|
||||
|
||||
// 检查内容是否为空(去除空白字符后)
|
||||
trimmedText := strings.TrimSpace(text)
|
||||
if trimmedText == "" {
|
||||
pb.logger.Warn("说明文本文件内容为空(只有空白字符)",
|
||||
zap.String("path", textFilePath),
|
||||
zap.Int("file_size", len(content)),
|
||||
)
|
||||
return ""
|
||||
}
|
||||
|
||||
// 记录读取成功的信息
|
||||
pb.logger.Info("成功读取说明文本文件",
|
||||
zap.String("path", textFilePath),
|
||||
zap.Int("file_size", len(content)),
|
||||
zap.Int64("file_size", fileInfo.Size()),
|
||||
zap.Int("content_length", len(content)),
|
||||
zap.Int("text_length", len(text)),
|
||||
zap.Int("line_count", len(strings.Split(text, "\n"))),
|
||||
)
|
||||
|
||||
@@ -90,16 +90,21 @@ func (g *PDFGeneratorRefactored) GenerateProductPDF(ctx context.Context, product
|
||||
product.Price = decimal.NewFromFloat(price)
|
||||
}
|
||||
|
||||
return g.generatePDF(product, doc)
|
||||
return g.generatePDF(product, doc, nil)
|
||||
}
|
||||
|
||||
// GenerateProductPDFFromEntity 从entity类型生成PDF(推荐使用)
|
||||
func (g *PDFGeneratorRefactored) GenerateProductPDFFromEntity(ctx context.Context, product *entities.Product, doc *entities.ProductDocumentation) ([]byte, error) {
|
||||
return g.generatePDF(product, doc)
|
||||
return g.generatePDF(product, doc, nil)
|
||||
}
|
||||
|
||||
// GenerateProductPDFWithSubProducts 从entity类型生成PDF,支持组合包子产品文档
|
||||
func (g *PDFGeneratorRefactored) GenerateProductPDFWithSubProducts(ctx context.Context, product *entities.Product, doc *entities.ProductDocumentation, subProductDocs []*entities.ProductDocumentation) ([]byte, error) {
|
||||
return g.generatePDF(product, doc, subProductDocs)
|
||||
}
|
||||
|
||||
// generatePDF 内部PDF生成方法
|
||||
func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *entities.ProductDocumentation) (result []byte, err error) {
|
||||
func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *entities.ProductDocumentation, subProductDocs []*entities.ProductDocumentation) (result []byte, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// 将panic转换为error,而不是重新抛出
|
||||
@@ -174,9 +179,61 @@ func (g *PDFGeneratorRefactored) generatePDF(product *entities.Product, doc *ent
|
||||
// 添加第一页(产品信息)
|
||||
pageBuilder.AddFirstPage(pdf, product, doc, chineseFontAvailable)
|
||||
|
||||
// 如果有关联的文档,添加接口文档页面
|
||||
if doc != nil {
|
||||
pageBuilder.AddDocumentationPages(pdf, doc, chineseFontAvailable)
|
||||
// 如果是组合包,需要特殊处理:先渲染所有文档,最后统一添加二维码
|
||||
if product.IsPackage {
|
||||
// 如果有关联的文档,添加接口文档页面(但不包含二维码和说明,后面统一添加)
|
||||
if doc != nil {
|
||||
pageBuilder.AddDocumentationPagesWithoutAdditionalInfo(pdf, doc, chineseFontAvailable)
|
||||
}
|
||||
|
||||
// 如果有子产品文档,为每个子产品添加接口文档页面
|
||||
if len(subProductDocs) > 0 {
|
||||
for i, subDoc := range subProductDocs {
|
||||
// 获取子产品信息(从文档中获取ProductID,然后查找对应的产品信息)
|
||||
// 注意:这里我们需要从product.PackageItems中查找对应的子产品信息
|
||||
var subProduct *entities.Product
|
||||
if product.PackageItems != nil && i < len(product.PackageItems) {
|
||||
if product.PackageItems[i].Product != nil {
|
||||
subProduct = product.PackageItems[i].Product
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找不到子产品信息,创建一个基本的子产品实体
|
||||
if subProduct == nil {
|
||||
subProduct = &entities.Product{
|
||||
ID: subDoc.ProductID,
|
||||
Code: subDoc.ProductID, // 使用ProductID作为临时Code
|
||||
Name: fmt.Sprintf("子产品 %d", i+1),
|
||||
}
|
||||
}
|
||||
|
||||
pageBuilder.AddSubProductDocumentationPages(pdf, subProduct, subDoc, chineseFontAvailable, false)
|
||||
}
|
||||
}
|
||||
|
||||
// 在所有接口文档渲染完成后,统一添加二维码和后勤服务说明
|
||||
// 使用主产品文档(如果存在),否则使用第一个子产品文档,如果都没有则创建一个空的文档对象
|
||||
var finalDoc *entities.ProductDocumentation
|
||||
if doc != nil {
|
||||
finalDoc = doc
|
||||
} else if len(subProductDocs) > 0 {
|
||||
finalDoc = subProductDocs[0]
|
||||
} else {
|
||||
// 如果没有文档,创建一个空的文档对象,用于添加二维码和说明
|
||||
finalDoc = &entities.ProductDocumentation{
|
||||
ProductID: product.ID,
|
||||
RequestMethod: "POST",
|
||||
Version: "1.0",
|
||||
}
|
||||
}
|
||||
|
||||
// 始终添加二维码和后勤服务说明
|
||||
pageBuilder.AddAdditionalInfo(pdf, finalDoc, chineseFontAvailable)
|
||||
} else {
|
||||
// 普通产品:使用原来的方法(包含二维码和说明)
|
||||
if doc != nil {
|
||||
pageBuilder.AddDocumentationPages(pdf, doc, chineseFontAvailable)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成PDF字节流
|
||||
|
||||
Reference in New Issue
Block a user