package service import ( "context" "errors" "fmt" "math/rand" "time" "order/consts" "order/dao" "order/model/dto" "order/model/entity" "gitea.com/red-future/common/http" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" "github.com/robfig/cron/v3" "go.mongodb.org/mongo-driver/v2/bson" ) // AssetServiceResponse 资产服务响应结构体 type AssetServiceResponse struct { ID string `json:"id"` Name string `json:"name"` UnlimitedStock bool `json:"unlimitedStock"` Price *struct { TotalStock int `json:"totalStock"` } `json:"price"` SaleMode string `json:"saleMode"` } type order struct{} // Order 订单服务 var Order = new(order) // convertOrderItemsFromDTO 从DTO转换订单商品项 func convertOrderItemsFromDTO(items []dto.OrderItemReq) []entity.OrderItem { var result []entity.OrderItem for _, item := range items { // 转换库存明细,支持明细模式和批次模式 var stocks []entity.OrderItemStock totalQuantity := 0 totalAmount := int64(0) for _, stock := range item.Stocks { stocks = append(stocks, entity.OrderItemStock{ StockID: stock.StockID, BatchID: stock.BatchID, BatchNo: stock.BatchNo, Quantity: stock.Quantity, Price: stock.Price, StockMode: stock.StockMode, StockAttrs: stock.StockAttrs, }) totalQuantity += stock.Quantity totalAmount += int64(stock.Quantity) * stock.Price } result = append(result, entity.OrderItem{ AssetID: item.AssetID, AssetName: item.AssetName, AssetType: item.AssetType, ImageURL: item.ImageURL, Quantity: totalQuantity, TotalPrice: totalAmount, Stocks: stocks, }) } return result } // convertOrderItems 转换订单商品项 func convertOrderItems(items []entity.OrderItem) []dto.OrderItem { var result []dto.OrderItem for _, item := range items { // 转换库存明细,支持明细模式和批次模式 var stocks []dto.OrderItemStock for _, stock := range item.Stocks { stocks = append(stocks, dto.OrderItemStock{ StockID: stock.StockID, BatchID: stock.BatchID, BatchNo: stock.BatchNo, Quantity: stock.Quantity, Price: stock.Price, StockMode: stock.StockMode, StockAttrs: stock.StockAttrs, }) } result = append(result, dto.OrderItem{ AssetID: item.AssetID, AssetName: item.AssetName, AssetType: item.AssetType, ImageURL: item.ImageURL, Quantity: item.Quantity, TotalPrice: item.TotalPrice, Stocks: stocks, }) } return result } // convertEntityOrderItemsToDTO 转换entity订单商品项到DTO func convertEntityOrderItemsToDTO(items []entity.OrderItem) []dto.OrderItem { var result []dto.OrderItem for _, item := range items { // 转换库存明细,支持明细模式和批次模式 var stocks []dto.OrderItemStock for _, stock := range item.Stocks { stocks = append(stocks, dto.OrderItemStock{ StockID: stock.StockID, BatchID: stock.BatchID, BatchNo: stock.BatchNo, Quantity: stock.Quantity, Price: stock.Price, StockMode: stock.StockMode, StockAttrs: stock.StockAttrs, }) } result = append(result, dto.OrderItem{ AssetID: item.AssetID, AssetName: item.AssetName, AssetType: item.AssetType, ImageURL: item.ImageURL, Quantity: item.Quantity, TotalPrice: item.TotalPrice, Stocks: stocks, }) } return result } // CreateOrder 创建订单 func (s *order) CreateOrder(ctx context.Context, req *dto.CreateOrderReq) (*dto.CreateOrderResp, error) { // 1. 参数验证 if req.UserID == 0 || req.Subject == "" { return nil, errors.New("必填参数不能为空") } if len(req.OrderItems) == 0 { return nil, errors.New("订单商品不能为空") } // 2. 检查库存扣减策略并处理库存 for i := range req.OrderItems { item := &req.OrderItems[i] // 判断是否需要扣减库存 shouldDeduct, saleMode, err := s.shouldDeductStock(ctx, gconv.String(req.TenantID), item.AssetID) if err != nil { return nil, fmt.Errorf("检查库存策略失败: %w", err) } // 如果需要扣减库存,判断扣减时机 if shouldDeduct { timing, err := s.getDeductStockTiming(ctx, gconv.String(req.TenantID), item.AssetID) if err != nil { return nil, fmt.Errorf("获取库存扣减时机失败: %w", err) } // 秒杀场景:下单时立即扣减库存 if timing == "order_create" { quantity := len(item.Stocks) // 每个库存项数量为1 // 先生成订单号用于库存操作 orderNo := s.generateOrderNo(gconv.String(req.TenantID)) if err := s.deductStock(ctx, gconv.String(req.TenantID), orderNo, item.AssetID, quantity, fmt.Sprintf("秒杀订单预占库存,售卖方式:%s", saleMode)); err != nil { return nil, fmt.Errorf("秒杀订单预占库存失败: %w", err) } // 记录库存已预占 g.Log().Infof(ctx, "资产 %s 秒杀订单 %s 预占库存成功,数量:%d", item.AssetID, orderNo, quantity) } } } // 3. 计算订单金额 totalAmount := int64(0) for i := range req.OrderItems { item := &req.OrderItems[i] itemTotal := int64(0) for j := range item.Stocks { stock := &item.Stocks[j] if stock.Price <= 0 { return nil, fmt.Errorf("资产价格无效: %s", item.AssetName) } itemTotal += stock.Price // 每个库存项数量为1 } totalAmount += itemTotal } if totalAmount <= 0 { return nil, errors.New("订单总金额必须大于0") } // 4. 生成订单号 orderNo := s.generateOrderNo(gconv.String(req.TenantID)) // 5. 设置订单过期时间(30分钟后) expiredAt := time.Now().Add(30 * time.Minute) // 5. 创建待支付订单 order := &entity.OrderPending{ OrderBase: entity.OrderBase{ OrderNo: orderNo, UserID: req.UserID, TotalAmount: totalAmount, PayAmount: totalAmount, // 暂时没有优惠,实付金额等于总金额 OrderType: req.OrderType, Subject: req.Subject, Description: req.Description, ExpiredAt: &expiredAt, }, OrderItems: convertOrderItemsFromDTO(req.OrderItems), ShippingInfo: (*entity.ShippingInfo)(&req.ShippingInfo), PayInfo: entity.PayInfo{}, } // 6. 保存订单 if err := dao.Order.CreatePendingOrder(ctx, order); err != nil { return nil, fmt.Errorf("创建订单失败: %w", err) } // 7. 返回结果 resp := &dto.CreateOrderResp{ OrderNo: orderNo, TotalAmount: totalAmount, PayAmount: totalAmount, ExpiredAt: expiredAt.Format(time.RFC3339), } return resp, nil } // generateOrderNo 生成订单号 func (s *order) generateOrderNo(tenantID string) string { timestamp := time.Now().Format("20060102150405") random := rand.Intn(10000) return fmt.Sprintf("%s%s%04d", tenantID, timestamp, random) } // QueryOrder 查询订单详情 func (s *order) QueryOrder(ctx context.Context, req *dto.QueryOrderReq) (*dto.QueryOrderResp, error) { // 1. 参数验证 if req.OrderNo == "" { return nil, errors.New("必填参数不能为空") } // 2. 查询订单 order, status, err := dao.Order.GetOrderByNo(ctx, req.OrderNo) if err != nil { return nil, fmt.Errorf("获取订单失败: %w", err) } if order == nil { return nil, errors.New("订单不存在") } // 3. 构建响应 var resp dto.QueryOrderResp switch status { case consts.OrderStatusPending: if pendingOrder, ok := order.(*entity.OrderPending); ok { resp.Order = s.convertPendingOrderToDetail(pendingOrder) } case consts.OrderStatusPaid: if paidOrder, ok := order.(*entity.OrderPaid); ok { resp.Order = s.convertPaidOrderToDetail(paidOrder) } case consts.OrderStatusShipped: if shippedOrder, ok := order.(*entity.OrderShipped); ok { resp.Order = s.convertShippedOrderToDetail(shippedOrder) } case consts.OrderStatusCompleted: if completedOrder, ok := order.(*entity.OrderCompleted); ok { resp.Order = s.convertCompletedOrderToDetail(completedOrder) } default: return nil, fmt.Errorf("不支持的订单状态: %s", status) } return &resp, nil } // convertPendingOrderToDetail 转换待支付订单为详情 func (s *order) convertPendingOrderToDetail(order *entity.OrderPending) dto.OrderDetail { return dto.OrderDetail{ ID: order.Id.Hex(), TenantID: gconv.String(order.TenantId), OrderNo: order.OrderNo, UserID: order.UserID, TotalAmount: order.TotalAmount, PayAmount: order.PayAmount, Status: string(consts.OrderStatusPending), PayMethod: order.PayMethod, PayStatus: "unpaid", OrderType: order.OrderType, Subject: order.Subject, Description: order.Description, OrderItems: convertOrderItems(order.OrderItems), ShippingInfo: dto.ShippingInfo{ Consignee: order.ShippingInfo.Consignee, Phone: order.ShippingInfo.Phone, Province: order.ShippingInfo.Province, City: order.ShippingInfo.City, District: order.ShippingInfo.District, Address: order.ShippingInfo.Address, PostalCode: order.ShippingInfo.PostalCode, }, PayInfo: dto.PayInfo{ OutTradeNo: order.PayInfo.OutTradeNo, PrepayID: order.PayInfo.PrepayID, QRCode: order.PayInfo.QRCode, PayURL: order.PayInfo.PayURL, }, CreatedAt: order.CreatedAt, UpdatedAt: order.UpdatedAt, ExpiredAt: order.ExpiredAt, } } // convertPaidOrderToDetail 转换已支付订单为详情 func (s *order) convertPaidOrderToDetail(order *entity.OrderPaid) dto.OrderDetail { return dto.OrderDetail{ ID: order.Id.Hex(), TenantID: order.TenantId, OrderNo: order.OrderNo, UserID: order.UserID, TotalAmount: order.TotalAmount, PayAmount: order.PayAmount, Status: string(consts.OrderStatusPaid), PayMethod: order.PayMethod, PayStatus: "paid", OrderType: order.OrderType, Subject: order.Subject, Description: order.Description, OrderItems: convertOrderItems(order.OrderItems), ShippingInfo: dto.ShippingInfo{ Consignee: order.ShippingInfo.Consignee, Phone: order.ShippingInfo.Phone, Province: order.ShippingInfo.Province, City: order.ShippingInfo.City, District: order.ShippingInfo.District, Address: order.ShippingInfo.Address, PostalCode: order.ShippingInfo.PostalCode, }, PayInfo: dto.PayInfo{ TransactionID: order.TransactionID, OutTradeNo: order.OrderNo, }, CreatedAt: order.CreatedAt, UpdatedAt: order.UpdatedAt, PaidAt: &order.PaidAt, } } // convertShippedOrderToDetail 转换已发货订单为详情 func (s *order) convertShippedOrderToDetail(order *entity.OrderShipped) dto.OrderDetail { return dto.OrderDetail{ ID: order.Id.Hex(), TenantID: order.TenantId, OrderNo: order.OrderNo, UserID: order.UserID, TotalAmount: order.TotalAmount, PayAmount: order.PayAmount, Status: string(consts.OrderStatusShipped), PayMethod: order.PayMethod, PayStatus: "paid", OrderType: order.OrderType, Subject: order.Subject, Description: order.Description, OrderItems: convertOrderItems(order.OrderItems), ShippingInfo: dto.ShippingInfo{ Consignee: order.ShippingInfo.Consignee, Phone: order.ShippingInfo.Phone, Province: order.ShippingInfo.Province, City: order.ShippingInfo.City, District: order.ShippingInfo.District, Address: order.ShippingInfo.Address, PostalCode: order.ShippingInfo.PostalCode, }, CreatedAt: order.CreatedAt, UpdatedAt: order.UpdatedAt, PaidAt: &order.PaidAt, } } // convertCompletedOrderToDetail 转换已完成订单为详情 func (s *order) convertCompletedOrderToDetail(order *entity.OrderCompleted) dto.OrderDetail { return dto.OrderDetail{ ID: order.Id.Hex(), TenantID: order.TenantId, OrderNo: order.OrderNo, UserID: order.UserID, TotalAmount: order.TotalAmount, PayAmount: order.PayAmount, Status: string(consts.OrderStatusCompleted), PayMethod: order.PayMethod, PayStatus: "paid", OrderType: order.OrderType, Subject: order.Subject, Description: order.Description, OrderItems: convertOrderItems(order.OrderItems), ShippingInfo: dto.ShippingInfo{ Consignee: order.ShippingInfo.Consignee, Phone: order.ShippingInfo.Phone, Province: order.ShippingInfo.Province, City: order.ShippingInfo.City, District: order.ShippingInfo.District, Address: order.ShippingInfo.Address, PostalCode: order.ShippingInfo.PostalCode, }, CreatedAt: order.CreatedAt, UpdatedAt: order.UpdatedAt, PaidAt: &order.PaidAt, } } // convertOrderItems 转换订单商品项 func (s *order) convertOrderItems(items []*entity.OrderItem) []dto.OrderItem { var result []dto.OrderItem for _, item := range items { // 转换库存明细,支持明细模式和批次模式 var stocks []dto.OrderItemStock for _, stock := range item.Stocks { stocks = append(stocks, dto.OrderItemStock{ StockID: stock.StockID, BatchID: stock.BatchID, BatchNo: stock.BatchNo, Quantity: stock.Quantity, Price: stock.Price, StockMode: stock.StockMode, StockAttrs: stock.StockAttrs, }) } result = append(result, dto.OrderItem{ AssetID: item.AssetID, AssetName: item.AssetName, AssetType: item.AssetType, ImageURL: item.ImageURL, Quantity: item.Quantity, TotalPrice: item.TotalPrice, Stocks: stocks, }) } return result } // CancelOrder 取消订单 func (s *order) CancelOrder(ctx context.Context, req *dto.CancelOrderReq) (*dto.CancelOrderResp, error) { // 1. 参数验证 if req.OrderNo == "" { return nil, errors.New("必填参数不能为空") } // 2. 查询订单 order, status, err := dao.Order.GetOrderByNo(ctx, req.OrderNo) if err != nil { return nil, fmt.Errorf("获取订单失败: %w", err) } if order == nil { return nil, errors.New("订单不存在") } // 3. 检查订单状态(只有待支付订单可以取消) if status != consts.OrderStatusPending { return nil, fmt.Errorf("订单状态不正确,当前状态: %s", status) } pendingOrder, ok := order.(*entity.OrderPending) if !ok { return nil, errors.New("订单类型错误") } // 4. 处理库存回充(针对秒杀场景预占的库存) for _, item := range pendingOrder.OrderItems { // 判断是否需要回充库存 shouldDeduct, saleMode, err := s.shouldDeductStock(ctx, req.TenantID, item.AssetID) if err != nil { g.Log().Errorf(ctx, "检查库存策略失败,订单:%s,资产:%s,错误:%v", req.OrderNo, item.AssetID, err) continue } // 如果该资产需要扣减库存且是秒杀场景,则需要回充预占库存 if shouldDeduct && saleMode == "flash" { timing, err := s.getDeductStockTiming(ctx, req.TenantID, item.AssetID) if err != nil { g.Log().Errorf(ctx, "获取库存扣减时机失败,订单:%s,资产:%s,错误:%v", req.OrderNo, item.AssetID, err) continue } // 秒杀场景:下单时已扣减库存,取消时需要回充 if timing == "order_create" { quantity := len(item.Stocks) // 每个库存项数量为1 if err := s.refundStock(ctx, req.TenantID, req.OrderNo, item.AssetID, quantity, fmt.Sprintf("秒杀订单取消回充库存,售卖方式:%s,原因:%s", saleMode, req.Reason)); err != nil { g.Log().Errorf(ctx, "订单取消回充库存失败,订单:%s,资产:%s,错误:%v", req.OrderNo, item.AssetID, err) // 库存回充失败不影响订单状态更新,但需要记录错误 continue } g.Log().Infof(ctx, "资产 %s 订单取消回充库存成功,订单:%s,数量:%d", item.AssetID, req.OrderNo, quantity) } } } // 5. 将订单移动到已取消状态 updateData := bson.M{ "cancel_reason": req.Reason, } if err := dao.Order.MoveOrderToStatus(ctx, consts.OrderStatusPending, consts.OrderStatusCancelled, req.OrderNo, updateData); err != nil { return nil, fmt.Errorf("取消订单失败: %w", err) } // 5. 返回结果 resp := &dto.CancelOrderResp{ OrderNo: req.OrderNo, Status: string(consts.OrderStatusCancelled), } return resp, nil } // ListOrders 查询订单列表 func (s *order) ListOrders(ctx context.Context, req *dto.ListOrdersReq) (*dto.ListOrdersResp, error) { // 1. 参数验证 if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 || req.PageSize > 100 { req.PageSize = 20 } // 2. 根据状态查询订单列表 var status consts.OrderStatus if req.Status != "" { status = consts.OrderStatus(req.Status) } else { // 默认查询所有状态 status = consts.OrderStatusPending } orders, total, err := dao.Order.ListOrdersByStatus(ctx, status, req.UserID, req.Page, req.PageSize) if err != nil { return nil, fmt.Errorf("查询订单列表失败: %w", err) } // 3. 转换订单列表 var orderSummaries []dto.OrderSummary switch status { case consts.OrderStatusPending: if pendingOrders, ok := orders.([]entity.OrderPending); ok { for _, order := range pendingOrders { orderSummaries = append(orderSummaries, dto.OrderSummary{ ID: order.Id.Hex(), OrderNo: order.OrderNo, TotalAmount: order.TotalAmount, PayAmount: order.PayAmount, Status: string(consts.OrderStatusPending), Subject: order.Subject, CreatedAt: order.CreatedAt, }) } } case consts.OrderStatusPaid: if paidOrders, ok := orders.([]entity.OrderPaid); ok { for _, order := range paidOrders { orderSummaries = append(orderSummaries, dto.OrderSummary{ ID: order.Id.Hex(), OrderNo: order.OrderNo, TotalAmount: order.TotalAmount, PayAmount: order.PayAmount, Status: string(consts.OrderStatusPaid), Subject: order.Subject, CreatedAt: order.CreatedAt, PaidAt: &order.PaidAt, }) } } // 其他状态类似处理... } // 4. 返回结果 resp := &dto.ListOrdersResp{ Orders: orderSummaries, Total: total, Page: req.Page, PageSize: req.PageSize, } return resp, nil } // ProcessExpiredOrders 处理过期订单 func (s *order) ProcessExpiredOrders(ctx context.Context) error { // 获取过期的待支付订单 expiredOrders, err := dao.Order.GetExpiredPendingOrders(ctx) if err != nil { return fmt.Errorf("获取过期订单失败: %w", err) } // 批量取消过期订单 for _, order := range expiredOrders { updateData := bson.M{ "cancel_reason": "订单超时自动取消", } if err := dao.Order.MoveOrderToStatus(ctx, consts.OrderStatusPending, consts.OrderStatusCancelled, order.OrderNo, updateData); err != nil { // 记录错误但继续处理其他订单 fmt.Printf("取消过期订单失败: %s, 错误: %v\n", order.OrderNo, err) } } return nil } // UpdatePayInfo 更新支付信息 func (s *order) UpdatePayInfo(ctx context.Context, orderNo string, payInfo entity.PayInfo) error { return dao.Order.UpdatePayInfo(ctx, orderNo, payInfo) } // shouldDeductStock 判断是否需要扣减库存 func (s *order) shouldDeductStock(ctx context.Context, tenantID, assetID string) (bool, string, error) { // 调用assets服务获取资产信息 assetResp, err := s.getAssetFromAssetService(ctx, tenantID, assetID) if err != nil { return false, "", fmt.Errorf("获取资产信息失败: %w", err) } if assetResp == nil { return false, "", errors.New("资产不存在") } // 无库存限制的资产不需要扣减库存 if assetResp.UnlimitedStock { return false, "unlimited_stock", nil } // 预售场景不扣减库存 if assetResp.SaleMode == "presale" { return false, "presale_no_deduct", nil } // 有库存限制的常规售卖和秒杀需要扣减库存 return true, assetResp.SaleMode, nil } // getDeductStockTiming 获取库存扣减时机 func (s *order) getDeductStockTiming(ctx context.Context, tenantID, assetID string) (string, error) { assetResp, err := s.getAssetFromAssetService(ctx, tenantID, assetID) if err != nil { return "", fmt.Errorf("获取资产信息失败: %w", err) } if assetResp == nil { return "", errors.New("资产不存在") } // 根据售卖方式确定扣减时机 switch assetResp.SaleMode { case "flash": return "order_create", nil // 秒杀:下单时扣减库存 case "regular": return "payment_success", nil // 常规售卖:支付成功时扣减库存 default: return "", errors.New("未知售卖方式") } } // deductStock 扣减库存 func (s *order) deductStock(ctx context.Context, tenantID, orderNo, assetID string, quantity int, reason string) error { // 调用assets服务扣减库存 err := s.deductStockFromAssetService(ctx, tenantID, assetID, quantity) if err != nil { return fmt.Errorf("扣减库存失败: %w", err) } g.Log().Infof(ctx, "库存扣减成功,订单:%s,资产:%s,数量:%d,原因:%s", orderNo, assetID, quantity, reason) return nil } // refundStock 回充库存 func (s *order) refundStock(ctx context.Context, tenantID, orderNo, assetID string, quantity int, reason string) error { // 调用资产服务回充库存 err := s.refundStockFromAssetService(ctx, tenantID, assetID, quantity) if err != nil { return fmt.Errorf("回充库存失败: %w", err) } g.Log().Infof(ctx, "库存回充成功,订单:%s,资产:%s,数量:%d,原因:%s", orderNo, assetID, quantity, reason) return nil } // getAssetFromAssetService 从资产服务获取资产信息 func (s *order) getAssetFromAssetService(ctx context.Context, tenantID, assetID string) (*AssetServiceResponse, error) { // 使用common/http中的封装方法调用资产服务 // 这里应该调用assets服务的getAsset接口 // 暂时返回模拟数据 var assetResp AssetServiceResponse err := http.Get(ctx, fmt.Sprintf("http://assets-service/internal/asset/%s", assetID), nil, &assetResp) if err != nil { return nil, err } return &assetResp, nil } // deductStockFromAssetService 从资产服务扣减库存 func (s *order) deductStockFromAssetService(ctx context.Context, tenantID, assetID string, quantity int) error { // 调用assets服务的扣减库存接口 req := map[string]interface{}{ "asset_id": assetID, "quantity": quantity, "reason": "订单扣减", } var result struct { Success bool `json:"success"` Message string `json:"message"` } err := http.Post(ctx, "http://assets-service/internal/stock/deduct", nil, &result, req) if err != nil { return err } if !result.Success { return errors.New(result.Message) } return nil } // refundStockFromAssetService 从资产服务回充库存 func (s *order) refundStockFromAssetService(ctx context.Context, tenantID, assetID string, quantity int) error { // 调用资产服务的回充库存接口 req := map[string]interface{}{ "asset_id": assetID, "quantity": quantity, "reason": "订单取消回充", } var result struct { Success bool `json:"success"` Message string `json:"message"` } err := http.Post(ctx, "http://assets-service/internal/stock/refund", nil, &result, req) if err != nil { return err } if !result.Success { return errors.New(result.Message) } return nil } // OrderTimeoutScheduler 订单超时处理定时任务 type OrderTimeoutScheduler struct { cron *cron.Cron } var orderTimeoutSchedulerInstance = &OrderTimeoutScheduler{} // StartOrderTimeoutScheduler 启动订单超时处理定时任务 func StartOrderTimeoutScheduler(ctx context.Context) error { return orderTimeoutSchedulerInstance.StartScheduler(ctx) } // StopOrderTimeoutScheduler 停止订单超时处理定时任务 func StopOrderTimeoutScheduler() { orderTimeoutSchedulerInstance.StopScheduler() } // StartScheduler 启动订单超时处理定时任务 func (s *OrderTimeoutScheduler) StartScheduler(ctx context.Context) error { if s.cron != nil { s.cron.Stop() } s.cron = cron.New(cron.WithSeconds()) // 每分钟检查一次超时订单 _, err := s.cron.AddFunc("0 */1 * * * *", func() { s.processTimeoutOrders(ctx) }) if err != nil { return fmt.Errorf("添加订单超时检查定时任务失败: %w", err) } s.cron.Start() g.Log().Info(ctx, "订单超时处理定时任务已启动") return nil } // StopScheduler 停止定时任务 func (s *OrderTimeoutScheduler) StopScheduler() { if s.cron != nil { s.cron.Stop() s.cron = nil } } // processTimeoutOrders 处理超时订单 func (s *OrderTimeoutScheduler) processTimeoutOrders(ctx context.Context) { g.Log().Info(ctx, "开始处理超时订单") // 查询所有超时的待支付订单 expiredOrders, err := dao.Order.GetExpiredPendingOrders(ctx) if err != nil { g.Log().Errorf(ctx, "查询超时订单失败: %v", err) return } if len(expiredOrders) == 0 { g.Log().Debug(ctx, "没有需要处理的超时订单") return } g.Log().Infof(ctx, "发现 %d 个超时订单", len(expiredOrders)) // 逐个处理超时订单 for _, order := range expiredOrders { if err := s.processTimeoutOrder(ctx, &order); err != nil { g.Log().Errorf(ctx, "处理超时订单失败,订单号:%s,错误:%v", order.OrderNo, err) continue } g.Log().Infof(ctx, "超时订单处理成功,订单号:%s", order.OrderNo) } } // processTimeoutOrder 处理单个超时订单 func (s *OrderTimeoutScheduler) processTimeoutOrder(ctx context.Context, order *entity.OrderPending) error { // 获取租户ID(从订单中获取) tenantID := order.TenantId if tenantID == "" { return fmt.Errorf("订单缺少租户ID") } // 将订单移动到已取消状态 updateData := map[string]interface{}{ "cancel_reason": "订单超时自动取消", } if err := dao.Order.MoveOrderToStatus(ctx, consts.OrderStatusPending, consts.OrderStatusCancelled, order.OrderNo, updateData); err != nil { return fmt.Errorf("更新订单状态失败: %w", err) } return nil }