package service import ( "context" "errors" "fmt" "math/rand" "time" "go.mongodb.org/mongo-driver/v2/bson" "order/consts" "order/dao" "order/model/dto" "order/model/entity" ) type order struct{} // Order 订单服务 var Order = new(order) // Init 初始化服务 func Init() error { return nil } // convertOrderItemsFromDTO 从DTO转换订单商品项 func convertOrderItemsFromDTO(items []dto.OrderItemReq) []entity.OrderItem { var result []entity.OrderItem for _, item := range items { // 转换库存明细,每个库存项只会有一个实例 var stocks []entity.OrderItemStock totalQuantity := len(item.Stocks) // 每个库存项数量为1 totalAmount := int64(0) for _, stock := range item.Stocks { stocks = append(stocks, entity.OrderItemStock{ StockID: stock.StockID, Price: stock.Price, StockAttrs: stock.StockAttrs, }) totalAmount += stock.Price // 每个库存项数量为1 } 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, Price: stock.Price, 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, Price: stock.Price, 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.TenantID == "" || req.UserID == "" || req.Subject == "" { return nil, errors.New("必填参数不能为空") } if len(req.OrderItems) == 0 { return nil, errors.New("订单商品不能为空") } // 2. 计算订单金额 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") } // 3. 生成订单号 orderNo := s.generateOrderNo(req.TenantID) // 4. 设置订单过期时间(30分钟后) expiredAt := time.Now().Add(30 * time.Minute) // 5. 创建待支付订单 order := &entity.OrderPending{ OrderBase: entity.OrderBase{ TenantID: req.TenantID, 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.TenantID == "" || req.OrderNo == "" { return nil, errors.New("必填参数不能为空") } // 2. 查询订单 order, status, err := dao.Order.GetOrderByNo(ctx, req.TenantID, 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: 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, Price: stock.Price, 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.TenantID == "" || req.OrderNo == "" { return nil, errors.New("必填参数不能为空") } // 2. 查询订单 order, status, err := dao.Order.GetOrderByNo(ctx, req.TenantID, 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) } // 4. 将订单移动到已取消状态 updateData := bson.M{ "cancel_reason": req.Reason, } if err := dao.Order.MoveOrderToStatus(ctx, consts.OrderStatusPending, consts.OrderStatusCancelled, req.TenantID, 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.TenantID == "" { return nil, errors.New("租户ID不能为空") } 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.TenantID, 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, tenantID string) error { // 获取过期的待支付订单 expiredOrders, err := dao.Order.GetExpiredPendingOrders(ctx, tenantID) 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, tenantID, order.OrderNo, updateData); err != nil { // 记录错误但继续处理其他订单 fmt.Printf("取消过期订单失败: %s, 错误: %v\n", order.OrderNo, err) } } return nil } // UpdatePayInfo 更新支付信息 func (s *order) UpdatePayInfo(ctx context.Context, tenantID, orderNo string, payInfo entity.PayInfo) error { return dao.Order.UpdatePayInfo(ctx, tenantID, orderNo, payInfo) }