diff --git a/platformapi/autonavi/autonavi.go b/platformapi/autonavi/autonavi.go index a81ddaaa..9ff7e07c 100644 --- a/platformapi/autonavi/autonavi.go +++ b/platformapi/autonavi/autonavi.go @@ -43,6 +43,7 @@ const ( const ( FakeDistrictPadding = 9000000 + MaxConvertCount = 40 ) var ( @@ -89,6 +90,11 @@ type District struct { Districts []*District `json:"districts"` } +type Coordinate struct { + Lng float64 `json:"lng"` + Lat float64 `json:"lat"` +} + type ResponseResult map[string]interface{} type API struct { @@ -127,6 +133,10 @@ func (a *API) signParams(mapData map[string]interface{}) string { return fmt.Sprintf("%X", md5.Sum([]byte(finalStr))) } +func coordinate2String(lng, lat float64) (str string) { + return fmt.Sprintf("%.6f,%.6f", lng, lat) +} + func (a *API) AccessAPI(apiStr string, params map[string]interface{}) (retVal ResponseResult, err error) { params2 := utils.MergeMaps(utils.Params2Map("key", a.key, "output", "json"), params) params2[signKey] = a.signParams(params2) @@ -161,6 +171,15 @@ func (a *API) AccessAPI(apiStr string, params map[string]interface{}) (retVal Re // 为了方便调用者编码,如果失败,也会返回未转换的原始值 func (a *API) CoordinateConvert(lng, lat float64, coordsys string) (retLng, retLat float64, err error) { + // outCoords, err := a.BatchCoordinateConvert([]*Coordinate{ + // &Coordinate{ + // Lng: lng, + // Lat: lat, + // }, + // }, coordsys) + // if err == nil { + // retLng, retLat = outCoords[0].Lng, outCoords[0].Lat + // } if coordsys == "" || coordsys == CoordSysAutonavi { return lng, lat, nil } @@ -168,7 +187,7 @@ func (a *API) CoordinateConvert(lng, lat float64, coordsys string) (retLng, retL return 0.0, 0.0, nil } params := map[string]interface{}{ - "locations": fmt.Sprintf("%.6f,%.6f", lng, lat), + "locations": coordinate2String(lng, lat), "coordsys": coordsys, } result, err := a.AccessAPI("assistant/coordinate/convert", params) @@ -180,6 +199,33 @@ func (a *API) CoordinateConvert(lng, lat float64, coordsys string) (retLng, retL return lng, lat, err } +func (a *API) BatchCoordinateConvert(coords []*Coordinate, coordsys string) (outCoords []*Coordinate, err error) { + if coordsys == "" || coordsys == CoordSysAutonavi { + return coords, nil + } + var coordsStrList []string + for _, v := range coords { + coordsStrList = append(coordsStrList, coordinate2String(v.Lng, v.Lat)) + } + params := map[string]interface{}{ + "locations": strings.Join(coordsStrList, "|"), + "coordsys": coordsys, + } + result, err := a.AccessAPI("assistant/coordinate/convert", params) + if err == nil { + coordinate := result["locations"].(string) + retCoordsStrList := strings.Split(coordinate, ";") + for _, v := range retCoordsStrList { + pair := strings.Split(v, ",") + outCoords = append(outCoords, &Coordinate{ + Lng: utils.Str2Float64(pair[0]), + Lat: utils.Str2Float64(pair[1]), + }) + } + } + return outCoords, err +} + func (a *API) GetCoordinateFromAddress(address string, cityInfo string) (lng, lat float64, districtCode int) { params := map[string]interface{}{ "address": address, @@ -218,7 +264,7 @@ func (a *API) GetCoordinateDistrictCode(lng, lat float64) (districtCode int) { func (a *API) GetCoordinateAreaInfo(lng, lat float64) (areaInfo map[string]interface{}, err error) { params := map[string]interface{}{ - "location": fmt.Sprintf("%.6f,%.6f", lng, lat), + "location": coordinate2String(lng, lat), } result, err := a.AccessAPI("geocode/regeo", params) return result, err @@ -283,3 +329,18 @@ func (a *API) getDistrictsFromInterface(districts interface{}) (districtList []* func GetDistrictLevel(levelName string) (level int) { return levelStr2IntMap[levelName] } + +// 两点之间的步行距离,单位为米 +func (a *API) WalkingDistance(lng1, lat1, lng2, lat2 float64) (distance float64) { + params := map[string]interface{}{ + "origin": coordinate2String(lng1, lat1), + "destination": coordinate2String(lng2, lat2), + } + result, err := a.AccessAPI("direction/walking", params) + if err == nil { + if paths, _ := result["route"].(map[string]interface{})["paths"].([]interface{}); len(paths) > 0 { + distance = utils.Interface2Float64WithDefault(paths[0].(map[string]interface{})["distance"], 0) + } + } + return distance +} diff --git a/platformapi/autonavi/autonavi_test.go b/platformapi/autonavi/autonavi_test.go index 81c2d857..26fd32a1 100644 --- a/platformapi/autonavi/autonavi_test.go +++ b/platformapi/autonavi/autonavi_test.go @@ -19,7 +19,7 @@ func init() { sugarLogger = logger.Sugar() baseapi.Init(sugarLogger) - autonaviAPI = New("4427170f870af2110becb8852d36ab08") + autonaviAPI = New("ef64f638f31e05cb7bde28790f7309fe") } func TestCoordinateConvert(t *testing.T) { @@ -88,3 +88,8 @@ func TestGetCoordinateFromAddress(t *testing.T) { lng, lat, districtCode := autonaviAPI.GetCoordinateFromAddress("天府广场", "成都市") t.Logf("lng:%f, lat:%f, districtCode:%d", lng, lat, districtCode) } + +func TestWalkingDistance(t *testing.T) { + distance := autonaviAPI.WalkingDistance(104.057289, 30.694798, 104.066289, 30.695598) + t.Logf("distance:%f", distance) +} diff --git a/platformapi/baidunavi/baidunavi.go b/platformapi/baidunavi/baidunavi.go new file mode 100644 index 00000000..20fde070 --- /dev/null +++ b/platformapi/baidunavi/baidunavi.go @@ -0,0 +1,168 @@ +package baidunavi + +import ( + "crypto/md5" + "fmt" + "net/http" + "net/url" + "sort" + "strings" + + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" +) + +const ( + signKey = "sn" + resultKey = "result" + prodURL = "http://api.map.baidu.com" +) + +const ( + StatusCodeSuccess = 0 + StatusCodeInternalErr = 1 // 服务器内部错误 + StatusCodeExceedDailyQuota = 301 // 永久配额超限,限制访问 + StatusCodeExceedQuota = 302 // 天配额超限,限制访问 + StatusCodeExceedDailyConcurrentQuota = 401 // 当前并发量已经超过约定并发配额,限制访问 + StatusCodeExceedConcurrentQuota = 402 // 当前并发量已经超过约定并发配额,并且服务总并发量也已经超过设定的总并发配额,限制访问 +) + +const ( + CoordSysWGS84 = 1 // GPS设备获取的角度坐标,WGS84坐标 + CoordSysGCJ02 = 3 // google地图、soso地图、aliyun地图、mapabc地图和amap地图所用坐标,国测局(GCJ02)坐标 + CoordSysBaiDu = 5 // 百度地图采用的经纬度坐标 +) + +const ( + MaxCoordsConvBatchSize = 100 +) + +var ( + exceedLimitCodes = map[int]int{ + StatusCodeExceedDailyQuota: 1, + StatusCodeExceedQuota: 1, + StatusCodeExceedDailyConcurrentQuota: 1, + StatusCodeExceedConcurrentQuota: 1, + } + + canRetryCodes = map[int]int{ + StatusCodeInternalErr: 1, + } +) + +type Coordinate struct { + Lng float64 `json:"x"` + Lat float64 `json:"y"` +} + +type API struct { + client *http.Client + config *platformapi.APIConfig + ak string + sk string +} + +func New(ak, sk string, config ...*platformapi.APIConfig) *API { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + return &API{ + ak: ak, + sk: sk, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, + } +} + +func (a *API) signParams(apiStr string, mapData map[string]interface{}) string { + keys := make([]string, 0) + for k := range mapData { + if k != signKey { + keys = append(keys, k) + } + } + sort.Strings(keys) + + strList := []string{} + for _, k := range keys { + strList = append(strList, k+"="+url.QueryEscape(fmt.Sprint(mapData[k]))) + } + finalStr := "/" + apiStr + "?" + strings.Join(strList, "&") + a.sk + // baseapi.SugarLogger.Debugf("sign str:%v", finalStr) + finalStr = url.QueryEscape(finalStr) + return fmt.Sprintf("%x", md5.Sum([]byte(finalStr))) +} + +func genGetURL(baseURL, apiStr string, params map[string]interface{}) string { + keys := make([]string, 0) + for k := range params { + if k != signKey { + keys = append(keys, k) + } + } + sort.Strings(keys) + + strList := []string{} + for _, k := range keys { + strList = append(strList, k+"="+url.QueryEscape(fmt.Sprint(params[k]))) + } + strList = append(strList, signKey+"="+url.QueryEscape(fmt.Sprint(params[signKey]))) + queryString := "?" + strings.Join(strList, "&") + if apiStr != "" { + return baseURL + "/" + apiStr + queryString + } + return baseURL + queryString +} + +func (a *API) AccessAPI(apiStr string, params map[string]interface{}) (retVal interface{}, err error) { + apiStr += "/" + params2 := utils.MergeMaps(utils.Params2Map("ak", a.ak, "output", "json"), params) + params2[signKey] = a.signParams(apiStr, params2) + + err = platformapi.AccessPlatformAPIWithRetry(a.client, + func() *http.Request { + request, _ := http.NewRequest(http.MethodGet, genGetURL(prodURL, apiStr, params2), nil) + return request + }, + a.config, + func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { + if jsonResult1 == nil { + return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") + } + status := int(utils.MustInterface2Int64(jsonResult1["status"])) + if status == StatusCodeSuccess { + retVal = jsonResult1[resultKey] + return platformapi.ErrLevelSuccess, nil + } + newErr := utils.NewErrorIntCode(utils.Interface2String(jsonResult1["message"]), status) + if _, ok := exceedLimitCodes[status]; ok { + return platformapi.ErrLevelExceedLimit, newErr + } else if _, ok := canRetryCodes[status]; ok { + return platformapi.ErrLevelRecoverableErr, newErr + } else { + return platformapi.ErrLevelCodeIsNotOK, newErr + } + }) + return retVal, err +} + +func (a *API) BatchCoordinateConvert(coords []*Coordinate, fromCoordSys, toCoordSys int) (outCoords []*Coordinate, err error) { + if fromCoordSys == toCoordSys { + return coords, nil + } + var coordsStrList []string + for _, v := range coords { + coordsStrList = append(coordsStrList, fmt.Sprintf("%.6f,%.6f", v.Lng, v.Lat)) + } + params := map[string]interface{}{ + "coords": strings.Join(coordsStrList, ";"), + "from": fromCoordSys, + "to": toCoordSys, + } + result, err := a.AccessAPI("geoconv/v1", params) + if err == nil { + err = utils.Map2StructByJson(result, &outCoords, false) + } + return outCoords, err +} diff --git a/platformapi/baidunavi/baidunavi_test.go b/platformapi/baidunavi/baidunavi_test.go new file mode 100644 index 00000000..3b11f2f4 --- /dev/null +++ b/platformapi/baidunavi/baidunavi_test.go @@ -0,0 +1,41 @@ +package baidunavi + +import ( + "testing" + + "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/utils" + + "go.uber.org/zap" +) + +var ( + api *API + sugarLogger *zap.SugaredLogger +) + +func init() { + logger, _ := zap.NewDevelopment() + sugarLogger = logger.Sugar() + baseapi.Init(sugarLogger) + + api = New("eL94zToVOdGDTkNQxV8dnEQ1ZRcB2UKb", "ZG0OOpOsOVURUwAkkmoHQFKRCbzn0zGb") +} + +func TestBatchCoordinateConvert(t *testing.T) { + result, err := api.BatchCoordinateConvert([]*Coordinate{ + &Coordinate{ + Lng: 104.057367, + Lat: 30.694686, + }, + &Coordinate{ + Lng: 104.057367, + Lat: 30.694686, + }, + }, CoordSysGCJ02, CoordSysBaiDu) + if err != nil { + t.Fatalf("TestCoordinateConvert failed with error:%v", err) + } else { + t.Log(utils.Format4Output(result, false)) + } +} diff --git a/platformapi/ebaiapi/activity.go b/platformapi/ebaiapi/activity.go index 27b8f5b0..4bf6591a 100644 --- a/platformapi/ebaiapi/activity.go +++ b/platformapi/ebaiapi/activity.go @@ -145,7 +145,7 @@ func (a *API) ActivitySkuAddBatch(activityID int64, shopID string, baiduShopID i params[skusKey] = skuList2Str(activityType, skuList, isSkuIDCustom) result, err := a.AccessAPI("activity.sku.add.batch", params) if err == nil { - return strings.Split(result.Data.(string), ","), nil + return strings.Split(utils.Interface2String(result.Data), ","), nil } return nil, err } @@ -180,7 +180,7 @@ func (a *API) ActivitySkuDeleteBatch(activityID int64, shopID string, baiduShopI params[skusKey] = strings.Join(skuIDs, ",") result, err := a.AccessAPI("activity.sku.delete.batch", params) if err == nil { - return strings.Split(result.Data.(string), ","), nil + return strings.Split(utils.Interface2String(result.Data), ","), nil } return nil, err } @@ -236,7 +236,7 @@ func (a *API) ActivitySkuUpdateBatch(activityID int64, actSkuInfoList []*Activit "act_sku_info": actSkuInfoList, }) if err == nil { - return strings.Split(result.Data.(string), ";"), nil + return strings.Split(utils.Interface2String(result.Data), ";"), nil } return nil, err } diff --git a/platformapi/ebaiapi/callback.go b/platformapi/ebaiapi/callback.go index 497400f2..c7be08be 100644 --- a/platformapi/ebaiapi/callback.go +++ b/platformapi/ebaiapi/callback.go @@ -113,16 +113,12 @@ func (a *API) unmarshalData(cmd string, data []byte, msg interface{}) (callbackR return nil } -func (a *API) CheckCallbackValidation(request *http.Request) (callbackResponse *CallbackResponse) { - params := make(url.Values) - for k, v := range request.PostForm { - params[k] = v - } +func (a *API) CheckCallbackValidation(cmd string, params url.Values) (callbackResponse *CallbackResponse) { sign := a.signParams(params) - if sign != request.FormValue(signKey) { - msg := fmt.Sprintf("Signature is not ok, mine:%v, get:%v", sign, request.FormValue(signKey)) + if sign != params.Get(signKey) { + msg := fmt.Sprintf("Signature is not ok, mine:%v, get:%v", sign, params.Get(signKey)) baseapi.SugarLogger.Info(msg) - return a.Err2CallbackResponse(GetCmd(request), errors.New(msg), nil) + return a.Err2CallbackResponse(cmd, errors.New(msg), nil) } return nil } @@ -130,15 +126,20 @@ func (a *API) CheckCallbackValidation(request *http.Request) (callbackResponse * func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackResponse *CallbackResponse) { err := request.ParseForm() if err == nil { - if callbackResponse = a.CheckCallbackValidation(request); callbackResponse != nil { - return nil, callbackResponse + params := make(url.Values) + for k := range request.PostForm { + decodedValue, _ := url.QueryUnescape(request.PostFormValue(k)) + params.Set(k, decodedValue) } msg = new(CallbackMsg) - if callbackResponse = a.unmarshalData(GetCmd(request), []byte(request.FormValue("body")), &msg.Body); callbackResponse != nil { + msg.Cmd = GetCmd(request) + if callbackResponse = a.CheckCallbackValidation(msg.Cmd, params); callbackResponse != nil { return nil, callbackResponse } - msg.Cmd = GetCmd(request) - msg.Timestamp = utils.Str2Int64(utils.Interface2String(request.FormValue("timestamp"))) + if callbackResponse = a.unmarshalData(msg.Cmd, []byte(params.Get("body")), &msg.Body); callbackResponse != nil { + return nil, callbackResponse + } + msg.Timestamp = utils.Str2Int64(utils.Interface2String(params.Get("timestamp"))) var tmpObj interface{} switch msg.Cmd { case CmdOrderPartRefund: @@ -158,6 +159,7 @@ func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackR return nil, a.Err2CallbackResponse("", err, nil) } -func GetCmd(request *http.Request) string { - return request.FormValue("cmd") +func GetCmd(request *http.Request) (cmd string) { + cmd, _ = url.QueryUnescape(request.FormValue("cmd")) + return cmd } diff --git a/platformapi/ebaiapi/common.go b/platformapi/ebaiapi/common.go index 49de80ec..4bdf8573 100644 --- a/platformapi/ebaiapi/common.go +++ b/platformapi/ebaiapi/common.go @@ -15,18 +15,7 @@ type CityInfo struct { func (a *API) CommonShopCities(parentID int) (cityList []*CityInfo, err error) { result, err := a.AccessAPI("common.shopcities", utils.Params2Map("pid", parentID)) if err == nil { - cityMapList := utils.Slice2MapSlice(result.Data.([]interface{})) - // baseapi.SugarLogger.Debug(utils.Format4Output(cityMapList, false)) - cityList = make([]*CityInfo, len(cityMapList)) - for k, v := range cityMapList { - cityList[k] = &CityInfo{ - ID: int(utils.Str2Int64(utils.Interface2String(v["city_id"]))), - Name: utils.Interface2String(v["city_name"]), - ParentID: int(utils.Str2Int64(utils.Interface2String(v["parent_id"]))), - IsOpen: int(utils.Str2Int64(utils.Interface2String(v["is_open"]))), - } - } - return cityList, nil + err = utils.Map2StructByJson(result.Data, &cityList, true) } - return nil, err + return cityList, err } diff --git a/platformapi/ebaiapi/ebaiapi.go b/platformapi/ebaiapi/ebaiapi.go index b3b5f50b..78bdfe8a 100644 --- a/platformapi/ebaiapi/ebaiapi.go +++ b/platformapi/ebaiapi/ebaiapi.go @@ -7,7 +7,6 @@ import ( "net/url" "sort" "strings" - "sync" "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/platformapi" @@ -43,9 +42,6 @@ type API struct { config *platformapi.APIConfig speedLimiter *platformapi.Limiter supplierID int64 - - locker sync.RWMutex - storeCookies map[string]string } func New(source, secret string, config ...*platformapi.APIConfig) *API { @@ -60,7 +56,6 @@ func New(source, secret string, config ...*platformapi.APIConfig) *API { client: &http.Client{Timeout: curConfig.ClientTimeout}, config: &curConfig, speedLimiter: platformapi.New(apiLimitConfigs, nil), //defaultAPILimitConfig), - storeCookies: make(map[string]string), supplierID: -1, } @@ -155,12 +150,12 @@ func (a *API) AccessAPI(cmd string, body map[string]interface{}) (retVal *Respon } func (a *API) GetSupplierID() (supplierID int64) { - a.locker.RLock() + a.RLock() supplierID = a.supplierID - a.locker.RUnlock() + a.RUnlock() if supplierID < 0 { - a.locker.Lock() - defer a.locker.Unlock() + a.Lock() + defer a.Unlock() a.supplierID = 0 if shopList, err := a.ShopList(SysStatusAll); err == nil { diff --git a/platformapi/ebaiapi/order.go b/platformapi/ebaiapi/order.go index bd596118..bfd410e6 100644 --- a/platformapi/ebaiapi/order.go +++ b/platformapi/ebaiapi/order.go @@ -41,6 +41,7 @@ const ( WaybillStatusSelfDelivery = "18" // 自行配送 WaybillStatusDontDeliver = "19" // 不再配送 WaybillStatusDeliveryRejected = "20" // 配送拒单 + WaybillStatusCourierArrived = "21" // 骑士到店 ) const ( @@ -66,8 +67,6 @@ const ( OrderUserCancelTypeBeforeSale = 1 // 表示订单完成前用户全单取消申请流程 OrderUserCancelTypeAfterSale = 2 // 表示订单完成后用户全单退款申请流程 - - SendImmediatelySelf = 6 // 饿百商家自送的配送状态 ) const ( @@ -188,31 +187,31 @@ type OrderDetailInfo struct { Ext struct { TaoxiFlag int `json:"taoxi_flag"` } `json:"ext"` - FinishedTime string `json:"finished_time"` - InvoiceTitle string `json:"invoice_title"` - IsColdBoxOrder int `json:"is_cold_box_order"` - IsPrivate int `json:"is_private"` - LatestSendTime int `json:"latest_send_time"` - MealNum string `json:"meal_num"` - NeedInvoice int `json:"need_invoice"` - OrderFlag int `json:"order_flag"` - OrderFrom string `json:"order_from"` - OrderID string `json:"order_id"` - OrderIndex string `json:"order_index"` - PackageFee int `json:"package_fee"` - PayStatus int `json:"pay_status"` - PayType int `json:"pay_type"` - PickupTime int `json:"pickup_time"` - Remark string `json:"remark"` - ResponsibleParty string `json:"responsible_party"` - SendFee int `json:"send_fee"` - SendImmediately int `json:"send_immediately"` - SendTime int `json:"send_time"` - ShopFee int `json:"shop_fee"` - Status int `json:"status"` - TaxerID string `json:"taxer_id"` - TotalFee int `json:"total_fee"` - UserFee int `json:"user_fee"` + FinishedTime string `json:"finished_time"` + InvoiceTitle string `json:"invoice_title"` + IsColdBoxOrder int `json:"is_cold_box_order"` + IsPrivate int `json:"is_private"` + LatestSendTime int `json:"latest_send_time"` + MealNum string `json:"meal_num"` + NeedInvoice int `json:"need_invoice"` + OrderFlag int `json:"order_flag"` + OrderFrom string `json:"order_from"` + OrderID string `json:"order_id"` + OrderIndex string `json:"order_index"` + PackageFee int `json:"package_fee"` + PayStatus int `json:"pay_status"` + PayType int `json:"pay_type"` + PickupTime int `json:"pickup_time"` + Remark string `json:"remark"` + // ResponsibleParty string `json:"responsible_party"` + SendFee int `json:"send_fee"` + SendImmediately int `json:"send_immediately"` + SendTime int `json:"send_time"` + ShopFee int `json:"shop_fee"` + Status int `json:"status"` + TaxerID string `json:"taxer_id"` + TotalFee int `json:"total_fee"` + UserFee int `json:"user_fee"` } `json:"order"` Products [][]*OrderProductInfo `json:"products"` Shop *struct { @@ -223,8 +222,8 @@ type OrderDetailInfo struct { Source string `json:"source"` User *struct { Address string `json:"address"` - City string `json:"city"` - Coord *struct { + // City string `json:"city"` + Coord *struct { Latitude string `json:"latitude"` Longitude string `json:"longitude"` } `json:"coord"` @@ -232,13 +231,13 @@ type OrderDetailInfo struct { Latitude string `json:"latitude"` Longitude string `json:"longitude"` } `json:"coord_amap"` - District string `json:"district"` + // District string `json:"district"` Gender string `json:"gender"` Name string `json:"name"` Phone string `json:"phone"` PrivacyPhone string `json:"privacy_phone"` - Province string `json:"province"` - UserID string `json:"user_id"` + // Province string `json:"province"` + UserID string `json:"user_id"` } `json:"user"` } diff --git a/platformapi/ebaiapi/shop.go b/platformapi/ebaiapi/shop.go index d4a8e4e6..7f2249d2 100644 --- a/platformapi/ebaiapi/shop.go +++ b/platformapi/ebaiapi/shop.go @@ -68,6 +68,18 @@ const ( DeliveryTypeElmXingHuoZBKA = 18 // 星火众包KA ) +// https://open-be.ele.me/dev/api/doc/v3/#api-Order_Up-order_get +const ( + DeliveryPartyFengNiao = 1 // 蜂鸟 + DeliveryPartyFengNiaoSelf = 2 // 蜂鸟自配送 + DeliveryPartyFengNiaoZB = 3 // 蜂鸟众包 + DeliveryPartyFengElmZB = 4 // 饿了么众包 + DeliveryPartyFengNiaoPS = 5 // 蜂鸟配送 + DeliveryPartyFengElmSelf = 6 // 饿了么自配送,商家自送? + DeliveryPartyFullCity = 7 // 全城送 + DeliveryPartyKuaiDiPS = 8 // 快递配送 +) + type ShopInfo struct { ShopID string `json:"shop_id"` BaiduShopID int64 `json:"baidu_shop_id"` diff --git a/platformapi/ebaiapi/shop_sku.go b/platformapi/ebaiapi/shop_sku.go index fd282197..f4caaf28 100644 --- a/platformapi/ebaiapi/shop_sku.go +++ b/platformapi/ebaiapi/shop_sku.go @@ -237,7 +237,7 @@ func (a *API) ShopCategoryCreate(shopID string, parentID int64, name string, ran 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 + if inMap, ok := result.Data.(map[string]interface{}); ok { cats := interface2CatList(inMap["categorys"], 1) return cats, nil } @@ -252,11 +252,6 @@ func (a *API) ShopCategoryUpdate(shopID string, categoryID int64, name string, r "name": name, "rank": rank, }) - if errWithCode, ok := err.(*utils.ErrorWithCode); ok { - if errWithCode.Level() == 0 && errWithCode.IntCode() == 1 { //忽略同名错误 - err = nil - } - } return err } @@ -367,8 +362,8 @@ func handleShopSkuBatchErr(err error) (opResult *BatchOpResult, outErr error) { var data interface{} if err2 := utils.UnmarshalUseNumber([]byte(errExt.ErrMsg()), &data); err2 == nil { if err2 = utils.Map2StructByJson(data, &opResult, true); err2 == nil { - // 将以\u表示的字符串标准化 - errExt.SetErrMsg(string(utils.MustMarshal(opResult))) + // 将以\u表示的字符串标准化,并去掉成功的 + errExt.SetErrMsg(string(utils.MustMarshal(opResult.FailedList))) outErr = errExt } } diff --git a/platformapi/ebaiapi/shop_sku_test.go b/platformapi/ebaiapi/shop_sku_test.go index d55f5e58..c9649afa 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("102493") + result, err := api.ShopCategoryGet("300034") if err != nil { t.Fatal(err) } else { @@ -69,7 +69,7 @@ func TestSkuUploadRTF(t *testing.T) { } func TestSkuCreate(t *testing.T) { - result, err := api.SkuCreate(testShopID, 17, map[string]interface{}{ + result, err := api.SkuCreate("", testShopID, 17, map[string]interface{}{ "name": "测试商品", "status": SkuStatusOnline, "left_num": MaxLeftNum, diff --git a/platformapi/ebaiapi/store_page.go b/platformapi/ebaiapi/store_page.go index e3ddfa4f..c8349d90 100644 --- a/platformapi/ebaiapi/store_page.go +++ b/platformapi/ebaiapi/store_page.go @@ -197,7 +197,7 @@ type PageListInnerShopInfo struct { ShopLabels string `json:"shop_labels"` ShopName string `json:"shop_name"` TakeoutCost float64 `json:"takeout_cost"` - TakeoutPrice int `json:"takeout_price"` + TakeoutPrice float64 `json:"takeout_price"` Type interface{} `json:"type"` WelfareInfo []struct { IconColor struct { @@ -263,39 +263,44 @@ type PageShopInfo struct { Tag []interface{} `json:"tag"` Text string `json:"text"` } `json:"delivery_mode"` - Description string `json:"description"` - DisplayRefundLabel int `json:"display_refund_label"` - Distance int `json:"distance"` - EleBusinessState int `json:"ele_business_state"` - EleID string `json:"ele_id"` - FirstOpenTime *BussinessTimeInfo `json:"first_open_time"` - HitGod int `json:"hit_god"` - ImagePath string `json:"image_path"` - IsColdChain int `json:"is_cold_chain"` - IsDoubleTwelve int `json:"is_double_twelve"` - IsMedicineShop int `json:"is_medicine_shop"` - IsOwnTheme int `json:"is_own_theme"` - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - MedicineQualification []interface{} `json:"medicine_qualification"` - Name string `json:"name"` - NewStyle bool `json:"new_style"` - OTakoutCost int `json:"o_takout_cost"` - OTakoutPrice int `json:"o_takout_price"` - OrderLeadTime interface{} `json:"order_lead_time"` - Phone string `json:"phone"` - PromotionInfo string `json:"promotion_info"` - Qualification string `json:"qualification"` - RecentOrderNum int `json:"recent_order_num"` - ShopID string `json:"shop_id"` - ShopScore float64 `json:"shop_score"` - ShopSourceFrom int `json:"shop_source_from"` - SkuCount int `json:"sku_count"` - TakeoutCost int `json:"takeout_cost"` - TakeoutInvoice int `json:"takeout_invoice"` - TakeoutInvoiceMinPrice string `json:"takeout_invoice_min_price"` - TakeoutOpenTime string `json:"takeout_open_time"` - TakeoutPrice int `json:"takeout_price"` + Description string `json:"description"` + DisplayRefundLabel int `json:"display_refund_label"` + Distance int `json:"distance"` + EleBusinessState int `json:"ele_business_state"` + EleID string `json:"ele_id"` + FirstOpenTime *BussinessTimeInfo `json:"first_open_time"` + HitGod int `json:"hit_god"` + ImagePath string `json:"image_path"` + IsColdChain int `json:"is_cold_chain"` + IsDoubleTwelve int `json:"is_double_twelve"` + IsMedicineShop int `json:"is_medicine_shop"` + IsOwnTheme int `json:"is_own_theme"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + MedicineQualification []*struct { + AptitudeName string `json:"aptitude_name"` + AptitudePhoto []string `json:"aptitude_photo"` + AptitudeType string `json:"aptitude_type"` + LicenseNumber string `json:"license_number"` + } `json:"medicine_qualification"` + Name string `json:"name"` + NewStyle bool `json:"new_style"` + OTakoutCost int `json:"o_takout_cost"` + OTakoutPrice int `json:"o_takout_price"` + OrderLeadTime interface{} `json:"order_lead_time"` + Phone string `json:"phone"` + PromotionInfo string `json:"promotion_info"` + Qualification string `json:"qualification"` + RecentOrderNum int `json:"recent_order_num"` + ShopID string `json:"shop_id"` + ShopScore float64 `json:"shop_score"` + ShopSourceFrom int `json:"shop_source_from"` + SkuCount int `json:"sku_count"` + TakeoutCost float64 `json:"takeout_cost"` + TakeoutInvoice float64 `json:"takeout_invoice"` + TakeoutInvoiceMinPrice string `json:"takeout_invoice_min_price"` + TakeoutOpenTime string `json:"takeout_open_time"` + TakeoutPrice float64 `json:"takeout_price"` } func (a *API) AccessStorePage2(subURL string, params map[string]interface{}, isPost bool, cookies map[string]string) (retVal map[string]interface{}, err error) { @@ -515,22 +520,45 @@ func (a *API) PageGetCustomCatList(baiduShopID int64) (catList []map[string]inte return nil, err } -func (a *API) GetStoreList(lng string, lat string) (retVal map[string]interface{}, err error) { +func (a *API) GetStoreList(lng string, lat string, pageNo, pageSize int) (shopListInfo *PageListInfo, err error) { + if pageNo <= 0 { + pageNo = 1 + } + if pageSize > 20 || pageSize <= 0 { + pageSize = 20 + } params := map[string]interface{}{ "channel": "kitchen", - "pn": 1, - "rn": 999, + "rn": pageSize, + "pn": pageNo, "lng": lng, "lat": lat, } - retVal, err = a.AccessStorePageNoCookie("/newretail/main/shoplist", params) - return retVal, err + result, err := a.AccessStorePageNoCookie("newretail/main/shoplist", params) + if err == nil && result != nil { + err = utils.Map2StructByJson(result, &shopListInfo, true) + } + return shopListInfo, err } func (a *API) GetStoreList2(lng float64, lat float64) (shopListInfo *PageListInfo, err error) { - retVal, err := a.GetStoreList(fmt.Sprintf("%.6f", lng), fmt.Sprintf("%.6f", lat)) - if err == nil { - err = utils.Map2StructByJson(retVal, &shopListInfo, true) + pageSize := 20 + pageNo := 1 + for { + retVal, err2 := a.GetStoreList(fmt.Sprintf("%.6f", lng), fmt.Sprintf("%.6f", lat), pageNo, pageSize) + if err = err2; err == nil { + if shopListInfo == nil { + shopListInfo = retVal + } else { + shopListInfo.ShopList = append(shopListInfo.ShopList, retVal.ShopList...) + } + if len(retVal.ShopList) < pageSize { + break + } + pageNo++ + } else { + break + } } return shopListInfo, err } diff --git a/platformapi/ebaiapi/store_page_test.go b/platformapi/ebaiapi/store_page_test.go index ca6bc4b4..7537fe8f 100644 --- a/platformapi/ebaiapi/store_page_test.go +++ b/platformapi/ebaiapi/store_page_test.go @@ -107,7 +107,7 @@ func TestGetShopHealthByDetail(t *testing.T) { } func TestGetStoreList(t *testing.T) { - result, err := api.GetStoreList("104.057218", "30.6949") + result, err := api.GetStoreList("104.057218", "30.6949", 0, 0) if err != nil { t.Fatal(err) } @@ -115,7 +115,7 @@ func TestGetStoreList(t *testing.T) { } func TestGetStoreList2(t *testing.T) { - result, err := api.GetStoreList2(120.074911, 29.306863) + result, err := api.GetStoreList2(104.010554, 30.637072) if err != nil { t.Fatal(err) } diff --git a/platformapi/jdapi/jdapi.go b/platformapi/jdapi/jdapi.go index 7c1b5e64..2c78e6c8 100644 --- a/platformapi/jdapi/jdapi.go +++ b/platformapi/jdapi/jdapi.go @@ -1,13 +1,13 @@ package jdapi import ( + "bytes" "crypto/md5" "encoding/json" "fmt" "net/http" "sort" "strings" - "sync" "git.rosy.net.cn/baseapi" @@ -66,9 +66,6 @@ type API struct { appSecret string client *http.Client config *platformapi.APIConfig - - locker sync.RWMutex - storeCookie string } var ( @@ -105,7 +102,8 @@ var ( KeyOutStationNo, KeyStationNo, "StoreNo", - "outSkuId", + KeyOutSkuId, + KeySkuId, } ) @@ -120,13 +118,14 @@ func (a *API) signParams(jdParams map[string]interface{}) string { } sort.Strings(keys) - allStr := a.appSecret + buf := &bytes.Buffer{} + buf.WriteString(a.appSecret) for _, k := range keys { - allStr += k + fmt.Sprint(jdParams[k]) + buf.WriteString(k) + buf.WriteString(fmt.Sprint(jdParams[k])) } - allStr = allStr + a.appSecret - - return fmt.Sprintf("%X", md5.Sum([]byte(allStr))) + buf.WriteString(a.appSecret) + return fmt.Sprintf("%X", md5.Sum(buf.Bytes())) } func New(token, appKey, appSecret string, config ...*platformapi.APIConfig) *API { @@ -143,6 +142,13 @@ func New(token, appKey, appSecret string, config ...*platformapi.APIConfig) *API } } +func NewPageOnly(cookie string, config ...*platformapi.APIConfig) *API { + api := New("", "", "", config...) + api.SetCookie(accessStorePageCookieName, cookie) + api.SetCookie(accessStorePageCookieName2, cookie) + return api +} + func (a *API) AccessAPI2(apiStr string, jdParams map[string]interface{}, traceInfo string) (retVal map[string]interface{}, err error) { params := make(map[string]interface{}) params["v"] = "1.0" @@ -267,59 +273,60 @@ func (a *API) AccessAPINoPage2(apiStr string, jdParams map[string]interface{}, k func (a *API) AccessAPINoPage(apiStr string, jdParams map[string]interface{}, keyToRemove, keyToKeep []string, resultParser func(data map[string]interface{}) (interface{}, error)) (interface{}, error) { return a.AccessAPINoPage2(apiStr, jdParams, keyToRemove, keyToKeep, resultParser, "") } +func genNormalHavePageResultParser(dataKey string) (handler PageResultParser) { + return func(data map[string]interface{}, totalCount int) ([]interface{}, int, error) { + var result map[string]interface{} + var retVal []interface{} -func normalHavePageResultParser(data map[string]interface{}, totalCount int) ([]interface{}, int, error) { - var result map[string]interface{} - var retVal []interface{} - - tempResult := data["result"] - if resultStr, ok := tempResult.(string); ok { - if err := utils.UnmarshalUseNumber([]byte(resultStr), &tempResult); err != nil { - return nil, 0, platformapi.ErrResponseDataFormatWrong - } - } - - result = tempResult.(map[string]interface{}) - - if totalCount == 0 { - for _, totalCountKey := range havePageTotalCountKeys { - if totalCount2, ok := result[totalCountKey]; ok { - totalCountInt64, _ := totalCount2.(json.Number).Int64() - totalCount = int(totalCountInt64) - if totalCount == 0 { - return make([]interface{}, 0), 0, nil - } - break + tempResult := data[dataKey] + if resultStr, ok := tempResult.(string); ok { + if err := utils.UnmarshalUseNumber([]byte(resultStr), &tempResult); err != nil { + return nil, 0, platformapi.ErrResponseDataFormatWrong } } + + result = tempResult.(map[string]interface{}) + if totalCount == 0 { - baseapi.SugarLogger.Errorf("can not find totalCount key, data:%v", result) - return nil, 0, platformapi.ErrResponseDataFormatWrong - } - } - - for _, inner2ResultKey := range havePageInner2DataKeys { - if inner2Result, ok := result[inner2ResultKey]; ok { - if inner2Result == nil { - retVal = nil - } else if inner2ResultStr, ok := inner2Result.(string); ok { - err := utils.UnmarshalUseNumber([]byte(inner2ResultStr), &retVal) - if err != nil { - return nil, 0, platformapi.ErrResponseDataFormatWrong + for _, totalCountKey := range havePageTotalCountKeys { + if totalCount2, ok := result[totalCountKey]; ok { + totalCountInt64, _ := totalCount2.(json.Number).Int64() + totalCount = int(totalCountInt64) + if totalCount == 0 { + return make([]interface{}, 0), 0, nil + } + break } - } else { - retVal = inner2Result.([]interface{}) } - return retVal, totalCount, nil + if totalCount == 0 { + baseapi.SugarLogger.Errorf("can not find totalCount key, data:%v", result) + return nil, 0, platformapi.ErrResponseDataFormatWrong + } } + + for _, inner2ResultKey := range havePageInner2DataKeys { + if inner2Result, ok := result[inner2ResultKey]; ok { + if inner2Result == nil { + retVal = nil + } else if inner2ResultStr, ok := inner2Result.(string); ok { + err := utils.UnmarshalUseNumber([]byte(inner2ResultStr), &retVal) + if err != nil { + return nil, 0, platformapi.ErrResponseDataFormatWrong + } + } else { + retVal = inner2Result.([]interface{}) + } + return retVal, totalCount, nil + } + } + baseapi.SugarLogger.Errorf("can not find result key, data:%v", result) + return nil, 0, platformapi.ErrResponseDataFormatWrong } - baseapi.SugarLogger.Errorf("can not find result key, data:%v", result) - return nil, 0, platformapi.ErrResponseDataFormatWrong } func (a *API) AccessAPIHavePage(apiStr string, jdParams map[string]interface{}, keyToRemove, keyToKeep []string, pageResultParser PageResultParser) ([]interface{}, int, error) { if pageResultParser == nil { - pageResultParser = normalHavePageResultParser + pageResultParser = genNormalHavePageResultParser("result") } localJdParams := make(map[string]interface{}) diff --git a/platformapi/jdapi/jdapi_test.go b/platformapi/jdapi/jdapi_test.go index b6bd0372..957260c2 100644 --- a/platformapi/jdapi/jdapi_test.go +++ b/platformapi/jdapi/jdapi_test.go @@ -23,11 +23,11 @@ func init() { // sandbox api = New("df97f334-f7d8-4b36-9664-5784d8ae0baf", "06692746f7224695ad4788ce340bc854", "d6b42a35a7414a5490d811654d745c84") // prod - // api = New("ccb10daf-e6f5-4a58-ada5-b97f9073a137", "1dba76d40cac446ca500c0391a0b6c9d", "a88d031a1e7b462cb1579f12e97fe7f4") + // api = New("77e703b7-7997-441b-a12a-2e522efb117a", "1dba76d40cac446ca500c0391a0b6c9d", "a88d031a1e7b462cb1579f12e97fe7f4") // 天天果园 // api = New("84541069-fbe2-424b-b625-9b2ba1d4c9e6", "5d5577a2506f41b8b4ec520ba83490f5", "0b01b9eeb15b41dab1c3d05d95c17a26") - const cookieValue = "YYJV3NHVBPHLD36FWP6F3EM5PTXJ2XZQS7U4HWRIDPP4IWGUKUIB4XG5N26CZRDLDF7PKOXBPD6BNTUAJLETLZOIWMCVFI3K6MYZIY4QBIXIMXYDJNUKFGJVQTN5356SAD6WPCIHWNQAG7DDMF7L7S3SHDYZP7PPVMRGO4VWG2JRBMKFTOGIWZ5L2XHXC3SXQ4OLX7EL4RKUPZQT6GOH63KE3EVK37L5LG7TGSDGXFQP4377YK72UB5YZG6IJH6PY25YLLCJYPMDSHKPGYBUFJ4MMMKGN6MWB37CP7XVDBBZJ3U462ENTEXH744AWCQCIG2AAE2PKYVHC" + const cookieValue = "YYJV3NHVBPHLD36FWP6F3EM5PTXJ2XZQS7U4HWRIDPP4IWGUKUIB4XG5N26CZRDLDF7PKOXBPD6BNTUAJLETLZOIWMCVFI3K6MYZIY4QBIXIMXYDJNUKFGJVQTN5356SAD6WPCIHWNQAG7DDMF7L7S3SHCT3RM3CQG7IJIPUQ3THS5UIUYWMKINM7ETUOQB7OBPOPZVCT3ZJY55243TDVXLO25PP4UYSPTTPMNQ7HPMWOJKJ3BJWGVHD243MXH7NZWW264TKN5UOCJBSSSOKD2QQII" api.SetCookie(accessStorePageCookieName, cookieValue) api.SetCookie(accessStorePageCookieName2, cookieValue) } diff --git a/platformapi/jdapi/order.go b/platformapi/jdapi/order.go index ee15e905..f1b62f5b 100644 --- a/platformapi/jdapi/order.go +++ b/platformapi/jdapi/order.go @@ -37,11 +37,10 @@ const ( ) const ( - FreightDiscountTypeByShop = 8 // 8:商家满免运费 - FreightDiscountTypeByVip = 7 // 7:VIP免运费 - FreightDiscountTypeByActivity = 12 // 12:首单地推满免运费 - FreightDiscountTypeByCoupons = 15 // 15:运费券 - SelfDeliveryCarrierNo = "2938" // 京东配送方式=商家自送 + FreightDiscountTypeByShop = 8 // 8:商家满免运费 + FreightDiscountTypeByVip = 7 // 7:VIP免运费 + FreightDiscountTypeByActivity = 12 // 12:首单地推满免运费 + FreightDiscountTypeByCoupons = 15 // 15:运费券 ) const ( @@ -59,16 +58,17 @@ const ( ) const ( - AfsReasonTypeGoodsQuality = 201 // 商品质量问题/做工粗糙/有瑕疵 - AfsReasonTypeWrongGoods = 202 // 发错货 - AfsReasonTypeMissingGoods = 203 // 部分商品未收到 - AfsReasonTypeNoGoods = 501 // 全部商品未收到 - AfsReasonTypeDamagedGoods = 208 // 外表损伤(压坏,磕坏等) - AfsReasonTypeGoodsQuantity = 207 // 缺斤少两 - AfsReasonTypeAgreedByMerchant = 209 // 与商家协商一致 + AfsReasonTypeGoodsQuality = 201 // 商品质量问题 + AfsReasonTypeWrongGoods = 202 // 送错货 + AfsReasonTypeMissingGoods = 203 // 缺件少件 + AfsReasonTypeNoGoods = 501 // 全部商品未收到 + AfsReasonTypeDamagedGoods = 208 // 包装脏污有破损 + AfsReasonTypeGoodsQuantity = 207 // 缺斤少两 + // AfsReasonTypeAgreedByMerchant = 209 // 与商家协商一致,2019/09/01取消 + AfsReasonTypeGoodsAbsent = 210 // 商家通知我缺货 AfsReasonTypeGoodsSizeNoSame = 302 // 大小尺寸与商品描述不符 - AfsReasonTypeGoodsColorNoSame = 303 // 颜色/款式/图案与描述不符 - AfsReasonWrongPurchase = 402 // 误购 + AfsReasonTypeGoodsColorNoSame = 303 // 实物与原图不符 + AfsReasonWrongPurchase = 402 // 不想要了 AfsReasonNotReceivedIntime = 502 // 未在时效内送达 ) @@ -310,6 +310,48 @@ type OrderInfo struct { Yn bool `json:"yn"` } +type OrderQueryParam struct { + PageNo int64 `json:"pageNo,omitempty"` // 当前页数,默认:1 + PageSize int `json:"pageSize,omitempty"` // 每页条数,默认:20,最大值100 + OrderID int64 `json:"orderId,omitempty"` + + OrderPurchaseTimeBegin string `json:"orderPurchaseTime_begin,omitempty"` // 购买成交时间-支付(开始) + OrderPurchaseTimeEnd string `json:"orderPurchaseTime_end,omitempty"` // 购买成交时间-支付(结束) + OrderStatus int `json:"orderStatus,omitempty"` + + DeliveryStationNo string `json:"deliveryStationNo,omitempty"` // 到家门店编码 + DeliveryStationNoIsv string `json:"deliveryStationNoIsv,omitempty"` // 商家门店编码 +} + +type SonTag struct { + MqProcessTime string `json:"mqProcessTime"` + OperTime string `json:"operTime"` + TagCode int `json:"tagCode"` + CodeName string `json:"codeName"` + OperPin string `json:"operPin"` + OperName int `json:"operName"` + MsgContent string `json:"msgContent"` +} + +type OrderTrack struct { + SonTagList []*SonTag `json:"sonTagList"` + IsThirdCarry int `json:"isThirdCarry"` + OperFrom string `json:"operFrom"` + MqProcessTime string `json:"mqProcessTime"` + TagIcon string `json:"tagIcon"` + OperTime string `json:"operTime"` + ThirdCarry int `json:"thirdCarry"` + TagTitle string `json:"tagTitle"` + CodeName string `json:"codeName"` + TagCode int `json:"tagCode"` + OperPin string `json:"operPin"` + OperName string `json:"operName"` + Care bool `json:"care"` + MsgContent string `json:"msgContent"` + MsgContentApp string `json:"msgContentApp"` + DoNotCareReason string `json:"doNotCareReason"` +} + var ( ErrCanNotFindOrder = errors.New("can not find order") ) @@ -336,14 +378,6 @@ func (a *API) OrderQuery(jdParams map[string]interface{}) (retVal []interface{}, return retVal, totalCount, err } -func (a *API) OrderQuery2(jdParams map[string]interface{}) (retVal []*OrderInfo, totalCount int, err error) { - orderList, totalCount, err := a.OrderQuery(jdParams) - if err == nil { - err = utils.Map2StructByJson(orderList, &retVal, true) - } - return retVal, totalCount, err -} - // orderFreightMoney 基础运费 // tips 商家承担小费 // merchantPaymentDistanceFreightMoney 取件服务费(开票)(正向单展示远距离运费;售后单则展示达达售后运费) @@ -361,6 +395,28 @@ func (a *API) QuerySingleOrder(orderId string) (map[string]interface{}, error) { return result[0].(map[string]interface{}), nil } +func (a *API) OrderQuery2(queryParam *OrderQueryParam) (retVal []*OrderInfo, totalCount int, err error) { + orderList, totalCount, err := a.OrderQuery(utils.Struct2MapByJson(queryParam)) + if err == nil { + err = utils.Map2StructByJson(orderList, &retVal, true) + } + return retVal, totalCount, err +} + +func (a *API) QuerySingleOrder2(orderID string) (orderInfo *OrderInfo, err error) { + orderList, _, err := a.OrderQuery2(&OrderQueryParam{ + OrderID: utils.Str2Int64(orderID), + }) + if err == nil { + if len(orderList) > 0 { + orderInfo = orderList[0] + } else { + err = ErrCanNotFindOrder + } + } + return orderInfo, err +} + // 商家确认接单接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=169&apiid=c1a15129d1374e9da7fa35487f878604 func (a *API) OrderAcceptOperate(orderId string, isAgreed bool, userName string) error { @@ -588,6 +644,7 @@ func (a *API) AfsSubmit(OrderID, pin, questionTypeCode, questionDesc, questionPi "orderId": OrderID, "pin": utils.GetAPIOperator(pin), "questionTypeCode": questionTypeCode, + "skuList": skuList, } if questionDesc != "" { jdParams["questionDesc"] = questionDesc @@ -624,13 +681,53 @@ func ProcessQuestionPic(questionPic string) (outQuestionPic string) { } // 订单商家加小费接口 +// tips必须是100的倍数,另外必须要等到家下发运单后才能调用,否则会报“订单号srcOrderNo的订单不存在”错 +// 这个tips是累计增的,比如第一次调用tips为100,再一次调用tips为200,则总共的tips就是300 // https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=169&apiid=ed9e3ca7325c4d4d8ceaf959ed0e7a62 -func (a *API) OrderAddTips(OrderID string, tips int, pin string) (err error) { +func (a *API) OrderAddTips(orderID string, tips int, operator string) (err error) { jdParams := map[string]interface{}{ - "orderId": OrderID, - "tips": tips, - "pin": utils.GetAPIOperator(pin), + "orderId": orderID, + "tips": tips, + "operator": utils.GetAPIOperator(operator), } _, err = a.AccessAPINoPage("order/addTips", jdParams, nil, nil, nullResultParser) return err } + +// 根据订单号查询订单跟踪接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=169&apiid=d9d4fd73fba14fd8851a4c054d2ee42e +func (a *API) GetByOrderNoForOaos(orderNo string) (orderTrackList []*OrderTrack, err error) { + jdParams := map[string]interface{}{ + "orderNo": orderNo, + } + result, err := a.AccessAPINoPage("orderTrace/getByOrderNoForOaos", jdParams, nil, nil, genNoPageResultParser("code", "msg", "orderTrackList", "0")) + if err == nil { + err = utils.Map2StructByJson(result, &orderTrackList, false) + } + return orderTrackList, err +} + +// 新版根据订单号查询订单跟踪接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=169&apiid=6450cd91dd5b4dc0bb6a6cd17af6d0a4 +func (a *API) GetByOrderNoForOaosNew(orderID string) (orderTrackList []*OrderTrack, err error) { + jdParams := map[string]interface{}{ + "orderId": orderID, + } + result, err := a.AccessAPINoPage("orderTrace/getByOrderNoForOaosNew", jdParams, nil, nil, genNoPageResultParser("code", "detail", "result", "0")) + if err == nil { + err = utils.Map2StructByJson(result.(map[string]interface{})["orderTrackList"], &orderTrackList, false) + } + return orderTrackList, err +} + +// 订单自提码核验接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=169&apiid=428fa2cb66784b64a85db36ec2972ff9 +func (a *API) CheckSelfPickCode(selfPickCode, orderID, operPin string) (err error) { + jdParams := map[string]interface{}{ + "selfPickCode": selfPickCode, + "orderId": orderID, + "operPin": operPin, + } + _, err = a.AccessAPINoPage("ocs/checkSelfPickCode", jdParams, nil, nil, nullResultParser) + return err +} diff --git a/platformapi/jdapi/order_test.go b/platformapi/jdapi/order_test.go index 18e65ff6..b95119c4 100644 --- a/platformapi/jdapi/order_test.go +++ b/platformapi/jdapi/order_test.go @@ -10,7 +10,8 @@ import ( ) func TestQuerySingleOrder(t *testing.T) { - retVal, err := api.QuerySingleOrder("815536199000222") + retVal, err := api.QuerySingleOrder("921160248000222") + t.Log(utils.Format4Output(retVal, false)) if err != nil { t.Error(err) } @@ -218,3 +219,43 @@ func TestOrderShoudSettlementService2(t *testing.T) { } sugarLogger.Debug(utils.Format4Output(result, false)) } + +func TestOrderAddTips(t *testing.T) { + err := api.OrderAddTips("918092290000042", 50, "xjh") + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestOrderQuery2(t *testing.T) { + orderList, _, err := api.OrderQuery2(&OrderQueryParam{ + OrderID: 918092290000042, + }) + t.Log(utils.Format4Output(orderList, false)) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestGetByOrderNoForOaos(t *testing.T) { + orderList, err := api.GetByOrderNoForOaos("921235438000341") + t.Log(utils.Format4Output(orderList, false)) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestGetByOrderNoForOaosNew(t *testing.T) { + orderList, err := api.GetByOrderNoForOaosNew("921235438000341") + t.Log(utils.Format4Output(orderList, false)) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestCheckSelfPickCode(t *testing.T) { + err := api.CheckSelfPickCode("020606", "921235438000341", "test") + if err != nil { + t.Fatal(err.Error()) + } +} diff --git a/platformapi/jdapi/promotion_audit.go b/platformapi/jdapi/promotion_audit.go index e993891f..a0c9d2a2 100644 --- a/platformapi/jdapi/promotion_audit.go +++ b/platformapi/jdapi/promotion_audit.go @@ -106,3 +106,18 @@ func (a *API) QueryPromotionInfo(promotionInfoId int64) (promotionInfo *Promotio } return promotionInfo, err } + +// 根据到家商品ID查询单品级优惠活动列表接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=196&apiid=d73baba02c484109a3c3c1b1236ca13d +func (a *API) QueryPromotionSku(promotionType int, skuID int64, promotionState int /*, pageNo int64, pageSize int*/) (skuResultList []*PromotionLspQuerySkuResult, err error) { + jdParams := map[string]interface{}{ + "promotionType": promotionType, + "skuId": skuID, + "promotionState": promotionState, + } + result, _, err := a.AccessAPIHavePage("singlePromote/queryPromotionSku", jdParams, nil, nil, genNormalHavePageResultParser("data")) + if err != nil { + err = utils.Map2StructByJson(result, &skuResultList, false) + } + return skuResultList, err +} diff --git a/platformapi/jdapi/promotion_audit_test.go b/platformapi/jdapi/promotion_audit_test.go index b1b670f9..70ec955a 100644 --- a/platformapi/jdapi/promotion_audit_test.go +++ b/platformapi/jdapi/promotion_audit_test.go @@ -21,3 +21,24 @@ func TestQueryPromotionInfo(t *testing.T) { } t.Log(utils.Format4Output(result, false)) } + +func TestQueryPromotionSku(t *testing.T) { + skuIDs := []int64{ + 2023335105, + // 2023335104, + // 2023335088, + // 2023335057, + // 2023335098, + // 2023335020, + } + for _, skuID := range skuIDs { + list, err := api.QueryPromotionSku(PromotionTypeDirectDown, skuID, PromotionStateConfirmed) + t.Log(utils.Format4Output(list, false)) + if err != nil { + t.Fatal(err) + } + // for _, v := range list { + // CancelPromotionSkuSingle() + // } + } +} diff --git a/platformapi/jdapi/promotion_sku_test.go b/platformapi/jdapi/promotion_sku_test.go index de1865b1..44eb1135 100644 --- a/platformapi/jdapi/promotion_sku_test.go +++ b/platformapi/jdapi/promotion_sku_test.go @@ -76,3 +76,70 @@ func TestCreatePromotionSkuLimitTime(t *testing.T) { t.Fatal(err) } } + +func TestCancelPromotion(t *testing.T) { + promotionIDs := []int64{ + 24636389, + 24753178, + 24754087, + 24970355, + 24970383, + 24970389, + 25439508, + 25444342, + 25869363, + 25871946, + 26291079, + 27036663, + 27407558, + 27407649, + 27424152, + 27424181, + 27424247, + 27424568, + 27508490, + 27555133, + 27674289, + 30969372, + 41809699, + 41810076, + 41810296, + 41811111, + 44179379, + 54080816, + 54080829, + 54080842, + 55346987, + 55347123, + 55347340, + 55348499, + 55348706, + 55348999, + 55349177, + 56723852, + 56724283, + 56725840, + 56725955, + 56726053, + 60713479, + 60714768, + 60719242, + 68818338, + } + + for _, v := range promotionIDs { + promotionInfo, err := api.QueryPromotionInfo(v) + if err != nil { + t.Fatal(err) + } + // t.Log(promotionInfo.PromotionType) + if promotionInfo.PromotionType == PromotionTypeLimitedTime { + err = api.CancelPromotionLimitTime(v, "", utils.GetUUID()) + } else { + err = api.CancelPromotionSingle(v, "", utils.GetUUID()) + } + if err != nil { + t.Fatal(err) + } + } +} diff --git a/platformapi/jdapi/sku.go b/platformapi/jdapi/sku.go index 9c5239db..abb122b4 100644 --- a/platformapi/jdapi/sku.go +++ b/platformapi/jdapi/sku.go @@ -1,12 +1,10 @@ package jdapi import ( - "fmt" "regexp" "strings" "git.rosy.net.cn/baseapi/utils" - "git.rosy.net.cn/jx-callback/globals" ) const ( @@ -65,12 +63,14 @@ const ( const ( MaxBatchSize4BatchUpdateOutSkuId = 50 + MaxPageSize4QuerySku = 50 + MaxSkuIDsCount4QueryListBySkuIds = 25 ) const ( - SkuFixedStatusOnline = 1 - SkuFixedStatusOffline = 2 - SkuFixedStatusDeleted = 4 + SkuFixedStatusOnline = 1 // 上架 + SkuFixedStatusOffline = 2 // 下架 + SkuFixedStatusDeleted = 4 // 删除 ) const ( @@ -79,12 +79,21 @@ const ( CreateSpuResultFailed = 3 ) +const ( + IsFilterDelTrue = "0" // 0代表不查已删除商品 +) + const ( MaxSkuNameCharCount = 45 // skuname最长字符数 SaleAttrIDBase = 1001 SaleAttrValueIDBase = 10 ) +const ( + ImgTypeMain = 1 // 商品图片 + ImgTypeDetail = 2 // 详情图片 +) + type SkuIDPair struct { SkuId int64 `json:"skuId"` OutSkuId string `json:"outSkuId"` @@ -119,11 +128,11 @@ type CategoryInfo struct { } type CreateByUpcParam struct { - Upc string - OutSkuId string - Price int // 单位为分 - ShopCategoryId int64 - IsSale bool + UniqueUpc string `json:"uniqueCode"` // 商品UPC码 + OutSku string `json:"outSku"` // 商家商品编码,商家系统中唯一编码,限1-35字符,与到家商品编码一对一对应 + JdPrice string `json:"jdPrice"` // 商家商品价格(单位:元),用于初始商品门店价格,所有的商品门店价格都会初始化成该值。后续修改商品门店价格需要通过价格类接口修改。 + ShopCategoryID int64 `json:"shopCategoryId"` // 商家店内分类编码,店内分类编码通过查询商家店内分类信息接口获取 + IsSale bool `json:"isSale"` // 门店商品可售状态,true为可售,false为不可售,默认为可售。 } type CreateByUpcPair struct { @@ -131,13 +140,58 @@ type CreateByUpcPair struct { SkuId int64 } +type QuerySkuParam struct { + SkuName string `json:"skuName,omitempty"` + UpcCode string `json:"upcCode,omitempty"` + SkuID int64 `json:"skuId,omitempty"` + IsFilterDel string `json:"isFilterDel,omitempty"` // 是否查询出已删除的上传商品(0代表不查已删除商品,不填则查出全部商品) + PageNo int `json:"pageNo,omitempty"` + PageSize int `json:"pageSize,omitempty"` +} + +type SkuMain struct { + SuperID int64 `json:"superId"` + SkuID int64 `json:"skuId"` + OutSkuID string `json:"outSkuId"` + CategoryID int64 `json:"categoryId"` + BrandID int64 `json:"brandId"` + ShopCategories []int64 `json:"shopCategories"` + SkuName string `json:"skuName"` + Slogan string `json:"slogan"` + FixedStatus int `json:"fixedStatus"` // 商家商品上下架状态(1:上架;2:下架;4:删除;) + FixedUpTime string `json:"fixedUpTime"` + OrgCode int `json:"orgCode"` + SellCities []int64 `json:"sellCities"` + SkuPrice int `json:"skuPrice"` + Weight float64 `json:"weight"` +} + +type QueryListBySkuIdsParam struct { + SkuIDs []int64 `json:"skuIds"` + ImgType int `json:"imgType,omitempty"` + HandleStatus []int `json:"handleStatus,omitempty"` +} + +type ImgHandleQueryResult struct { + ID string `json:"id"` + SkuID int64 `json:"skuId"` + IsMain int `json:"isMain"` + ImgType int `json:"imgType"` + SourceImgURL string `json:"sourceImgUrl"` + SkuImgSort int `json:"skuImgSort"` + HandleStatus int `json:"handleStatus"` + HandleStatusStr string `json:"handleStatusStr"` + HandleRemark string `json:"handleRemark"` + HandleErrLog string `json:"handleErrLog"` +} + var ( skuExistReg = regexp.MustCompile(`商家skuId已存在:(\d+)`) ) // 分页查询商品品牌信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=180&apiid=1ca07a3e767649a7a44fc6ea7e9ed8dd -func (a *API) QueryPageBrandInfo(pageNo, pageSize, brandId int, brandName string) (brandList []*BrandInfo, totalCount int, err error) { +func (a *API) QueryPageBrandInfo(pageNo, pageSize int, brandId int64, brandName string) (brandList []*BrandInfo, totalCount int, err error) { if pageNo <= 0 { pageNo = 1 } @@ -320,44 +374,28 @@ func (a *API) BatchUpdateOutSkuId(skuInfoList []*SkuIDPair) (retVal interface{}, // 查询商家已上传商品信息列表接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=180&apiid=e433b95f74524dab91718432c0358977 // pageNo 从1开始 -func (a *API) QuerySkuInfos(skuName string, skuId int64, pageNo, pageSize int, isFilterDel bool) (retVal []map[string]interface{}, totalCount int, err error) { - if pageNo <= 0 { - pageNo = 1 +func (a *API) QuerySkuInfos(queryParam *QuerySkuParam) (skuList []*SkuMain, totalCount int, err error) { + if queryParam.PageNo <= 0 { + queryParam.PageNo = 1 } - if pageSize == 0 { - pageSize = 50 + if queryParam.PageSize == 0 { + queryParam.PageSize = MaxPageSize4QuerySku } - params := map[string]interface{}{ - KeyPageNo: pageNo, // pageNo好像必须要有值,否则一直不返回 - KeyPageSize: pageSize, - } - if skuName != "" { - params[KeySkuName] = skuName - } - if skuId != 0 { - params[KeySkuId] = skuId - } - if isFilterDel { - params[KeyIsFilterDel] = "1" - } else { - params[KeyIsFilterDel] = "0" - } - result, totalCount, err := a.AccessAPIHavePage("pms/querySkuInfos", params, nil, nil, nil) + result, totalCount, err := a.AccessAPIHavePage("pms/querySkuInfos", utils.Struct2MapByJson(queryParam), nil, nil, nil) if err == nil { - return utils.Slice2MapSlice(result), totalCount, nil + err = utils.Map2StructByJson(result, &skuList, false) } - return nil, 0, err + return skuList, totalCount, err } // 查询商品图片处理结果接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=180&apiid=17506653e03542f9a49023711780c30d -func (a *API) QueryListBySkuIds(skuIds []int64, addParams map[string]interface{}) (retVal []map[string]interface{}, err error) { - result, err := a.AccessAPINoPage("order/queryListBySkuIds", utils.MergeMaps(addParams, utils.Params2Map("skuIds", skuIds)), nil, nil, nil) +func (a *API) QueryListBySkuIds(queryParam *QueryListBySkuIdsParam) (imgList []*ImgHandleQueryResult, err error) { + result, err := a.AccessAPINoPage("order/queryListBySkuIds", utils.Struct2MapByJson(queryParam), nil, nil, nil) if err == nil { - return utils.Slice2MapSlice(result.([]interface{})), nil + err = utils.Map2StructByJson(result, &imgList, false) } - return nil, err - + return imgList, err } // 分页查询京东到家商品前缀库接口 @@ -543,22 +581,12 @@ func (a *API) GetSpuSaleAttr(outSpuId string) (attrList []map[string]interface{} return nil, err } -func (a *API) BatchAddSku(paramList []*CreateByUpcParam) (pairs []*CreateByUpcPair, err error) { - batchSkuRequestList := make([]map[string]interface{}, len(paramList)) - for k, v := range paramList { - batchSkuRequestList[k] = map[string]interface{}{ - "uniqueCode": v.Upc, - "outSku": v.OutSkuId, - "jdPrice": fmt.Sprintf("%.2f", float32(v.Price)/100), - "shopCategoryId": v.ShopCategoryId, - "isSale": v.IsSale, - } - } +func (a *API) BatchAddSku(batchSkuRequestList []*CreateByUpcParam) (pairs []*CreateByUpcPair, err error) { result, err := a.AccessAPINoPage("PmsSkuMainService/batchAddSku", map[string]interface{}{ "batchSkuRequestList": batchSkuRequestList, }, nil, nil, genNoPageResultParser("code", "result", "detail", "0")) if err == nil { - globals.SugarLogger.Debug(utils.Format4Output(result, false)) + // globals.SugarLogger.Debug(utils.Format4Output(result, false)) // todo 这个API在找不到UPC创建失败时,code也是0,底层不能判断失败 if result2, ok := result.([]interface{}); ok && len(result2) > 0 { detail := utils.Slice2MapSlice(result2) diff --git a/platformapi/jdapi/sku_test.go b/platformapi/jdapi/sku_test.go index 950dcbbf..b44cb514 100644 --- a/platformapi/jdapi/sku_test.go +++ b/platformapi/jdapi/sku_test.go @@ -58,7 +58,11 @@ func TestBatchUpdateOutSkuId(t *testing.T) { func TestQuerySkuInfos(t *testing.T) { pageSize := 20 - result, totalCount, err := api.QuerySkuInfos("一个高级商品", 0, 0, pageSize, true) + result, totalCount, err := api.QuerySkuInfos(&QuerySkuParam{ + IsFilterDel: IsFilterDelTrue, + }) + t.Log(utils.Format4Output(result, false)) + t.Log(totalCount) if err != nil { t.Fatal(err) } @@ -73,7 +77,10 @@ func TestQueryListBySkuIds(t *testing.T) { 2018806493, 2018805873, } - result, err := api.QueryListBySkuIds(ids, nil) + result, err := api.QueryListBySkuIds(&QueryListBySkuIdsParam{ + SkuIDs: ids, + }) + t.Log(utils.Format4Output(result, false)) if err != nil { t.Fatal(err) } @@ -152,10 +159,10 @@ func TestGetSpuSaleAttr(t *testing.T) { func TestBatchAddSku(t *testing.T) { paramList := []*CreateByUpcParam{ &CreateByUpcParam{ - Upc: "6948939649102", - OutSkuId: "50001", - Price: 213, - ShopCategoryId: 4247719, + UniqueUpc: "6948939649102", + OutSku: "50001", + JdPrice: "2.13", + ShopCategoryID: 4247719, IsSale: true, }, } diff --git a/platformapi/jdapi/store.go b/platformapi/jdapi/store.go index 71cb7629..e5811e31 100644 --- a/platformapi/jdapi/store.go +++ b/platformapi/jdapi/store.go @@ -29,6 +29,19 @@ const ( KeyStandByPhone = "standByPhone" ) +const ( + CarrierNoCrowdSourcing = 9966 // 众包 + CarrierNoSelfDelivery = 2938 // 自送 + CarrierNoSelfTake = 9999 // 到店自提 +) + +const ( + CoordinateTypeGoogle = 1 // 谷歌 + CoordinateTypeBaidu = 2 // 百度 + CoordinateTypeAutonavi = 3 // 高德 + CoordinateTypeTencent = 4 // 腾讯 +) + type CreateShopResult struct { DeliveryRangeType int `json:"deliveryRangeType"` CoordinatePoints string `json:"coordinatePoints"` @@ -44,6 +57,165 @@ type CityInfo struct { Yn int `json:"yn"` } +type OpStoreParams struct { + // 以下可用于修改(及查询) + StationNo string `json:"stationNo"` // 强制参数,主键 + StationName string `json:"stationName,omitempty"` + OutSystemID string `json:"outSystemId,omitempty"` + Mobile string `json:"mobile,omitempty"` + Phone string `json:"phone,omitempty"` + Lat float64 `json:"lat,omitempty"` + Lng float64 `json:"lng,omitempty"` + City int `json:"city,omitempty"` + County int `json:"county,omitempty"` + StationAddress string `json:"stationAddress,omitempty"` + Operator string `json:"operator"` // 返回值无 + ServiceTimeEnd1 int `json:"serviceTimeEnd1,omitempty"` + ServiceTimeStart1 int `json:"serviceTimeStart1,omitempty"` + ServiceTimeEnd2 int `json:"serviceTimeEnd2,omitempty"` + ServiceTimeStart2 int `json:"serviceTimeStart2,omitempty"` + DeliveryRangeType int8 `json:"deliveryRangeType,omitempty"` // 返回值无,划分配送服务范围的类型(3、圆心半径、2、不规则多边形(手动画范围))。若更新此字段需传完整值。 + CoordinateType int `json:"coordinateType,omitempty"` // 返回值无,使用的地图类型(1,谷歌), (2,百度), (3,高德), (4,腾讯) + DeliveryRangeRadius int `json:"deliveryRangeRadius,omitempty"` // 返回值无,时效服务范围半径(单位:米)(如果服务范围为类型3的话,该字段有值) + CoordinatePoints string `json:"coordinatePoints,omitempty"` // 返回值无,坐标点集合(如果服务范围为类型2的话,该字段有值),每个点以:经度,纬度 的格式表示,用“;”隔开多个点;对于腾讯地图、谷歌地图和高德地图,整个coordinatePoints的长度必须小于2k;对于百度地图,整个coordinatePoints的长度必须小于1k + CloseStatus int `json:"closeStatus"` // 0是有意义的值,所以不能是omitempty + StoreNotice string `json:"storeNotice,omitempty"` + StandByPhone string `json:"standByPhone,omitempty"` +} + +type StoreDetail struct { + // 以下可用于修改(及查询) + StationNo string `json:"stationNo"` // 强制参数,主键 + StationName string `json:"stationName,omitempty"` + OutSystemID string `json:"outSystemId,omitempty"` + Mobile string `json:"mobile,omitempty"` + Phone string `json:"phone,omitempty"` + Lat float64 `json:"lat,omitempty"` + Lng float64 `json:"lng,omitempty"` + City int `json:"city,omitempty"` + County int `json:"county,omitempty"` + StationAddress string `json:"stationAddress,omitempty"` + // Operator string `json:"operator"` // 返回值无 + ServiceTimeEnd1 int `json:"serviceTimeEnd1,omitempty"` + ServiceTimeStart1 int `json:"serviceTimeStart1,omitempty"` + ServiceTimeEnd2 int `json:"serviceTimeEnd2,omitempty"` + ServiceTimeStart2 int `json:"serviceTimeStart2,omitempty"` + // DeliveryRangeType int8 `json:"deliveryRangeType,omitempty"` // 返回值无,划分配送服务范围的类型(3、圆心半径、2、不规则多边形(手动画范围))。若更新此字段需传完整值。 + // CoordinateType int `json:"coordinateType,omitempty"` // 返回值无,使用的地图类型(1,谷歌), (2,百度), (3,高德), (4,腾讯) + // DeliveryRangeRadius int `json:"deliveryRangeRadius,omitempty"` // 返回值无,时效服务范围半径(单位:米)(如果服务范围为类型3的话,该字段有值) + // CoordinatePoints string `json:"coordinatePoints,omitempty"` // 返回值无,坐标点集合(如果服务范围为类型2的话,该字段有值),每个点以:经度,纬度 的格式表示,用“;”隔开多个点;对于腾讯地图、谷歌地图和高德地图,整个coordinatePoints的长度必须小于2k;对于百度地图,整个coordinatePoints的长度必须小于1k + CloseStatus int `json:"closeStatus"` // 0是有意义的值,所以不能是omitempty + StoreNotice string `json:"storeNotice,omitempty"` + StandByPhone string `json:"standByPhone,omitempty"` + + // 以下仅用于查询 + AllowRangeOptimized int `json:"allowRangeOptimized"` + CacheKey4StoreList string `json:"cacheKey4StoreList"` + CarrierNo int `json:"carrierNo"` + CityName string `json:"cityName"` + Coordinate string `json:"coordinate"` + CoordinateAddress string `json:"coordinateAddress"` + CountyName string `json:"countyName"` + CreatePin string `json:"createPin"` + CreateTime *utils.JavaDate `json:"createTime"` + ID int64 `json:"id"` + IndustryTag int `json:"industryTag"` + InnerNoStatus int `json:"innerNoStatus"` + IsAutoOrder int `json:"isAutoOrder"` // 是否自动接单,0:是1:否 + IsMembership int `json:"isMembership"` + IsNoPaper int `json:"isNoPaper"` + OnlineTime *utils.JavaDate `json:"onlineTime"` + OrderAging int `json:"orderAging"` + OrderNoticeType int `json:"orderNoticeType"` + PreWarehouse int `json:"preWarehouse"` + Province int `json:"province"` + ProvinceName string `json:"provinceName"` + QualifyStatus int `json:"qualifyStatus"` + RegularFlag int `json:"regularFlag"` + StationDeliveryStatus int `json:"stationDeliveryStatus"` + SupportInvoice int `json:"supportInvoice"` + SupportOfflinePurchase int `json:"supportOfflinePurchase"` + TestMark int `json:"testMark"` + TimeAmType int `json:"timeAmType"` + TimePmType int `json:"timePmType"` + Ts *utils.JavaDate `json:"ts"` + UpdatePin string `json:"updatePin"` + UpdateTime *utils.JavaDate `json:"updateTime"` + VenderID string `json:"venderId"` + VenderName string `json:"venderName"` + WareType int `json:"wareType"` + WhiteDelivery bool `json:"whiteDelivery"` + Yn int8 `json:"yn"` // 门店状态,0启用,1禁用 +} + +type OrderProdCommentVo struct { + CreatePin string `json:"createPin"` + CreateTime *utils.JavaDate `json:"createTime"` + ID int64 `json:"id"` + IsPraise int `json:"isPraise"` + Score int `json:"score"` + ScoreLevel int `json:"scoreLevel"` + SkuID int64 `json:"skuId"` + SkuName string `json:"skuName"` + UpdatePin string `json:"updatePin"` + UpdateTime *utils.JavaDate `json:"updateTime"` + Yn int8 `json:"yn"` +} + +type OrderCommentInfo struct { + AppVersion string `json:"appVersion"` + CreatePin string `json:"createPin"` + CreateTime *utils.JavaDate `json:"createTime"` + DeliveryCarrierNo string `json:"deliveryCarrierNo"` + DeliveryConfirmTime *utils.JavaDate `json:"deliveryConfirmTime"` + DeliveryConfirmTime2 *utils.JavaDate `json:"deliveryConfirmTime2"` + DeliveryDifTime int `json:"deliveryDifTime"` + DeliveryDifTime2 int `json:"deliveryDifTime2"` + DeliveryManNo string `json:"deliveryManNo"` + DocID interface{} `json:"docId"` + ID int64 `json:"id"` + IsUpdate int `json:"isUpdate"` + OrderID int64 `json:"orderId"` + OrderProdCommentVoList []*OrderProdCommentVo `json:"orderProdCommentVoList"` + OrderType int `json:"orderType"` + OrgCode int `json:"orgCode"` + OrgCommentContent string `json:"orgCommentContent"` + OrgCommentStatus int `json:"orgCommentStatus"` + OrgCommentTime *utils.JavaDate `json:"orgCommentTime"` + OrgName string `json:"orgName"` + Score3 int `json:"score3"` + Score3Content string `json:"score3Content"` + Score4 int `json:"score4"` + Score4Content string `json:"score4Content"` + ScoreLevel int `json:"scoreLevel"` + StoreID int64 `json:"storeId"` + StoreName string `json:"storeName"` + Tags []string `json:"tags"` + TagsKey []string `json:"tagsKey"` + UpdatePin string `json:"updatePin"` + UpdateTime *utils.JavaDate `json:"updateTime"` + VenderTageKey []string `json:"venderTageKey"` + VenderTags []string `json:"venderTags"` + Yn int8 `json:"yn"` +} + +type StoreDeliveryRange struct { + Adresses string `json:"adresses"` + CreatePin string `json:"createPin"` + CreateTime *utils.JavaDate `json:"createTime"` + DeliveryRange string `json:"deliveryRange"` + DeliveryRangeRadius int `json:"deliveryRangeRadius"` + DeliveryRangeType int `json:"deliveryRangeType"` + DeliveryServiceType int `json:"deliveryServiceType"` + ID int `json:"id"` + StationNo string `json:"stationNo"` + Ts *utils.JavaDate `json:"ts"` + UpdatePin string `json:"updatePin"` + UpdateTime *utils.JavaDate `json:"updateTime"` + VenderID string `json:"venderId"` + Yn int8 `json:"yn"` +} + func (a *API) GetAllCities() (cities []*CityInfo, err error) { result, err := a.AccessAPINoPage("address/allcities", nil, nil, nil, genNoPageResultParser("code", "msg", "result", "0")) if err == nil { @@ -69,23 +241,9 @@ func (a *API) GetStationsByVenderId() ([]string, error) { // 新增不带资质的门店信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=194&apiid=93acef27c3aa4d8286d5c8c26b493629 -func (a *API) CreateStore(stationName, phone string, city, county int, stationAddress, userName string, serviceTimeStart1, serviceTimeEnd1 int, lng, lat float64, deliveryRangeType, coordinateType int, standByPhone string, addParams map[string]interface{}) (*CreateShopResult, error) { - params := map[string]interface{}{ - KeyStationName: stationName, - KeyPhone: phone, - KeyCity: city, - KeyCounty: county, - KeyStationAddress: stationAddress, - KeyOperator: utils.GetAPIOperator(userName), - KeyServiceTimeStart1: serviceTimeStart1, - KeyServiceTimeEnd2: serviceTimeEnd1, - KeyLng: lng, - KeyLat: lat, - KeyDeliveryRangeType: deliveryRangeType, - KeyCoordinateType: coordinateType, - KeyStandByPhone: standByPhone, - } - result, err := a.AccessAPINoPage("store/createStore", utils.MergeMaps(params, addParams), nil, nil, func(data map[string]interface{}) (interface{}, error) { +func (a *API) CreateStore(createParams *OpStoreParams) (*CreateShopResult, error) { + createParams.Operator = utils.GetAPIOperator(createParams.Operator) + result, err := a.AccessAPINoPage("store/createStore", utils.Struct2MapByJson(createParams), nil, nil, func(data map[string]interface{}) (interface{}, error) { innerCode := data["code"].(string) if data["code"] == "0" { mapData := data["data"].(map[string]interface{}) @@ -102,37 +260,66 @@ func (a *API) CreateStore(stationName, phone string, city, county int, stationAd // 根据到家门店编码查询门店基本信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=194&apiid=4c48e347027146d5a103e851055cb1a7 -func (a *API) GetStoreInfoByStationNo(storeNo string) (map[string]interface{}, error) { +// func (a *API) GetStoreInfoByStationNo(storeNo string) (map[string]interface{}, error) { +// result, err := a.AccessAPINoPage("storeapi/getStoreInfoByStationNo", utils.Params2Map("StoreNo", storeNo), nil, nil, nil) +// if err == nil { +// return result.(map[string]interface{}), nil +// } +// return nil, err +// } + +func (a *API) GetStoreInfoByStationNo2(storeNo string) (storeDetail *StoreDetail, err error) { result, err := a.AccessAPINoPage("storeapi/getStoreInfoByStationNo", utils.Params2Map("StoreNo", storeNo), nil, nil, nil) if err == nil { - return result.(map[string]interface{}), nil + err = utils.Map2StructByJson(result, &storeDetail, false) } - return nil, err + return storeDetail, err } // 修改门店基础信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=194&apiid=2600369a456446f0921e918f3d15e96a -func (a *API) UpdateStoreInfo4Open(storeNo, userName string, addParams map[string]interface{}) error { - jdParams := map[string]interface{}{ - "stationNo": storeNo, - "operator": utils.GetAPIOperator(userName), +// func (a *API) UpdateStoreInfo4Open(storeNo, userName string, addParams map[string]interface{}) error { +// jdParams := map[string]interface{}{ +// "stationNo": storeNo, +// "operator": utils.GetAPIOperator(userName), +// } +// jdParams = utils.MergeMaps(jdParams, addParams) +// _, err := a.AccessAPINoPage("store/updateStoreInfo4Open", jdParams, nil, nil, nullResultParser) +// return err +// } + +func (a *API) UpdateStoreInfo4Open2(updateParams *OpStoreParams, modifyCloseStatus bool) (err error) { + updateParams.Operator = utils.GetAPIOperator(updateParams.Operator) + mapData := utils.Struct2MapByJson(updateParams) + if !modifyCloseStatus { + delete(mapData, "closeStatus") } - jdParams = utils.MergeMaps(jdParams, addParams) - _, err := a.AccessAPINoPage("store/updateStoreInfo4Open", jdParams, nil, nil, nullResultParser) + _, err = a.AccessAPINoPage("store/updateStoreInfo4Open", mapData, nil, nil, nullResultParser) return err } // 根据订单号查询商家门店评价信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=194&apiid=bd23397725bb4e74b69e2f2fa1c88d43 -func (a *API) GetCommentByOrderId(orderId int64) (map[string]interface{}, error) { +// func (a *API) GetCommentByOrderId(orderId int64) (map[string]interface{}, error) { +// jdParams := map[string]interface{}{ +// "orderId": orderId, +// } +// result, err := a.AccessAPINoPage("commentOutApi/getCommentByOrderId", jdParams, nil, nil, genNoPageResultParser("code", "msg", "result", "200")) +// if err == nil { +// return result.(map[string]interface{}), nil +// } +// return nil, err +// } + +func (a *API) GetCommentByOrderId2(orderId int64) (orderComment *OrderCommentInfo, err error) { jdParams := map[string]interface{}{ "orderId": orderId, } result, err := a.AccessAPINoPage("commentOutApi/getCommentByOrderId", jdParams, nil, nil, genNoPageResultParser("code", "msg", "result", "200")) if err == nil { - return result.(map[string]interface{}), nil + err = utils.Map2StructByJson(result, &orderComment, false) } - return nil, err + return orderComment, err } // 商家门店评价信息回复接口 @@ -169,15 +356,26 @@ func (a *API) UpdateStoreConfig4Open(stationNo string, isAutoOrder bool) (bool, // 获取门店配送范围接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=194&apiid=8f6d0ac75d734c68bf5bd2a09f376a78 -func (a *API) GetDeliveryRangeByStationNo(stationNo string) (map[string]interface{}, error) { +// func (a *API) GetDeliveryRangeByStationNo(stationNo string) (map[string]interface{}, error) { +// jdParams := map[string]interface{}{ +// "stationNo": stationNo, +// } +// result, err := a.AccessAPINoPage("store/getDeliveryRangeByStationNo", jdParams, nil, nil, nil) +// if err == nil { +// return result.(map[string]interface{}), nil +// } +// return nil, err +// } + +func (a *API) GetDeliveryRangeByStationNo2(stationNo string) (deliveryRange *StoreDeliveryRange, err error) { jdParams := map[string]interface{}{ "stationNo": stationNo, } result, err := a.AccessAPINoPage("store/getDeliveryRangeByStationNo", jdParams, nil, nil, nil) if err == nil { - return result.(map[string]interface{}), nil + err = utils.Map2StructByJson(result, &deliveryRange, false) } - return nil, err + return deliveryRange, err } // 私有函数 diff --git a/platformapi/jdapi/store_page.go b/platformapi/jdapi/store_page.go index c4e56f73..b162c194 100644 --- a/platformapi/jdapi/store_page.go +++ b/platformapi/jdapi/store_page.go @@ -1,9 +1,13 @@ package jdapi import ( + "bytes" "crypto/md5" "fmt" + "io/ioutil" + "mime/multipart" "net/http" + "net/url" "regexp" "strings" @@ -36,9 +40,9 @@ type CorporationInfo struct { EconKind string `json:"econ_kind"` EndDate string `json:"end_date"` TermEnd string `json:"term_end"` - NeedID bool `json:"needID"` - Address string `json:"address"` - Partners []struct { + // NeedID bool `json:"needID"` + Address string `json:"address"` + Partners []struct { IdentifyType string `json:"identify_type"` ShouldCapiItems []interface{} `json:"should_capi_items"` StockType string `json:"stock_type"` @@ -136,14 +140,108 @@ type PageShopInfo struct { StoreShareURL string `json:"storeShareUrl"` } -var ( - monthSaleNumReg = regexp.MustCompile(`(\d+)([千|万])`) +type PageSku struct { + ButtonEnable bool `json:"buttonEnable"` + CatID string `json:"catId"` + FixedStatus bool `json:"fixedStatus"` + FuncIndicatins string `json:"funcIndicatins"` + H5SwichItem struct { + IsLeadApp bool `json:"isLeadApp"` + } `json:"h5SwichItem"` + HasSaleAttr bool `json:"hasSaleAttr"` + IconType int `json:"iconType"` + Image []*SkuPageImg `json:"image"` + InCartCount int `json:"inCartCount"` + IsInScope bool `json:"isInScope"` + IsRemind bool `json:"isRemind"` + MarkingPrice string `json:"markingPrice"` + Name string `json:"name"` + OrgCode string `json:"orgCode"` + Prescription bool `json:"prescription"` + PriceUnit string `json:"priceUnit"` + ProductComment struct { + CommentNum string `json:"commentNum"` + GoodRate float64 `json:"goodRate"` + GoodRating string `json:"goodRating"` + HasMore bool `json:"hasMore"` + TotalScore string `json:"totalScore"` + } `json:"productComment"` + ProductInfoType int `json:"productInfoType"` + ProductType int `json:"productType"` + ProductTypeEnum string `json:"productTypeEnum"` + ShareProductURL string `json:"shareProductUrl"` + ShowState int `json:"showState"` + ShowStateEnum string `json:"showStateEnum"` + ShowStateName string `json:"showStateName"` + ShowTimLine bool `json:"showTimLine"` + SkuID int64 `json:"skuId"` + SkuPriceVO struct { + BasicPrice string `json:"basicPrice"` + MkPrice string `json:"mkPrice"` + Promotion int `json:"promotion"` + RealTimePrice string `json:"realTimePrice"` + SkuID string `json:"skuId"` + } `json:"skuPriceVO"` + Standard string `json:"standard"` + StoreInfo struct { + Show bool `json:"show"` + StoreID string `json:"storeId"` + } `json:"storeInfo"` + Subtitle string `json:"subtitle"` + Tags []string `json:"tags"` + UserActionSku string `json:"userActionSku"` + VenderID string `json:"venderId"` +} + +const ( + QualifyTypeCompany = "25" // 营业执照 + QualifyTypePerson = "22" // 身份证,个体工商户要求填 + QualifyTypeAddInfo = "31" // 附加信息,如果身份证是长期有效,要求身份证背面信息 + + SaveQualifyActionTypeCommit = 0 // 提交 + SaveQualifyActionTypeSave = 1 // 暂时保存 ) -func (a *API) AccessStorePage(fullURL string, params map[string]interface{}, isPost bool) (retVal map[string]interface{}, err error) { +type QualifyItem struct { + QualifyURL string `json:"qualifyUrl"` + QualifyType string `json:"qualifyType"` + QualifyExpireForever int `json:"qualifyExpireForever"` // 0:永久有性,1:非永久有效(需要填QualifyExpireEnd) + QualifyExpireStart string `json:"qualifyExpireStart"` + QualifyExpireEnd string `json:"qualifyExpireEnd,omitempty"` + QualifyName string `json:"qualifyName,omitempty"` + QualifyOwner string `json:"qualifyOwner,omitempty"` + LicenceType string `json:"licenceType,omitempty"` // -1 + QualifyNumber string `json:"qualifyNumber,omitempty"` + QualifyAddress string `json:"qualifyAddress,omitempty"` + LicenceName string `json:"licenceName,omitempty"` + EconKind string `json:"econKind,omitempty"` + Scope string `json:"scope,omitempty"` +} + +var ( + monthSaleNumReg = regexp.MustCompile(`(\d+)([千|万])`) + pageExceedLimitCodes = map[string]int{ + "403": 1, + } + pageCanRetryCodes = map[string]int{} +) + +const ( + KeyImgData = "imgData" + KeyImgName = "imgName" + + ResultKeyData = "data" + ResultKeyResult = "result" +) + +func (a *API) AccessStorePage2(fullURL string, params map[string]interface{}, isPost bool, resultKey string) (retVal interface{}, err error) { if a.GetCookieCount() == 0 { return nil, fmt.Errorf("需要设置Store Cookie才能使用此方法") } + imgData := params[KeyImgData] + if imgData != nil { + delete(params, KeyImgData) + } err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { var request *http.Request @@ -151,8 +249,26 @@ func (a *API) AccessStorePage(fullURL string, params map[string]interface{}, isP request, _ = http.NewRequest(http.MethodGet, utils.GenerateGetURL(fullURL, "", params), nil) } else { request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(utils.Map2URLValues(params).Encode())) + if params[KeyImgName] == nil { + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } else { + var b bytes.Buffer + w := multipart.NewWriter(&b) + if fw, err := w.CreateFormFile("uploadFile", params[KeyImgName].(string)); err != nil { + panic(err.Error()) + } else { + fw.Write(imgData.([]byte)) + } + for k, v := range params { + // baseapi.SugarLogger.Debug(k, " ", v) + w.WriteField(k, url.QueryEscape(fmt.Sprint(v))) + } + w.Close() + // b.WriteString(utils.Map2URLValues(params).Encode()) + request, _ = http.NewRequest(http.MethodPost, fullURL, &b) + request.Header.Set("Content-Type", w.FormDataContentType()) + } request.Header.Set("charset", "UTF-8") - request.Header.Set("Content-Type", "application/x-www-form-urlencoded") } if err != nil { return nil @@ -166,15 +282,20 @@ func (a *API) AccessStorePage(fullURL string, params map[string]interface{}, isP return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") } retVal = jsonResult1 - code := jsonResult1["code"].(string) + code, ok := jsonResult1["code"].(string) + if !ok { + return platformapi.ErrLevelGeneralFail, utils.NewErrorCode(utils.Format4Output(jsonResult1, true), "999") + } if code == ResponseCodeSuccess { - retVal, _ = jsonResult1["result"].(map[string]interface{}) + if resultKey != "" { + retVal = jsonResult1[resultKey] + } return platformapi.ErrLevelSuccess, nil } newErr := utils.NewErrorCode(jsonResult1["msg"].(string), code) - if _, ok := exceedLimitCodes[code]; ok { + if _, ok := pageExceedLimitCodes[code]; ok { return platformapi.ErrLevelExceedLimit, newErr - } else if _, ok := canRetryCodes[code]; ok { + } else if _, ok := pageCanRetryCodes[code]; ok { return platformapi.ErrLevelRecoverableErr, newErr } else { baseapi.SugarLogger.Debugf("jd AccessStorePage failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) @@ -184,6 +305,14 @@ func (a *API) AccessStorePage(fullURL string, params map[string]interface{}, isP return retVal, err } +func (a *API) AccessStorePage(fullURL string, params map[string]interface{}, isPost bool) (retVal map[string]interface{}, err error) { + result, err := a.AccessStorePage2(fullURL, params, isPost, ResultKeyResult) + if err == nil { + retVal, _ = result.(map[string]interface{}) + } + return retVal, err +} + func (a *API) GetRealMobile4Order(orderId, stationNo string) (mobile string, err error) { retVal, err := a.GetStoreOrderInfo(orderId, stationNo) if err == nil { @@ -202,7 +331,7 @@ func (a *API) GetStoreOrderInfo(orderId, stationNo string) (storeOrderInfo map[s if stationNo != "" { params["stationNo"] = stationNo } - retVal, err := a.AccessStorePage("http://store.jddj.com/order/newManager/search", params, false) + retVal, err := a.AccessStorePage("http://order.jddj.com/order/newManager/search", params, false) // baseapi.SugarLogger.Debug(utils.Format4Output(retVal, false)) if err == nil { newOrderinfoMains := retVal["newOrderinfoMains"].(map[string]interface{}) @@ -229,7 +358,7 @@ func (a *API) GetStoreOrderInfoList(fromTime, toTime string) (storeOrderList []m for { params["pageNo"] = pageNo - retVal, err := a.AccessStorePage("http://store.jddj.com/order/newManager/tabQuery/all", params, false) + retVal, err := a.AccessStorePage("http://order.jddj.com/order/newManager/tabQuery/all", params, false) // baseapi.SugarLogger.Debug(utils.Format4Output(retVal, false)) if err == nil { newOrderinfoMains := retVal["newOrderinfoMains"].(map[string]interface{}) @@ -246,7 +375,7 @@ func (a *API) GetStoreOrderInfoList(fromTime, toTime string) (storeOrderList []m return nil, err } -func (a *API) GetSkuPageInfo(skuId int64) (skuPageInfo map[string]interface{}, err error) { +func (a *API) GetSkuPageInfo(skuId int64) (skuPageInfo *PageSku, err error) { skuIDMap := map[string]interface{}{ "skuId": utils.Int64ToStr(skuId), "storeId": "0", @@ -258,14 +387,17 @@ func (a *API) GetSkuPageInfo(skuId int64) (skuPageInfo map[string]interface{}, e "body": utils.Format4Output(skuIDMap, true), } - skuPageInfo, err = a.AccessStorePage("https://daojia.jd.com/client", params, false) + result, err := a.AccessStorePage("https://daojia.jd.com/client", params, false) + if err == nil { + err = utils.Map2StructByJson(result, &skuPageInfo, false) + } return skuPageInfo, err } func (a *API) GetSkuPageImageInfo(skuId int64) (imgList []*SkuPageImg, err error) { skuPageInfo, err := a.GetSkuPageInfo(skuId) if err == nil { - err = utils.Map2StructByJson(skuPageInfo["image"], &imgList, false) + imgList = skuPageInfo.Image } return imgList, err } @@ -346,3 +478,47 @@ func MonthSaleNum2Int(monthSaleNumStr string) (monthSaleNum int) { } return monthSaleNum } + +func (a *API) StoreUploadImg(imgFileName string, imgBin []byte) (imgURL string, err error) { + result, err := a.AccessStorePage2("https://sta-store.jddj.com/store/uploadImg.json", map[string]interface{}{ + KeyImgData: imgBin, + KeyImgName: imgFileName, + }, true, ResultKeyData) + if err == nil { + imgURL = result.(string) + } + return imgURL, err +} + +func (a *API) StoreUploadImgByURL(inImgURL string) (imgURL string, err error) { + response, err := http.Get(inImgURL) + if err == nil { + defer func() { + response.Body.Close() + }() + if response.StatusCode == http.StatusOK { + bodyData, err2 := ioutil.ReadAll(response.Body) + if err = err2; err == nil { + imgName := utils.GetUUID() + if lastSlashIndex := strings.LastIndex(inImgURL, "/"); lastSlashIndex >= 0 { + imgName = inImgURL[lastSlashIndex+1:] + } + return a.StoreUploadImg(imgName, bodyData) + } + } else { + err = platformapi.ErrHTTPCodeIsNot200 + } + } + return "", err +} + +func (a *API) SaveQualify(stationNo string, actionType int, qualifyList []*QualifyItem) (err error) { + _, err = a.AccessStorePage2("https://sta-store.jddj.com/store/saveQualify.o2o", map[string]interface{}{ + "stationNo": stationNo, + "actionType": actionType, + "qualifyList": utils.Format4Output(qualifyList, true), + "type": 1, + "degrade": "no", + }, true, "") + return err +} diff --git a/platformapi/jdapi/store_page_test.go b/platformapi/jdapi/store_page_test.go index 5e773b19..cc2f4045 100644 --- a/platformapi/jdapi/store_page_test.go +++ b/platformapi/jdapi/store_page_test.go @@ -8,9 +8,9 @@ import ( ) func TestGetRealMobileNumber4Order(t *testing.T) { - orderId := "910170516000941" - desiredMobile := "13398196274" - mobile, err := api.GetRealMobile4Order(orderId, "") + orderId := "921823424000122" + desiredMobile := "13722455105" + mobile, err := api.GetRealMobile4Order(orderId, "11893205") if err != nil { t.Fatal(err) } @@ -21,8 +21,8 @@ func TestGetRealMobileNumber4Order(t *testing.T) { } func TestGetStoreOrderInfo(t *testing.T) { - orderId := "910170516000941" - orderInfo, err := api.GetStoreOrderInfo(orderId, "") + orderId := "921823424000122" + orderInfo, err := api.GetStoreOrderInfo(orderId, "11893205") if err != nil { t.Fatal(err) } @@ -56,7 +56,7 @@ func TestGetSkuPageImageInfo(t *testing.T) { } func TestGetCorporationInfo(t *testing.T) { - imgList, err := api.GetCorporationInfo("", "915101003431062533") + imgList, err := api.GetCorporationInfo("", "92330104MA28XPXH5G") if err != nil { t.Fatal(err) } @@ -101,3 +101,91 @@ func TestMonthSaleNum2Int(t *testing.T) { t.Fatalf("num3:%d", num3) } } + +func TestStoreUploadImgByURL(t *testing.T) { + outImgURL, err := api.StoreUploadImgByURL("http://image.jxc4.com/940c151db7e396f2ceaec0304597836f.jpg") + t.Log(outImgURL) + if err != nil { + t.Fatal(err) + } +} + +func TestSaveQualify(t *testing.T) { + jsonStr := ` + [ + { + "qualifyUrl":"http://img30.360buyimg.com/vendersettle/jfs/t1/69834/24/6602/68812/5d4d35fdEaaf373c6/5c1c50e7bb6330e4.jpg", + "qualifyType":"25", + "qualifyExpireForever":0, + "qualifyExpireStart":"2017-09-07 00:00:00", + "qualifyName":"刘男", + "licenceType":"-1", + "qualifyNumber":"92330104MA28XPXH5G", + "qualifyAddress":"浙江省杭州市江干区八堡家园5排10号一楼102", + "licenceName":"杭州市江干区刘男便利店", + "econKind":"个体工商户", + "scope":"食品经营(凭有效许可证经营);零售:卷烟、雪茄烟(凭有效许可证经营);批发、零售:日用百货,五金。(依法须经批准的项目,经相关部门批准后方可开展经营活动)" + }, + { + "qualifyUrl":"http://img30.360buyimg.com/vendersettle/jfs/t1/58554/26/7134/19343/5d4d3639E57b14138/bcce25e1eac11be8.jpg", + "qualifyType":"22", + "qualifyExpireForever":1, + "qualifyExpireStart":"2013-07-22 16:59:38", + "qualifyExpireEnd":"2033-07-22 16:59:50", + "qualifyOwner":"刘男", + "qualifyNumber":"420621198110303336" + }, + { + "qualifyUrl":"", + "qualifyType":"33", + "qualifyExpireForever":1, + "qualifyExpireStart":"", + "qualifyExpireEnd":"" + }, + { + "qualifyUrl":"", + "qualifyType":"8", + "qualifyExpireForever":1, + "qualifyExpireStart":"", + "qualifyExpireEnd":"" + }, + { + "qualifyUrl":"", + "qualifyType":"9", + "qualifyExpireForever":1, + "qualifyExpireStart":"", + "qualifyExpireEnd":"" + }, + { + "qualifyUrl":"", + "qualifyType":"10", + "qualifyExpireForever":1, + "qualifyExpireStart":"", + "qualifyExpireEnd":"" + }, + { + "qualifyUrl":"", + "qualifyType":"29", + "qualifyExpireForever":1, + "qualifyExpireStart":"", + "qualifyExpireEnd":"" + }, + { + "qualifyUrl":"", + "qualifyType":"31", + "qualifyExpireForever":1, + "qualifyExpireStart":"", + "qualifyExpireEnd":"" + } + ] + ` + var qualityList []*QualifyItem + err := utils.UnmarshalUseNumber([]byte(jsonStr), &qualityList) + if err != nil { + t.Fatal(err) + } + err = api.SaveQualify("11902261", SaveQualifyActionTypeSave, qualityList) + if err != nil { + t.Fatal(err) + } +} diff --git a/platformapi/jdapi/store_sku.go b/platformapi/jdapi/store_sku.go index 08ae1886..cee2f4cf 100644 --- a/platformapi/jdapi/store_sku.go +++ b/platformapi/jdapi/store_sku.go @@ -9,6 +9,8 @@ import ( const ( MaxStoreSkuBatchSize = 50 MaxStockQty = 100000000 + + MaxAddByStoreAndSkusCount = 30 // 批量置顶商品排序接口的最大个数 ) type SkuPriceInfo struct { @@ -109,7 +111,7 @@ func (a *API) UpdateVendorStationPrice(trackInfo string, outStationNo, stationNo result, err := a.AccessAPINoPage2("venderprice/updateStationPrice", jdParams, nil, nil, genNoPageResultParser("code", "msg", "result", "0"), trackInfo) if result != nil { var err2 error - if responseList, err2 = a.handleBatchOpResult(len(skuPriceInfoList), result, "json2"); err2 != nil && err == nil { + if responseList, err2 = a.handleBatchOpResult(len(skuPriceInfoList), err, result, "json2"); err2 != nil && err == nil { err = err2 } } @@ -146,18 +148,20 @@ func (a *API) GetStationInfoList(stationNo string, skuIds []int64) (priceInfo [] return priceInfo, err } -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) +func (a *API) handleBatchOpResult(batchCount int, inErr error, result interface{}, tagName string) (responseList []*StoreSkuBatchUpdateResponse, err error) { + if result != nil { + 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 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) } - } - 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) } } return responseList, err @@ -181,7 +185,7 @@ func (a *API) BatchUpdateCurrentQtys(trackInfo, outStationNo, stationNo string, result, err := a.AccessAPINoPage2("stock/batchUpdateCurrentQtys", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0"), trackInfo) if result != nil { var err2 error - if responseList, err2 = a.handleBatchOpResult(len(skuStockList), result, ""); err2 != nil && err == nil { + if responseList, err2 = a.handleBatchOpResult(len(skuStockList), err, result, ""); err2 != nil && err == nil { err = err2 } } @@ -231,7 +235,7 @@ func (a *API) UpdateVendibility(trackInfo string, listBaseStockCenterRequest []* result, err := a.AccessAPINoPage2("stock/updateVendibility", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0"), trackInfo) if result != nil { var err2 error - if responseList, err2 = a.handleBatchOpResult(len(listBaseStockCenterRequest), result, ""); err2 != nil && err == nil { + if responseList, err2 = a.handleBatchOpResult(len(listBaseStockCenterRequest), err, result, ""); err2 != nil && err == nil { err = err2 } } @@ -253,10 +257,11 @@ func (a *API) BatchUpdateVendibility(trackInfo, outStationNo, stationNo string, } else { jdParams["stationNo"] = stationNo } + // 此函数在全部失败时,err仍然返回成功 result, err := a.AccessAPINoPage2("stock/batchUpdateVendibility", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0"), trackInfo) if result != nil { var err2 error - if responseList, err2 = a.handleBatchOpResult(len(stockVendibilityList), result, ""); err2 != nil && err == nil { + if responseList, err2 = a.handleBatchOpResult(len(stockVendibilityList), err, result, ""); err2 != nil && err == nil { err = err2 } } @@ -291,3 +296,14 @@ func (a *API) QueryStockCenter(outStationNo string, skuIds []*SkuIdEntity, userP } return vendibilityResponse, err } + +// 批量置顶商品排序接口 +// https://opendj.jd.com/staticnew/widgets/resources.html?groupid=180&apiid=a7677f1a75984ed3a6ea3d4827f5a6b4 +func (a *API) AddByStoreAndSkus(stationNo int64, skuIDs []int64) (err error) { + jdParams := map[string]interface{}{ + "storeId": stationNo, + "skuIds": skuIDs, + } + _, err = a.AccessAPINoPage("OrgSortService/addByStoreAndSkus", jdParams, nil, nil, genNoPageResultParser("status", "message", "", "200")) + return err +} diff --git a/platformapi/jdapi/store_test.go b/platformapi/jdapi/store_test.go index 2b62eeab..47f5d5f6 100644 --- a/platformapi/jdapi/store_test.go +++ b/platformapi/jdapi/store_test.go @@ -11,6 +11,9 @@ import ( const ( mustExistStoreID = "11053496" mustExistStoreJXID = "2" + + // mustExistStoreID = "11734851" + // mustExistStoreJXID = "100118" ) func TestGetAllCities(t *testing.T) { @@ -39,56 +42,51 @@ func TestGetStationsByVenderId(t *testing.T) { } func TestGetStoreInfoByStationNo(t *testing.T) { - result, err := api.GetStoreInfoByStationNo(mustExistStoreID) + result, err := api.GetStoreInfoByStationNo2(mustExistStoreID) if err != nil { t.Fatal(err) } - outSystemId := result["outSystemId"].(string) - if outSystemId != "100285" { - baseapi.SugarLogger.Fatalf("outSystemId is not correct, its:%s", outSystemId) + t.Log(utils.Format4Output(result, false)) + if result.OutSystemID != mustExistStoreJXID { + baseapi.SugarLogger.Fatalf("outSystemId is not correct, its:%s", result.OutSystemID) } } func TestUpdateStoreInfo4Open(t *testing.T) { - result, err := api.GetStoreInfoByStationNo(mustExistStoreID) + result, err := api.GetStoreInfoByStationNo2(mustExistStoreID) if err != nil { t.Fatal(err) } - oldAddress := result["stationAddress"].(string) - testAddress := oldAddress + "T" - addParams := map[string]interface{}{ - "stationAddress": testAddress, + oldAddress := result.StationAddress + params := &OpStoreParams{ + StationNo: mustExistStoreID, + Operator: "test", + StationAddress: oldAddress + "T", } - err = api.UpdateStoreInfo4Open(mustExistStoreID, "test", addParams) + err = api.UpdateStoreInfo4Open2(params, false) if err != nil { t.Fatal(err) } - result, err = api.GetStoreInfoByStationNo(mustExistStoreID) - newAddress := result["stationAddress"].(string) - if newAddress != testAddress { + result, err = api.GetStoreInfoByStationNo2(mustExistStoreID) + newAddress := result.StationAddress + if newAddress != params.StationAddress { t.Fatalf("address not match, newAddress:%s, oldAddress:%s", newAddress, oldAddress) } - addParams = map[string]interface{}{ - "stationAddress": oldAddress, - } - api.UpdateStoreInfo4Open(mustExistStoreID, "test", addParams) + params.StationAddress = oldAddress + api.UpdateStoreInfo4Open2(params, false) if err != nil { t.Fatal(err) } - } + func TestGetCommentByOrderId(t *testing.T) { - testOrderID := int64(822347450000922) - result, err := api.GetCommentByOrderId(testOrderID) + testOrderID := int64(922520919000622) + result, err := api.GetCommentByOrderId2(testOrderID) if err != nil { t.Fatal(err.Error()) } - gotOrderID := utils.MustInterface2Int64(result["orderId"]) - if gotOrderID != testOrderID { - t.Fatalf("GetCommentByOrderId wrong, gotOrderID:%d", gotOrderID) - } t.Log(utils.Format4Output(result, false)) } @@ -104,38 +102,38 @@ func TestUpdateStoreConfig4Open(t *testing.T) { t.Fatal(result) } time.Sleep(2 * time.Second) - result2, err := api.GetStoreInfoByStationNo(testStationNo) + result2, err := api.GetStoreInfoByStationNo2(testStationNo) if err != nil { t.Fatal(err.Error()) } - isAutoOrder := int(utils.MustInterface2Int64(result2["isAutoOrder"])) + isAutoOrder := result2.IsAutoOrder if isAutoOrder != 0 && desiredValue || isAutoOrder == 0 && !desiredValue { t.Fatalf("UpdateStoreConfig4Open failed, isAutoOrder:%d", isAutoOrder) } } func TestGetDeliveryRangeByStationNo(t *testing.T) { - const testStoreID = "11738152" - result, err := api.GetDeliveryRangeByStationNo(testStoreID) - if err != nil { - t.Fatal(err) - } - result2, err := api.GetStoreInfoByStationNo(testStoreID) + const testStoreID = "11734851" + result, err := api.GetDeliveryRangeByStationNo2(testStoreID) if err != nil { t.Fatal(err) } baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) - baseapi.SugarLogger.Debug(utils.Format4Output(result2, false)) - deliveryRange := result["deliveryRange"].(string) - params := map[string]interface{}{ - "lng": result2["lng"], - "lat": result2["lat"], - "coordinateType": 3, - "deliveryRangeType": 2, - "coordinatePoints": deliveryRange, - } - err = api.UpdateStoreInfo4Open(testStoreID, "test", params) +} + +func TestDisableAutoOrder4AllStores(t *testing.T) { + storeIDs, err := api.GetStationsByVenderId() if err != nil { t.Fatal(err) } + for _, storeID := range storeIDs { + if storeInfo, err := api.GetStoreInfoByStationNo2(storeID); err == nil { + if storeInfo.Yn == 0 && storeInfo.IsAutoOrder == 0 { + t.Log(storeID) + api.UpdateStoreConfig4Open(storeID, false) + } + t.Log(utils.Format4Output(storeInfo, false)) + } + break + } } diff --git a/platformapi/mtpsapi/mtpsapi_test.go b/platformapi/mtpsapi/mtpsapi_test.go index d053345c..c8731a7f 100644 --- a/platformapi/mtpsapi/mtpsapi_test.go +++ b/platformapi/mtpsapi/mtpsapi_test.go @@ -25,7 +25,7 @@ func init() { // prod // api = New("3c0a05d464c247c19d7ec13accc78605", "b1M}9?:sTbsB[OF2gNORnN(|(iy9rB8(`7]|[wGLnbmt`evfM>E:A90DjHAW:UPE") - api.SetCookie("token", "0seqGSJnhbr4XJ0EaIQL6CoOpnaV1ErgS42uOlzNXYIX7PeuLuyCFQQZKKWGExJ7IMTQQQDe5H6YMmVFnxjCkw") + api.SetCookie("token", "M0p9VatZSeSHfrosD5IViAVl73IcA8mlcuHIV5sG6Zpv83a7JE0wY3t26aEhrrs_MR5gtLSFF1UIkt8HAjaXow") } func handleError(t *testing.T, err error) { diff --git a/platformapi/mtwmapi/mtwmapi.go b/platformapi/mtwmapi/mtwmapi.go index 3aa53dff..e6aa16be 100644 --- a/platformapi/mtwmapi/mtwmapi.go +++ b/platformapi/mtwmapi/mtwmapi.go @@ -9,7 +9,6 @@ import ( "net/url" "sort" "strings" - "sync" "time" "git.rosy.net.cn/baseapi" @@ -45,10 +44,12 @@ const ( ErrCodeSysErr = 700 // 系统错误,按美团外卖技术支持的说法,可当成需重试的错误 ErrCodeAccessLimited = 711 // 接口调用过于频繁,触发流控,请降低调用频率 - ErrCodeNoAppFood = 805 // 不存在此菜品 - ErrCodeNoSuchOrder = 808 // 不存在此订单 - ErrCodeSkuCategoryNotExist = 1021 // 菜品分类不存在 - ErrCodeSkuCategoryExist = 1037 // 菜品分类已存在 + ErrCodeNoAppFood = 805 // 不存在此菜品 + ErrCodeNoSuchOrder = 806 // 不存在此订单 + ErrCodeOpFailed = 808 // 操作失败(如订单在操作时,状态已变更等情况) + ErrCodeSkuCategoryNotExist = 1021 // 菜品分类不存在 + ErrCodeSkuCategoryExist = 1037 // 菜品分类已存在 + ErrCodeCanNotModifyStoreDeliveryInfo = 3018 // 商家已接入美团配送,不可修改门店配送相关信息 ) type API struct { @@ -59,16 +60,15 @@ type API struct { callbackURL string client *http.Client config *platformapi.APIConfig - - locker sync.RWMutex - userCookies map[string]string } var ( canRetryCodes = map[int]int{ - ErrCodeSysErr: 1, ErrCodeAccessLimited: 1, } + canRecoverCodes = map[int]int{ + ErrCodeSysErr: 1, + } ) func New(appID, secret, callbackURL string, config ...*platformapi.APIConfig) *API { @@ -82,8 +82,6 @@ func New(appID, secret, callbackURL string, config ...*platformapi.APIConfig) *A callbackURL: callbackURL, client: &http.Client{Timeout: curConfig.ClientTimeout}, config: &curConfig, - - userCookies: make(map[string]string), } } @@ -145,7 +143,7 @@ func (a *API) AccessAPI2(cmd string, isGet bool, bizParams map[string]interface{ fw.Write(imgData.([]byte)) } for k, v := range params { - fmt.Println(k, " ", v) + // baseapi.SugarLogger.Debug(k, " ", v) w.WriteField(k, url.QueryEscape(fmt.Sprint(v))) } w.Close() @@ -173,6 +171,8 @@ func (a *API) AccessAPI2(cmd string, isGet bool, bizParams map[string]interface{ newErr := utils.NewErrorIntCode(errorInfo["msg"].(string), int(utils.MustInterface2Int64(errorInfo["code"]))) if canRetryCodes[newErr.IntCode()] == 1 { return platformapi.ErrLevelExceedLimit, newErr + } else if canRecoverCodes[newErr.IntCode()] == 1 { + return platformapi.ErrLevelRecoverableErr, newErr } return platformapi.ErrLevelCodeIsNotOK, newErr } diff --git a/platformapi/mtwmapi/order.go b/platformapi/mtwmapi/order.go index c3498b0e..803ee838 100644 --- a/platformapi/mtwmapi/order.go +++ b/platformapi/mtwmapi/order.go @@ -67,6 +67,11 @@ const ( NotifyTypeCancelRefundComplaint = "cancelRefundComplaint" // 用户取消退款申诉 ) +const ( + OrderPickTypeNormal = 0 // 普通(配送) + OrderPickTypeSelf = 1 // 用户到店自取 +) + const ( UserApplyCancelWaitMinute = 30 // 用户申请退款后,商家未在30分钟内(大连锁商家为3小时内)处理退款请求,系统将自动同意退款 ) @@ -308,6 +313,23 @@ type GetOrderIdByDaySeqResult struct { OrderIDs []int64 `json:"order_ids"` } +type UserRealPhoneNumberInfo struct { + OrderID int64 `json:"order_id"` + AppPoiCode string `json:"app_poi_code"` + WmOrderIDView string `json:"wm_order_id_view"` + DaySeq int `json:"day_seq"` + RealPhoneNumber string `json:"real_phone_number"` // 订单收货人的真实手机号码 + RealOrderPhoneNumber string `json:"real_order_phone_number"` // 鲜花绿植类订单预订人的真实手机号码,如无则返回空。 +} + +type RiderRealPhoneNumberInfo struct { + OrderID int64 `json:"order_id"` + AppPoiCode string `json:"app_poi_code"` + WmOrderIDView string `json:"wm_order_id_view"` + RiderName string `json:"rider_name"` + RiderRealPhoneNumber string `json:"rider_real_phone_number"` // 骑手真实手机号 +} + func (a *API) OrderReceived(orderID int64) (err error) { _, err = a.AccessAPI("order/poi_received", true, map[string]interface{}{ KeyOrderID: orderID, @@ -462,8 +484,10 @@ func (a *API) OrderLogisticsStatus(orderID int64) (status map[string]interface{} return nil, err } +// 拉取用户真实手机号(必接) +// https://developer.waimai.meituan.com/home/docDetail/222 // limit最大为MaxBatchPullPhoneNumberLimit = 1000 -func (a *API) OrderBatchPullPhoneNumber(poiCode string, offset, limit int) (realNumberList []map[string]interface{}, err error) { +func (a *API) OrderBatchPullPhoneNumber(poiCode string, offset, limit int) (realNumberList []*UserRealPhoneNumberInfo, err error) { params := map[string]interface{}{ "offset": offset, "limit": limit, @@ -473,9 +497,27 @@ func (a *API) OrderBatchPullPhoneNumber(poiCode string, offset, limit int) (real } result, err := a.AccessAPI("order/batchPullPhoneNumber", false, params) if err == nil { - return utils.Slice2MapSlice(result.([]interface{})), nil + err = utils.Map2StructByJson(result, &realNumberList, false) } - return nil, err + return realNumberList, err +} + +// 拉取骑手真实手机号(必接),美团2019-09-17才开始灰度上线 +// https://developer.waimai.meituan.com/home/docDetail/388 +// limit最大为MaxBatchPullPhoneNumberLimit = 1000 +func (a *API) OrderGetRiderInfoPhoneNumber(poiCode string, offset, limit int) (realNumberList []*RiderRealPhoneNumberInfo, err error) { + params := map[string]interface{}{ + "offset": offset, + "limit": limit, + } + if poiCode != "" { + params[KeyAppPoiCode] = poiCode + } + result, err := a.AccessAPI("order/getRiderInfoPhoneNumber", false, params) + if err == nil { + err = utils.Map2StructByJson(result, &realNumberList, false) + } + return realNumberList, err } // 专快混配送转为商家自配送 diff --git a/platformapi/mtwmapi/order_test.go b/platformapi/mtwmapi/order_test.go index 838ac1d5..444d3130 100644 --- a/platformapi/mtwmapi/order_test.go +++ b/platformapi/mtwmapi/order_test.go @@ -80,14 +80,19 @@ func TestOrderLogisticsStatus(t *testing.T) { } func TestOrderBatchPullPhoneNumber(t *testing.T) { - result, err := api.OrderBatchPullPhoneNumber(testPoiCode, 0, 10) + result, err := api.OrderBatchPullPhoneNumber(testPoiCode, 0, MaxBatchPullPhoneNumberLimit) + t.Log(utils.Format4Output(result, false)) if err != nil { t.Fatal(err) } - if len(result) == 0 { - t.Fatal("result should have value") +} + +func TestOrderGetRiderInfoPhoneNumber(t *testing.T) { + result, err := api.OrderGetRiderInfoPhoneNumber(testPoiCode, 0, MaxBatchPullPhoneNumberLimit) + t.Log(utils.Format4Output(result, false)) + if err != nil { + t.Fatal(err) } - // t.Log(utils.Format4Output(result, false)) } func TestGetOrderRefundDetail(t *testing.T) { diff --git a/platformapi/mtwmapi/poi.go b/platformapi/mtwmapi/poi.go index bc407d74..a2263a19 100644 --- a/platformapi/mtwmapi/poi.go +++ b/platformapi/mtwmapi/poi.go @@ -47,6 +47,9 @@ type PoiInfo struct { Utime int64 `json:"utime,omitempt"` } +// todo 此函数在open_level与is_online开店,由于一些原因并没有成功时,好像并不会报错 +// 经常会奇怪的报错:商家已接入美团配送,不可修改门店配送相关信息,但实际并没有修改任何与配送相关的东西 +// 参见:https://developer.waimai.meituan.com/home/myquestionDetail/6194 func (a *API) PoiSave(poiCode string, poiParams map[string]interface{}) (err error) { _, err = a.AccessAPI("poi/save", false, utils.MergeMaps(utils.Params2Map(KeyAppPoiCode, poiCode), poiParams)) return err @@ -150,6 +153,8 @@ func (a *API) PoiShipTimeUpdate(poiCode, shippingTime string) (err error) { } // 美团要求必须是jpg|jpeg图片格式,且imgName必须以jpg或jpeg结尾 +// 此接口虽然要求poiCode参数,但经过测试,发现上传的图片可以跨门店使用 +// imgName好像不支持中文,否则会报签名错 func (a *API) ImageUpload(poiCode, imgName string, imgData []byte) (imgID string, err error) { result, err := a.AccessAPI("image/upload", false, map[string]interface{}{ KeyAppPoiCode: poiCode, diff --git a/platformapi/mtwmapi/poi_test.go b/platformapi/mtwmapi/poi_test.go index e8874199..a089a5d9 100644 --- a/platformapi/mtwmapi/poi_test.go +++ b/platformapi/mtwmapi/poi_test.go @@ -22,7 +22,8 @@ func TestPoiGetIDs(t *testing.T) { } func TestPoiMGet(t *testing.T) { - result, err := api.PoiMGet([]string{testPoiCode}) + result, err := api.PoiMGet([]string{"2461723"}) + t.Log(utils.Format4Output(result, false)) if err != nil { t.Fatal(err) } @@ -32,7 +33,6 @@ func TestPoiMGet(t *testing.T) { if result[0].AppPoiCode != testPoiCode { t.Fatal("test_poi_01 is not equal") } - t.Log(utils.Format4Output(result, false)) } func TestPoiSave(t *testing.T) { @@ -80,3 +80,21 @@ func TestPoiStatus(t *testing.T) { t.Fatal(err) } } + +func TestPoiShipTimeUpdate(t *testing.T) { + err := api.PoiShipTimeUpdate("7174130", "00:00-23:00") + if err != nil { + t.Fatal(err) + } + err = api.PoiOpen("6741258") + if err != nil { + t.Fatal(err) + } +} + +func TestPoiOpen(t *testing.T) { + err := api.PoiOpen("6735933") + if err != nil { + t.Fatal(err) + } +} diff --git a/platformapi/mtwmapi/retail.go b/platformapi/mtwmapi/retail.go index 856c2cef..fe3249ff 100644 --- a/platformapi/mtwmapi/retail.go +++ b/platformapi/mtwmapi/retail.go @@ -113,6 +113,7 @@ type AppFoodResult struct { // 创建二级分类,secondaryName为二级分类名, // (如果originName为空,同时创建一级分类,所以如果只是创建二级分类,originName与name要填一样的,此时sequence指的二级分类的sequence,一级分类的sequence为缺省值) // 修改二级分类,originName为二级分类名,name为二级分类新名,secondaryName为空 +// https://developer.waimai.meituan.com/home/questionDetail/4669 func (a *API) RetailCatUpdate(poiCode, originName, name, secondaryName string, sequence int) (err error) { params := map[string]interface{}{ KeyAppPoiCode: poiCode, diff --git a/platformapi/platformapi.go b/platformapi/platformapi.go index 63e04287..7b313a73 100644 --- a/platformapi/platformapi.go +++ b/platformapi/platformapi.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "math" "net" "net/http" "net/url" @@ -17,18 +18,18 @@ import ( ) const ( - DefClientTimeout = 15 * time.Second - DefSleepSecondWhenExceedLimit = 3 * time.Second - DefMaxRecoverableRetryCount = 1 - DefMaxExceedLimitRetryCount = 25 + DefClientTimeout = 30 * time.Second + DefMaxSleepSecondWhenExceedLimit = 62 // 超频类错误最大重试间隙(秒) + DefMaxExceedLimitRetryCount = 25 // 超频类错误最大重试次数 + DefMaxRecoverableRetryCount = 1 // 可恢复类错误(一般指网络错),最大重试次数 KeyTrackInfo = "TrackInfo" ) type APIRetryConfig struct { - MaxExceedLimitRetryCount int - MaxRecoverableRetryCount int - SleepSecondWhenExceedLimit time.Duration + MaxExceedLimitRetryCount int + MaxRecoverableRetryCount int + MaxSleepSecondWhenExceedLimit int } type APIConfig struct { @@ -45,9 +46,9 @@ type AccessPlatformAPIWithRetryParam struct { var ( DefAPIConfig = APIConfig{ APIRetryConfig: APIRetryConfig{ - MaxExceedLimitRetryCount: DefMaxExceedLimitRetryCount, - MaxRecoverableRetryCount: DefMaxRecoverableRetryCount, - SleepSecondWhenExceedLimit: DefSleepSecondWhenExceedLimit, + MaxExceedLimitRetryCount: DefMaxExceedLimitRetryCount, + MaxRecoverableRetryCount: DefMaxRecoverableRetryCount, + MaxSleepSecondWhenExceedLimit: DefMaxSleepSecondWhenExceedLimit, }, ClientTimeout: DefClientTimeout, } @@ -90,6 +91,11 @@ func getClonedData(requestURL *url.URL, r *bytes.Buffer) string { return retVal } +func NewDefAPIConfig() (conf *APIConfig) { + obj := DefAPIConfig + return &obj +} + func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http.Request, config *APIConfig, handleResponse func(response *http.Response, bodyStr string, bodyMap map[string]interface{}) (string, error)) error { exceedLimitRetryCount := 0 recoverableErrorRetryCount := 0 @@ -101,12 +107,10 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. } beginTime := time.Now() trackInfo := request.Header.Get(KeyTrackInfo) - if trackInfo == "" { - trackInfo = utils.GetUUID() - // request.Header.Set(KeyTrackInfo, trackID) - } else { + if trackInfo != "" { request.Header.Del(KeyTrackInfo) } + trackInfo += ", " + utils.GetUUID() baseapi.SugarLogger.Debugf("begin AccessPlatformAPIWithRetry:%s do:%s url:%v", trackInfo, request.Method, request.URL) response, err := client.Do(request) baseapi.SugarLogger.Debugf("end AccessPlatformAPIWithRetry:%s do url:%v, request:%s", trackInfo, request.URL, getClonedData(request.URL, savedBuf)) @@ -174,7 +178,11 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. } else if errLevel == ErrLevelExceedLimit { exceedLimitRetryCount++ if exceedLimitRetryCount <= config.MaxExceedLimitRetryCount { - time.Sleep(config.SleepSecondWhenExceedLimit) + sleepSeconds := int(math.Exp2(float64(exceedLimitRetryCount))) + if sleepSeconds > config.MaxSleepSecondWhenExceedLimit { + sleepSeconds = config.MaxSleepSecondWhenExceedLimit + } + time.Sleep(time.Duration(sleepSeconds) * time.Second) continue } } else if errLevel == ErrLevelRecoverableErr { diff --git a/platformapi/platformapi_cookie.go b/platformapi/platformapi_cookie.go index 90a7bd9b..e6dd8998 100644 --- a/platformapi/platformapi_cookie.go +++ b/platformapi/platformapi_cookie.go @@ -11,7 +11,7 @@ import ( ) type APICookie struct { - locker sync.RWMutex + sync.RWMutex storeCookies map[string]string } @@ -44,28 +44,28 @@ func (a *APICookie) SetCookieWithStr(cookieStr string) { } func (a *APICookie) SetCookie(key, value string) { - a.locker.Lock() - defer a.locker.Unlock() + a.Lock() + defer a.Unlock() a.createMapIfNeeded() a.storeCookies[key] = value } func (a *APICookie) GetCookie(key string) string { - a.locker.RLock() - defer a.locker.RUnlock() + a.RLock() + defer a.RUnlock() a.createMapIfNeeded() return a.storeCookies[key] } func (a *APICookie) GetCookieCount() int { - a.locker.RLock() - defer a.locker.RUnlock() + a.RLock() + defer a.RUnlock() return len(a.storeCookies) } func (a *APICookie) FillRequestCookies(r *http.Request) *http.Request { - a.locker.RLock() - defer a.locker.RUnlock() + a.RLock() + defer a.RUnlock() for k, v := range a.storeCookies { r.AddCookie(&http.Cookie{ Name: k, diff --git a/platformapi/yilianyunapi/yilianyunapi.go b/platformapi/yilianyunapi/yilianyunapi.go index 5019fd25..ef08c085 100644 --- a/platformapi/yilianyunapi/yilianyunapi.go +++ b/platformapi/yilianyunapi/yilianyunapi.go @@ -56,6 +56,15 @@ type TokenInfo struct { MachineCode string `json:"machine_code"` } +type PrinterOrderResult struct { + Content string `json:"content"` + ID string `json:"id"` + OriginID string `json:"origin_id"` + PrintTime string `json:"print_time"` + ReceivingTime string `json:"receiving_time"` + Status int `json:"status"` +} + func New(clientID, clientSecret string, config ...*platformapi.APIConfig) *API { curConfig := platformapi.DefAPIConfig if len(config) > 0 { @@ -238,7 +247,6 @@ func (a *API) GetPrinterToken(machineCode, qrKey string) (tokenInfo *TokenInfo, }, "") if err == nil { err = utils.Map2StructByJson(result, &tokenInfo, false) - a.SetToken(tokenInfo.AccessToken) } return tokenInfo, err } @@ -257,3 +265,16 @@ func (a *API) CancelAll(machineCode, token string) (err error) { func (a *API) PlayText(machineCode, orderID, text, token string) (err error) { return a.printMsg(machineCode, orderID, fmt.Sprintf("", strings.Replace(text, ",", ".", -1)), token) } + +// pageIndex从1开始,pageSize最大为100 +func (a *API) GetOrderPagingList(machineCode, token string, pageIndex, pageSize int) (orderResultList []*PrinterOrderResult, err error) { + result, err := a.AccessAPI("printer/getorderpaginglist", map[string]interface{}{ + "machine_code": machineCode, + "page_index": pageIndex, + "page_size": pageSize, + }, token) + if err != nil { + err = utils.Map2StructByJson(result, &orderResultList, false) + } + return orderResultList, err +} diff --git a/platformapi/yilianyunapi/yilianyunapi_test.go b/platformapi/yilianyunapi/yilianyunapi_test.go index 3e3f79f1..f39b0601 100644 --- a/platformapi/yilianyunapi/yilianyunapi_test.go +++ b/platformapi/yilianyunapi/yilianyunapi_test.go @@ -21,7 +21,7 @@ func init() { // 自有应用 api = New("1039586024", "4885d07c2997b661102e4b6099c0bf3b") - api.SetToken("7884617f9eeb4c28933569f94a95b5c3") + api.SetToken("3a38e3cec7974b459a6f0a381c9b0312") // 开放应用 // api = New("1098307169", "d5eedb40c99e6691b1ca2ba82a363d6a") @@ -42,19 +42,19 @@ func TestRetrieveToken(t *testing.T) { func TestAddPrinter(t *testing.T) { // err := api.AddPrinter("4004600675", "fem2ukwvduik", "公司测试打印机1") - // 4004617180 公司测试打印机2 - err := api.AddPrinter("4004600675", "fem2ukwvduik", "测试打印机1") + // 4004617180, 381870509796 公司测试打印机2 + err := api.AddPrinter("4004615546", "7nxsw668yqtk", "测试打印机1") handleError(t, err) } func TestDeletePrinter(t *testing.T) { - err := api.DeletePrinter("4004611945") + err := api.DeletePrinter("4004615546") handleError(t, err) } func TestPrintMsg(t *testing.T) { // 4004606481 - err := api.PrintMsg("4004600675", utils.GetUUID(), "饿百取货码") + err := api.PrintMsg("4004615546", utils.GetUUID(), "饿百取货码") handleError(t, err) } @@ -65,13 +65,13 @@ func TestPrintMsgWithToken(t *testing.T) { } func TestGetPrintStatus(t *testing.T) { - state, err := api.GetPrintStatus("4004617180") + state, err := api.GetPrintStatus("4004615546") handleError(t, err) baseapi.SugarLogger.Debug(state) } func TestGetPrintStatusWithToken(t *testing.T) { - state, err := api.GetPrintStatusWithToken("4004600675", "b1a36aa8de5647d2b08af49db11a9c9d") + state, err := api.GetPrintStatusWithToken("4004617242", "88de89a384714436a5d1ca23e8991e15") handleError(t, err) baseapi.SugarLogger.Debug(state) } @@ -91,3 +91,15 @@ func TestPlayText(t *testing.T) { err := api.PlayText("4004617180", utils.GetUUID(), "我们已安排%s配送员%s负责配送。^_^", "") handleError(t, err) } + +func TestRefreshToken(t *testing.T) { + tokenInfo, err := api.RefreshToken("c565a8983887488eb2a708ca118ce43c") + handleError(t, err) + t.Log(utils.Format4Output(tokenInfo, true)) +} + +func TestGetOrderPagingList(t *testing.T) { + result, err := api.GetOrderPagingList("4004613792", "", 1, 50) + handleError(t, err) + t.Log(utils.Format4Output(result, true)) +} diff --git a/utils/typeconv.go b/utils/typeconv.go index 9e75380a..e81d978d 100644 --- a/utils/typeconv.go +++ b/utils/typeconv.go @@ -7,12 +7,13 @@ import ( "fmt" "math" "net/url" + "reflect" "strconv" "strings" "time" "git.rosy.net.cn/baseapi" - "github.com/fatih/structs" + "github.com/gazeboxu/structs" "github.com/mitchellh/mapstructure" ) @@ -70,15 +71,22 @@ func TryInterface2Int64(data interface{}) (num int64, err error) { if dataNumber, ok := data.(int); ok { return int64(dataNumber), nil } - dataNumber, ok := data.(json.Number) - if !ok { - return 0, fmt.Errorf("data is not json.Number:%v to int64", data) + if dataNumber, ok := data.(int32); ok { + return int64(dataNumber), nil } - retVal, err := dataNumber.Int64() - if err != nil { - return num, err + if dataNumber, ok := data.(int16); ok { + return int64(dataNumber), nil } - return retVal, nil + if dataNumber, ok := data.(int8); ok { + return int64(dataNumber), nil + } + if dataNumber, ok := data.(json.Number); ok { + return dataNumber.Int64() + } + if str, ok := data.(string); ok { + return Str2Int64WithDefault(str, 0), nil + } + return 0, fmt.Errorf("data is not json.Number, it's %s, value:%v", reflect.TypeOf(data).String(), data) } func MustInterface2Int64(data interface{}) int64 { @@ -114,15 +122,13 @@ func TryInterface2Float64(data interface{}) (num float64, err error) { if dataNumber, ok := data.(float32); ok { return float64(dataNumber), nil } - dataNumber, ok := data.(json.Number) - if !ok { - return num, fmt.Errorf("data is not json.Number:%v", data) + if dataNumber, ok := data.(json.Number); ok { + return dataNumber.Float64() } - retVal, err := dataNumber.Float64() - if err != nil { - return num, err + if str, ok := data.(string); ok { + return Str2Float64WithDefault(str, 0), nil } - return retVal, nil + return 0, fmt.Errorf("data is not json.Number, it's %s, value:%v", reflect.TypeOf(data).String(), data) } func MustInterface2Float64(data interface{}) float64 { @@ -329,6 +335,10 @@ func IsTimeZero(timeValue time.Time) bool { return timeValue == DefaultTimeValue || timeValue == ZeroTimeValue } +func IsPtrTimeZero(timePtr *time.Time) bool { + return timePtr == nil || *timePtr == DefaultTimeValue || *timePtr == ZeroTimeValue +} + func HTTPBody2Values(data []byte, needDecode bool) (url.Values, error) { bodyStr := string(data) if needDecode { @@ -443,36 +453,22 @@ func MergeMaps(firstMap map[string]interface{}, otherMaps ...map[string]interfac return retVal } -func Struct2MapByJson(obj interface{}) (mapData map[string]interface{}) { +func Struct2Map(obj interface{}, tagName string, isFlattenAnonymous bool) (mapData map[string]interface{}) { + if tagName == "" { + tagName = "json" + } structsObj := structs.New(obj) - structsObj.TagName = "json" + structsObj.TagName = tagName + structsObj.IsFlattenAnonymous = true return structsObj.Map() } -// 此函数将MAP中所有的子MAP中的数据提升到最上层,相同字段会覆盖父MAP的 -func FlatMap(in map[string]interface{}) map[string]interface{} { - keys := []string{} - maps := []map[string]interface{}{} - for k, v := range in { - if vMap, ok := v.(map[string]interface{}); ok { - vMap = FlatMap(vMap) - maps = append(maps, vMap) - keys = append(keys, k) - } - } - if len(maps) > 0 { - retVal := MergeMaps(in, maps...) - for _, v := range keys { - delete(retVal, v) - } - return retVal - } - return in +func Struct2MapByJson(obj interface{}) (mapData map[string]interface{}) { + return Struct2Map(obj, "", false) } func Struct2FlatMap(obj interface{}) map[string]interface{} { - m := Struct2MapByJson(obj) - return FlatMap(m) + return Struct2Map(obj, "", true) } // !!! 此函数好像不支持struct是内嵌结构的 diff --git a/utils/typeconv_test.go b/utils/typeconv_test.go index d49f2122..081503e3 100644 --- a/utils/typeconv_test.go +++ b/utils/typeconv_test.go @@ -84,3 +84,40 @@ func TestTime(t *testing.T) { } } } + +func TestStruct2MapByJson(t *testing.T) { + type InnerKK struct { + IntData int + StrData string + ObjData time.Time + } + type KK struct { + IntData int + A int + B string + C time.Time + InnerKK + InnerKK2 InnerKK + } + kk := &KK{ + InnerKK: InnerKK{ + IntData: 1, + StrData: "hello", + }, + } + mapData := Struct2MapByJson(kk) + t.Log(Format4Output(mapData, false)) + // t.Log(mapData) + // t.Log(kk) +} + +// func TestStruct2MapByJson(t *testing.T) { +// mapData := Struct2MapByJson(&struct { +// IntData int `structs:"dataInt"` +// StrData string `json:"-"` +// }{ +// IntData: 1, +// StrData: "2", +// }) +// t.Log(mapData) +// } diff --git a/utils/utils_test.go b/utils/utils_test.go index ffdab708..79fb4b29 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -175,17 +175,6 @@ func TestTrimBlanChar(t *testing.T) { } } -func TestStruct2MapByJson(t *testing.T) { - mapData := Struct2MapByJson(&struct { - IntData int `structs:"dataInt"` - StrData string `json:"-"` - }{ - IntData: 1, - StrData: "2", - }) - t.Log(mapData) -} - func TestLimitUTF8StringLen(t *testing.T) { for _, v := range [][]interface{}{ []interface{}{ diff --git a/utils/utils_type.go b/utils/utils_type.go new file mode 100644 index 00000000..1e9e7f91 --- /dev/null +++ b/utils/utils_type.go @@ -0,0 +1,69 @@ +package utils + +import "time" + +func String2Pointer(value string) *string { + return &value +} + +func Pointer2String(ptr *string) (value string) { + if ptr != nil { + value = *ptr + } + return value +} + +func Int2Pointer(value int) *int { + return &value +} + +func Pointer2Int(ptr *int) (value int) { + if ptr != nil { + value = *ptr + } + return value +} + +func Int64ToPointer(value int64) *int64 { + return &value +} + +func Pointer2Int64(ptr *int64) (value int64) { + if ptr != nil { + value = *ptr + } + return value +} + +func Float32ToPointer(value float32) *float32 { + return &value +} + +func Pointer2Float32(ptr *float32) (value float32) { + if ptr != nil { + value = *ptr + } + return value +} + +func Float64ToPointer(value float64) *float64 { + return &value +} + +func Pointer2Float64(ptr *float64) (value float64) { + if ptr != nil { + value = *ptr + } + return value +} + +func Time2Pointer(value time.Time) *time.Time { + return &value +} + +func Pointer2Time(ptr *time.Time) (value time.Time) { + if ptr != nil { + value = *ptr + } + return value +}