This commit is contained in:
2025-12-03 12:03:42 +08:00
parent 1cf64e831c
commit 63252fa30f
27 changed files with 7167 additions and 36 deletions

View File

@@ -2,12 +2,17 @@
package handlers
import (
"fmt"
"net/http"
"strconv"
"strings"
"tyapi-server/internal/application/product"
"tyapi-server/internal/application/product/dto/commands"
"tyapi-server/internal/application/product/dto/queries"
_ "tyapi-server/internal/application/product/dto/responses"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/pdf"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
@@ -15,14 +20,15 @@ import (
// ProductHandler 产品相关HTTP处理器
type ProductHandler struct {
appService product.ProductApplicationService
apiConfigService product.ProductApiConfigApplicationService
categoryService product.CategoryApplicationService
subAppService product.SubscriptionApplicationService
appService product.ProductApplicationService
apiConfigService product.ProductApiConfigApplicationService
categoryService product.CategoryApplicationService
subAppService product.SubscriptionApplicationService
documentationAppService product.DocumentationApplicationServiceInterface
responseBuilder interfaces.ResponseBuilder
validator interfaces.RequestValidator
logger *zap.Logger
responseBuilder interfaces.ResponseBuilder
validator interfaces.RequestValidator
pdfGenerator *pdf.PDFGenerator
logger *zap.Logger
}
// NewProductHandler 创建产品HTTP处理器
@@ -34,17 +40,19 @@ func NewProductHandler(
documentationAppService product.DocumentationApplicationServiceInterface,
responseBuilder interfaces.ResponseBuilder,
validator interfaces.RequestValidator,
pdfGenerator *pdf.PDFGenerator,
logger *zap.Logger,
) *ProductHandler {
return &ProductHandler{
appService: appService,
apiConfigService: apiConfigService,
categoryService: categoryService,
subAppService: subAppService,
appService: appService,
apiConfigService: apiConfigService,
categoryService: categoryService,
subAppService: subAppService,
documentationAppService: documentationAppService,
responseBuilder: responseBuilder,
validator: validator,
logger: logger,
responseBuilder: responseBuilder,
validator: validator,
pdfGenerator: pdfGenerator,
logger: logger,
}
}
@@ -630,3 +638,168 @@ func (h *ProductHandler) GetProductDocumentation(c *gin.Context) {
h.responseBuilder.Success(c, doc, "获取文档成功")
}
// DownloadProductDocumentation 下载产品接口文档PDF文件
// @Summary 下载产品接口文档
// @Description 根据产品ID从数据库获取产品信息和文档信息动态生成PDF文档并下载。
// @Tags 数据大厅
// @Accept json
// @Produce application/pdf
// @Param id path string true "产品ID"
// @Success 200 {file} file "PDF文档文件"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 404 {object} map[string]interface{} "产品不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/products/{id}/documentation/download [get]
func (h *ProductHandler) DownloadProductDocumentation(c *gin.Context) {
productID := c.Param("id")
if productID == "" {
h.responseBuilder.BadRequest(c, "产品ID不能为空")
return
}
// 检查PDF生成器是否可用
if h.pdfGenerator == nil {
h.logger.Error("PDF生成器未初始化")
h.responseBuilder.InternalError(c, "PDF生成器未初始化")
return
}
// 获取产品信息
product, err := h.appService.GetProductByID(c.Request.Context(), &queries.GetProductQuery{ID: productID})
if err != nil {
h.logger.Error("获取产品信息失败", zap.Error(err))
h.responseBuilder.NotFound(c, "产品不存在")
return
}
// 检查产品编码是否存在
if product.Code == "" {
h.logger.Warn("产品编码为空", zap.String("product_id", productID))
h.responseBuilder.BadRequest(c, "产品编码不存在")
return
}
h.logger.Info("开始生成PDF文档",
zap.String("product_id", productID),
zap.String("product_code", product.Code),
zap.String("product_name", product.Name),
)
// 获取产品文档信息
doc, docErr := h.documentationAppService.GetDocumentationByProductID(c.Request.Context(), productID)
if docErr != nil {
h.logger.Warn("获取产品文档失败,将只生成产品基本信息",
zap.String("product_id", productID),
zap.Error(docErr),
)
}
// 将响应类型转换为entity类型
var docEntity *entities.ProductDocumentation
if doc != nil {
docEntity = &entities.ProductDocumentation{
ID: doc.ID,
ProductID: doc.ProductID,
RequestURL: doc.RequestURL,
RequestMethod: doc.RequestMethod,
BasicInfo: doc.BasicInfo,
RequestParams: doc.RequestParams,
ResponseFields: doc.ResponseFields,
ResponseExample: doc.ResponseExample,
ErrorCodes: doc.ErrorCodes,
Version: doc.Version,
}
}
// 使用数据库数据生成PDF
h.logger.Info("准备调用PDF生成器",
zap.String("product_id", productID),
zap.String("product_name", product.Name),
zap.Bool("has_doc", docEntity != nil),
)
defer func() {
if r := recover(); r != nil {
h.logger.Error("PDF生成过程中发生panic",
zap.String("product_id", productID),
zap.Any("panic_value", r),
)
// 确保在panic时也能返回响应
if !c.Writer.Written() {
h.responseBuilder.InternalError(c, fmt.Sprintf("生成PDF文档时发生错误: %v", r))
}
}
}()
// 直接调用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.String("product_id", productID),
zap.Bool("has_error", genErr != nil),
zap.Int("pdf_size", len(pdfBytes)),
)
if genErr != nil {
h.logger.Error("生成PDF文档失败",
zap.String("product_id", productID),
zap.String("product_code", product.Code),
zap.Error(genErr),
)
h.responseBuilder.InternalError(c, fmt.Sprintf("生成PDF文档失败: %s", genErr.Error()))
return
}
h.logger.Info("PDF生成器调用完成",
zap.String("product_id", productID),
zap.Int("pdf_size", len(pdfBytes)),
)
if len(pdfBytes) == 0 {
h.logger.Error("生成的PDF文档为空",
zap.String("product_id", productID),
zap.String("product_code", product.Code),
)
h.responseBuilder.InternalError(c, "生成的PDF文档为空")
return
}
// 生成文件名(清理文件名中的非法字符)
fileName := fmt.Sprintf("%s_接口文档.pdf", product.Name)
if product.Name == "" {
fileName = fmt.Sprintf("%s_接口文档.pdf", product.Code)
}
// 清理文件名中的非法字符
fileName = strings.ReplaceAll(fileName, "/", "_")
fileName = strings.ReplaceAll(fileName, "\\", "_")
fileName = strings.ReplaceAll(fileName, ":", "_")
fileName = strings.ReplaceAll(fileName, "*", "_")
fileName = strings.ReplaceAll(fileName, "?", "_")
fileName = strings.ReplaceAll(fileName, "\"", "_")
fileName = strings.ReplaceAll(fileName, "<", "_")
fileName = strings.ReplaceAll(fileName, ">", "_")
fileName = strings.ReplaceAll(fileName, "|", "_")
h.logger.Info("成功生成PDF文档",
zap.String("product_id", productID),
zap.String("product_code", product.Code),
zap.String("file_name", fileName),
zap.Int("file_size", len(pdfBytes)),
)
// 设置响应头并返回PDF文件
c.Header("Content-Type", "application/pdf")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName))
c.Header("Content-Length", fmt.Sprintf("%d", len(pdfBytes)))
c.Data(http.StatusOK, "application/pdf", pdfBytes)
}