package jdapi
import (
"bytes"
"crypto/md5"
"fmt"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"regexp"
"strings"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
)
const (
getStoreURL = "https://daojia.jd.com"
accessStorePageCookieName = "shop.o2o.jd.com1"
accessStorePageCookieName2 = "lsp-store1.jddj.com"
accessStorePageCookieName3 = "josl-privilege1.jddj.com"
htmlResponeURL = "login-o2o.jddj.com/jpuser/"
)
type SkuPageImg struct {
Big string `json:"big"`
Share string `json:"share"`
Small string `json:"small"`
}
type CorporationInfo struct {
Scope string `json:"scope"`
OperName string `json:"oper_name"`
Status string `json:"status"`
BelongOrg string `json:"belong_org"`
CreditNo string `json:"credit_no"`
RegNo string `json:"reg_no"`
ID string `json:"id"`
OrgNo string `json:"org_no"`
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 {
IdentifyType string `json:"identify_type"`
ShouldCapiItems []interface{} `json:"should_capi_items"`
StockType string `json:"stock_type"`
IdentifyNo string `json:"identify_no"`
RealCapiItems []interface{} `json:"real_capi_items"`
Name string `json:"name"`
} `json:"partners"`
Name string `json:"name"`
Province string `json:"province"`
TermStart string `json:"term_start"`
AbnormalItems []interface{} `json:"abnormal_items"`
CheckDate string `json:"check_date"`
RegistCapi string `json:"regist_capi"`
StartDate string `json:"start_date"`
Changerecords []struct {
BeforeContent string `json:"before_content"`
ChangeDate string `json:"change_date"`
ChangeItem string `json:"change_item"`
AfterContent string `json:"after_content"`
} `json:"changerecords"`
Branches []interface{} `json:"branches"`
Employees []struct {
JobTitle string `json:"job_title"`
Name string `json:"name"`
} `json:"employees"`
}
type PageShopInfo struct {
DisCatName string `json:"disCatName"`
DisplayType string `json:"displayType"`
FreightWords string `json:"freightWords"`
IsCart bool `json:"isCart"`
IsJb int `json:"isJb"`
IsWithHome bool `json:"isWithHome"`
LogoURL string `json:"logoUrl"`
StoreActPageList []interface{} `json:"storeActPageList"`
StoreCommentVO struct {
BuyerShowName string `json:"buyerShowName"`
CreateTime string `json:"createTime"`
IsOrgComment int `json:"isOrgComment"`
OrgCommentContent string `json:"orgCommentContent"`
Score4 int `json:"score4"`
Score4Content string `json:"score4Content"`
ScoreAvg float64 `json:"scoreAvg"`
ShowTotalCount bool `json:"showTotalCount"`
Skus []interface{} `json:"skus"`
StoreStar float64 `json:"storeStar"`
TagInfoItemList []string `json:"tagInfoItemList"`
TotalCount int `json:"totalCount"`
} `json:"storeCommentVO"`
StoreDesc string `json:"storeDesc"`
StoreInfo struct {
BackgroundType string `json:"backgroundType"`
CarrierNo int `json:"carrierNo"`
CloseStatus int `json:"closeStatus"`
DeliveryFirst string `json:"deliveryFirst"`
DeliverySecond string `json:"deliverySecond"`
ExpectArrivedTips []struct {
Msg string `json:"msg"`
Type int `json:"type"`
} `json:"expectArrivedTips"`
Flag bool `json:"flag"`
FollowNo string `json:"followNo"`
FreightTag struct {
BelongIndustry int `json:"belongIndustry"`
Type int `json:"type"`
Words string `json:"words"`
} `json:"freightTag"`
FreightWords string `json:"freightWords"`
InSaleNum string `json:"inSaleNum"`
Industry string `json:"industry"`
IsFollow bool `json:"isFollow"`
IsMembership bool `json:"isMembership"`
IsOverZone bool `json:"isOverZone"`
IsTimeFight int `json:"isTimeFight"`
LogoURL string `json:"logoUrl"`
MonthSaleNum string `json:"monthSaleNum"`
OrgCode string `json:"orgCode"`
Params string `json:"params"`
SearchLinkageFlag bool `json:"searchLinkageFlag"`
ServiceTimes []struct {
EndTime string `json:"endTime"`
StartTime string `json:"startTime"`
} `json:"serviceTimes"`
ShowType string `json:"showType"`
StationStatus int `json:"stationStatus"`
StoreAddress string `json:"storeAddress"`
StoreCertificateURL string `json:"storeCertificateUrl"`
StoreID string `json:"storeId"`
StoreName string `json:"storeName"`
StoreTel string `json:"storeTel"`
To string `json:"to"`
UpToSendprice int `json:"upToSendprice"`
} `json:"storeInfo"`
StoreShareURL string `json:"storeShareUrl"`
}
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"`
}
type StoreUserInfo struct {
Cn string `json:"cn"`
CreateApp string `json:"createApp"`
CreatePin string `json:"createPin"`
CreateTime *utils.JavaDate `json:"createTime"`
DefaultPwdType interface{} `json:"defaultPwdType"`
Email interface{} `json:"email"`
ErpAccount interface{} `json:"erpAccount"`
ExtFields struct {
OrgName string `json:"orgName"`
} `json:"extFields"`
ID int `json:"id"`
JdPin interface{} `json:"jdPin"`
LastLoginTime interface{} `json:"lastLoginTime"`
LockStatus string `json:"lockStatus"`
LoginCredentials interface{} `json:"loginCredentials"`
LoginFailCount int `json:"loginFailCount"`
LoginName string `json:"loginName"`
OrgCode string `json:"orgCode"`
OrgID int `json:"orgId"`
PartTimeOrgIds interface{} `json:"partTimeOrgIds"`
Phone string `json:"phone"`
Remark interface{} `json:"remark"`
RoleNameStr string `json:"roleNameStr"`
Roles interface{} `json:"roles"`
SortNo int `json:"sortNo"`
Status int `json:"status"`
SysVersion int `json:"sysVersion"`
UpdatePin interface{} `json:"updatePin"`
UpdateTime interface{} `json:"updateTime"`
UserCode string `json:"userCode"`
}
const (
QualifyTypeCompany = "25" // 营业执照
QualifyTypePerson = "22" // 身份证,个体工商户要求填
QualifyTypeAddInfo = "31" // 附加信息,如果身份证是长期有效,要求身份证背面信息
SaveQualifyActionTypeCommit = 0 // 提交
SaveQualifyActionTypeSave = 1 // 暂时保存
JdUserStatusEnable = 0 //启用
JdUserStatusDisable = 1 //禁用
)
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{}
regexpTable = regexp.MustCompile(`
`)
regexpTd = regexp.MustCompile(`([0-9].*) | `)
regexpJDUserPage = regexp.MustCompile(`共([\s\S].*)页/([\s\S].*)条记录`)
regexpJDTr = regexp.MustCompile(`([\s\S]*?)
`)
regexpJDUserInfo = regexp.MustCompile(`([\s\S]*?) | `)
regexpJDUserID = regexp.MustCompile(`value="(.*)"`)
)
const (
KeyImgData = "imgData"
KeyImgName = "imgName"
ResultKeyData = "data"
ResultKeyResult = "result"
)
func (a *API) SetJdCookie(cookieValue string) {
a.SetCookie(accessStorePageCookieName, cookieValue)
a.SetCookie(accessStorePageCookieName2, cookieValue)
a.SetCookie(accessStorePageCookieName3, cookieValue)
}
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
if !isPost {
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")
}
if err != nil {
return nil
}
a.FillRequestCookies(request)
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")
}
if strings.Index(fullURL, htmlResponeURL) >= 0 && jsonResult1[platformapi.KeyData] != nil {
retVal = bodyStr
return platformapi.ErrLevelSuccess, nil
}
retVal = jsonResult1
code, ok := jsonResult1["code"].(string)
if !ok {
return platformapi.ErrLevelGeneralFail, utils.NewErrorCode(utils.Format4Output(jsonResult1, true), "999")
}
if code == ResponseCodeSuccess {
if resultKey != "" {
retVal = jsonResult1[resultKey]
}
return platformapi.ErrLevelSuccess, nil
}
newErr := utils.NewErrorCode(jsonResult1["msg"].(string), code)
if _, ok := pageExceedLimitCodes[code]; ok {
return platformapi.ErrLevelExceedLimit, newErr
} else if _, ok := pageCanRetryCodes[code]; ok {
return platformapi.ErrLevelRecoverableErr, newErr
} else {
baseapi.SugarLogger.Debugf("jd AccessStorePage failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true))
return platformapi.ErrLevelCodeIsNotOK, newErr
}
})
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 {
return retVal["mobile"].(string), nil
}
return "", err
}
func (a *API) GetStoreOrderInfo(orderId, stationNo string) (storeOrderInfo map[string]interface{}, err error) {
params := map[string]interface{}{
"pageNo": 1,
"pageSize": 1,
"desc": true,
"param": orderId,
}
if stationNo != "" {
params["stationNo"] = stationNo
}
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{})
resultList := newOrderinfoMains["resultList"].([]interface{})
if len(resultList) > 0 {
return resultList[0].(map[string]interface{}), nil
}
return nil, fmt.Errorf("不能找到订单:%s的相关信息", orderId)
}
return nil, err
}
func (a *API) GetStoreOrderInfoList(fromTime, toTime string) (storeOrderList []map[string]interface{}, err error) {
pageSize := 100
pageNo := 1
params := map[string]interface{}{
"o2oOrderType": 10000,
"pageSize": pageSize,
"desc": true,
"startTimeQuery": fromTime,
"endTimeQuery": toTime,
// "stationNo": 0,
}
for {
params["pageNo"] = pageNo
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{})
resultList := newOrderinfoMains["resultList"].([]interface{})
storeOrderList = append(storeOrderList, utils.Slice2MapSlice(resultList)...)
if len(storeOrderList) >= int(utils.MustInterface2Int64(newOrderinfoMains["totalCount"])) {
return storeOrderList, nil
}
pageNo++
} else {
return nil, err
}
}
return nil, err
}
func (a *API) GetSkuPageInfo(skuId int64) (skuPageInfo *PageSku, err error) {
skuIDMap := map[string]interface{}{
"skuId": utils.Int64ToStr(skuId),
"storeId": "0",
}
params := map[string]interface{}{
"platCode": "H5",
"functionId": "product/detailV6_0",
"appVersion": "6.7.0",
"body": utils.Format4Output(skuIDMap, true),
}
result, err := a.AccessStorePage("https://daojia.jd.com/client", params, false)
if err == nil {
err = JdMap2StructByJson(result, &skuPageInfo, false)
}
return skuPageInfo, err
}
func (a *API) GetSkuPageImageInfo(skuId int64) (imgList []*SkuPageImg, err error) {
skuPageInfo, err := a.GetSkuPageInfo(skuId)
if err == nil {
imgList = skuPageInfo.Image
}
return imgList, err
}
func (a *API) GetStoreInfo(storeId string) (storeInfo map[string]interface{}, err error) {
body := map[string]interface{}{
"storeId": storeId,
}
params := map[string]interface{}{
"appVersion": "6.1.0",
"functionId": "store/storeDetailV220",
"body": utils.Format4Output(body, true),
}
retVal, err := a.AccessStorePage("https://daojia.jd.com/client", params, false)
return retVal, err
}
func (a *API) GetStoreInfo2(storeID string) (storeInfo *PageShopInfo, err error) {
retVal, err := a.GetStoreInfo(storeID)
if err == nil {
err = JdMap2StructByJson(retVal, &storeInfo, false)
}
return storeInfo, err
}
func signGetStoreList(bodyStr string) (signResult string) {
wb := "923047ae3f8d11d8b19aeb9f3d1bc200"
return fmt.Sprintf("%X", md5.Sum([]byte(bodyStr+wb)))
}
func (a *API) GetStoreList(lng string, lat string) (retVal map[string]interface{}, err error) {
body := map[string]interface{}{
// "channelId": "3997", // 原来
"channelId": "4037", // 菜市场
// "channelId": "4038", // 水果店
"currentPage": 1,
"pageSize": 999,
"coordType": "2",
"platform": "1",
}
bodyStr := utils.Format4Output(body, true)
signResult := signGetStoreList(bodyStr)
params := map[string]interface{}{
"platCode": "H5",
"appVersion": "6.5.0",
"functionId": "zone/recommendStoreList",
"body": utils.Format4Output(body, true),
"signKey": signResult,
"lng": lng,
"lat": lat,
}
retVal, err = a.AccessStorePage("https://daojia.jd.com/client", params, false)
return retVal, err
}
func (a *API) GetCorporationInfo(stationNo, qualifyNumber string) (corporatonInfo *CorporationInfo, err error) {
result, err := a.AccessStorePage("https://sta-store.jddj.com/store/requestQualify.o2o", map[string]interface{}{
"stationNo": stationNo,
"qualifyNumber": qualifyNumber,
}, true)
if err == nil {
err = JdMap2StructByJson(result, &corporatonInfo, false)
}
return corporatonInfo, err
}
func MonthSaleNum2Int(monthSaleNumStr string) (monthSaleNum int) {
searchResult := monthSaleNumReg.FindStringSubmatch(monthSaleNumStr)
if searchResult != nil && len(searchResult[1]) > 0 && len(searchResult[2]) > 0 {
monthSaleNum = int(utils.Str2Int64WithDefault(searchResult[1], 0))
if searchResult[2] == "千" {
monthSaleNum *= 1000
} else if searchResult[2] == "万" {
monthSaleNum *= 10000
}
} else {
monthSaleNum = int(utils.Str2Int64WithDefault(monthSaleNumStr, 0))
}
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
}
func (a *API) GetJdUserBindStoreIDs(userID int) (vendorStoreIDs []string, err error) {
body, err := a.AccessStorePage2("https://login-o2o.jddj.com/jpuser/bindStore/"+utils.Int2Str(userID), nil, false, "")
if err != nil {
return nil, err
}
bodyStr := body.(string)
tableStr := regexpTable.FindString(bodyStr)
vendorStoreIDsRegexp := regexpTd.FindAllStringSubmatch(tableStr, -1)
for _, v := range vendorStoreIDsRegexp {
vendorStoreIDs = append(vendorStoreIDs, v[1])
}
return vendorStoreIDs, err
}
// 查询商家中心账号信息接口
// https://login-o2o.jddj.com/jpuser/list?doPage=false&pageNo=1&appCode=lsp-store&pageSize=20&autoCount=false&_=1574936207872
func (a *API) PrivilegeSearchUser(pageNo int) (storeUserList []*StoreUserInfo, totalCount, totalPage int, err error) {
params := map[string]interface{}{
"pageNo": pageNo,
"appCode": "lsp-store",
"doPage": false,
"autoCount": false,
"pageSize": 20,
}
body, err := a.AccessStorePage2("https://login-o2o.jddj.com/jpuser/list", params, false, "")
if err != nil {
return nil, 0, 0, err
}
bodyStr := body.(string)
list := regexpJDTr.FindAllStringSubmatch(bodyStr, -1)
totalCount = int(utils.Str2Int64(regexpJDUserPage.FindStringSubmatch(bodyStr)[2]))
totalPage = int(utils.Str2Int64(regexpJDUserPage.FindStringSubmatch(bodyStr)[1]))
for _, v := range list {
tdList := regexpJDUserInfo.FindAllStringSubmatch(v[1], -1)
if len(tdList) > 0 {
id := regexpJDUserID.FindStringSubmatch(tdList[10][0])[1]
storeUserInfo := &StoreUserInfo{
LoginName: tdList[0][1],
LockStatus: tdList[9][1],
ID: int(utils.Str2Int64(id)),
RoleNameStr: tdList[4][1],
}
storeUserList = append(storeUserList, storeUserInfo)
}
}
return storeUserList, totalCount, totalPage, err
}
// 查询全部商家中心账号信息接口
// https://login-o2o.jddj.com/jpuser/list?doPage=false&pageNo=1&appCode=lsp-store&pageSize=20&autoCount=false&_=1574936207872
func (a *API) PrivilegeSearchUserAll() (storeUserLists []*StoreUserInfo, err error) {
_, _, totalPage, err := a.PrivilegeSearchUser(1)
for i := 1; i <= totalPage; i++ {
storeUserList, _, _, err := a.PrivilegeSearchUser(i)
if err != nil {
return nil, err
}
storeUserLists = append(storeUserLists, storeUserList...)
}
return storeUserLists, err
}
// 禁用/启用商家中心账号信息接口
// https: //login-o2o.jddj.com/jpuser/enable/346408
// https: //login-o2o.jddj.com/jpuser/disable/346408
func (a *API) PrivilegeUpdateJdUserStatus(id int64, status int) (err error) {
url := "https://login-o2o.jddj.com/jpuser/"
if status == JdUserStatusEnable {
url += "enable/"
} else {
url += "disable/"
}
url += utils.Int64ToStr(id)
_, err = a.AccessStorePage2(url, nil, true, "")
return err
}