diff --git a/business/controller/elm/order.go b/business/controller/elm/order.go index d957a923a..4e8cc4d67 100644 --- a/business/controller/elm/order.go +++ b/business/controller/elm/order.go @@ -130,7 +130,6 @@ func (c *OrderController) getOrderInfo(orderID string) (order *model.GoodsOrder, order = &model.GoodsOrder{ VendorOrderID: orderID, VendorID: model.VendorIDELM, - WaybillVendorID: model.VendorIDELM, VendorStoreID: utils.Int64ToStr(utils.MustInterface2Int64(result["shopId"])), StoreID: int(utils.Str2Int64WithDefault(utils.Interface2String(result["openId"]), 0)), StoreName: result["shopName"].(string), diff --git a/business/controller/jd/order.go b/business/controller/jd/order.go index 0ff25d691..e6f2525c9 100644 --- a/business/controller/jd/order.go +++ b/business/controller/jd/order.go @@ -68,7 +68,6 @@ func (c *OrderController) getOrderInfo(msg *jdapi.CallbackOrderMsg) (order *mode order = &model.GoodsOrder{ VendorOrderID: msg.BillID, VendorID: model.VendorIDJD, - WaybillVendorID: model.VendorIDJD, VendorStoreID: result["produceStationNo"].(string), StoreID: int(utils.Str2Int64WithDefault(utils.Interface2String(result["produceStationNoIsv"]), 0)), StoreName: result["produceStationName"].(string), diff --git a/business/controller/order.go b/business/controller/order.go index dc92bc0e8..e977c3a9c 100644 --- a/business/controller/order.go +++ b/business/controller/order.go @@ -76,7 +76,7 @@ func (c *OrderController) saveOrder(order *model.GoodsOrder, isAdjust bool, db o c.updateOrderOtherInfo(order, db) db.Begin() // globals.SugarLogger.Debugf("new order:%v", order) - // order.WaybillVendorID = model.VendorIDUnknown + order.WaybillVendorID = model.VendorIDUnknown order.OrderFinishedAt = utils.DefaultTimeValue order.ID = 0 created, _, err2 := db.ReadOrCreate(order, "VendorOrderID", "VendorID") @@ -235,6 +235,10 @@ func (c *OrderController) UpdateWaybillVendorID(bill *model.Waybill) (err error) params := orm.Params{ "waybill_vendor_id": bill.WaybillVendorID, } + // 如果运单被取消,则要保持在已拣货状态 + if bill.WaybillVendorID == model.VendorIDUnknown { + params["status"] = model.OrderStatusFinishedPickup + } utils.CallFuncLogError(func() error { _, err = db.QueryTable("goods_order").Filter("vendor_order_id", bill.VendorOrderID).Filter("vendor_id", bill.OrderVendorID).Update(params) return err diff --git a/business/jxutils/jxutils.go b/business/jxutils/jxutils.go index 07de6f62d..67cc5d051 100644 --- a/business/jxutils/jxutils.go +++ b/business/jxutils/jxutils.go @@ -1,6 +1,7 @@ package jxutils import ( + "math/rand" "strings" "sync" "time" @@ -18,6 +19,10 @@ type SyncMapWithTimeout struct { sync.Map } +func init() { + rand.Seed(time.Now().Unix()) +} + func (m *SyncMapWithTimeout) StoreWithTimeout(key, value interface{}, timeout time.Duration) { m.Map.Store(key, value) time.AfterFunc(timeout, func() { diff --git a/business/model/order.go b/business/model/order.go index 722ab5583..c148f27a9 100644 --- a/business/model/order.go +++ b/business/model/order.go @@ -33,9 +33,9 @@ type GoodsOrder struct { LockStatus int OrderSeq int // 门店订单序号 BuyerComment string `orm:"size(255)"` - ExpectedDeliveredTime time.Time `orm:"type(datetime)"` // 预期送达时间 - CancelApplyReason string `orm:"size(255)"` // ""表示没有申请,不为null表示用户正在取消申请 - WaybillVendorID int `orm:"column(waybill_vendor_id)"` + ExpectedDeliveredTime time.Time `orm:"type(datetime)"` // 预期送达时间 + CancelApplyReason string `orm:"size(255)"` // ""表示没有申请,不为null表示用户正在取消申请 + WaybillVendorID int `orm:"column(waybill_vendor_id)"` // 表示当前承运商,-1表示还没有安排 DuplicatedCount int // 重复新订单消息数,这个一般不是由于消息重发造成的(消息重发由OrderStatus过滤),一般是业务逻辑造成的 OrderCreatedAt time.Time `orm:"type(datetime);index"` OrderFinishedAt time.Time `orm:"type(datetime)"` @@ -90,7 +90,7 @@ type Waybill struct { ActualFee int64 // 实际要支付给快递公司的实际费用 DesiredFee int64 // 根据合同计算出来的预期费用 DuplicatedCount int // 重复新订单消息数,这个一般不是由于消息重发造成的(消息重发由OrderStatus过滤),一般是业务逻辑造成的 - WaybillCreatedAt time.Time `orm:"type(datetime);index"` + WaybillCreatedAt time.Time `orm:"type(datetime);index"` // 此字段在此结构体用于传递非新运单消息时,为事件发生事件(而非运单创建时间) WaybillFinishedAt time.Time `orm:"type(datetime)"` ModelTimeInfo OriginalData string `orm:"type(text)"` diff --git a/business/scheduler/defsch/defsch.go b/business/scheduler/defsch/defsch.go index 8978dce72..15a25a8f2 100644 --- a/business/scheduler/defsch/defsch.go +++ b/business/scheduler/defsch/defsch.go @@ -3,6 +3,8 @@ package defsch import ( "time" + "math/rand" + "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/model" @@ -13,12 +15,16 @@ import ( ) const ( - defTime2Delivery = 1 * time.Hour + defTime2Delivered = 1 * time.Hour // 正常订单都是1小时达 + defTime2Schedule3rdCarrier = 330 * time.Second // 京东要求5分钟后才能转自送,保险起见,设置为5分半钟 + time2Schedule3rdCarrierGap4OrderStatus = 3 * time.Minute // 京东要求是运单状态为待抢单且超时5分钟,但为了防止没有运单事件,所以就拣货完成事件开始算,添加3分钟 + defTime2AutoPickupMin = 25 * time.Minute + time2AutoPickupGap = 5 * time.Minute ) type WatchOrderInfo struct { - order *model.GoodsOrder - waybills []*model.Waybill + order *model.GoodsOrder // order里的信息是保持更新的 + waybills []*model.Waybill // 这个waybills里的状态信息是不真实的,只使用id相关的信息 timer *time.Timer } @@ -33,7 +39,6 @@ func init() { sch := &DefScheduler{} sch.Init() scheduler.CurrentScheduler = sch - sch.defWorkflowConfig = map[int]*scheduler.StatusActionConfig{ model.OrderStatusNew: &scheduler.StatusActionConfig{ // 自动接单 Timeout: 1 * time.Second, @@ -44,16 +49,16 @@ func init() { return nil }, }, - model.OrderStatusAccepted: &scheduler.StatusActionConfig{ // 20分钟后自动拣货 - Timeout: 20 * time.Minute, + model.OrderStatusAccepted: &scheduler.StatusActionConfig{ // 自动拣货 + Timeout: defTime2AutoPickupMin, TimeoutAction: func(order *model.GoodsOrder) (err error) { return sch.GetPurchasePlatformFromVendorID(order.VendorID).PickedUpGoods(order) }, }, - model.OrderStatusFinishedPickup: &scheduler.StatusActionConfig{ // 拣货完成8分钟后开始在外部快递平台创建运单 - Timeout: 8 * time.Minute, + model.OrderStatusFinishedPickup: &scheduler.StatusActionConfig{ // 尝试召唤更多物流 + Timeout: defTime2Schedule3rdCarrier, TimeoutAction: func(order *model.GoodsOrder) (err error) { - return sch.createWaybillOn3rdProviders(order) + return sch.createWaybillOn3rdProviders(order, nil) }, }, } @@ -61,40 +66,28 @@ func init() { // 以下是订单 func (s *DefScheduler) OnOrderNew(order *model.GoodsOrder) (err error) { - config := s.mergeOrderStatusConfig(order.Status, s.GetPurchasePlatformFromVendorID(order.VendorID).GetStatusActionConfig(order.Status)) watchInfo := &WatchOrderInfo{ order: order, - timer: time.AfterFunc(jxutils.GetRealTimeout(order.OrderCreatedAt, config.Timeout), func() { - config.TimeoutAction(order) - }), } + s.resetTimer(model.OrderStatusNew, watchInfo, 0) s.orderMap.Store(jxutils.ComposeUniversalOrderID(order.VendorOrderID, order.VendorID), watchInfo) return err } func (s *DefScheduler) OnOrderStatusChanged(status *model.OrderStatus) (err error) { - if savedOrderInfo := s.loadWatchOrderFromMap(status.VendorOrderID, status.VendorID); savedOrderInfo != nil { - if savedOrderInfo.timer != nil { - savedOrderInfo.timer.Stop() - } - if status.Status > model.OrderStatusUnknown && status.Status < model.OrderStatusEndBegin { - s.updateOrderByStatus(savedOrderInfo.order, status) - config := s.mergeOrderStatusConfig(status.Status, s.GetPurchasePlatformFromVendorID(status.VendorID).GetStatusActionConfig(status.Status)) - if config != nil && config.TimeoutAction != nil { - var timeout time.Duration - if status.Status == model.OrderStatusAccepted { - savedOrderInfo := s.loadWatchOrderFromMap(status.VendorOrderID, status.VendorID) - timeout = s.getLatestPickupTimeout(savedOrderInfo.order, config.Timeout) - } else { - timeout = jxutils.GetRealTimeout(savedOrderInfo.order.StatusTime, config.Timeout) - } - savedOrderInfo.timer = time.AfterFunc(timeout, func() { - config.TimeoutAction(savedOrderInfo.order) - }) - } - } else { - s.orderMap.Delete(jxutils.GetUniversalOrderIDFromOrderStatus(status)) + savedOrderInfo := s.loadWatchOrderFromMap(status.VendorOrderID, status.VendorID) + if status.Status > model.OrderStatusUnknown && status.Status < model.OrderStatusEndBegin { + s.updateOrderByStatus(savedOrderInfo.order, status) + gap := 0 * time.Second + if status.Status == model.OrderStatusAccepted { + gap = time.Duration(rand.Int63n(int64(time2AutoPickupGap))) + } else if status.Status == model.OrderStatusFinishedPickup { + gap = time2Schedule3rdCarrierGap4OrderStatus } + s.resetTimer(status.Status, savedOrderInfo, gap) + } else { + savedOrderInfo.timer.Stop() + s.orderMap.Delete(jxutils.GetUniversalOrderIDFromOrderStatus(status)) } return err } @@ -103,7 +96,13 @@ func (s *DefScheduler) OnOrderStatusChanged(status *model.OrderStatus) (err erro func (s *DefScheduler) OnWaybillStatusChanged(bill *model.Waybill) (err error) { savedOrderInfo := s.loadWatchOrderFromMap(bill.VendorOrderID, bill.OrderVendorID) if bill.Status == model.WaybillStatusNew { - err = s.addWaybill2Map(bill) + if savedOrderInfo.order.WaybillVendorID == model.VendorIDUnknown { + s.resetTimer(model.OrderStatusFinishedPickup, savedOrderInfo, 0) + err = s.addWaybill2Map(bill) + } else { + globals.SugarLogger.Infof("OnWaybillStatusChanged multiple waybill created, bill:%v", bill) + s.GetDeliveryPlatformFromVendorID(bill.WaybillVendorID).CancelWaybill(bill) + } } else { findIt := false for _, v := range savedOrderInfo.waybills { @@ -115,23 +114,35 @@ func (s *DefScheduler) OnWaybillStatusChanged(bill *model.Waybill) (err error) { if findIt { switch bill.Status { case model.WaybillStatusAccepted: + savedOrderInfo.timer.Stop() // todo 这里应该另外启动一个TIMER s.cancelOtherWaybills(bill) - if bill.WaybillVendorID != bill.OrderVendorID { - s.swtich2SelfDeliverWithRetry(bill, 2, 10*time.Second) - } + s.CurOrderManager.UpdateWaybillVendorID(bill) savedOrderInfo.order.WaybillVendorID = bill.WaybillVendorID - case model.WaybillStatusAcceptCanceled, model.WaybillStatusCanceled, model.WaybillStatusFailed: - s.removeWaybillFromMap(bill, savedOrderInfo) + case model.WaybillStatusAcceptCanceled: + s.createWaybillOn3rdProviders(savedOrderInfo.order, bill) if savedOrderInfo.order.WaybillVendorID == bill.WaybillVendorID { - s.createWaybillOn3rdProviders(savedOrderInfo.order) bill.WaybillVendorID = model.VendorIDUnknown s.CurOrderManager.UpdateWaybillVendorID(bill) + savedOrderInfo.order.WaybillVendorID = bill.WaybillVendorID + } + case model.WaybillStatusCanceled, model.WaybillStatusFailed: + s.removeWaybillFromMap(bill, savedOrderInfo) + if savedOrderInfo.order.WaybillVendorID == bill.WaybillVendorID { + s.createWaybillOn3rdProviders(savedOrderInfo.order, nil) + + bill.WaybillVendorID = model.VendorIDUnknown + s.CurOrderManager.UpdateWaybillVendorID(bill) + savedOrderInfo.order.WaybillVendorID = bill.WaybillVendorID } case model.WaybillStatusDelivering: - s.GetPurchasePlatformFromVendorID(bill.OrderVendorID).SelfDeliverDelievering(savedOrderInfo.order) + if savedOrderInfo.order.VendorID != bill.WaybillVendorID { + s.GetPurchasePlatformFromVendorID(bill.OrderVendorID).SelfDeliverDelievering(savedOrderInfo.order) + } case model.WaybillStatusDelivered: - s.GetPurchasePlatformFromVendorID(bill.OrderVendorID).SelfDeliverDelievered(savedOrderInfo.order) + if savedOrderInfo.order.VendorID != bill.WaybillVendorID { + s.GetPurchasePlatformFromVendorID(bill.OrderVendorID).SelfDeliverDelievered(savedOrderInfo.order) + } s.removeWaybillFromMap(bill, savedOrderInfo) } } else { @@ -154,20 +165,13 @@ func (s *DefScheduler) addWaybill2Map(bill *model.Waybill) (err error) { return nil } -func (s *DefScheduler) onWaybillFailed(bill *model.Waybill) (err error) { - savedOrderInfo := s.loadWatchOrderFromMap(bill.VendorOrderID, bill.OrderVendorID) - s.removeWaybillFromMap(bill, savedOrderInfo) - if len(savedOrderInfo.waybills) == 0 { - s.createWaybillOn3rdProviders(savedOrderInfo.order) - } - return nil -} - -func (s *DefScheduler) createWaybillOn3rdProviders(order *model.GoodsOrder) (err error) { +func (s *DefScheduler) createWaybillOn3rdProviders(order *model.GoodsOrder, excludeBill *model.Waybill) (err error) { successCount := 0 - for _, v := range s.DeliveryPlatformHandlers { - if err = v.CreateWaybill(order); err == nil { - successCount++ + for k, v := range s.DeliveryPlatformHandlers { + if excludeBill == nil || k != excludeBill.WaybillVendorID { + if err = v.CreateWaybill(order); err == nil { + successCount++ + } } } if successCount != 0 { @@ -179,26 +183,27 @@ func (s *DefScheduler) createWaybillOn3rdProviders(order *model.GoodsOrder) (err func (s *DefScheduler) cancelOtherWaybills(bill *model.Waybill) (err error) { savedOrderInfo := s.loadWatchOrderFromMap(bill.VendorOrderID, bill.OrderVendorID) for _, v := range savedOrderInfo.waybills { - if !(v.WaybillVendorID == bill.WaybillVendorID && v.VendorWaybillID == bill.VendorWaybillID) { + if v.WaybillVendorID != bill.OrderVendorID && !(v.WaybillVendorID == bill.WaybillVendorID && v.VendorWaybillID == bill.VendorWaybillID) { _ = s.GetDeliveryPlatformFromVendorID(v.WaybillVendorID).CancelWaybill(v) } } + if bill.WaybillVendorID != bill.OrderVendorID { + s.swtich2SelfDeliverWithRetry(bill, 2, 10*time.Second) + } return nil } func (s *DefScheduler) swtich2SelfDeliverWithRetry(bill *model.Waybill, retryCount int, duration time.Duration) { - if err := s.GetPurchasePlatformFromVendorID(bill.OrderVendorID).Swtich2SelfDeliver(bill.VendorOrderID); err != nil { - if retryCount >= 0 { - time.AfterFunc(duration, func() { - s.swtich2SelfDeliverWithRetry(bill, retryCount-1, duration) - }) - } else { + utils.CallFuncRetryAsync(func(index int) error { + err := s.GetPurchasePlatformFromVendorID(bill.OrderVendorID).Swtich2SelfDeliver(bill.VendorOrderID) + if err == nil { + s.removeWaybillFromMap(bill, nil) // todo 是否在这里删除运单,还是在运单事件里处理更好些? + } else if index == 0 { // 如果购买平台转商家自送失败,最终还是要取消3方物流 s.GetDeliveryPlatformFromVendorID(bill.WaybillVendorID).CancelWaybill(bill) } - } else { - s.removeWaybillFromMap(bill, nil) - } + return err + }, duration, retryCount) } func (s *DefScheduler) loadWatchOrderFromMap(vendorOrderID string, vendorID int) *WatchOrderInfo { @@ -235,11 +240,29 @@ func (s *DefScheduler) removeWaybillFromMap(bill *model.Waybill, savedOrderInfo func (s *DefScheduler) getLatestPickupTimeout(order *model.GoodsOrder, configTimeout time.Duration) (retVal time.Duration) { beginTime := order.StatusTime if order.ExpectedDeliveredTime != utils.DefaultTimeValue { - beginTime = order.ExpectedDeliveredTime.Add(-defTime2Delivery) + beginTime = order.ExpectedDeliveredTime.Add(-defTime2Delivered) } return jxutils.GetRealTimeout(beginTime, configTimeout) } +func (s *DefScheduler) resetTimer(status int, savedOrderInfo *WatchOrderInfo, gap time.Duration) { + if savedOrderInfo.timer != nil { + savedOrderInfo.timer.Stop() + } + config := s.mergeOrderStatusConfig(status, s.GetPurchasePlatformFromVendorID(savedOrderInfo.order.VendorID).GetStatusActionConfig(status)) + if config != nil && config.TimeoutAction != nil { + var timeout time.Duration + if status == model.OrderStatusAccepted { + timeout = s.getLatestPickupTimeout(savedOrderInfo.order, config.Timeout) + } else { + timeout = jxutils.GetRealTimeout(savedOrderInfo.order.StatusTime, config.Timeout) + } + savedOrderInfo.timer = time.AfterFunc(timeout+gap, func() { + config.TimeoutAction(savedOrderInfo.order) + }) + } +} + func (s *DefScheduler) handleAutoAcceptOrder(orderID string, vendorID int, userMobile string, jxStoreID int, db orm.Ormer, handler func(accepted bool) error) int { handleType := 0 if userMobile != "" {