This commit is contained in:
2026-01-14 15:59:15 +08:00
parent 7a589a9c13
commit 6618e35869
3 changed files with 93 additions and 66 deletions

View File

@@ -565,13 +565,14 @@ func (s *ComponentReportOrderService) createFreeOrder(
// 创建下载记录 // 创建下载记录
download := &productEntities.ComponentReportDownload{ download := &productEntities.ComponentReportDownload{
UserID: req.UserID, UserID: req.UserID,
ProductID: req.ProductID, ProductID: req.ProductID,
ProductCode: product.Code, ProductCode: product.Code,
ProductName: product.Name, ProductName: product.Name,
OrderID: &createdPurchaseOrder.ID, // 关联购买订单ID OrderID: &createdPurchaseOrder.ID, // 关联购买订单ID
OrderNumber: &createdPurchaseOrder.OrderNo, // 外部订单号 OrderNumber: &createdPurchaseOrder.OrderNo, // 外部订单号
ExpiresAt: calculateExpiryTime(), // 30天后过期 DownloadPrice: finalPrice, // 设置下载价格
ExpiresAt: calculateExpiryTime(), // 30天后过期
} }
err = s.componentReportRepo.Create(ctx, download) err = s.componentReportRepo.Create(ctx, download)
@@ -735,7 +736,7 @@ func (s *ComponentReportOrderService) CheckPaymentStatus(ctx context.Context, or
// DownloadFile 下载文件 // DownloadFile 下载文件
func (s *ComponentReportOrderService) DownloadFile(ctx context.Context, orderID string) (string, error) { func (s *ComponentReportOrderService) DownloadFile(ctx context.Context, orderID string) (string, error) {
s.logger.Info("开始下载文件", zap.String("order_id", orderID)) s.logger.Info("开始下载文件", zap.String("order_id", orderID))
// 首先通过orderID查询购买订单 // 首先通过orderID查询购买订单
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, orderID) purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, orderID)
if err != nil { if err != nil {
@@ -747,70 +748,71 @@ func (s *ComponentReportOrderService) DownloadFile(ctx context.Context, orderID
s.logger.Error("购买订单不存在", zap.String("order_id", orderID)) s.logger.Error("购买订单不存在", zap.String("order_id", orderID))
return "", fmt.Errorf("购买订单不存在") return "", fmt.Errorf("购买订单不存在")
} }
// 检查购买订单状态 // 检查购买订单状态
if purchaseOrder.Status != entities.PurchaseOrderStatusPaid { if purchaseOrder.Status != entities.PurchaseOrderStatusPaid {
s.logger.Error("订单未支付,无法下载文件", s.logger.Error("订单未支付,无法下载文件",
zap.String("order_id", orderID), zap.String("order_id", orderID),
zap.String("status", string(purchaseOrder.Status))) zap.String("status", string(purchaseOrder.Status)))
return "", fmt.Errorf("订单未支付,无法下载文件") return "", fmt.Errorf("订单未支付,无法下载文件")
} }
// 获取产品信息 // 获取产品信息
product, err := s.productRepo.GetByID(ctx, purchaseOrder.ProductID) product, err := s.productRepo.GetByID(ctx, purchaseOrder.ProductID)
if err != nil { if err != nil {
s.logger.Error("获取产品信息失败", zap.Error(err), zap.String("product_id", purchaseOrder.ProductID)) s.logger.Error("获取产品信息失败", zap.Error(err), zap.String("product_id", purchaseOrder.ProductID))
return "", fmt.Errorf("获取产品信息失败: %w", err) return "", fmt.Errorf("获取产品信息失败: %w", err)
} }
// 检查是否已有下载记录 // 检查是否已有下载记录
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, orderID) download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, orderID)
if err != nil { if err != nil {
s.logger.Warn("查询下载记录失败,将创建新记录", zap.Error(err), zap.String("order_id", orderID)) s.logger.Warn("查询下载记录失败,将创建新记录", zap.Error(err), zap.String("order_id", orderID))
download = nil download = nil
} }
// 如果没有下载记录,创建一个新的 // 如果没有下载记录,创建一个新的
if download == nil { if download == nil {
s.logger.Info("创建新的下载记录", s.logger.Info("创建新的下载记录",
zap.String("order_id", orderID), zap.String("order_id", orderID),
zap.String("user_id", purchaseOrder.UserID), zap.String("user_id", purchaseOrder.UserID),
zap.String("product_id", purchaseOrder.ProductID)) zap.String("product_id", purchaseOrder.ProductID))
// 创建新的下载记录 // 创建新的下载记录
newDownload := &productEntities.ComponentReportDownload{ newDownload := &productEntities.ComponentReportDownload{
UserID: purchaseOrder.UserID, UserID: purchaseOrder.UserID,
ProductID: purchaseOrder.ProductID, ProductID: purchaseOrder.ProductID,
ProductCode: product.Code, ProductCode: product.Code,
ProductName: product.Name, ProductName: product.Name,
OrderID: &purchaseOrder.ID, OrderID: &purchaseOrder.ID,
OrderNumber: &purchaseOrder.OrderNo, OrderNumber: &purchaseOrder.OrderNo,
ExpiresAt: calculateExpiryTime(), DownloadPrice: purchaseOrder.Amount, // 设置下载价格(从订单获取)
ExpiresAt: calculateExpiryTime(),
} }
// 保存下载记录 // 保存下载记录
err = s.componentReportRepo.Create(ctx, newDownload) err = s.componentReportRepo.Create(ctx, newDownload)
if err != nil { if err != nil {
s.logger.Error("创建下载记录失败", zap.Error(err)) s.logger.Error("创建下载记录失败", zap.Error(err))
return "", fmt.Errorf("创建下载记录失败: %w", err) return "", fmt.Errorf("创建下载记录失败: %w", err)
} }
s.logger.Info("成功创建下载记录", s.logger.Info("成功创建下载记录",
zap.String("order_id", orderID), zap.String("order_id", orderID),
zap.String("download_id", newDownload.ID), zap.String("download_id", newDownload.ID),
zap.String("product_id", newDownload.ProductID)) zap.String("product_id", newDownload.ProductID))
download = newDownload download = newDownload
} }
// 检查是否过期 // 检查是否过期
if download.IsExpired() { if download.IsExpired() {
s.logger.Error("下载链接已过期", s.logger.Error("下载链接已过期",
zap.String("order_id", orderID), zap.String("order_id", orderID),
zap.Time("expires_at", *download.ExpiresAt)) zap.Time("expires_at", *download.ExpiresAt))
return "", fmt.Errorf("下载链接已过期,无法下载文件") return "", fmt.Errorf("下载链接已过期,无法下载文件")
} }
// 检查文件是否已存在 // 检查文件是否已存在
if download.FilePath != nil && *download.FilePath != "" { if download.FilePath != nil && *download.FilePath != "" {
// 文件已存在,直接返回文件路径 // 文件已存在,直接返回文件路径
@@ -897,7 +899,7 @@ func (s *ComponentReportOrderService) createDownloadRecordForPaidOrder(ctx conte
zap.String("order_no", purchaseOrder.OrderNo), zap.String("order_no", purchaseOrder.OrderNo),
zap.String("user_id", purchaseOrder.UserID), zap.String("user_id", purchaseOrder.UserID),
zap.String("product_id", purchaseOrder.ProductID)) zap.String("product_id", purchaseOrder.ProductID))
// 检查是否已有下载记录 // 检查是否已有下载记录
existingDownload, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, purchaseOrder.ID) existingDownload, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, purchaseOrder.ID)
if err == nil && existingDownload != nil { if err == nil && existingDownload != nil {
@@ -906,41 +908,42 @@ func (s *ComponentReportOrderService) createDownloadRecordForPaidOrder(ctx conte
zap.String("download_id", existingDownload.ID)) zap.String("download_id", existingDownload.ID))
return nil return nil
} }
// 获取产品信息 // 获取产品信息
product, err := s.productRepo.GetByID(ctx, purchaseOrder.ProductID) product, err := s.productRepo.GetByID(ctx, purchaseOrder.ProductID)
if err != nil { if err != nil {
s.logger.Error("获取产品信息失败", s.logger.Error("获取产品信息失败",
zap.Error(err), zap.Error(err),
zap.String("product_id", purchaseOrder.ProductID)) zap.String("product_id", purchaseOrder.ProductID))
return fmt.Errorf("获取产品信息失败: %w", err) return fmt.Errorf("获取产品信息失败: %w", err)
} }
// 创建新的下载记录 // 创建新的下载记录
download := &productEntities.ComponentReportDownload{ download := &productEntities.ComponentReportDownload{
UserID: purchaseOrder.UserID, UserID: purchaseOrder.UserID,
ProductID: purchaseOrder.ProductID, ProductID: purchaseOrder.ProductID,
ProductCode: product.Code, ProductCode: product.Code,
ProductName: product.Name, ProductName: product.Name,
OrderID: &purchaseOrder.ID, OrderID: &purchaseOrder.ID,
OrderNumber: &purchaseOrder.OrderNo, OrderNumber: &purchaseOrder.OrderNo,
ExpiresAt: calculateExpiryTime(), // 30天后过期 DownloadPrice: purchaseOrder.Amount, // 设置下载价格(从订单获取)
ExpiresAt: calculateExpiryTime(), // 30天后过期
} }
// 保存下载记录 // 保存下载记录
err = s.componentReportRepo.Create(ctx, download) err = s.componentReportRepo.Create(ctx, download)
if err != nil { if err != nil {
s.logger.Error("创建下载记录失败", zap.Error(err)) s.logger.Error("创建下载记录失败", zap.Error(err))
return fmt.Errorf("创建下载记录失败: %w", err) return fmt.Errorf("创建下载记录失败: %w", err)
} }
s.logger.Info("成功为已支付订单创建下载记录", s.logger.Info("成功为已支付订单创建下载记录",
zap.String("purchase_order_id", purchaseOrder.ID), zap.String("purchase_order_id", purchaseOrder.ID),
zap.String("order_no", purchaseOrder.OrderNo), zap.String("order_no", purchaseOrder.OrderNo),
zap.String("download_id", download.ID), zap.String("download_id", download.ID),
zap.String("product_id", download.ProductID), zap.String("product_id", download.ProductID),
zap.String("user_id", download.UserID)) zap.String("user_id", download.UserID))
return nil return nil
} }
@@ -1121,7 +1124,7 @@ func (s *ComponentReportOrderService) queryAlipayOrderStatusAndUpdate(ctx contex
zap.String("order_no", purchaseOrder.OrderNo), zap.String("order_no", purchaseOrder.OrderNo),
zap.String("status", "paid"), zap.String("status", "paid"),
) )
// 支付成功后,自动创建下载记录 // 支付成功后,自动创建下载记录
err = s.createDownloadRecordForPaidOrder(ctx, purchaseOrder) err = s.createDownloadRecordForPaidOrder(ctx, purchaseOrder)
if err != nil { if err != nil {
@@ -1296,7 +1299,7 @@ func (s *ComponentReportOrderService) queryWechatOrderStatusAndUpdate(ctx contex
zap.String("order_no", purchaseOrder.OrderNo), zap.String("order_no", purchaseOrder.OrderNo),
zap.String("status", "paid"), zap.String("status", "paid"),
) )
// 支付成功后,自动创建下载记录 // 支付成功后,自动创建下载记录
err = s.createDownloadRecordForPaidOrder(ctx, purchaseOrder) err = s.createDownloadRecordForPaidOrder(ctx, purchaseOrder)
if err != nil { if err != nil {

View File

@@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/shopspring/decimal"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -22,6 +23,9 @@ type ComponentReportDownload struct {
SubProductIDs string `gorm:"type:text" comment:"子产品ID列表JSON数组组合包使用"` SubProductIDs string `gorm:"type:text" comment:"子产品ID列表JSON数组组合包使用"`
SubProductCodes string `gorm:"type:text" comment:"子产品编号列表JSON数组"` SubProductCodes string `gorm:"type:text" comment:"子产品编号列表JSON数组"`
// 价格相关字段
DownloadPrice decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"实际支付价格"`
// 下载相关信息 // 下载相关信息
FilePath *string `gorm:"type:varchar(500)" comment:"生成的ZIP文件路径用于二次下载"` FilePath *string `gorm:"type:varchar(500)" comment:"生成的ZIP文件路径用于二次下载"`
FileHash *string `gorm:"type:varchar(64)" comment:"文件哈希值(用于缓存验证)"` FileHash *string `gorm:"type:varchar(64)" comment:"文件哈希值(用于缓存验证)"`

View File

@@ -333,20 +333,20 @@ func (h *ComponentReportHandler) GenerateAndDownloadZip(c *gin.Context) {
// 如果有待支付订单,尝试主动查询支付状态 // 如果有待支付订单,尝试主动查询支付状态
if pendingOrder != nil { if pendingOrder != nil {
h.logger.Info("发现待支付订单,尝试主动查询支付状态", h.logger.Info("发现待支付订单,尝试主动查询支付状态",
zap.String("order_id", pendingOrder.ID), zap.String("order_id", pendingOrder.ID),
zap.String("pay_channel", pendingOrder.PayChannel)) zap.String("pay_channel", pendingOrder.PayChannel))
// 如果是支付宝订单,主动查询状态 // 如果是支付宝订单,主动查询状态
if pendingOrder.PayChannel == "alipay" && h.aliPayService != nil { if pendingOrder.PayChannel == "alipay" && h.aliPayService != nil {
// 这里可以调用支付宝查询服务,但为了简化,我们只记录日志 // 这里可以调用支付宝查询服务,但为了简化,我们只记录日志
h.logger.Info("支付宝订单状态待查询,但当前实现简化处理", h.logger.Info("支付宝订单状态待查询,但当前实现简化处理",
zap.String("order_id", pendingOrder.ID)) zap.String("order_id", pendingOrder.ID))
} }
// 如果是微信订单,主动查询状态 // 如果是微信订单,主动查询状态
if pendingOrder.PayChannel == "wechat" && h.wechatPayService != nil { if pendingOrder.PayChannel == "wechat" && h.wechatPayService != nil {
// 这里可以调用微信查询服务,但为了简化,我们只记录日志 // 这里可以调用微信查询服务,但为了简化,我们只记录日志
h.logger.Info("微信订单状态待查询,但当前实现简化处理", h.logger.Info("微信订单状态待查询,但当前实现简化处理",
zap.String("order_id", pendingOrder.ID)) zap.String("order_id", pendingOrder.ID))
} }
} }
@@ -397,9 +397,14 @@ func (h *ComponentReportHandler) GenerateAndDownloadZip(c *gin.Context) {
} }
}() }()
// 创建带超时的上下文避免ZIP生成时间过长导致网关超时
// 设置超时时间为 25 秒,略小于服务器的 write_timeout (30秒)
ctx, cancel := context.WithTimeout(c.Request.Context(), 25*time.Second)
defer cancel()
// 生成ZIP文件 // 生成ZIP文件
zipPath, err := h.zipGenerator.GenerateZipFile( zipPath, err := h.zipGenerator.GenerateZipFile(
c.Request.Context(), ctx,
req.ProductID, req.ProductID,
req.SubProductCodes, req.SubProductCodes,
h.exampleJSONGenerator, h.exampleJSONGenerator,
@@ -407,6 +412,18 @@ func (h *ComponentReportHandler) GenerateAndDownloadZip(c *gin.Context) {
) )
if err != nil { if err != nil {
h.logger.Error("生成ZIP文件失败", zap.Error(err), zap.String("product_id", req.ProductID)) h.logger.Error("生成ZIP文件失败", zap.Error(err), zap.String("product_id", req.ProductID))
// 检查是否是超时错误
if ctx.Err() == context.DeadlineExceeded {
h.logger.Error("ZIP文件生成超时", zap.String("product_id", req.ProductID))
c.JSON(http.StatusRequestTimeout, gin.H{
"code": 504,
"message": "文件生成超时,请稍后重试",
"error": "请求处理时间过长",
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"code": 500, "code": 500,
"message": "生成ZIP文件失败", "message": "生成ZIP文件失败",
@@ -987,8 +1004,9 @@ func (h *ComponentReportHandler) CreatePaymentOrder(c *gin.Context) {
SubProductIDs: string(subProductIDsJSON), SubProductIDs: string(subProductIDsJSON),
SubProductCodes: string(subProductCodesJSON), SubProductCodes: string(subProductCodesJSON),
// 关联购买订单ID // 关联购买订单ID
OrderID: &createdPurchaseOrder.ID, OrderID: &createdPurchaseOrder.ID,
OrderNumber: &outTradeNo, OrderNumber: &outTradeNo,
DownloadPrice: finalPrice, // 设置下载价格
} }
// 记录创建前的详细信息用于调试 // 记录创建前的详细信息用于调试
@@ -1384,13 +1402,14 @@ func (h *ComponentReportHandler) createDownloadRecordIfEligible(ctx context.Cont
// 3. 创建下载记录 // 3. 创建下载记录
download := &entities.ComponentReportDownload{ download := &entities.ComponentReportDownload{
UserID: userID, UserID: userID,
ProductID: productID, ProductID: productID,
ProductCode: product.Code, ProductCode: product.Code,
ProductName: product.Name, ProductName: product.Name,
OrderID: &validOrder.ID, // 添加OrderID字段 OrderID: &validOrder.ID, // 添加OrderID字段
OrderNumber: &validOrder.OrderNo, // 使用OrderNumber字段 OrderNumber: &validOrder.OrderNo, // 使用OrderNumber字段
ExpiresAt: calculateExpiryTime(), // 从创建日起30天 DownloadPrice: validOrder.Amount, // 设置下载价格(从订单获取)
ExpiresAt: calculateExpiryTime(), // 从创建日起30天
} }
// 4. 如果是组合包,获取子产品信息 // 4. 如果是组合包,获取子产品信息
@@ -1562,13 +1581,14 @@ func (h *ComponentReportHandler) createDownloadRecordForPaidOrder(ctx context.Co
// 创建下载记录 // 创建下载记录
download := &entities.ComponentReportDownload{ download := &entities.ComponentReportDownload{
UserID: order.UserID, UserID: order.UserID,
ProductID: order.ProductID, ProductID: order.ProductID,
ProductCode: order.ProductCode, ProductCode: order.ProductCode,
ProductName: order.ProductName, ProductName: order.ProductName,
OrderID: &order.ID, OrderID: &order.ID,
OrderNumber: &order.OrderNo, OrderNumber: &order.OrderNo,
ExpiresAt: calculateExpiryTime(), DownloadPrice: order.Amount, // 设置下载价格(从订单获取)
ExpiresAt: calculateExpiryTime(),
} }
// 如果是组合包,获取子产品信息 // 如果是组合包,获取子产品信息