gomod引用

This commit is contained in:
2025-12-15 09:02:30 +08:00
parent 465c138f21
commit 97fd0af10f
8 changed files with 1048 additions and 508 deletions

View File

@@ -12,20 +12,29 @@ import (
"order/model/dto"
"order/model/entity"
"gitee.com/red-future---jilin-g/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)
// Init 初始化服务
func Init() error {
return nil
}
// convertOrderItemsFromDTO 从DTO转换订单商品项
func convertOrderItemsFromDTO(items []dto.OrderItemReq) []entity.OrderItem {
var result []entity.OrderItem
@@ -122,7 +131,41 @@ func (s *order) CreateOrder(ctx context.Context, req *dto.CreateOrderReq) (*dto.
return nil, errors.New("订单商品不能为空")
}
// 2. 计算订单金额
// 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]
@@ -141,10 +184,10 @@ func (s *order) CreateOrder(ctx context.Context, req *dto.CreateOrderReq) (*dto.
return nil, errors.New("订单总金额必须大于0")
}
// 3. 生成订单号
// 4. 生成订单号
orderNo := s.generateOrderNo(gconv.String(req.TenantID))
// 4. 设置订单过期时间30分钟后
// 5. 设置订单过期时间30分钟后
expiredAt := time.Now().Add(30 * time.Minute)
// 5. 创建待支付订单
@@ -414,7 +457,44 @@ func (s *order) CancelOrder(ctx context.Context, req *dto.CancelOrderReq) (*dto.
return nil, fmt.Errorf("订单状态不正确,当前状态: %s", status)
}
// 4. 将订单移动到已取消状态
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,
}
@@ -530,3 +610,236 @@ func (s *order) ProcessExpiredOrders(ctx context.Context) error {
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), &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", &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", &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
}