From 414c359200cb307a75b780852ba944b493ccb75c Mon Sep 17 00:00:00 2001 From: gazebo Date: Sun, 21 Jul 2019 16:08:37 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E6=B8=85=E7=90=86=E4=B8=89=E4=B8=AA?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E7=9A=84=E9=97=A8=E5=BA=97=E5=95=86=E5=93=81?= =?UTF-8?q?=E6=89=B9=E5=A4=84=E7=90=86=E6=93=8D=E4=BD=9C=EF=BC=8C=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=A4=B1=E8=B4=A5=E8=BF=94=E5=9B=9E=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platformapi/ebaiapi/shop_sku.go | 262 ++++++++++++++++++++------- platformapi/ebaiapi/shop_sku_test.go | 93 ++++++++-- platformapi/jdapi/jdapi.go | 25 +-- platformapi/jdapi/sku_test.go | 9 + platformapi/jdapi/store_sku.go | 75 ++++---- platformapi/mtwmapi/act.go | 6 +- platformapi/mtwmapi/mtwmapi.go | 5 +- platformapi/mtwmapi/retail.go | 107 ++++++++--- platformapi/mtwmapi/retail_test.go | 121 ++++++++++++- platformapi/platformapi.go | 6 +- utils/errorwithcode.go | 19 +- utils/typeconv.go | 48 ++++- 12 files changed, 597 insertions(+), 179 deletions(-) diff --git a/platformapi/ebaiapi/shop_sku.go b/platformapi/ebaiapi/shop_sku.go index a4f33995..2416f9a9 100644 --- a/platformapi/ebaiapi/shop_sku.go +++ b/platformapi/ebaiapi/shop_sku.go @@ -2,8 +2,11 @@ package ebaiapi import ( "fmt" + "regexp" "strings" + "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/utils" ) @@ -24,6 +27,10 @@ const ( MaxStoreSkuBatchSize = 100 // sku.offline, sku.online, sku.price.update.batch和sku.stock.update.batch这些批量操作的最大值 ) +var ( + shopSkuBatchFailedSkuReg = regexp.MustCompile(`请核查以下sku:(\{.*\})`) +) + type CategoryInfo struct { CategoryID int64 `json:"category_id"` Name string `json:"name"` @@ -95,10 +102,112 @@ type PageDataInfo struct { List []*SkuInfo `json:"List"` } -func genSkuIDParams(skuID int64, customSkuID, upc string) map[string]interface{} { +type ShopSkuPriceUpdateResponseItem struct { + SkuID int64 `json:"sku_id"` + Price int `json:"price"` + ErrorNo int `json:"error_no,omitempty"` + ErrorMsg string `json:"error_msg,omitempty"` +} + +type ShopSkuPriceUpdateResponse struct { + FailedList []*ShopSkuPriceUpdateResponseItem `json:"failed_list"` + SuccessList []*ShopSkuPriceUpdateResponseItem `json:"success_list"` +} + +const ( + SkuIDTypeUnknown = 0 + SkuIDTypeSkuID = 1 + SkuIDTypeCustomSkuID = 2 + SkuIDTypeUpc = 3 +) + +var ( + priceUpdateKeyIDMap = map[int]string{ + SkuIDTypeSkuID: "skuid_price", + SkuIDTypeCustomSkuID: "custom_sku_id", + SkuIDTypeUpc: "upc_price", + } + stockUpdateKeyIDMap = map[int]string{ + SkuIDTypeSkuID: "skuid_stocks", + SkuIDTypeCustomSkuID: "custom_sku_id", + SkuIDTypeUpc: "upc_stocks", + } +) + +type ShopSkuInfo struct { + SkuID int64 + CustomSkuID string + Upc string + + SalePrice int64 + MarketPrice int64 + Stock int +} + +type ShopSkuInfoList []*ShopSkuInfo + +func (v *ShopSkuInfo) GuessIDType() int { + if v.SkuID > 0 { + return SkuIDTypeSkuID + } else if v.CustomSkuID != "" { + return SkuIDTypeCustomSkuID + } + return SkuIDTypeUpc +} + +func (v *ShopSkuInfo) ToSkuID(skuIDType int) (str string) { + if skuIDType == SkuIDTypeUnknown { + skuIDType = v.GuessIDType() + } + if skuIDType == SkuIDTypeSkuID { + str = utils.Int64ToStr(v.SkuID) + } else if skuIDType == SkuIDTypeCustomSkuID { + str = v.CustomSkuID + } else { + str = v.Upc + } + return str +} + +func (v *ShopSkuInfo) PriceString(skuIDType int) (str string) { + str = v.ToSkuID(skuIDType) + ":" + utils.Int64ToStr(v.SalePrice) + if v.MarketPrice > 0 { + str += "," + utils.Int64ToStr(v.MarketPrice) + } + return str +} + +func (v *ShopSkuInfo) StockString(skuIDType int) (str string) { + str = v.ToSkuID(skuIDType) + ":" + utils.Int2Str(v.Stock) + return str +} + +func (l ShopSkuInfoList) PriceString(skuIDType int) (str string) { + if len(l) > 0 { + strList := make([]string, len(l)) + for k, v := range l { + strList[k] = v.PriceString(skuIDType) + } + str = strings.Join(strList, ";") + } + return str +} + +func (l ShopSkuInfoList) StockString(skuIDType int) (str string) { + if len(l) > 0 { + strList := make([]string, len(l)) + for k, v := range l { + strList[k] = v.StockString(skuIDType) + } + str = strings.Join(strList, ";") + } + return str +} + +func genSkuIDParams(skuIDstr string, customSkuID, upc string) map[string]interface{} { params := map[string]interface{}{} - if skuID != 0 { - params[KeySkuID] = skuID + if skuIDstr != "" { + params[KeySkuID] = skuIDstr } else if customSkuID != "" { params[KeyCustomSkuID] = customSkuID } else if upc != "" { @@ -216,100 +325,123 @@ func (a *API) SkuUpdate(shopID string, ebaiSkuID int64, params map[string]interf return 0, err } -func (a *API) SkuDelete(shopID, skuIDsStr string) (err error) { - params := map[string]interface{}{ - KeyShopID: shopID, - KeySkuID: skuIDsStr, - } - _, err = a.AccessAPI("sku.delete", params) - return err +func intIDs2Str(intIDs []int64) (str string) { + return strings.Join(utils.Int64Slice2String(intIDs), ",") } -func (a *API) SkuDeleteByCustomIDs(shopID, customSkuIDsStr string) (err error) { - params := map[string]interface{}{ - KeyShopID: shopID, - KeyCustomSkuID: customSkuIDsStr, - } - _, err = a.AccessAPI("sku.delete", params) - return err +func strIDs2Str(strIDs []string) (str string) { + return strings.Join(strIDs, ",") } -func (a *API) SkuOnline(shopID, skuIDsStr string) (err error) { - params := map[string]interface{}{ - KeyShopID: shopID, - KeySkuID: skuIDsStr, +func handleShopSkuBatchResult(result *ResponseResult) (failedSkuIDs []int64, err error) { + if dataStr, ok := result.Data.(string); ok && dataStr != "" { + matchList := shopSkuBatchFailedSkuReg.FindStringSubmatch(dataStr) + if len(matchList) == 2 { + var failedMap map[string]interface{} + if err = utils.UnmarshalUseNumber([]byte(matchList[1]), &failedMap); err == nil && len(failedMap) > 0 { + for _, v := range failedMap { + if vStr, ok := v.(string); ok { + failedSkuIDs = append(failedSkuIDs, utils.Str2Int64WithDefault(vStr, 0)) + } else { + failedSkuIDs = append(failedSkuIDs, utils.Interface2Int64WithDefault(v, 0)) + } + } + } + } } - _, err = a.AccessAPI("sku.online", params) - return err + return failedSkuIDs, err +} + +// 文档上说支持custom_sku_id,但实际好像只支持skuid +func (a *API) SkuDelete(shopID string, skuIDs []int64, customSkuDs []string) (failedSkuIDs []int64, err error) { + params := genSkuIDParams(intIDs2Str(skuIDs), strIDs2Str(customSkuDs), "") + params[KeyShopID] = shopID + result, err := a.AccessAPI("sku.delete", params) + if err == nil { + failedSkuIDs, err = handleShopSkuBatchResult(result) + } + return failedSkuIDs, err +} + +func (a *API) SkuOnline(shopID string, skuIDs []int64, customSkuDs, upcs []string) (failedSkuIDs []int64, err error) { + params := genSkuIDParams(intIDs2Str(skuIDs), strIDs2Str(customSkuDs), strIDs2Str(upcs)) + params[KeyShopID] = shopID + result, err := a.AccessAPI("sku.online", params) + if err == nil { + failedSkuIDs, err = handleShopSkuBatchResult(result) + } + return failedSkuIDs, err } func (a *API) SkuOnlineOne(shopID string, skuID int64, customSkuID, upc string) (err error) { - params := genSkuIDParams(skuID, customSkuID, upc) + params := genSkuIDParams(utils.Int64ToStrNoZero(skuID), customSkuID, upc) params[KeyShopID] = shopID _, err = a.AccessAPI("sku.online.one", params) return err } -func (a *API) SkuOffline(shopID, skuIDsStr string) (err error) { - params := map[string]interface{}{ - KeyShopID: shopID, - KeySkuID: skuIDsStr, +func (a *API) SkuOffline(shopID string, skuIDs []int64, customSkuDs, upcs []string) (failedSkuIDs []int64, err error) { + params := genSkuIDParams(intIDs2Str(skuIDs), strIDs2Str(customSkuDs), strIDs2Str(upcs)) + params[KeyShopID] = shopID + result, err := a.AccessAPI("sku.offline", params) + if err == nil { + failedSkuIDs, err = handleShopSkuBatchResult(result) } - _, err = a.AccessAPI("sku.offline", params) - return err + return failedSkuIDs, err } func (a *API) SkuOfflineOne(shopID string, skuID int64, customSkuID, upc string) (err error) { - params := genSkuIDParams(skuID, customSkuID, upc) + params := genSkuIDParams(utils.Int64ToStrNoZero(skuID), customSkuID, upc) params[KeyShopID] = shopID _, err = a.AccessAPI("sku.offline.one", params) return err } -func genSkuPriceParams(isPrice bool, skuPriceStr, customSkuPriceStr, upcPriceStr string) map[string]interface{} { - params := map[string]interface{}{} - skuKey := "skuid_price" - upcKey := "upc_price" - if !isPrice { - skuKey = "skuid_stocks" - upcKey = "upc_stocks" +// 此函数在部分失败时,会返回错误,全部成功时不会返回明细,其它ShopSku批处理操作的的则在部分失败时会返回成功 +func (a *API) SkuPriceUpdateBatch(shopID string, priceList ShopSkuInfoList, skuIDType int) (updateResponse *ShopSkuPriceUpdateResponse, err error) { + params := map[string]interface{}{ + KeyShopID: shopID, + priceUpdateKeyIDMap[skuIDType]: priceList.PriceString(skuIDType), } - if skuPriceStr != "" { - params[skuKey] = skuPriceStr - } else if skuPriceStr != "" { - params["custom_sku_id"] = customSkuPriceStr - } else if upcPriceStr != "" { - params[upcKey] = upcPriceStr - } else { - panic("skuPriceStr, customSkuPriceStr and upcPriceStr are all empty!") - } - return params -} - -func (a *API) SkuPriceUpdateBatch(shopID, skuPriceStr, customSkuPriceStr, upcPriceStr string) (err error) { - params := genSkuPriceParams(true, skuPriceStr, customSkuPriceStr, upcPriceStr) - params[KeyShopID] = shopID _, err = a.AccessAPI("sku.price.update.batch", params) - return err + if err != nil { + // 文档上说的详情在data中,但其实是在error中... + if errExt, ok := err.(*utils.ErrorWithCode); ok { + baseapi.SugarLogger.Debug(errExt.ErrMsg()) + utils.UnmarshalUseNumber([]byte(errExt.ErrMsg()), &updateResponse) + } + } + return updateResponse, err } -func (a *API) SkuPriceUpdateOne(shopID, skuPriceStr, customSkuPriceStr, upcPriceStr string) (err error) { - params := genSkuPriceParams(true, skuPriceStr, customSkuPriceStr, upcPriceStr) - params[KeyShopID] = shopID +func (a *API) SkuPriceUpdateOne(shopID string, priceInfo *ShopSkuInfo) (err error) { + skuIDType := priceInfo.GuessIDType() + params := map[string]interface{}{ + KeyShopID: shopID, + priceUpdateKeyIDMap[skuIDType]: priceInfo.PriceString(skuIDType), + } _, err = a.AccessAPI("sku.price.update.one", params) return err } -func (a *API) SkuStockUpdateBatch(shopID, skuStockStr, customSkuStockStr, upcStockStr string) (err error) { - params := genSkuPriceParams(false, skuStockStr, customSkuStockStr, upcStockStr) - params[KeyShopID] = shopID - _, err = a.AccessAPI("sku.stock.update.batch", params) - return err +func (a *API) SkuStockUpdateBatch(shopID string, stockList ShopSkuInfoList, skuIDType int) (failedSkuIDs []int64, err error) { + params := map[string]interface{}{ + KeyShopID: shopID, + stockUpdateKeyIDMap[skuIDType]: stockList.PriceString(skuIDType), + } + result, err := a.AccessAPI("sku.stock.update.batch", params) + if err == nil { + failedSkuIDs, err = handleShopSkuBatchResult(result) + } + return failedSkuIDs, err } -func (a *API) SkuStockUpdateOne(shopID, skuStockStr, customSkuStockStr, upcStockStr string) (err error) { - params := genSkuPriceParams(false, skuStockStr, customSkuStockStr, upcStockStr) - params[KeyShopID] = shopID +func (a *API) SkuStockUpdateOne(shopID string, stockInfo *ShopSkuInfo) (err error) { + skuIDType := stockInfo.GuessIDType() + params := map[string]interface{}{ + KeyShopID: shopID, + stockUpdateKeyIDMap[skuIDType]: stockInfo.PriceString(skuIDType), + } _, err = a.AccessAPI("sku.stock.update.one", params) return err } diff --git a/platformapi/ebaiapi/shop_sku_test.go b/platformapi/ebaiapi/shop_sku_test.go index 501250ff..c08974e1 100644 --- a/platformapi/ebaiapi/shop_sku_test.go +++ b/platformapi/ebaiapi/shop_sku_test.go @@ -16,7 +16,7 @@ func TestShopCategoryCreate(t *testing.T) { } func TestShopCategoryGet(t *testing.T) { - result, err := api.ShopCategoryGet(testShopID) + result, err := api.ShopCategoryGet("102493") if err != nil { t.Fatal(err) } else { @@ -48,8 +48,8 @@ func TestSkuGetItemsByCategoryId(t *testing.T) { } func TestSkuList(t *testing.T) { - result, err := api.SkuList(testShopID, &SkuListParams{ - SkuID: 15579787500720732, + result, err := api.SkuList("102023", &SkuListParams{ + // SkuID: 15579787500720732, }) if err != nil { t.Fatal(err) @@ -107,23 +107,82 @@ func TestSkuUpdate(t *testing.T) { } } -func TestSkuDelete(t *testing.T) { - err := api.SkuDelete(testShopID, "153922044227304") - if err != nil { - t.Fatal(err) - } -} - -func TestSkuDeleteByCustomIDs(t *testing.T) { - err := api.SkuDeleteByCustomIDs(testShopID, "17") - if err != nil { - t.Fatal(err) - } -} - func TestSkuShopCategoryMap(t *testing.T) { err := api.SkuShopCategoryMap(testShopID, 15378849314129969, "153760472317166") if err != nil { t.Fatal(err) } } + +func TestSkuDelete(t *testing.T) { + failedSkuIDs, err := api.SkuDelete(testShopID, []int64{12345678, 12423432}, nil) + if err != nil { + // t.Fatal(err) + } + t.Log(utils.Format4Output(failedSkuIDs, false)) +} + +func TestSkuOnline(t *testing.T) { + failedSkuIDs, err := api.SkuOnline(testShopID, []int64{156369111807787, 12345678, 12423432}, nil, nil) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(failedSkuIDs, false)) +} + +func TestSkuOnlineOne(t *testing.T) { + err := api.SkuOnlineOne(testShopID, 13211, "", "") + if err == nil { + t.Fatal("应该要报错才对") + } +} + +func TestSkuOffline(t *testing.T) { + failedSkuIDs, err := api.SkuOffline(testShopID, []int64{156291398007698, 12345678, 12423432}, nil, nil) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(failedSkuIDs, false)) +} + +func TestSkuPriceUpdateBatch(t *testing.T) { + failedSkuIDs, err := api.SkuPriceUpdateBatch(testShopID, ShopSkuInfoList{ + &ShopSkuInfo{ + SkuID: 156369111807787, + SalePrice: 100, + }, + &ShopSkuInfo{ + SkuID: 156369111707770, + SalePrice: 100, + }, + &ShopSkuInfo{ + SkuID: 12321, + SalePrice: 100, + }, + }, SkuIDTypeSkuID) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(failedSkuIDs, false)) +} + +func TestSkuStockUpdateBatch(t *testing.T) { + failedSkuIDs, err := api.SkuStockUpdateBatch(testShopID, ShopSkuInfoList{ + &ShopSkuInfo{ + SkuID: 156291398007698, + Stock: 2, + }, + &ShopSkuInfo{ + SkuID: 13121231, + Stock: 2, + }, + &ShopSkuInfo{ + SkuID: 12321, + Stock: 2, + }, + }, SkuIDTypeSkuID) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(failedSkuIDs, false)) +} diff --git a/platformapi/jdapi/jdapi.go b/platformapi/jdapi/jdapi.go index 1ed4c26d..65d74a21 100644 --- a/platformapi/jdapi/jdapi.go +++ b/platformapi/jdapi/jdapi.go @@ -207,28 +207,23 @@ func (a *API) AccessAPI(apiStr string, jdParams map[string]interface{}) (retVal } func genNoPageResultParser(codeKey, msgKey, resultKey, okCode string) func(data map[string]interface{}) (interface{}, error) { - return func(data map[string]interface{}) (interface{}, error) { + return func(data map[string]interface{}) (innerData interface{}, err error) { rawInnerCode, ok := data[codeKey] if !ok { panic(fmt.Sprintf("genNoPageResultParser codeKey %s can not be found in result:%v", codeKey, data)) } innerCode := forceInnerCode2Str(rawInnerCode) - errMsg := formatErrorMsg(data[msgKey]) - if innerCode == okCode && errMsg != "调用订单中心修改承运商接口失败!" { // todo 京东的order/modifySellerDelivery在失败时也返回成功code,临时处理一下 - if resultKey != "" { - if innerData, ok := data[resultKey]; ok { - return innerData, nil - } - baseapi.SugarLogger.Warnf("genNoPageResultParser resultKey %s can not be found in result:%v", resultKey, utils.Format4Output(data, false)) - return nil, nil // 容错 - // panic(fmt.Sprintf("genNoPageResultParser resultKey %s can not be found in result:%v", resultKey, data)) + if resultKey != "" { + innerData, _ = data[resultKey] + } + if innerCode != okCode { + errMsg := formatErrorMsg(data[msgKey]) + if innerCode == ResponseInnerCodePartialFailed { + errMsg += ", " + utils.Format4Output(innerData, true) } - return nil, nil + err = utils.NewErrorCode(errMsg, innerCode, 1) } - if innerCode == ResponseInnerCodePartialFailed { - errMsg += ", " + utils.Format4Output(data[resultKey], true) - } - return nil, utils.NewErrorCode(errMsg, innerCode, 1) + return innerData, err } } diff --git a/platformapi/jdapi/sku_test.go b/platformapi/jdapi/sku_test.go index 478daed2..950dcbbf 100644 --- a/platformapi/jdapi/sku_test.go +++ b/platformapi/jdapi/sku_test.go @@ -187,3 +187,12 @@ func TestUpdateSpu(t *testing.T) { t.Fatal(err) } } + +func TestUpdateSku(t *testing.T) { + _, err := api.UpdateSku("27379", map[string]interface{}{ + "upc": "ttld20190712", + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/platformapi/jdapi/store_sku.go b/platformapi/jdapi/store_sku.go index acda0e88..07700cd7 100644 --- a/platformapi/jdapi/store_sku.go +++ b/platformapi/jdapi/store_sku.go @@ -2,7 +2,6 @@ package jdapi import ( "errors" - "fmt" "git.rosy.net.cn/baseapi/utils" ) @@ -63,22 +62,23 @@ type QueryStockResponse struct { } type UpdateVendibilityResponse struct { - Code int `json:"code"` - CurrentQty int `json:"currentQty"` - LockQty int `json:"lockQty"` - Msg string `json:"msg"` - OrderQty int `json:"orderQty"` - OutSkuID string `json:"outSkuId"` - SkuID int64 `json:"skuId"` - UsableQty int `json:"usableQty"` - Vendibility int `json:"vendibility"` -} - -type StoreSkuBatchUpdateResponse struct { OutSkuID string `json:"outSkuId"` Code int `json:"code"` Msg string `json:"msg"` + SkuID int64 `json:"skuId"` + CurrentQty int `json:"currentQty"` + LockQty int `json:"lockQty"` + OrderQty int `json:"orderQty"` + UsableQty int `json:"usableQty"` + Vendibility int `json:"vendibility"` +} + +type StoreSkuBatchUpdateResponse struct { + OutSkuID string `json:"outSkuId" json2:"outSkuId"` + Code int `json:"code" json2:"errorCode"` + Msg string `json:"msg" json2:"errorMessage"` + // UpdateVendibility会返回以下字段 SkuID int64 `json:"skuId"` StationNo string `json:"stationNo"` @@ -97,10 +97,7 @@ type StoreSkuBatchUpdateResponse struct { // 根据商家商品编码和商家门店编码批量修改门店价格接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=205&apiid=fcbf346648a54d03b92dec8fa62ea643 -func (a *API) UpdateVendorStationPrice(outStationNo, stationNo string, skuPriceInfoList []*SkuPriceInfo) ([]map[string]interface{}, error) { - if (outStationNo == "" && stationNo == "") || (outStationNo != "" && stationNo != "") { - return nil, errors.New("outStationNo and stationNo can not all be empty or have value") - } +func (a *API) UpdateVendorStationPrice(outStationNo, stationNo string, skuPriceInfoList []*SkuPriceInfo) (responseList []*StoreSkuBatchUpdateResponse, err error) { jdParams := map[string]interface{}{ "skuPriceInfoList": skuPriceInfoList, } @@ -109,11 +106,14 @@ func (a *API) UpdateVendorStationPrice(outStationNo, stationNo string, skuPriceI } else { jdParams["stationNo"] = stationNo } - result, err := a.AccessAPINoPage("venderprice/updateStationPrice", jdParams, nil, nil, nil) - if err == nil && result != nil { - return utils.Slice2MapSlice(result.([]interface{})), nil + result, err := a.AccessAPINoPage("venderprice/updateStationPrice", jdParams, nil, nil, genNoPageResultParser("code", "msg", "result", "0")) + if result != nil { + var err2 error + if responseList, err2 = a.handleBatchOpResult(len(skuPriceInfoList), result, "json2"); err2 != nil && err == nil { + err = err2 + } } - return nil, err + return responseList, err } // 根据到家商品编码和到家门店编码修改门店价格接口 @@ -146,16 +146,16 @@ func (a *API) GetStationInfoList(stationNo string, skuIds []int64) (priceInfo [] return priceInfo, err } -func (a *API) handleBatchOpResult(batchCount int, result interface{}) (responseList []*StoreSkuBatchUpdateResponse, err error) { - if err = utils.Map2StructByJson(result, &responseList, true); err == nil { +func (a *API) handleBatchOpResult(batchCount int, result interface{}, tagName string) (responseList []*StoreSkuBatchUpdateResponse, err error) { + if err = utils.Map2Struct(result, &responseList, true, tagName); err == nil { var failedList []*StoreSkuBatchUpdateResponse for _, v := range responseList { if v.Code != 0 { failedList = append(failedList, v) } } - if batchCount == len(failedList) { - err = fmt.Errorf(string(utils.MustMarshal(failedList))) + if len(failedList) >= batchCount { + err = utils.NewErrorCode(string(utils.MustMarshal(failedList)), ResponseCodeAccessFailed, 1) // 此错误基本用不到 } else if len(failedList) > 0 { // 部分失败 err = utils.NewErrorCode(string(utils.MustMarshal(failedList)), ResponseInnerCodePartialFailed, 1) } @@ -179,8 +179,11 @@ func (a *API) BatchUpdateCurrentQtys(outStationNo, stationNo string, skuStockLis jdParams["stationNo"] = stationNo } result, err := a.AccessAPINoPage("stock/batchUpdateCurrentQtys", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0")) - if err == nil && result != nil { - responseList, err = a.handleBatchOpResult(len(skuStockList), result) + if result != nil { + var err2 error + if responseList, err2 = a.handleBatchOpResult(len(skuStockList), result, ""); err2 != nil && err == nil { + err = err2 + } } return responseList, err } @@ -220,16 +223,19 @@ func (a *API) UpdateCurrentQty(stationNo string, skuID int64, currentQty int) er // 根据到家商品编码和到家门店编码批量修改门店商品可售状态接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=200&apiid=b783a508e2cf4aca94681e4eed9af5bc -// 尽量不用这个接口,用下面那个 +// 尽量不用这个接口,用下面那个,原因是这个不支持设置操作人,BatchUpdateVendibility可以 func (a *API) UpdateVendibility(listBaseStockCenterRequest []*QueryStockRequest) (responseList []*StoreSkuBatchUpdateResponse, err error) { jdParams := map[string]interface{}{ "listBaseStockCenterRequest": listBaseStockCenterRequest, } result, err := a.AccessAPINoPage("stock/updateVendibility", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0")) - if err == nil && result != nil { - responseList, err = a.handleBatchOpResult(len(listBaseStockCenterRequest), result) + if result != nil { + var err2 error + if responseList, err2 = a.handleBatchOpResult(len(listBaseStockCenterRequest), result, ""); err2 != nil && err == nil { + err = err2 + } } - return nil, err + return responseList, err } // 根据商家商品编码和门店编码批量修改门店商品可售状态接口 @@ -248,8 +254,11 @@ func (a *API) BatchUpdateVendibility(outStationNo, stationNo string, stockVendib jdParams["stationNo"] = stationNo } result, err := a.AccessAPINoPage("stock/batchUpdateVendibility", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0")) - if err == nil && result != nil { - responseList, err = a.handleBatchOpResult(len(stockVendibilityList), result) + if result != nil { + var err2 error + if responseList, err2 = a.handleBatchOpResult(len(stockVendibilityList), result, ""); err2 != nil && err == nil { + err = err2 + } } return responseList, err } diff --git a/platformapi/mtwmapi/act.go b/platformapi/mtwmapi/act.go index 45749a0a..bdadd767 100644 --- a/platformapi/mtwmapi/act.go +++ b/platformapi/mtwmapi/act.go @@ -186,7 +186,7 @@ func (a *API) FullDiscountBatchSave(poiCode string, actInfo *FullDiscountActInfo if actInfo.ActType == ActTypeSkuFullDiscount { params["app_foods"] = string(utils.MustMarshal(actSkuList)) } - result, err := a.AccessAPI2("act/full/discount/batchsave", false, params, resultKeyMsg) + result, err := a.AccessAPI2("act/full/discount/batchsave", false, params, resultKeySuccessMsg) if err == nil { err = utils.UnmarshalUseNumber([]byte(result.(string)), &actIDList) } @@ -322,7 +322,7 @@ func (a *API) RetailDiscountBatchSave(poiCode string, actData []*RetailDiscountA result, err := a.AccessAPI2("act/retail/discount/batchsave", false, map[string]interface{}{ KeyAppPoiCode: poiCode, "act_data": string(utils.MustMarshal(actData)), - }, resultKeyMsg) + }, resultKeySuccessMsg) if err == nil { err = utils.UnmarshalUseNumber([]byte(result.(string)), &actResult) } @@ -394,7 +394,7 @@ func (a *API) InStoreCouponBatchSave(poiCode string, limitTime *LimitTime, coupo KeyAppPoiCode: poiCode, "limit_time": string(utils.MustMarshal(limitTime)), "act_data": string(utils.MustMarshal(couponInfoList)), - }, resultKeyMsg) + }, resultKeySuccessMsg) if err == nil { err = utils.UnmarshalUseNumber([]byte(result.(string)), &couponResultList) } diff --git a/platformapi/mtwmapi/mtwmapi.go b/platformapi/mtwmapi/mtwmapi.go index 95aecb1b..ac2d2437 100644 --- a/platformapi/mtwmapi/mtwmapi.go +++ b/platformapi/mtwmapi/mtwmapi.go @@ -33,8 +33,9 @@ const ( ) const ( - resultKeyData = "data" - resultKeyMsg = "success_msg" + resultKeyData = "data" + resultKeyMsg = "msg" + resultKeySuccessMsg = "success_msg" ) const ( GeneralMaxLimit = 200 // 大多数的API的批处理最大条数 diff --git a/platformapi/mtwmapi/retail.go b/platformapi/mtwmapi/retail.go index de8f5137..23156e43 100644 --- a/platformapi/mtwmapi/retail.go +++ b/platformapi/mtwmapi/retail.go @@ -1,6 +1,7 @@ package mtwmapi import ( + "regexp" "strings" "git.rosy.net.cn/baseapi/utils" @@ -14,6 +15,15 @@ const ( MaxBatchDeleteSize = 100 // retailCat/batchdelete/catandretail这个接口的批量最大值 ) +const ( + SellStatusOnline = 0 // 上架 + SellStatusOffline = 1 // 下架 +) + +var ( + retailBatchFailedSkuReg = regexp.MustCompile(`((?:\d+;)+)`) +) + type RetailCategoryInfo struct { Name string `json:"name"` Sequence int `json:"sequence"` @@ -91,6 +101,11 @@ type AppFood struct { ZhName string `json:"zh_name"` } +type AppFoodResult struct { + AppFoodCode string `json:"app_food_code"` + ErrorMsg string `json:"error_msg"` +} + // 美团分类没有ID,就以名字为唯一标识,不论级别都必须不能重名 // name(和originName)的长度不能超过10个字符(字符,不是字节) // 创建一级分类,originName为空,name为新分类名,secondaryName为空 @@ -168,28 +183,78 @@ func (a *API) RetailList(poiCode string, offset, limit int) (foodList []*AppFood return foodList, err } -func (a *API) RetailBatchInitData(poiCode string, foodDataList []map[string]interface{}) (err error) { - _, err = a.AccessAPI("retail/batchinitdata", false, map[string]interface{}{ +func handleRetailBatchResult(result interface{}) (failedFoodList []*AppFoodResult, err error) { + if msg, ok := result.(string); ok && msg != "" { + err = utils.UnmarshalUseNumber([]byte(msg), &failedFoodList) + } + return failedFoodList, err +} + +func handleRetailBatchResultByRegexp(result interface{}) (failedFoodList []*AppFoodResult, err error) { + if msg, ok := result.(string); ok && msg != "" { + findList := retailBatchFailedSkuReg.FindStringSubmatch(msg) + if len(findList) == 2 { + ids := strings.Split(strings.Trim(findList[1], ";"), ";") + if len(ids) > 0 { + failedFoodList = make([]*AppFoodResult, len(ids)) + for k, v := range ids { + failedFoodList[k] = &AppFoodResult{ + AppFoodCode: v, + ErrorMsg: "", + } + } + } + } + } + return failedFoodList, err +} + +func (a *API) RetailBatchInitData(poiCode string, foodDataList []map[string]interface{}) (failedFoodList []*AppFoodResult, err error) { + result, err := a.AccessAPI2("retail/batchinitdata", false, map[string]interface{}{ KeyAppPoiCode: poiCode, "food_data": string(utils.MustMarshal(foodDataList)), - }) - return err + }, resultKeyMsg) + if err == nil { + failedFoodList, err = handleRetailBatchResult(result) + } + return failedFoodList, err } -func (a *API) RetailSkuPrice(poiCode string, foodData []*BareStoreFoodInfo) (err error) { - _, err = a.AccessAPI("retail/sku/price", false, map[string]interface{}{ +func (a *API) RetailSkuPrice(poiCode string, foodData []*BareStoreFoodInfo) (failedFoodList []*AppFoodResult, err error) { + result, err := a.AccessAPI2("retail/sku/price", false, map[string]interface{}{ KeyAppPoiCode: poiCode, "food_data": string(utils.MustMarshal(foodData)), - }) - return err + }, resultKeyMsg) + if err == nil { + failedFoodList, err = handleRetailBatchResult(result) + } + return failedFoodList, err } -func (a *API) RetailSkuStock(poiCode string, foodData []*BareStoreFoodInfo) (err error) { - _, err = a.AccessAPI("retail/sku/stock", false, map[string]interface{}{ +func (a *API) RetailSkuStock(poiCode string, foodData []*BareStoreFoodInfo) (failedFoodList []*AppFoodResult, err error) { + result, err := a.AccessAPI2("retail/sku/stock", false, map[string]interface{}{ KeyAppPoiCode: poiCode, "food_data": string(utils.MustMarshal(foodData)), - }) - return err + }, resultKeyMsg) + if err == nil { + failedFoodList, err = handleRetailBatchResult(result) + } + return failedFoodList, err +} + +// retail/sku/sellStatus在部分失败时会返回错误,其它相应的批处理函数则会返回成功 +func (a *API) RetailSkuSellStatus(poiCode string, foodData []*BareStoreFoodInfo, sellStatus int) (failedFoodList []*AppFoodResult, err error) { + _, err = a.AccessAPI2("retail/sku/sellStatus", false, map[string]interface{}{ + KeyAppPoiCode: poiCode, + "food_data": string(utils.MustMarshal(foodData)), + "sell_status": sellStatus, + }, resultKeyMsg) + if err != nil { + if errExt, ok := err.(*utils.ErrorWithCode); ok { + failedFoodList, _ = handleRetailBatchResultByRegexp(errExt.ErrMsg()) + } + } + return failedFoodList, err } func (a *API) RetailGet(poiCode, foodCode string) (food *AppFood, err error) { @@ -216,24 +281,6 @@ func (a *API) RetailSkuSave(poiCode, foodCode string, standardSkus, unstandardSk return err } -func (a *API) RetailSkuSellStatus(poiCode string, foodData []map[string]interface{}, sellStatus int) (err error) { - _, err = a.AccessAPI("retail/sku/sellStatus", false, map[string]interface{}{ - KeyAppPoiCode: poiCode, - "food_data": string(utils.MustMarshal(foodData)), - "sell_status": sellStatus, - }) - return err -} - -func (a *API) RetailSkuSellStatus2(poiCode string, foodData []*BareStoreFoodInfo, sellStatus int) (err error) { - _, err = a.AccessAPI("retail/sku/sellStatus", false, map[string]interface{}{ - KeyAppPoiCode: poiCode, - "food_data": string(utils.MustMarshal(foodData)), - "sell_status": sellStatus, - }) - return err -} - func (a *API) RetailDelete(poiCode, foodCode string) (err error) { _, err = a.AccessAPI("retail/delete", false, map[string]interface{}{ KeyAppPoiCode: poiCode, diff --git a/platformapi/mtwmapi/retail_test.go b/platformapi/mtwmapi/retail_test.go index 53d1fd3d..7a1cec24 100644 --- a/platformapi/mtwmapi/retail_test.go +++ b/platformapi/mtwmapi/retail_test.go @@ -82,13 +82,14 @@ func TestRetailDelete(t *testing.T) { t.Fatal(err) } } + func TestRetailBatchInitData(t *testing.T) { - err := api.RetailBatchInitData("2464052", []map[string]interface{}{ + failedFoods, err := api.RetailBatchInitData(testPoiCode, []map[string]interface{}{ map[string]interface{}{ "app_food_code": "23841", "box_num": 0, "box_price": 0, - "category_name": "豆制良品🌾🌾", + "category_name": "南北干货", "description": "", "is_sold_out": 0, "min_order_count": 1, @@ -104,6 +105,29 @@ func TestRetailBatchInitData(t *testing.T) { "upc": "", }, }, + // "tag_id": "200000380", + "unit": "份", + }, + map[string]interface{}{ + "app_food_code": "23840", + "box_num": 0, + "box_price": 0, + "category_name": "南北干货", + "description": "", + "is_sold_out": 0, + "min_order_count": 1, + "name": "干腐竹约150g/份", + "picture": "http://image.jxc4.com/5f7fba025fc9348796039423c48ac3f5.jpg", + "price": 1000, + "skus": []map[string]interface{}{ + map[string]interface{}{ + "price": 1000, + "sku_id": "23840", + "spec": "150g", + "stock": "*", + "upc": "", + }, + }, "tag_id": "200000380", "unit": "份", }, @@ -111,4 +135,97 @@ func TestRetailBatchInitData(t *testing.T) { if err != nil { t.Fatal(err) } + t.Log(utils.Format4Output(failedFoods, false)) +} + +func TestRetailSkuPrice(t *testing.T) { + result, err := api.RetailSkuPrice(testPoiCode, []*BareStoreFoodInfo{ + &BareStoreFoodInfo{ + AppFoodCode: "23841", + Skus: []*BareStoreSkuInfo{ + &BareStoreSkuInfo{ + SkuID: "23841", + Price: "1.2", + }, + }, + }, + &BareStoreFoodInfo{ + AppFoodCode: "23840", + Skus: []*BareStoreSkuInfo{ + &BareStoreSkuInfo{ + SkuID: "23840", + Price: "1.1", + }, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestRetailSkuStock(t *testing.T) { + result, err := api.RetailSkuStock(testPoiCode, []*BareStoreFoodInfo{ + &BareStoreFoodInfo{ + AppFoodCode: "23841", + Skus: []*BareStoreSkuInfo{ + &BareStoreSkuInfo{ + SkuID: "23841", + Price: "1.2", + Stock: "123", + }, + }, + }, + &BareStoreFoodInfo{ + AppFoodCode: "23840", + Skus: []*BareStoreSkuInfo{ + &BareStoreSkuInfo{ + SkuID: "23840", + Stock: "123", + }, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestRetailSkuSellStatus(t *testing.T) { + result, err := api.RetailSkuSellStatus(testPoiCode, []*BareStoreFoodInfo{ + &BareStoreFoodInfo{ + AppFoodCode: "23841", + Skus: []*BareStoreSkuInfo{ + &BareStoreSkuInfo{ + SkuID: "23841", + Price: "1.2", + Stock: "123", + }, + }, + }, + &BareStoreFoodInfo{ + AppFoodCode: "23840", + Skus: []*BareStoreSkuInfo{ + &BareStoreSkuInfo{ + SkuID: "23840", + Stock: "123", + }, + }, + }, + &BareStoreFoodInfo{ + AppFoodCode: "2384999", + Skus: []*BareStoreSkuInfo{ + &BareStoreSkuInfo{ + SkuID: "2384999", + Stock: "123", + }, + }, + }, + }, SellStatusOffline) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) } diff --git a/platformapi/platformapi.go b/platformapi/platformapi.go index 8d808d7b..ab911c43 100644 --- a/platformapi/platformapi.go +++ b/platformapi/platformapi.go @@ -183,15 +183,11 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. func RebuildError(inErr error, bzParams map[string]interface{}, watchKeys []string) (outErr error) { if inErr != nil { if codeErr, ok := inErr.(*utils.ErrorWithCode); ok { - appendErrList := []string{} for _, key := range watchKeys { if bzParams[key] != nil { - appendErrList = append(appendErrList, fmt.Sprintf("[%s:%v]", key, bzParams[key])) + codeErr.AddPrefixMsg(fmt.Sprintf("[%s:%v]", key, bzParams[key])) } } - if len(appendErrList) > 0 { - inErr = utils.NewErrorCode(strings.Join(appendErrList, ",")+", "+codeErr.ErrMsg(), codeErr.Code()) - } } } return inErr diff --git a/utils/errorwithcode.go b/utils/errorwithcode.go index 18b3eb64..5419ec9b 100644 --- a/utils/errorwithcode.go +++ b/utils/errorwithcode.go @@ -7,10 +7,11 @@ import ( ) type ErrorWithCode struct { - level int - errMsg string - code string - intCode int + level int + prefixList []string + errMsg string + code string + intCode int } func NewErrorCode(errMsg, code string, level ...int) *ErrorWithCode { @@ -31,7 +32,11 @@ func NewErrorIntCode(errMsg string, code int, level ...int) *ErrorWithCode { } func (e *ErrorWithCode) Error() string { - return fmt.Sprintf("%s level:%d, code:%s", e.errMsg, e.level, e.code) + fullErrMsg := e.ErrMsg() + if len(e.prefixList) > 0 { + fullErrMsg = strings.Join(e.prefixList, ",") + ", " + fullErrMsg + } + return fmt.Sprintf("%s level:%d, code:%s", fullErrMsg, e.Level(), e.Code()) } func (e *ErrorWithCode) String() string { @@ -54,6 +59,10 @@ func (e *ErrorWithCode) ErrMsg() string { return e.errMsg } +func (e *ErrorWithCode) AddPrefixMsg(prefix string) { + e.prefixList = append(e.prefixList, prefix) +} + func IsErrMatch(err error, strCode string, strList []string) (isMatch bool) { if err != nil { if codeErr, ok := err.(*ErrorWithCode); ok { diff --git a/utils/typeconv.go b/utils/typeconv.go index b0cbabba..b2d31977 100644 --- a/utils/typeconv.go +++ b/utils/typeconv.go @@ -247,6 +247,13 @@ func Int64ToStr(value int64) string { return strconv.FormatInt(value, 10) } +func Int64ToStrNoZero(value int64) string { + if value == 0 { + return "" + } + return strconv.FormatInt(value, 10) +} + func Int2Str(value int) string { return strconv.Itoa(value) } @@ -469,11 +476,48 @@ func Struct2FlatMap(obj interface{}) map[string]interface{} { } // !!! 此函数好像不支持struct是内嵌结构的 -func Map2StructByJson(inObj interface{}, outObjAddr interface{}, weaklyTypedInput bool) (err error) { +func Map2Struct(inObj interface{}, outObjAddr interface{}, weaklyTypedInput bool, tagName string) (err error) { + if tagName == "" { + tagName = "json" + } decoder, _ := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - TagName: "json", + TagName: tagName, Result: outObjAddr, WeaklyTypedInput: weaklyTypedInput, }) return decoder.Decode(inObj) } + +func Map2StructByJson(inObj interface{}, outObjAddr interface{}, weaklyTypedInput bool) (err error) { + return Map2Struct(inObj, outObjAddr, weaklyTypedInput, "") +} + +func Int64Slice2String(intList []int64) (outList []string) { + if len(intList) > 0 { + outList = make([]string, len(intList)) + for k, v := range intList { + outList[k] = Int64ToStr(v) + } + } + return outList +} + +func StringSlice2Int64(intList []string) (outList []int64) { + if len(intList) > 0 { + outList = make([]int64, len(intList)) + for k, v := range intList { + outList[k] = Str2Int64WithDefault(v, 0) + } + } + return outList +} + +func IntSlice2Int64(intList []int) (outList []int64) { + if len(intList) > 0 { + outList = make([]int64, len(intList)) + for k, v := range intList { + outList[k] = int64(v) + } + } + return outList +}