//nolint:unused 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" "github.com/shopspring/decimal" "go.uber.org/zap" ) // ProductHandler 产品相关HTTP处理器 type ProductHandler struct { appService product.ProductApplicationService apiConfigService product.ProductApiConfigApplicationService categoryService product.CategoryApplicationService subAppService product.SubscriptionApplicationService documentationAppService product.DocumentationApplicationServiceInterface responseBuilder interfaces.ResponseBuilder validator interfaces.RequestValidator pdfGenerator *pdf.PDFGenerator pdfCacheManager *pdf.PDFCacheManager logger *zap.Logger } // NewProductHandler 创建产品HTTP处理器 func NewProductHandler( appService product.ProductApplicationService, apiConfigService product.ProductApiConfigApplicationService, categoryService product.CategoryApplicationService, subAppService product.SubscriptionApplicationService, documentationAppService product.DocumentationApplicationServiceInterface, responseBuilder interfaces.ResponseBuilder, validator interfaces.RequestValidator, pdfGenerator *pdf.PDFGenerator, pdfCacheManager *pdf.PDFCacheManager, logger *zap.Logger, ) *ProductHandler { return &ProductHandler{ appService: appService, apiConfigService: apiConfigService, categoryService: categoryService, subAppService: subAppService, documentationAppService: documentationAppService, responseBuilder: responseBuilder, validator: validator, pdfGenerator: pdfGenerator, pdfCacheManager: pdfCacheManager, logger: logger, } } // ListProducts 获取产品列表(数据大厅) // @Summary 获取产品列表 // @Description 分页获取可用的产品列表,支持筛选,默认只返回可见的产品 // @Tags 数据大厅 // @Accept json // @Produce json // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(10) // @Param keyword query string false "搜索关键词" // @Param category_id query string false "分类ID" // @Param is_enabled query bool false "是否启用" // @Param is_visible query bool false "是否可见" // @Param is_package query bool false "是否组合包" // @Param is_subscribed query bool false "是否已订阅(需要认证)" // @Param sort_by query string false "排序字段" // @Param sort_order query string false "排序方向" Enums(asc, desc) // @Success 200 {object} responses.ProductListResponse "获取产品列表成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/products [get] func (h *ProductHandler) ListProducts(c *gin.Context) { // 获取当前用户ID(可选认证) userID := h.getCurrentUserID(c) // 解析查询参数 page := h.getIntQuery(c, "page", 1) pageSize := h.getIntQuery(c, "page_size", 10) // 构建筛选条件 filters := make(map[string]interface{}) // 搜索关键词筛选 if keyword := c.Query("keyword"); keyword != "" { filters["keyword"] = keyword } // 分类ID筛选 if categoryID := c.Query("category_id"); categoryID != "" { filters["category_id"] = categoryID } // 启用状态筛选 if isEnabled := c.Query("is_enabled"); isEnabled != "" { if enabled, err := strconv.ParseBool(isEnabled); err == nil { filters["is_enabled"] = enabled } } // 可见状态筛选 - 用户端默认只显示可见的产品 if isVisible := c.Query("is_visible"); isVisible != "" { if visible, err := strconv.ParseBool(isVisible); err == nil { filters["is_visible"] = visible } } else { // 如果没有指定可见状态,默认只显示可见的产品 filters["is_visible"] = true } // 产品类型筛选 if isPackage := c.Query("is_package"); isPackage != "" { if pkg, err := strconv.ParseBool(isPackage); err == nil { filters["is_package"] = pkg } } // 订阅状态筛选(需要认证) if userID != "" { if isSubscribed := c.Query("is_subscribed"); isSubscribed != "" { if subscribed, err := strconv.ParseBool(isSubscribed); err == nil { filters["is_subscribed"] = subscribed } } // 添加用户ID到筛选条件 filters["user_id"] = userID } // 排序字段 sortBy := c.Query("sort_by") if sortBy == "" { sortBy = "created_at" } // 排序方向 sortOrder := c.Query("sort_order") if sortOrder == "" { sortOrder = "desc" } // 构建分页选项 options := interfaces.ListOptions{ Page: page, PageSize: pageSize, Sort: sortBy, Order: sortOrder, } result, err := h.appService.ListProducts(c.Request.Context(), filters, options) if err != nil { h.logger.Error("获取产品列表失败", zap.Error(err)) h.responseBuilder.InternalError(c, "获取产品列表失败") return } h.responseBuilder.Success(c, result, "获取产品列表成功") } // getIntQuery 获取整数查询参数 func (h *ProductHandler) getIntQuery(c *gin.Context, key string, defaultValue int) int { if value := c.Query(key); value != "" { if intValue, err := strconv.Atoi(value); err == nil && intValue > 0 { return intValue } } return defaultValue } // getCurrentUserID 获取当前用户ID func (h *ProductHandler) getCurrentUserID(c *gin.Context) string { if userID, exists := c.Get("user_id"); exists { if id, ok := userID.(string); ok { return id } } return "" } // GetProductDetail 获取产品详情 // @Summary 获取产品详情 // @Description 获取产品详细信息,详情接口不受 is_visible 字段影响,可通过直接访问查看任何产品 // @Tags 数据大厅 // @Accept json // @Produce json // @Param id path string true "产品ID" // @Param with_document query bool false "是否包含文档信息" // @Success 200 {object} responses.ProductInfoWithDocumentResponse "获取产品详情成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 404 {object} map[string]interface{} "产品不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/products/{id} [get] func (h *ProductHandler) GetProductDetail(c *gin.Context) { var query queries.GetProductDetailQuery query.ID = c.Param("id") if query.ID == "" { h.responseBuilder.BadRequest(c, "产品ID不能为空") return } // 解析可选参数 if withDocument := c.Query("with_document"); withDocument != "" { if withDoc, err := strconv.ParseBool(withDocument); err == nil { query.WithDocument = &withDoc } } result, err := h.appService.GetProductByIDForUser(c.Request.Context(), &query) if err != nil { h.logger.Error("获取产品详情失败", zap.Error(err)) h.responseBuilder.NotFound(c, "产品不存在") return } h.responseBuilder.Success(c, result, "获取产品详情成功") } // SubscribeProduct 订阅产品 // @Summary 订阅产品 // @Description 用户订阅指定产品 // @Tags 数据大厅 // @Accept json // @Produce json // @Security Bearer // @Param id path string true "产品ID" // @Success 200 {object} map[string]interface{} "订阅成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 404 {object} map[string]interface{} "产品不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/products/{id}/subscribe [post] func (h *ProductHandler) SubscribeProduct(c *gin.Context) { userID := c.GetString("user_id") // 从JWT中间件获取 if userID == "" { h.responseBuilder.Unauthorized(c, "用户未登录") return } var cmd commands.CreateSubscriptionCommand if err := h.validator.ValidateParam(c, &cmd); err != nil { return } // 设置用户ID cmd.UserID = userID if err := h.subAppService.CreateSubscription(c.Request.Context(), &cmd); err != nil { h.logger.Error("订阅产品失败", zap.Error(err), zap.String("user_id", userID), zap.String("product_id", cmd.ProductID)) h.responseBuilder.BadRequest(c, err.Error()) return } h.responseBuilder.Success(c, nil, "订阅成功") } // GetProductStats 获取产品统计信息 // @Summary 获取产品统计 // @Description 获取产品相关的统计信息 // @Tags 数据大厅 // @Accept json // @Produce json // @Success 200 {object} responses.ProductStatsResponse "获取统计信息成功" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/products/stats [get] func (h *ProductHandler) GetProductStats(c *gin.Context) { result, err := h.appService.GetProductStats(c.Request.Context()) if err != nil { h.logger.Error("获取产品统计失败", zap.Error(err)) h.responseBuilder.InternalError(c, "获取产品统计失败") return } h.responseBuilder.Success(c, result, "获取产品统计成功") } // GetProductApiConfig 获取产品API配置 // @Summary 获取产品API配置 // @Description 根据产品ID获取API配置信息 // @Tags 产品API配置 // @Accept json // @Produce json // @Param id path string true "产品ID" // @Success 200 {object} responses.ProductApiConfigResponse "获取成功" // @Failure 400 {object} interfaces.APIResponse "请求参数错误" // @Failure 404 {object} interfaces.APIResponse "配置不存在" // @Router /api/v1/products/{id}/api-config [get] func (h *ProductHandler) GetProductApiConfig(c *gin.Context) { productID := c.Param("id") if productID == "" { h.responseBuilder.BadRequest(c, "产品ID不能为空") return } config, err := h.apiConfigService.GetProductApiConfig(c.Request.Context(), productID) if err != nil { h.logger.Error("获取产品API配置失败", zap.Error(err), zap.String("product_id", productID)) h.responseBuilder.NotFound(c, "产品API配置不存在") return } h.responseBuilder.Success(c, config, "获取产品API配置成功") } // GetProductApiConfigByCode 根据产品代码获取API配置 // @Summary 根据产品代码获取API配置 // @Description 根据产品代码获取API配置信息 // @Tags 产品API配置 // @Accept json // @Produce json // @Param product_code path string true "产品代码" // @Success 200 {object} responses.ProductApiConfigResponse "获取成功" // @Failure 400 {object} interfaces.APIResponse "请求参数错误" // @Failure 404 {object} interfaces.APIResponse "配置不存在" // @Router /api/v1/products/code/{product_code}/api-config [get] func (h *ProductHandler) GetProductApiConfigByCode(c *gin.Context) { productCode := c.Param("product_code") if productCode == "" { h.responseBuilder.BadRequest(c, "产品代码不能为空") return } config, err := h.apiConfigService.GetProductApiConfigByCode(c.Request.Context(), productCode) if err != nil { h.logger.Error("根据产品代码获取API配置失败", zap.Error(err), zap.String("product_code", productCode)) h.responseBuilder.NotFound(c, "产品API配置不存在") return } h.responseBuilder.Success(c, config, "获取产品API配置成功") } // ================ 分类相关方法 ================ // ListCategories 获取分类列表 // @Summary 获取分类列表 // @Description 获取产品分类列表,支持筛选 // @Tags 数据大厅 // @Accept json // @Produce json // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(10) // @Param is_enabled query bool false "是否启用" // @Param is_visible query bool false "是否可见" // @Success 200 {object} responses.CategoryListResponse "获取分类列表成功" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/categories [get] func (h *ProductHandler) ListCategories(c *gin.Context) { var query queries.ListCategoriesQuery if err := h.validator.ValidateQuery(c, &query); err != nil { return } // 设置默认值 if query.Page <= 0 { query.Page = 1 } if query.PageSize <= 0 { query.PageSize = 10 } if query.PageSize > 100 { query.PageSize = 100 } // 调用应用服务 categories, err := h.categoryService.ListCategories(c.Request.Context(), &query) if err != nil { h.logger.Error("获取分类列表失败", zap.Error(err)) h.responseBuilder.InternalError(c, "获取分类列表失败") return } // 返回结果 h.responseBuilder.Success(c, categories, "获取分类列表成功") } // GetCategoryDetail 获取分类详情 // @Summary 获取分类详情 // @Description 根据分类ID获取分类详细信息 // @Tags 数据大厅 // @Accept json // @Produce json // @Param id path string true "分类ID" // @Success 200 {object} responses.CategoryInfoResponse "获取分类详情成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 404 {object} map[string]interface{} "分类不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/categories/{id} [get] func (h *ProductHandler) GetCategoryDetail(c *gin.Context) { categoryID := c.Param("id") if categoryID == "" { h.responseBuilder.BadRequest(c, "分类ID不能为空") return } // 构建查询命令 query := &queries.GetCategoryQuery{ ID: categoryID, } // 调用应用服务 category, err := h.categoryService.GetCategoryByID(c.Request.Context(), query) if err != nil { h.logger.Error("获取分类详情失败", zap.String("category_id", categoryID), zap.Error(err)) h.responseBuilder.NotFound(c, "分类不存在") return } // 返回结果 h.responseBuilder.Success(c, category, "获取分类详情成功") } // ================ 我的订阅相关方法 ================ // ListMySubscriptions 获取我的订阅列表 // @Summary 获取我的订阅列表 // @Description 获取当前用户的订阅列表 // @Tags 我的订阅 // @Accept json // @Produce json // @Security Bearer // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(10) // @Param keyword query string false "搜索关键词" // @Param product_name query string false "产品名称" // @Param start_time query string false "订阅开始时间" format(date-time) // @Param end_time query string false "订阅结束时间" format(date-time) // @Param sort_by query string false "排序字段" // @Param sort_order query string false "排序方向" Enums(asc, desc) // @Success 200 {object} responses.SubscriptionListResponse "获取订阅列表成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/my/subscriptions [get] func (h *ProductHandler) ListMySubscriptions(c *gin.Context) { userID := c.GetString("user_id") if userID == "" { h.responseBuilder.Unauthorized(c, "用户未登录") return } var query queries.ListSubscriptionsQuery if err := h.validator.ValidateQuery(c, &query); err != nil { return } // 设置默认值 if query.Page <= 0 { query.Page = 1 } if query.PageSize <= 0 { query.PageSize = 10 } if query.PageSize > 1000 { query.PageSize = 1000 } // 设置默认排序 if query.SortBy == "" { query.SortBy = "created_at" } if query.SortOrder == "" { query.SortOrder = "desc" } // 用户端不支持企业名称筛选,清空该字段 query.CompanyName = "" result, err := h.subAppService.ListMySubscriptions(c.Request.Context(), userID, &query) if err != nil { h.logger.Error("获取我的订阅列表失败", zap.Error(err), zap.String("user_id", userID)) h.responseBuilder.InternalError(c, "获取我的订阅列表失败") return } h.responseBuilder.Success(c, result, "获取我的订阅列表成功") } // GetMySubscriptionStats 获取我的订阅统计 // @Summary 获取我的订阅统计 // @Description 获取当前用户的订阅统计信息 // @Tags 我的订阅 // @Accept json // @Produce json // @Security Bearer // @Success 200 {object} responses.SubscriptionStatsResponse "获取订阅统计成功" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/my/subscriptions/stats [get] func (h *ProductHandler) GetMySubscriptionStats(c *gin.Context) { userID := c.GetString("user_id") if userID == "" { h.responseBuilder.Unauthorized(c, "用户未登录") return } result, err := h.subAppService.GetMySubscriptionStats(c.Request.Context(), userID) if err != nil { h.logger.Error("获取我的订阅统计失败", zap.Error(err), zap.String("user_id", userID)) h.responseBuilder.InternalError(c, "获取我的订阅统计失败") return } h.responseBuilder.Success(c, result, "获取我的订阅统计成功") } // GetMySubscriptionDetail 获取我的订阅详情 // @Summary 获取我的订阅详情 // @Description 获取指定订阅的详细信息 // @Tags 我的订阅 // @Accept json // @Produce json // @Security Bearer // @Param id path string true "订阅ID" // @Success 200 {object} responses.SubscriptionInfoResponse "获取订阅详情成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 404 {object} map[string]interface{} "订阅不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/my/subscriptions/{id} [get] func (h *ProductHandler) GetMySubscriptionDetail(c *gin.Context) { userID := c.GetString("user_id") if userID == "" { h.responseBuilder.Unauthorized(c, "用户未登录") return } subscriptionID := c.Param("id") if subscriptionID == "" { h.responseBuilder.BadRequest(c, "订阅ID不能为空") return } var query queries.GetSubscriptionQuery query.ID = subscriptionID result, err := h.subAppService.GetSubscriptionByID(c.Request.Context(), &query) if err != nil { h.logger.Error("获取我的订阅详情失败", zap.Error(err), zap.String("user_id", userID), zap.String("subscription_id", subscriptionID)) h.responseBuilder.NotFound(c, "订阅不存在") return } // 验证订阅是否属于当前用户 if result.UserID != userID { h.logger.Error("用户尝试访问不属于自己的订阅", zap.String("user_id", userID), zap.String("subscription_user_id", result.UserID), zap.String("subscription_id", subscriptionID)) h.responseBuilder.Forbidden(c, "无权访问此订阅") return } h.responseBuilder.Success(c, result, "获取我的订阅详情成功") } // GetMySubscriptionUsage 获取我的订阅使用情况 // @Summary 获取我的订阅使用情况 // @Description 获取指定订阅的使用情况统计 // @Tags 我的订阅 // @Accept json // @Produce json // @Security Bearer // @Param id path string true "订阅ID" // @Success 200 {object} map[string]interface{} "获取使用情况成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 404 {object} map[string]interface{} "订阅不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/my/subscriptions/{id}/usage [get] func (h *ProductHandler) GetMySubscriptionUsage(c *gin.Context) { userID := c.GetString("user_id") if userID == "" { h.responseBuilder.Unauthorized(c, "用户未登录") return } subscriptionID := c.Param("id") if subscriptionID == "" { h.responseBuilder.BadRequest(c, "订阅ID不能为空") return } result, err := h.subAppService.GetSubscriptionUsage(c.Request.Context(), subscriptionID) if err != nil { h.logger.Error("获取我的订阅使用情况失败", zap.Error(err), zap.String("user_id", userID), zap.String("subscription_id", subscriptionID)) h.responseBuilder.NotFound(c, "订阅不存在") return } // 验证订阅是否属于当前用户(通过获取订阅详情来验证) var query queries.GetSubscriptionQuery query.ID = subscriptionID subscription, err := h.subAppService.GetSubscriptionByID(c.Request.Context(), &query) if err != nil { h.logger.Error("获取订阅详情失败", zap.Error(err), zap.String("subscription_id", subscriptionID)) h.responseBuilder.NotFound(c, "订阅不存在") return } // 验证订阅是否属于当前用户 if subscription.UserID != userID { h.logger.Error("用户尝试访问不属于自己的订阅", zap.String("user_id", userID), zap.String("subscription_user_id", subscription.UserID), zap.String("subscription_id", subscriptionID)) h.responseBuilder.Forbidden(c, "无权访问此订阅") return } h.responseBuilder.Success(c, result, "获取我的订阅使用情况成功") } // CancelMySubscription 取消我的订阅 // @Summary 取消我的订阅 // @Description 取消指定的订阅(软删除) // @Tags 我的订阅 // @Accept json // @Produce json // @Security Bearer // @Param id path string true "订阅ID" // @Success 200 {object} map[string]interface{} "取消订阅成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 401 {object} map[string]interface{} "未认证" // @Failure 403 {object} map[string]interface{} "无权操作" // @Failure 404 {object} map[string]interface{} "订阅不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/my/subscriptions/{id}/cancel [post] func (h *ProductHandler) CancelMySubscription(c *gin.Context) { userID := c.GetString("user_id") if userID == "" { h.responseBuilder.Unauthorized(c, "用户未登录") return } subscriptionID := c.Param("id") if subscriptionID == "" { h.responseBuilder.BadRequest(c, "订阅ID不能为空") return } err := h.subAppService.CancelMySubscription(c.Request.Context(), userID, subscriptionID) if err != nil { h.logger.Error("取消订阅失败", zap.Error(err), zap.String("user_id", userID), zap.String("subscription_id", subscriptionID)) // 根据错误类型返回不同的响应 if err.Error() == "订阅不存在" { h.responseBuilder.NotFound(c, "订阅不存在") } else if err.Error() == "无权取消此订阅" { h.responseBuilder.Forbidden(c, "无权取消此订阅") } else { h.responseBuilder.BadRequest(c, err.Error()) } return } h.responseBuilder.Success(c, nil, "取消订阅成功") } // GetProductDocumentation 获取产品文档 // @Summary 获取产品文档 // @Description 获取指定产品的文档信息 // @Tags 数据大厅 // @Accept json // @Produce json // @Param id path string true "产品ID" // @Success 200 {object} responses.DocumentationResponse "获取产品文档成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 404 {object} map[string]interface{} "产品不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/products/{id}/documentation [get] func (h *ProductHandler) GetProductDocumentation(c *gin.Context) { productID := c.Param("id") if productID == "" { h.responseBuilder.BadRequest(c, "产品ID不能为空") return } doc, err := h.documentationAppService.GetDocumentationByProductID(c.Request.Context(), productID) if err != nil { h.logger.Error("获取产品文档失败", zap.Error(err)) h.responseBuilder.NotFound(c, "文档不存在") return } 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 var docVersion string 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, } docVersion = doc.Version } else { // 如果没有文档,使用默认版本号 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 if h.pdfCacheManager != nil { var cacheErr error pdfBytes, cacheHit, cacheErr = h.pdfCacheManager.Get(productID, docVersion) if cacheErr != nil { h.logger.Warn("从缓存获取PDF失败,将重新生成", zap.String("product_id", productID), zap.Error(cacheErr), ) } else if cacheHit { h.logger.Info("PDF缓存命中", zap.String("product_id", productID), zap.String("version", docVersion), zap.Int("pdf_size", len(pdfBytes)), ) // 直接返回缓存的PDF 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, "|", "_") 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.Header("X-Cache", "HIT") // 添加缓存命中标识 c.Data(http.StatusOK, "application/pdf", pdfBytes) return } } // 缓存未命中,需要生成PDF h.logger.Info("PDF缓存未命中,开始生成PDF", zap.String("product_id", productID), zap.String("product_name", product.Name), zap.String("version", docVersion), 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)) } } }() // 构建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生成器", 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), 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 } // 保存到缓存(异步,不阻塞响应) if h.pdfCacheManager != nil { go func() { if err := h.pdfCacheManager.Set(productID, docVersion, pdfBytes); err != nil { h.logger.Warn("保存PDF到缓存失败", zap.String("product_id", productID), zap.String("version", docVersion), zap.Error(err), ) } }() } // 生成文件名(清理文件名中的非法字符) 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)), zap.Bool("cached", false), ) // 设置响应头并返回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.Header("X-Cache", "MISS") // 添加缓存未命中标识 c.Data(http.StatusOK, "application/pdf", pdfBytes) }