From e06ec24013fdf6daf3050c10ed99cca0a7defb98 Mon Sep 17 00:00:00 2001 From: gazebo Date: Fri, 18 Jan 2019 09:17:02 +0800 Subject: [PATCH] - weimob wsc api --- platformapi/platformapi.go | 10 +- platformapi/weimobapi/goods.go | 314 ++++++++++++++++++++++++ platformapi/weimobapi/goods_test.go | 108 ++++++++ platformapi/weimobapi/weimobapi.go | 57 ++++- platformapi/weimobapi/weimobapi_test.go | 13 +- utils/typeconv.go | 7 + 6 files changed, 486 insertions(+), 23 deletions(-) diff --git a/platformapi/platformapi.go b/platformapi/platformapi.go index 6f827a63..6cabcb26 100644 --- a/platformapi/platformapi.go +++ b/platformapi/platformapi.go @@ -58,6 +58,10 @@ const ( ErrLevelCodeIsNotOK = "JXC4_CODE_IS_NOT_OK" ) +const ( + maxDataSizeDontOutput = 200 * 1024 +) + // common api access error var ( ErrAPIAccessFailed = errors.New("access API failed") @@ -77,7 +81,11 @@ func init() { } func getClonedData(r *bytes.Buffer) string { - return string(r.Bytes()) + retVal := string(r.Bytes()) + if len(retVal) > maxDataSizeDontOutput { + return "request data is too large" + } + return retVal } func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http.Request, config *APIConfig, handleResponse func(bodyMap map[string]interface{}) (string, error)) error { diff --git a/platformapi/weimobapi/goods.go b/platformapi/weimobapi/goods.go index 4921ff7f..59238451 100644 --- a/platformapi/weimobapi/goods.go +++ b/platformapi/weimobapi/goods.go @@ -1,9 +1,62 @@ package weimobapi import ( + "fmt" + "io/ioutil" + "net/http" + + "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" ) +const ( + DeductStockTypePlaceOrder = 1 + DeductStockTypePay = 2 +) + +type PendingSaveB2CGoodsVo struct { + FreightTemplateId int64 `json:"freightTemplateId"` + DeliveryTypeIdList []int64 `json:"deliveryTypeIdList"` + B2cGoodsType int `json:"b2cGoodsType"` +} + +type PendingSaveB2CSkuVo struct { + Weight float32 `json:"weight"` + Volume float32 `json:"volume"` +} + +type PendingSaveSkuVo struct { + OuterSkuCode string `json:"outerSkuCode"` + ImageURL string `json:"imageUrl"` + SalePrice float32 `json:"salePrice"` + OriginalPrice float32 `json:"originalPrice"` + CostPrice float32 `json:"costPrice"` + EditStockNum int `json:"editStockNum"` + B2cSku *PendingSaveB2CSkuVo `json:"b2cSku"` +} + +type Category struct { + CategoryID int64 `json:"categoryId"` + Title string `json:"title"` + Level int `json:"level"` + ParentID int64 `json:"parentId"` +} + +type GoodsClassify struct { + ClassifyID int64 `json:"classifyId"` + ImageURL string `json:"imageUrl"` + Title string `json:"title"` + Level int `json:"level"` + ChildrenClassify []*GoodsClassify `json:"childrenClassify"` +} + +type DeliveryType struct { + DeliveryID int64 `json:"deliveryId"` + DeliveryTypeName string `json:"deliveryTypeName"` + DeliveryType int `json:"deliveryType"` + Selected bool `json:"selected"` +} + func (a *API) QueryGoodsList(pageNum, pageSize int) (retVal []map[string]interface{}, totalCount int, err error) { result, err := a.AccessAPI("goods/queryGoodsList", map[string]interface{}{ "pageNum": pageNum, @@ -18,3 +71,264 @@ func (a *API) QueryGoodsList(pageNum, pageSize int) (retVal []map[string]interfa } return nil, 0, err } + +func (a *API) QueryCategoryTree() (retVal []*Category, err error) { + result, err := a.AccessAPI("category/queryCategoryTree", nil) + if err == nil { + categoryList := result["categoryList"].([]interface{}) + retVal = make([]*Category, len(categoryList)) + for k, v := range categoryList { + retVal[k] = map2Category(1, 0, v.(map[string]interface{})) + } + return retVal, nil + } + return nil, err +} + +func map2Category(level int, parentID int64, mapData map[string]interface{}) *Category { + return &Category{ + CategoryID: utils.MustInterface2Int64(mapData["categoryId"]), + Title: utils.Interface2String(mapData["title"]), + Level: level, + ParentID: parentID, + } +} + +func (a *API) QueryChildrenCategory(categoryId int64) (retVal []*Category, err error) { + result, err := a.AccessAPI("category/queryChildrenCategory", map[string]interface{}{ + "categoryId": categoryId, + }) + if err == nil { + categoryList := result["categoryList"].([]interface{}) + retVal = make([]*Category, len(categoryList)) + for k, v := range categoryList { + retVal[k] = map2Category(2, categoryId, v.(map[string]interface{})) + } + return retVal, nil + } + return nil, err +} + +func (a *API) QueryClassifyInfoList() (retVal []*GoodsClassify, err error) { + result, err := a.AccessAPI("goodsClassify/queryClassifyInfoList", nil) + if err == nil { + goodsClassifyList := interface2ClassifyList(result["goodsClassifyList"], nil) + return goodsClassifyList, nil + } + return nil, err +} + +func interface2Classify(data interface{}) (clf *GoodsClassify) { + mapData, ok := data.(map[string]interface{}) + if ok { + clf = &GoodsClassify{ + ClassifyID: utils.MustInterface2Int64(mapData["classifyId"]), + ImageURL: utils.Interface2String(mapData["imageUrl"]), + Title: utils.Interface2String(mapData["title"]), + Level: int(utils.Interface2Int64WithDefault(mapData["level"], 0)), + ChildrenClassify: interface2ClassifyList(mapData["childrenClassify"], nil), + } + } + return clf +} + +func interface2ClassifyList(data interface{}, interface2CatHandler func(data interface{}) (clf *GoodsClassify)) (clfs []*GoodsClassify) { + if interface2CatHandler == nil { + interface2CatHandler = interface2Classify + } + maps, ok := data.([]interface{}) + if ok { + clfs = make([]*GoodsClassify, len(maps)) + for index, v := range maps { + clfs[index] = interface2CatHandler(v) + } + } + return clfs +} + +func (a *API) AddClassify(title string, parentID int64, imageURL string) (goodsClassifyID int64, err error) { + apiParams := map[string]interface{}{ + "title": title, + } + if parentID > 0 { + apiParams["parentId"] = parentID + } + if imageURL != "" { + apiParams["imageUrl"] = imageURL + } + result, err := a.AccessAPI("goodsClassify/addClassify", apiParams) + if err == nil { + return utils.MustInterface2Int64(result["goodsClassifyId"]), nil + } + return 0, err +} + +func (a *API) UpdateClassify(classifyID int64, title string, imageURL string) (err error) { + apiParams := map[string]interface{}{ + "title": title, + "classifyId": classifyID, + } + if imageURL != "" { + apiParams["imageUrl"] = imageURL + } + _, err = a.AccessAPI("goodsClassify/updateClassify", apiParams) + return err +} + +func (a *API) AddGoods(outerGoodsCode, title string, isMultiSku bool, goodsImageUrl []string, goodsDesc string, isPutAway bool, sort int, categoryId int64, classifyIdList []int64, b2cGoods *PendingSaveB2CGoodsVo, skuList []map[string]interface{}, addParams map[string]interface{}) (goodsId int64, skuMap map[string]int64, err error) { + goodsInfo := map[string]interface{}{ + "outerGoodsCode": outerGoodsCode, + "title": title, + "isMultiSku": utils.Bool2Int(isMultiSku), + "goodsImageUrl": goodsImageUrl, + "goodsDesc": goodsDesc, + "isPutAway": 1 - utils.Bool2Int(isPutAway), + "sort": sort, + "categoryId": categoryId, + "b2cGoods": b2cGoods, + "skuList": skuList, + "selectedClassifyIdList": classifyIdList, + "initialSales": 100, + } + mergedMap := utils.MergeMaps(addParams, goodsInfo) + if _, ok := mergedMap["deductStockType"]; !ok { + mergedMap["deductStockType"] = DeductStockTypePay + } + result, err := a.AccessAPI("goods/addGoods", map[string]interface{}{ + "goods": mergedMap, + }) + if err == nil { + skuMap := make(map[string]int64) + skuList := result["skuList"].([]interface{}) + for _, v := range skuList { + sku := v.(map[string]interface{}) + skuMap[utils.Interface2String(sku[KeyOuterSkuCode])] = utils.MustInterface2Int64(sku[KeySkuID]) + } + return utils.MustInterface2Int64(result["goodsId"]), skuMap, nil + } + return 0, nil, err +} + +func (a *API) UpdateGoods(goodsID int64, title string, isMultiSku bool, goodsImageUrl []string, goodsDesc string, isPutAway bool, sort int, categoryId int64, classifyIdList []int64, b2cGoods *PendingSaveB2CGoodsVo, skuList []map[string]interface{}, addParams map[string]interface{}) (goodsId int64, skuMap map[string]int64, err error) { + goodsInfo := map[string]interface{}{ + "goodsId": goodsID, + "title": title, + "isMultiSku": utils.Bool2Int(isMultiSku), + "goodsImageUrl": goodsImageUrl, + "goodsDesc": goodsDesc, + "isPutAway": 1 - utils.Bool2Int(isPutAway), + "sort": sort, + "categoryId": categoryId, + "b2cGoods": b2cGoods, + "skuList": skuList, + "selectedClassifyIdList": classifyIdList, + } + mergedMap := utils.MergeMaps(addParams, goodsInfo) + if _, ok := mergedMap["deductStockType"]; !ok { + mergedMap["deductStockType"] = DeductStockTypePay + } + result, err := a.AccessAPI("goods/updateGoods", map[string]interface{}{ + "goods": mergedMap, + }) + if err == nil { + skuMap := make(map[string]int64) + skuList := result["skuList"].([]interface{}) + for _, v := range skuList { + sku := v.(map[string]interface{}) + skuMap[utils.Interface2String(sku[KeyOuterSkuCode])] = utils.MustInterface2Int64(sku[KeySkuID]) + } + return utils.MustInterface2Int64(result["goodsId"]), skuMap, nil + } + return 0, nil, err +} + +func (a *API) UpdateGoodsShelfStatus(goodsIDs []int64, isPutAway bool) (err error) { + _, err = a.AccessAPI("goods/updateGoodsShelfStatus", map[string]interface{}{ + "goodsIdList": goodsIDs, + "isPutAway": 1 - utils.Bool2Int(isPutAway), + }) + return err +} + +func (a *API) UpdateGoodsTitle(goodsID int64, title string) (err error) { + _, err = a.AccessAPI("goods/updateGoodsTitle", map[string]interface{}{ + "goodsId": goodsID, + "title": title, + }) + return err +} + +func (a *API) FindDeliveryTypeList(goodsID int64) (retVal []*DeliveryType, err error) { + apiParams := map[string]interface{}{} + if goodsID > 0 { + apiParams["goodsId"] = goodsID + } + result, err := a.AccessAPI("goods/findDeliveryTypeList", apiParams) + if err == nil { + deliveryTypeList := result["deliveryTypeList"].([]interface{}) + retVal = make([]*DeliveryType, len(deliveryTypeList)) + for k, v := range deliveryTypeList { + mapData := v.(map[string]interface{}) + retVal[k] = &DeliveryType{ + DeliveryID: utils.MustInterface2Int64(mapData["deliveryId"]), + DeliveryType: int(utils.MustInterface2Int64(mapData["deliveryType"])), + DeliveryTypeName: utils.Interface2String(mapData["deliveryTypeName"]), + Selected: mapData["selected"].(bool), + } + } + return retVal, nil + } + return nil, err +} + +func (a *API) uploadImg(imgData []byte, name string) (imgURL string, err error) { + apiParams := map[string]interface{}{ + "file": imgData, + } + if name != "" { + apiParams["name"] = name + } + result, err := a.AccessAPI("goodsImage/uploadImg", apiParams) + if err == nil { + urlInfo := result["urlInfo"].([]interface{}) + if len(urlInfo) > 0 { + urlInfo0 := urlInfo[0].(map[string]interface{}) + if utils.MustInterface2Int64(urlInfo0["legalStatus"]) == 0 { + return utils.Interface2String(urlInfo0["url"]), nil + } + return "", fmt.Errorf("上传的图片:%s不合法", name) + } + return "", fmt.Errorf("上传的图片:%s返回为空", name) + } + return "", err +} + +func (a *API) UploadImgByURL(uploadImgURL string, name string) (imgURL string, err error) { + response, err := http.Get(uploadImgURL) + if err == nil { + defer func() { + response.Body.Close() + }() + if response.StatusCode == http.StatusOK { + bodyData, err2 := ioutil.ReadAll(response.Body) + if err = err2; err == nil { + return a.uploadImg(bodyData, name) + } + } else { + err = platformapi.ErrHTTPCodeIsNot200 + } + } + return "", err +} + +func (a *API) FindFreightTemplateList(goodsID int64) (retVal map[string]interface{}, err error) { + apiParams := map[string]interface{}{} + if goodsID > 0 { + apiParams["goodsId"] = goodsID + } + result, err := a.AccessAPI("goods/findFreightTemplateList", apiParams) + if err == nil { + return result, nil + } + return nil, err +} diff --git a/platformapi/weimobapi/goods_test.go b/platformapi/weimobapi/goods_test.go index 6952385e..b0cc9d9a 100644 --- a/platformapi/weimobapi/goods_test.go +++ b/platformapi/weimobapi/goods_test.go @@ -15,3 +15,111 @@ func TestQueryGoodsList(t *testing.T) { baseapi.SugarLogger.Debug(totalCount) baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) } + +func TestQueryCategoryTree(t *testing.T) { + result, err := api.QueryCategoryTree() + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) +} + +func TestQueryChildrenCategory(t *testing.T) { + result, err := api.QueryChildrenCategory(1) + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) +} + +func TestQueryClassifyInfoList(t *testing.T) { + result, err := api.QueryClassifyInfoList() + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) +} + +func TestAddClassify(t *testing.T) { + result, err := api.AddClassify("我的测试子分类1", 442315148, "") + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) +} + +func TestFindDeliveryTypeList(t *testing.T) { + result, err := api.FindDeliveryTypeList(0) + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) +} + +func TestAddGoods(t *testing.T) { + result, skuMap, err := api.AddGoods("SPU1", "测试土豆", false, []string{"https://image-c.weimobwmc.com/openruntime/8ff6950112974cff8b2f1ae1634c074d.png"}, "测试的高级土豆", true, 1, 35, + []int64{438963248}, + &PendingSaveB2CGoodsVo{ + FreightTemplateId: 6537248, + DeliveryTypeIdList: []int64{177445}, + B2cGoodsType: 0, + }, + []map[string]interface{}{ + map[string]interface{}{ + KeyOuterSkuCode: "SKU2", + KeyImageURL: "https://image-c.weimobwmc.com/openruntime/8ff6950112974cff8b2f1ae1634c074d.png", + KeySalePrice: 12.34, + KeyEditStockNum: 99999, + KeyB2cSku: &PendingSaveB2CSkuVo{ + Weight: 0.12, + }, + }, + }, nil) + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) + baseapi.SugarLogger.Debug(utils.Format4Output(skuMap, false)) +} + +func TestUpdateGoods(t *testing.T) { + result, skuMap, err := api.UpdateGoods(20446200148, "测试土豆修改", false, []string{"https://image-c.weimobwmc.com/openruntime/8ff6950112974cff8b2f1ae1634c074d.png"}, "测试的高级土豆", true, 1, 35, + []int64{438963248}, + &PendingSaveB2CGoodsVo{ + FreightTemplateId: 6537248, + DeliveryTypeIdList: []int64{177445}, + B2cGoodsType: 0, + }, + []map[string]interface{}{ + map[string]interface{}{ + KeyOuterSkuCode: "SKU22", + KeyImageURL: "https://image-c.weimobwmc.com/openruntime/8ff6950112974cff8b2f1ae1634c074d.png", + KeySalePrice: 12.34, + KeyEditStockNum: 99999, + KeyB2cSku: &PendingSaveB2CSkuVo{ + Weight: 0.12, + }, + }, + }, nil) + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) + baseapi.SugarLogger.Debug(utils.Format4Output(skuMap, false)) +} + +func TestUploadImgByURL(t *testing.T) { + result, err := api.UploadImgByURL("http://www.jxc4.com/static/img/logo200.png", "") + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) +} + +func TestFindFreightTemplateList(t *testing.T) { + result, err := api.FindFreightTemplateList(0) + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) +} diff --git a/platformapi/weimobapi/weimobapi.go b/platformapi/weimobapi/weimobapi.go index ae4cfe8c..5b631266 100644 --- a/platformapi/weimobapi/weimobapi.go +++ b/platformapi/weimobapi/weimobapi.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "mime/multipart" "net/http" "sync" @@ -20,8 +21,23 @@ const ( const ( prodURL = "https://dopen.weimob.com/api/1_0/ec" + mediaURL = "https://dopen.weimob.com/media/1_0/ec" authURL = "https://dopen.weimob.com/fuwu/b" accessTokenAPI = "oauth2/token" + uploadImgAPI = "goodsImage/uploadImg" +) + +const ( + // sku + KeySkuID = "skuId" + KeyOuterSkuCode = "outerSkuCode" + KeyImageURL = "imageUrl" + KeySalePrice = "salePrice" + KeyOriginalPrice = "originalPrice" + KeyCostPrice = "costPrice" + KeyEditStockNum = "editStockNum" + KeyB2cSku = "b2cSku" + KeySkuAttrMap = "skuAttrMap" ) type TokenInfo struct { @@ -100,20 +116,37 @@ func (a *API) AccessAPI(apiStr string, apiParams map[string]interface{}) (retVal })) request, _ = http.NewRequest(http.MethodPost, fullURL, nil) } else { - fullURL := utils.GenerateGetURL(prodURL, apiStr, utils.Params2Map("accesstoken", a.MustGetToken().AccessToken)) - var body io.Reader - if apiParams != nil { - apiParamsBytes, err := json.Marshal(apiParams) - if err != nil { - panic(fmt.Sprintf("Error when marshal %v, error:%v", apiParams, err)) + if apiStr == uploadImgAPI { + fullURL := utils.GenerateGetURL(mediaURL, apiStr, utils.Params2Map("accesstoken", a.MustGetToken().AccessToken)) + var b bytes.Buffer + w := multipart.NewWriter(&b) + imgData := apiParams["file"].([]byte) + imgName, _ := apiParams["name"].(string) + if fw, err := w.CreateFormFile("file", imgName); err != nil { + panic(err.Error()) + } else { + fw.Write(imgData) } - body = bytes.NewReader(apiParamsBytes) + w.Close() + // b.WriteString(utils.Map2URLValues(params).Encode()) + request, _ = http.NewRequest(http.MethodPost, fullURL, &b) + request.Header.Set("Content-Type", w.FormDataContentType()) + } else { + fullURL := utils.GenerateGetURL(prodURL, apiStr, utils.Params2Map("accesstoken", a.MustGetToken().AccessToken)) + var body io.Reader + if apiParams != nil { + apiParamsBytes, err := json.Marshal(apiParams) + if err != nil { + panic(fmt.Sprintf("Error when marshal %v, error:%v", apiParams, err)) + } + body = bytes.NewReader(apiParamsBytes) + } + request, _ = http.NewRequest(http.MethodPost, fullURL, body) + request.Header.Set("Content-Type", "application/json; charset=utf-8") + request.Header.Set("Content-Encoding", "gzip, deflate") + request.Header.Set("User-Agent", "weimob-golang-api") + // request.Close = true //todo 为了性能考虑还是不要关闭 } - request, _ = http.NewRequest(http.MethodPost, fullURL, body) - request.Header.Set("Content-Type", "application/json; charset=utf-8") - request.Header.Set("Content-Encoding", "gzip, deflate") - request.Header.Set("User-Agent", "weimob-golang-api") - // request.Close = true //todo 为了性能考虑还是不要关闭 } return request }, diff --git a/platformapi/weimobapi/weimobapi_test.go b/platformapi/weimobapi/weimobapi_test.go index 8b667986..0aa64302 100644 --- a/platformapi/weimobapi/weimobapi_test.go +++ b/platformapi/weimobapi/weimobapi_test.go @@ -19,16 +19,9 @@ func init() { baseapi.Init(sugarLogger) tokenStr := ` - { - "token_type": "bearer", - "access_token": "db2f4a02-2097-4636-8c8a-cc5576f402cf", - "expires_in": 7199, - "refresh_token": "7b961b6b-0dc4-43e1-8c38-a9a10e2e130d", - "refresh_token_expires_in": 604799, - "scope": "default", - "public_account_id": "100000386048", - "business_id": "1224609670" - }` + {"token_type":"bearer","access_token":"4c170b37-e70b-4fc0-aa03-d02fc1ebf43d","expires_in":7199,"refresh_token":"7b961b6b-0dc4-43e1-8c38-a9a10e2e130d","refresh_token_expires_in":607932,"scope":"default","public_account_id":"100000386048","business_id":"1224609670"} + + ` var token *TokenInfo if err := utils.UnmarshalUseNumber([]byte(tokenStr), &token); err != nil { panic(err) diff --git a/utils/typeconv.go b/utils/typeconv.go index 1fe3e72e..24494d54 100644 --- a/utils/typeconv.go +++ b/utils/typeconv.go @@ -190,6 +190,13 @@ func Bool2String(value bool) string { return "false" } +func Bool2Int(value bool) int { + if value { + return 1 + } + return 0 +} + func Str2Int64WithDefault(str string, defValue int64) int64 { retVal, err := strconv.ParseInt(str, 10, 64) if err != nil {