Merge branch 'master' of e.coding.net:rosydev/baseapi

This commit is contained in:
Rosy-zhudan
2019-10-09 11:18:06 +08:00
43 changed files with 1737 additions and 480 deletions

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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))
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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"`
}

View File

@@ -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"`

View File

@@ -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
}
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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{})

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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())
}
}

View File

@@ -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
}

View File

@@ -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()
// }
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)

View File

@@ -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,
},
}

View File

@@ -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
}
// 私有函数

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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
}
// 专快混配送转为商家自配送

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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("<audio>%s,9,0</audio>", 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
}

View File

@@ -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(), "<FS2>饿百取货码</FS2>")
err := api.PrintMsg("4004615546", utils.GetUUID(), "<FS2>饿百取货码</FS2>")
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))
}

View File

@@ -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是内嵌结构的

View File

@@ -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)
// }

View File

@@ -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{}{

69
utils/utils_type.go Normal file
View File

@@ -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
}