package ebaiapi import ( "fmt" "regexp" "strings" "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/utils" ) const ( SkuStatusOnline = 1 // 为上架 SkuStatusOffline = 0 // 为下架 SkuStatusDeleted = 2 // 为删除 ) const ( UPCTypeStandard = 1 UPCTypePrivate = 0 ) const ( MaxLeftNum = 99999 MaxSkuNameByteCount = 100 // skuname的最大字节数,注意不是字符数,超长饿百会报错:{"data":"","errno":20200,"error":"invalid param:[name]... 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"` Rank int `json:"rank"` // 店内分类独有 Children []*CategoryInfo `json:"children"` Level int `json:"level"` } type SkuListParams struct { Page int `json:"page,omitempty"` PageSize int `json:"pagesize,omitempty"` Upc string `json:"upc,omitempty"` SkuID int64 `json:"sku_id,omitempty"` CustomSkuID string `json:"custom_sku_id,omitempty"` UpcType int `json:"upc_type,omitempty"` GetUncate int `json:"get_uncate,omitempty"` Delete int `json:"delete,omitempty"` Enabled int `json:"enabled,omitempty"` StartTime int `json:"start_time,omitempty"` EndTime int `json:"end_time,omitempty"` } type SkuPhotoInfo struct { IsMaster int `json:"is_master"` URL string `json:"url"` } type SkuCustomCatInfo struct { CustomCatID string `json:"custom_cat_id"` CustomCatName string `json:"custom_cat_name"` } type SkuInfo struct { CustomCatIDs string `json:"custom_cat_ids"` CustomCatList []*SkuCustomCatInfo `json:"custom_cat_list"` CustomSkuID string `json:"custom_sku_id"` IsInActivity int `json:"is_in_activity"` LeftNum int `json:"left_num"` MarketPrice int `json:"market_price"` Minimum int `json:"minimum"` Name string `json:"name"` NeedIce int `json:"need_ice"` Photos []*SkuPhotoInfo `json:"photos"` PreminusWeight int `json:"preminus_weight"` PreparationTime string `json:"preparation_time"` ProductionAddr1 string `json:"production_addr1"` ProductionAddr2 string `json:"production_addr2"` ProductionAddr3 string `json:"production_addr3"` Rtf string `json:"rtf"` SalePrice int64 `json:"sale_price"` SaleStep string `json:"sale_step"` SaleUnit string `json:"sale_unit"` ShelfNumber string `json:"shelf_number"` SkuID int64 `json:"sku_id"` // SkuProperty []interface{} `json:"sku_property"` Status int `json:"status"` Summary string `json:"summary"` Upc string `json:"upc"` UpcType string `json:"upc_type"` UpdateTime string `json:"update_time"` Weight int `json:"weight"` WeightFlag int `json:"weight_flag"` } type PageDataInfo struct { Total int `json:"Total"` Page int `json:"Page"` Pages int `json:"Pages"` List []*SkuInfo `json:"List"` } 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 skuIDstr != "" { params[KeySkuID] = skuIDstr } else if customSkuID != "" { params[KeyCustomSkuID] = customSkuID } else if upc != "" { params[KeyUPC] = upc } else { panic("skuID, customSkuID and upc are all no value!") } return params } // category相关的函数,shop_custom_id可重 func (a *API) ShopCategoryCreate(shopID string, parentID int64, name string, rank int) (catID int64, err error) { result, err := a.AccessAPI("sku.shop.category.create", map[string]interface{}{ KeyShopID: shopID, "parent_category_id": parentID, "name": name, "rank": rank, }) if err == nil { return utils.Str2Int64(utils.Interface2String(result.Data.(map[string]interface{})["category_id"])), nil } return 0, err } func (a *API) ShopCategoryGet(shopID string) (cats []*CategoryInfo, err error) { result, err := a.AccessAPI("sku.shop.category.get", utils.Params2Map(KeyShopID, shopID)) if err == nil { if inMap, ok := result.Data.(map[string]interface{}); ok { // fuck it cats := interface2CatList(inMap["categorys"], 1) return cats, nil } } return nil, err } func (a *API) ShopCategoryUpdate(shopID string, categoryID int64, name string, rank int) (err error) { _, err = a.AccessAPI("sku.shop.category.update", map[string]interface{}{ KeyShopID: shopID, "category_id": categoryID, "name": name, "rank": rank, }) if errWithCode, ok := err.(*utils.ErrorWithCode); ok { if errWithCode.Level() == 0 && errWithCode.IntCode() == 1 { //忽略同名错误 err = nil } } return err } func (a *API) ShopCategoryDelete(shopID string, categoryID int64) (err error) { _, err = a.AccessAPI("sku.shop.category.delete", map[string]interface{}{ KeyShopID: shopID, "category_id": categoryID, }) return err } func (a *API) SkuGetItemsByCategoryId(shopID string, categoryID int64) (skus []map[string]interface{}, err error) { result, err := a.AccessAPI("sku.getItemsByCategoryId", map[string]interface{}{ KeyShopID: shopID, "category_id": categoryID, }) if err == nil { return utils.Slice2MapSlice(result.Data.([]interface{})), nil } return nil, err } func (a *API) SkuList(shopID string, params *SkuListParams) (skuInfo *PageDataInfo, err error) { paramMap := utils.Struct2FlatMap(params) paramMap[KeyShopID] = shopID result, err := a.AccessAPI("sku.list", paramMap) if err == nil { err = utils.Map2StructByJson(result.Data, &skuInfo, true) } return skuInfo, err } // 饿百商品名可以相同,不会报错 func (a *API) SkuCreate(shopID string, customSkuID int, params map[string]interface{}) (skuID int64, err error) { defParams := map[string]interface{}{ KeyShopID: shopID, KeyCustomSkuID: customSkuID, } if params["upc"] == nil { defParams["upc_type"] = UPCTypePrivate defParams["upc"] = "upc-" + utils.Int2Str(customSkuID) } if params["brand_id"] == nil { defParams["brand_id"] = 0 } if params["brand_name"] == nil { defParams["brand_name"] = "无" // 很狗血的是,你还必须填个无才行。。。 } params = utils.MergeMaps(params, defParams) // baseapi.SugarLogger.Debugf(utils.Format4Output(params, false)) result, err := a.AccessAPI("sku.create", params) if err == nil { return utils.Str2Int64(utils.Interface2String(result.Data.(map[string]interface{})[KeySkuID])), nil } return 0, err } func (a *API) SkuUpdate(shopID string, ebaiSkuID int64, params map[string]interface{}) (skuID int64, err error) { defParams := map[string]interface{}{ KeyShopID: shopID, KeySkuID: ebaiSkuID, } result, err := a.AccessAPI("sku.update", utils.MergeMaps(params, defParams)) if err == nil { return utils.Str2Int64(utils.Interface2String(result.Data.(map[string]interface{})[KeySkuID])), nil } return 0, err } func intIDs2Str(intIDs []int64) (str string) { return strings.Join(utils.Int64Slice2String(intIDs), ",") } func strIDs2Str(strIDs []string) (str string) { return strings.Join(strIDs, ",") } 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)) } } } } } 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(utils.Int64ToStrNoZero(skuID), customSkuID, upc) params[KeyShopID] = shopID _, err = a.AccessAPI("sku.online.one", params) return err } 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) } return failedSkuIDs, err } func (a *API) SkuOfflineOne(shopID string, skuID int64, customSkuID, upc string) (err error) { params := genSkuIDParams(utils.Int64ToStrNoZero(skuID), customSkuID, upc) params[KeyShopID] = shopID _, err = a.AccessAPI("sku.offline.one", params) return err } // 此函数在部分失败时,会返回错误,全部成功时不会返回明细,其它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), } _, err = a.AccessAPI("sku.price.update.batch", params) 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 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 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 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 } func (a *API) SkuShopCategoryMap(shopID string, skuID int64, categoryIDsStr string) (err error) { params := map[string]interface{}{ KeyShopID: shopID, KeySkuID: skuID, "category_id": categoryIDsStr, } _, err = a.AccessAPI("sku.shop.category.map", params) return err } // 饿百的这个API有点怪,虽然有shopID参数,但返回的链接可以跨店使用 func (a *API) SkuUploadRTF(shopID, rtfDetail string) (rtfURL string, err error) { params := map[string]interface{}{ KeyShopID: shopID, "rtf_detail": rtfDetail, } result, err := a.AccessAPI("sku.uploadrtf", params) if err == nil { rtfURL = utils.Interface2String(result.Data.(map[string]interface{})["url"]) } return rtfURL, err } func BuildRFTFromImgs(imgList ...string) string { imgList2 := make([]string, len(imgList)) for index, img := range imgList { imgList2[index] = fmt.Sprintf(`%s`, img, img) } return strings.Join(imgList2, "\n") } // func interface2CatList(data interface{}, level int) (cats []*CategoryInfo) { maps, ok := data.([]interface{}) if ok { cats = make([]*CategoryInfo, len(maps)) for index, v := range maps { cats[index] = interface2Cat(v, level) } } return cats } func interface2Cat(data interface{}, level int) (cat *CategoryInfo) { catMap := data.(map[string]interface{}) cat = &CategoryInfo{ CategoryID: utils.MustInterface2Int64(catMap["category_id"]), Name: utils.Interface2String(catMap["name"]), Rank: int(utils.MustInterface2Int64(catMap["rank"])), Children: interface2CatList(catMap["children"], level+1), Level: level, } return cat } func IsErrCategoryExist(err error) (isExist bool) { return utils.IsErrMatch(err, "1", []string{"商户已存在该自定义分类"}) } func IsErrCategoryNotExist(err error) (isNotExist bool) { return utils.IsErrMatch(err, "", []string{"不存在该自定义分类ID", "商户不存在该自定义分类"}) } func IsErrSkuExist(err error) (isExist bool) { return utils.IsErrMatch(err, "1", []string{"商品已存在", "shop sku exist"}) } func IsErrSkuNotExist(err error) (isExist bool) { return utils.IsErrMatch(err, "1", []string{"sku_id与shop_id不匹配", "SKU不存在或者已经被删除"}) } func (a *API) GetEbaiSkuIDFromCustomID(shopID, customSkuID string) (ebaiSkuID int64) { if skuInfo, err2 := a.SkuList(shopID, &SkuListParams{ CustomSkuID: customSkuID, }); err2 == nil && skuInfo != nil && len(skuInfo.List) > 0 { ebaiSkuID = skuInfo.List[0].SkuID } return ebaiSkuID } func (a *API) GetEbaiCatIDFromName(shopID, catName string) (ebaiCatID int64) { if catList, err2 := a.ShopCategoryGet(shopID); err2 == nil { for _, v := range catList { if v.Name == catName { ebaiCatID = v.CategoryID break } for _, v2 := range v.Children { if v2.Name == catName { ebaiCatID = v2.CategoryID break } } } } return ebaiCatID }