diff --git a/business/jxstore/promotion/jd_promotion.go b/business/jxstore/promotion/jd_promotion.go index 4feccbbca..86cbe70a6 100644 --- a/business/jxstore/promotion/jd_promotion.go +++ b/business/jxstore/promotion/jd_promotion.go @@ -10,6 +10,7 @@ import ( "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/jxutils/storeskulock" "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" @@ -58,6 +59,12 @@ type SkuPrice struct { IsLock int8 `json:"isLock"` } +type tPromotionItemInfo struct { + model.PromotionSku + EndAt time.Time + StoreID int `orm:"column(store_id)"` +} + type tSimpleStore struct { StoreID int `orm:"column(store_id)" json:"storeID"` Name string `orm:"column(name)" json:"name"` @@ -160,6 +167,23 @@ func init() { gob.Register([]*SkuPrice{}) } +func scheduleDailyRoutine() { + executeTime := utils.GetCurDate().Add(24*time.Hour + 5*time.Minute) // 凌晨00:05执行 + duration := executeTime.Sub(time.Now()) + // globals.SugarLogger.Debug(duration) + time.AfterFunc(duration, func() { + UpdateJdPromotionStatus() + RefreshJdLockStoreSku() + scheduleDailyRoutine() + }) +} + +func Init() { + UpdateJdPromotionStatus() + RefreshJdLockStoreSku() + scheduleDailyRoutine() +} + func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync bool, params *PromotionParams, userName string) (hint string, err error) { if len(params.SkuPrices) == 0 { return "", ErrEmptySkus @@ -286,6 +310,7 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync bool, params PriceType: skuPrice.PriceType, Price: skuPrice.Price, LimitSkuCount: skuPrice.LimitSkuCount, + IsLock: skuPrice.IsLock, } dao.WrapAddIDCULDEntity(promotionSku, ctx.GetUserName()) if err = dao.CreateEntity(db, promotionSku); err != nil { @@ -307,9 +332,9 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync bool, params } dao.Commit(db) - rootTask := tasksch.NewSeqTask("CreateJdPromotion", userName, func(task *tasksch.SeqTask, step int, params ...interface{}) (result interface{}, err error) { + rootTask := tasksch.NewSeqTask("CreateJdPromotion", userName, func(task *tasksch.SeqTask, step int, params2 ...interface{}) (result interface{}, err error) { if step == 0 { - task1 := tasksch.NewParallelTask("CreateJdPromotion update sku price", nil, userName, func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + task1 := tasksch.NewParallelTask("CreateJdPromotion update sku price", nil, userName, func(t *tasksch.ParallelTask, batchItemList []interface{}, params2 ...interface{}) (retVal interface{}, err error) { storeID := batchItemList[0].(int) modifyPricesList := jxutils.SplitSlice(modifyPricesList[storeID], jdapi.MaxStoreSkuBatchSize) for _, modifyPrices := range modifyPricesList { @@ -317,8 +342,10 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync bool, params for k, v := range modifyPrices { modifyPrices2[k] = v.(*jdapi.SkuPriceInfo) } - if _, err = api.JdAPI.UpdateVendorStationPrice(utils.Int2Str(storeID), "", modifyPrices2); err != nil { - return nil, err + if globals.EnableStoreWrite { + if _, err = api.JdAPI.UpdateVendorStationPrice(utils.Int2Str(storeID), "", modifyPrices2); err != nil { + return nil, err + } } } return nil, nil @@ -328,7 +355,7 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync bool, params } else if step == 1 { err = promotionHandler.CreatePromotionRules(infoId, "", 1, 1, 1, 1) } else if step == 2 { - task2 := tasksch.NewParallelTask("CreateJdPromotion CreatePromotionSku", tasksch.NewParallelConfig().SetBatchSize(MaxPromotionSkuCount), userName, func(task *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { + task2 := tasksch.NewParallelTask("CreateJdPromotion CreatePromotionSku", tasksch.NewParallelConfig().SetBatchSize(MaxPromotionSkuCount), userName, func(task *tasksch.ParallelTask, batchItemList []interface{}, params2 ...interface{}) (retVal interface{}, err error) { skus := make([]map[string]interface{}, len(batchItemList)) for k, v := range batchItemList { skus[k] = v.(map[string]interface{}) @@ -346,9 +373,11 @@ func CreateJdPromotion(ctx *jxcontext.Context, isIDJd bool, isAsync bool, params promotion.Status = model.PromotionStatusRemoteFailed } db := dao.GetDB() - _, err = dao.UpdateEntityLogically(db, promotion, map[string]interface{}{ + if _, err = dao.UpdateEntityLogically(db, promotion, map[string]interface{}{ "Status": promotion.Status, - }, ctx.GetUserName(), nil) + }, ctx.GetUserName(), nil); err == nil { + RefreshJdPromotionLockStatus(promotion.ID) + } } return nil, err }, 4) @@ -563,13 +592,99 @@ func CancelJdPromotion(ctx *jxcontext.Context, promotionID int) (err error) { return errors.New("非法的促销类型") } if err = promotionHandler.CancelPromotion(utils.Str2Int64(promotion.VendorPromotionID), ""); err == nil { - _, err = dao.UpdateEntityLogically(db, promotion, map[string]interface{}{ + if _, err = dao.UpdateEntityLogically(db, promotion, map[string]interface{}{ "Status": model.PromotionStatusCanceled, - }, ctx.GetUserName(), nil) + }, ctx.GetUserName(), nil); err == nil { + RefreshJdPromotionLockStatus(promotionID) + } } return err } +// 每晚凌晨运行一次 +func RefreshJdLockStoreSku() (err error) { + sql := ` + SELECT t1.end_at, t2.store_id, t3.* + FROM promotion t1 + JOIN promotion_store t2 ON t1.id = t2.promotion_id + JOIN promotion_sku t3 ON t1.id = t3.promotion_id + WHERE t1.deleted_at = ? AND t1.vendor_id = ? AND t1.status = ? AND (t1.beginAt <= ? AND t1.endAt >= ?) + ` + nowDate := utils.GetCurDate() + sqlParams := []interface{}{ + utils.DefaultTimeValue, + model.VendorIDJD, + model.PromotionStatusRemoteCreated, + nowDate, + nowDate, + } + var promotionItemList []*tPromotionItemInfo + db := dao.GetDB() + if err = dao.GetRows(db, &promotionItemList, sql, sqlParams...); err != nil { + return err + } + return RefreshJdPromotionItemListLockStatus(promotionItemList) +} + +// 每晚凌晨运行一次 +func UpdateJdPromotionStatus() (num int64, err error) { + sql := ` + UPDATE promotion t1 + SET t1.status = ? + WHERE t1.deleted_at = ? AND t1.vendor_id = ? AND t1.status = ? AND (t1.begin_at > ? OR t1.end_at < ?) + ` + nowDate := utils.GetCurDate() + sqlParams := []interface{}{ + model.PromotionStatusEnded, + utils.DefaultTimeValue, + model.VendorIDJD, + model.PromotionStatusRemoteCreated, + nowDate, + nowDate, + } + db := dao.GetDB() + return dao.ExecuteSQL(db, sql, sqlParams...) +} + +func RefreshJdPromotionItemListLockStatus(promotionItemList []*tPromotionItemInfo) (err error) { + if len(promotionItemList) > 0 { + expire := promotionItemList[0].EndAt.Add(24 * time.Hour) + for _, item := range promotionItemList { + if item.IsLock != 0 { + storeskulock.LockJdStoreSku(item.StoreID, item.SkuID, expire) + } else { + storeskulock.UnlockJdStoreSku(item.StoreID, item.SkuID) + } + } + } + return err +} + +func RefreshJdPromotionLockStatus(promotionID int) (err error) { + sql := ` + SELECT t1.end_at, t2.store_id, t3.*, IF(t1.begin_at <= ? AND t1.end_at >= ? AND t1.status = ?, t3.is_lock, 0) is_lock + FROM promotion t1 + JOIN promotion_store t2 ON t1.id = t2.promotion_id + JOIN promotion_sku t3 ON t1.id = t3.promotion_id + WHERE t1.id = ? + ` + nowDate := utils.GetCurDate() + sqlParams := []interface{}{ + nowDate, + nowDate, + model.PromotionStatusRemoteCreated, + promotionID, + } + var promotionItemList []*tPromotionItemInfo + db := dao.GetDB() + // globals.SugarLogger.Debug(sql) + // globals.SugarLogger.Debug(utils.Format4Output(sqlParams, false)) + if err = dao.GetRows(db, &promotionItemList, sql, sqlParams...); err != nil { + return err + } + return RefreshJdPromotionItemListLockStatus(promotionItemList) +} + func excelStr2Time(timeStr string) (tm time.Time, err error) { return time.ParseInLocation("2006年1月2日15点4分5秒", timeStr, time.Local) } diff --git a/business/jxutils/storeskulock/storeskulock.go b/business/jxutils/storeskulock/storeskulock.go new file mode 100644 index 000000000..55dde9d1f --- /dev/null +++ b/business/jxutils/storeskulock/storeskulock.go @@ -0,0 +1,32 @@ +package storeskulock + +import ( + "fmt" + "time" + + "git.rosy.net.cn/jx-callback/globals" +) + +const ( + cacheKeyPrefix = "jdpromotion" +) + +func LockJdStoreSku(storeID, skuID int, expire time.Time) { + globals.SugarLogger.Debug(expire, " ", time.Now()) + duration := expire.Sub(time.Now()) + if duration > 0 { + globals.Cacher.Set(genCacheKey(storeID, skuID), 1, duration) + } +} + +func UnlockJdStoreSku(storeID, skuID int) { + globals.Cacher.Del(genCacheKey(storeID, skuID)) +} + +func IsJdStoreSkuLocked(storeID, skuID int) bool { + return globals.Cacher.Get(genCacheKey(storeID, skuID)) != nil +} + +func genCacheKey(storeID, skuID int) string { + return fmt.Sprintf("%s.%d.%d", cacheKeyPrefix, storeID, skuID) +} diff --git a/business/partner/purchase/jd/store_sku.go b/business/partner/purchase/jd/store_sku.go index 145806d75..bafedbe60 100644 --- a/business/partner/purchase/jd/store_sku.go +++ b/business/partner/purchase/jd/store_sku.go @@ -5,6 +5,7 @@ import ( "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + "git.rosy.net.cn/jx-callback/business/jxutils/storeskulock" "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" @@ -69,7 +70,9 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks } else { alreadyAddStock = true } - skuStockList = append(skuStockList, stock) + if stock.StockQty != 0 || !storeskulock.IsJdStoreSkuLocked(storeID, storeSku.SkuID) { + skuStockList = append(skuStockList, stock) + } } if storeSku.JdSyncStatus&(model.SyncFlagPriceMask|model.SyncFlagNewMask) != 0 { skuPriceInfoList = append(skuPriceInfoList, &jdapi.SkuPriceInfo{ @@ -91,7 +94,9 @@ func (p *PurchaseHandler) SyncStoreSkus(ctx *jxcontext.Context, parentTask tasks } skuStockList = append(skuStockList, stock) } - skuVendibilityList = append(skuVendibilityList, vendibility) + if vendibility.DoSale || !storeskulock.IsJdStoreSkuLocked(storeID, storeSku.SkuID) { + skuVendibilityList = append(skuVendibilityList, vendibility) + } } } } diff --git a/controllers/promotion.go b/controllers/promotion.go index 3c1c952cc..9e1dfe0c6 100644 --- a/controllers/promotion.go +++ b/controllers/promotion.go @@ -140,9 +140,9 @@ func (c *PromotionController) GetPromotions() { // @Param promotionID query int true "活动id" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult -// @router /CancelPomotion [put] -func (c *PromotionController) CancelPomotion() { - c.callCancelPomotion(func(params *tPromotionCancelPomotionParams) (retVal interface{}, errCode string, err error) { +// @router /CancelPromotion [put] +func (c *PromotionController) CancelPromotion() { + c.callCancelPromotion(func(params *tPromotionCancelPromotionParams) (retVal interface{}, errCode string, err error) { err = promotion.CancelJdPromotion(params.Ctx, params.PromotionID) return retVal, "", err }) diff --git a/main.go b/main.go index 8984229eb..a82a7ddd3 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "git.rosy.net.cn/jx-callback/business/jxcallback/orderman" "git.rosy.net.cn/jx-callback/business/jxstore/cms" + "git.rosy.net.cn/jx-callback/business/jxstore/promotion" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" "git.rosy.net.cn/jx-callback/globals/beegodb" @@ -29,6 +30,9 @@ func Init() { beegodb.Init() api.Init() cms.InitServiceInfo(Version, BuildDate, GitCommit) + if globals.EnableStore { + promotion.Init() + } } // 返回true表示非运行服务 diff --git a/routers/commentsRouter_controllers.go b/routers/commentsRouter_controllers.go index 8ed82ef5b..5eddec1f7 100644 --- a/routers/commentsRouter_controllers.go +++ b/routers/commentsRouter_controllers.go @@ -217,8 +217,8 @@ func init() { beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:PromotionController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:PromotionController"], beego.ControllerComments{ - Method: "CancelPomotion", - Router: `/CancelPomotion`, + Method: "CancelPromotion", + Router: `/CancelPromotion`, AllowHTTPMethods: []string{"put"}, MethodParams: param.Make(), Params: nil})