├── .gitignore ├── utils_test.go ├── crypto.go ├── context.go ├── ddk_goods_test.go ├── error.go ├── pdd.go ├── go.mod ├── pdd_test.go ├── ddk_theme.go ├── type.go ├── utils.go ├── ddk_pid.go ├── http.go ├── goods.go ├── params.go ├── ddk_cms.go ├── go.sum ├── ddk.go ├── ddk_order.go ├── README.md └── ddk_goods.go /.gitignore: -------------------------------------------------------------------------------- 1 | ddk_test.go 2 | key.go 3 | test.go 4 | .idea -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import "testing" 4 | 5 | func TestTransformPids(t *testing.T) { 6 | s := TransformPids([]string{"1"}) 7 | t.Log(s) 8 | if s != `["1"]` { 9 | t.Error("error") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /crypto.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "crypto/md5" 5 | "strings" 6 | "encoding/hex" 7 | ) 8 | 9 | func createSign(signStr string) string { 10 | h := md5.New() 11 | h.Write([]byte(signStr)) 12 | cipherStr := h.Sum(nil) 13 | return strings.ToUpper(hex.EncodeToString(cipherStr)) 14 | } -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | type Context struct { 4 | ClientId string 5 | ClientSecret string 6 | RetryTimes int 7 | Debug bool 8 | } 9 | 10 | func NewContext(cfg *Config) *Context { 11 | return &Context{ 12 | ClientId: cfg.ClientId, 13 | ClientSecret: cfg.ClientSecret, 14 | RetryTimes: cfg.RetryTimes, 15 | Debug: cfg.Debug, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ddk_goods_test.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "testing" 5 | "fmt" 6 | ) 7 | 8 | func TestGoodsAPI_GoodsCatGet(t *testing.T) { 9 | g := newGoodsAPIWithContext(&Context{ 10 | ClientId: ClientId, 11 | ClientSecret: ClientSecret, 12 | }) 13 | r, err := g.GoodsCatGet(0) 14 | fmt.Println(err) 15 | fmt.Printf("%v", r) 16 | for _, value := range r { 17 | fmt.Printf("%#v\n", value) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import "encoding/json" 4 | 5 | type Error struct { 6 | ErrorResponse struct{ 7 | ErrorMsg string `json:"error_msg"` 8 | SubMsg string `json:"sub_msg"` 9 | SubCode int `json:"sub_code"` 10 | ErrorCode int `json:"error_code"` 11 | RequestId string `json:"request_id"` 12 | } `json:"error_response"` 13 | } 14 | 15 | func (e Error) Error() string { 16 | r, _ := json.Marshal(e) 17 | return string(r) 18 | } -------------------------------------------------------------------------------- /pdd.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | type Pdd struct { 4 | Context *Context 5 | } 6 | 7 | type Config struct { 8 | ClientId string 9 | ClientSecret string 10 | RetryTimes int 11 | Debug bool 12 | } 13 | 14 | func NewPdd(c *Config) *Pdd { 15 | return &Pdd{ 16 | Context: NewContext(c), 17 | } 18 | } 19 | 20 | func (p *Pdd) GetDDK() *DDK { 21 | return newDDKWithContext(p.Context) 22 | } 23 | 24 | func (p *Pdd) GetGoodsAPI() *GoodsAPI { 25 | return newGoodsAPIWithContext(p.Context) 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/liunian1004/pdd 2 | 3 | require ( 4 | github.com/bitly/go-simplejson v0.5.0 5 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect 6 | github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a // indirect 7 | github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3 8 | github.com/kr/pretty v0.1.0 // indirect 9 | github.com/moul/http2curl v1.0.0 // indirect 10 | github.com/parnurzeal/gorequest v0.2.15 11 | github.com/pkg/errors v0.8.1 // indirect 12 | github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /pdd_test.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "testing" 5 | "fmt" 6 | ) 7 | 8 | func TestNewPdd(t *testing.T) { 9 | p := NewPdd(&Config{ 10 | ClientId: "", 11 | ClientSecret: "", 12 | }) 13 | d := p.GetDDK() 14 | fmt.Println(p, d) 15 | r, err := d.GoodsDetail(1143441478) 16 | if err != nil { 17 | t.Error(err) 18 | } 19 | fmt.Printf("%+v", r) 20 | } 21 | 22 | func TestPdd_GetThemeList(t *testing.T) { 23 | //p := NewPdd(&Config{ 24 | // ClientId: "", 25 | // ClientSecret: "", 26 | //}) 27 | //d := ddk.NewDDK(p) 28 | //s, err := d.GetThemeList(1, 20) 29 | //if err != nil { 30 | // t.Error(err) 31 | //} 32 | //t.Logf("%+v", s) 33 | // 34 | //SetDebug(true) 35 | //SetRetryTimes(3) 36 | } 37 | -------------------------------------------------------------------------------- /ddk_theme.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type ThemeListResponse struct { 8 | ThemeList []*Theme `json:"theme_list"` 9 | Total int `json:"total"` 10 | } 11 | 12 | type Theme struct { 13 | Id int `json:"id"` 14 | ImageUrl string `json:"image_url"` 15 | Name string `json:"name"` 16 | GoodsNum int `json:"goods_num"` // 商品数量 17 | } 18 | 19 | func (d *DDK) ThemeListGet(page, pageSize int) (res *ThemeListResponse, err error) { 20 | params := NewParamsWithType(DDK_ThemeListGet) 21 | params.Set("page", page) 22 | params.Set("page_size", pageSize) 23 | 24 | r, err := Call(d.Context, params) 25 | if err != nil { 26 | return 27 | } 28 | bytes, err := GetResponseBytes(r, "theme_list_get_response") 29 | res = new(ThemeListResponse) 30 | err = json.Unmarshal(bytes, res) 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /type.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | type MethodType string 4 | 5 | const ( 6 | // 多多客 7 | DDK_ThemeListGet MethodType = "pdd.ddk.theme.list.get" 8 | DDK_RPPromUrlGenerate MethodType = "pdd.ddk.rp.prom.url.generate" // 生成红包推广链接 9 | DDK_OrderListIncrementGet MethodType = "pdd.ddk.order.list.increment.get" 10 | DDK_ColorOrderIncrementGet MethodType = "pdd.ddk.color.order.increment.get" 11 | DDK_GoodsDetail MethodType = "pdd.ddk.goods.detail" 12 | DDK_GoodsSearch MethodType = "pdd.ddk.goods.search" 13 | DDK_GoodsPidQuery MethodType = "pdd.ddk.goods.pid.query" 14 | DDK_GoodsPidGenerate MethodType = "pdd.ddk.goods.pid.generate" 15 | DDK_GoodsPromotionUrlGenerate MethodType = "pdd.ddk.goods.promotion.url.generate" 16 | DDK_LotteryUrlGen MethodType = "pdd.ddk.lottery.url.gen" 17 | DDK_CMSPromUrlGenerate MethodType = "pdd.ddk.cms.prom.url.generate" 18 | DDK_TopGoodsListQuery MethodType = "pdd.ddk.top.goods.list.query" //多多客获取爆款排行商品接口 19 | DDK_GoodsZsUnitUrlGen MethodType = "pdd.ddk.goods.zs.unit.url.gen" //多多进宝转链接口 20 | 21 | //商品 API 22 | GoodsCatsGet MethodType = "pdd.goods.cats.get" //商品标准类目接口 23 | GoodsOptGet MethodType = "pdd.goods.opt.get" //查询商品标签列表 24 | ) 25 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "github.com/bitly/go-simplejson" 5 | "fmt" 6 | ) 7 | 8 | // the basic call method 9 | func Call(context *Context, params Params) (r []byte, err error) { 10 | params.Sign(context) 11 | r, err = Post(context, params.GetQuery()) 12 | return 13 | } 14 | 15 | // 兼容多层 Key 读取 16 | func GetResponseBytes(data []byte, keys ...string) (b []byte, err error) { 17 | js, err := simplejson.NewJson(data) 18 | if err != nil { 19 | return 20 | } 21 | for _, key := range keys { 22 | js = js.Get(key) 23 | } 24 | b, err = js.Encode() 25 | return 26 | } 27 | 28 | func GetResponseArrayIndexBytes(data []byte, index int, keys ...string) (b []byte, err error) { 29 | js, err := simplejson.NewJson(data) 30 | if err != nil { 31 | return 32 | } 33 | for _, key := range keys { 34 | js = js.Get(key) 35 | } 36 | 37 | js = js.GetIndex(index) 38 | 39 | b, err = js.Encode() 40 | return 41 | } 42 | 43 | func TransformPids(pids []string) (r string) { 44 | if len(pids) == 0 { 45 | return "" 46 | } 47 | if len(pids) == 1 { 48 | return fmt.Sprintf(`["%s"]`, pids[0]) 49 | } 50 | r += `["` 51 | for k, v := range pids { 52 | if k == 0 { 53 | r += v 54 | } else { 55 | r += "," + v 56 | } 57 | } 58 | r += `"]` 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /ddk_pid.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type PidQueryListResponse struct { 8 | PIDList []*PidQuery `json:"p_id_list"` 9 | TotalCount int `json:"total_count"` 10 | } 11 | 12 | type PidQuery struct { 13 | PId string `json:"p_id"` 14 | PName string `json:"pid_name"` 15 | CreateTime int64 `json:"create_time"` 16 | } 17 | 18 | // 分页查询生成的推广位信息 19 | func (d *DDK) GoodsPidQuery(notMustparams ...Params) (res *PidQueryListResponse, err error) { 20 | params := NewParamsWithType(DDK_GoodsPidQuery, notMustparams...) 21 | 22 | r, err := Call(d.Context, params) 23 | if err != nil { 24 | return 25 | } 26 | bytes, err := GetResponseBytes(r, "p_id_query_response") 27 | res = new(PidQueryListResponse) 28 | err = json.Unmarshal(bytes, res) 29 | return 30 | } 31 | 32 | type PIdGenerateListResponse struct { 33 | PIDList []*PIdGenerate `json:"p_id_list"` 34 | } 35 | 36 | type PIdGenerate struct { 37 | PId string `json:"p_id"` 38 | PIdName string `json:"pid_name"` 39 | CreateTime int64 `json:"create_time"` 40 | } 41 | 42 | // 创建推广位 43 | func (d *DDK) GoodsPidGenerate(number int, notMustparams ...Params) (res *PIdGenerateListResponse, err error) { 44 | params := NewParamsWithType(DDK_GoodsPidGenerate, notMustparams...) 45 | params.Set("number", number) 46 | 47 | r, err := Call(d.Context, params) 48 | if err != nil { 49 | return 50 | } 51 | bytes, err := GetResponseBytes(r, "p_id_generate_response") 52 | res = new(PIdGenerateListResponse) 53 | err = json.Unmarshal(bytes, res) 54 | return 55 | } 56 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "github.com/parnurzeal/gorequest" 5 | "sync" 6 | "fmt" 7 | "log" 8 | "encoding/json" 9 | ) 10 | 11 | const EndPoint = "https://gw-api.pinduoduo.com/api/router" 12 | 13 | var clients *sync.Pool 14 | 15 | func init() { 16 | clients = &sync.Pool{ 17 | New: func() interface{} { 18 | return gorequest.New() 19 | }, 20 | } 21 | } 22 | 23 | func Post(context *Context, query string) (b []byte, err error) { 24 | b, err = post(query) 25 | if err != nil { 26 | times := 0 27 | for times < context.RetryTimes { 28 | b, err = post(query) 29 | if err != nil { 30 | log.Printf("第 %d 次重试失败:%s", times + 1, err) 31 | } else { 32 | return 33 | } 34 | times++ 35 | } 36 | } 37 | return 38 | } 39 | 40 | func post(query string) (b []byte, err error) { 41 | client := clients.Get().(*gorequest.SuperAgent) 42 | _, b, errors := client.Post(EndPoint). 43 | Type("json"). 44 | Query(query). 45 | EndBytes() 46 | // push back 47 | clients.Put(client) 48 | if IsBadPddRequest(b) { 49 | err = new(Error) 50 | json.Unmarshal(b, err) 51 | return 52 | } 53 | if err = getErrorsError(errors); err != nil { 54 | return 55 | } 56 | return 57 | } 58 | 59 | func getErrorsError(errors []error) (err error) { 60 | if len(errors) == 0 { 61 | return nil 62 | } 63 | for _, e := range errors { 64 | if e != nil { 65 | err = fmt.Errorf("%s | %s", err, e) 66 | } 67 | } 68 | return 69 | } 70 | 71 | func IsBadPddRequest(body []byte) bool { 72 | return len(body) >= 16 && string(body[2:16]) == "error_response" 73 | } 74 | -------------------------------------------------------------------------------- /goods.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type GoodsAPI struct { 8 | Context *Context 9 | } 10 | 11 | func NewGoodsAPI(cfg *Config) *GoodsAPI { 12 | return &GoodsAPI{Context: NewContext(cfg)} 13 | } 14 | 15 | func newGoodsAPIWithContext(c *Context) *GoodsAPI { 16 | return &GoodsAPI{Context: c} 17 | } 18 | 19 | type Category struct { 20 | Level int `json:"level"` // 层级,1-一级,2-二级,3-三级,4-四级 21 | CatId int `json:"cat_id"` // 22 | ParentCatId int `json:"parent_cat_id"` 23 | CatName string `json:"cat_name"` 24 | } 25 | 26 | func (g *GoodsAPI) GoodsCatGet(parentCatId int) (res []*Category, err error) { 27 | params := NewParamsWithType(GoodsCatsGet) 28 | params.Set("parent_cat_id", parentCatId) 29 | 30 | r, err := Call(g.Context, params) 31 | if err != nil { 32 | return 33 | } 34 | bytes, err := GetResponseBytes(r, "goods_cats_get_response", "goods_cats_list") 35 | json.Unmarshal(bytes, &res) 36 | return 37 | } 38 | 39 | type Opt struct { 40 | Level int `json:"level"` // 层级,1-一级,2-二级,3-三级,4-四级 41 | ParentOptID int `json:"parent_opt_id"` // id 所属父 Id,其中,parent_id = 0 时为顶级节点 42 | OptName string `json:"opt_name"` // 商品标签名 43 | OptID int `json:"opt_id"` // 商品标签ID 44 | } 45 | 46 | func (g *GoodsAPI) GoodsOptGet(parentOptId int) (res []*Opt, err error) { 47 | params := NewParamsWithType(GoodsOptGet) 48 | params.Set("parent_opt_id", parentOptId) 49 | 50 | r, err := Call(g.Context, params) 51 | if err != nil { 52 | return 53 | } 54 | 55 | bytes, err := GetResponseBytes(r, "goods_opt_get_response", "goods_opt_list") 56 | json.Unmarshal(bytes, &res) 57 | return 58 | } -------------------------------------------------------------------------------- /params.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | "sort" 7 | "net/url" 8 | "encoding/json" 9 | ) 10 | 11 | // pdd params 12 | type Params map[string]interface{} 13 | 14 | func NewParams() Params { 15 | p := make(Params) 16 | return p 17 | } 18 | 19 | func NewParamsWithType(_type MethodType, params ...Params) Params { 20 | p := make(Params) 21 | p["type"] = _type 22 | p["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10) 23 | for _, v := range params { 24 | p.SetParams(v) 25 | } 26 | return p 27 | } 28 | 29 | func (p Params) Sign(context *Context) { 30 | p["client_id"] = context.ClientId 31 | // 排序所有的 key 32 | var keys []string 33 | for key := range p { 34 | keys = append(keys, key) 35 | } 36 | sort.Strings(keys) 37 | signStr := context.ClientSecret 38 | for _, key := range keys { 39 | signStr += key + getString(p[key]) 40 | } 41 | signStr += context.ClientSecret 42 | p["sign"] = createSign(signStr) 43 | } 44 | 45 | func (p Params) Set(key string, value interface{}) { 46 | p[key] = value 47 | } 48 | 49 | func (p Params) SetParams(params Params) { 50 | for key, value := range params { 51 | p[key] = value 52 | } 53 | } 54 | 55 | func (p Params) GetQuery() string { 56 | u := url.Values{} 57 | for k, v := range p { 58 | u.Set(k, getString(v)) 59 | } 60 | return u.Encode() 61 | } 62 | 63 | func getString(i interface{}) string { 64 | switch v := i.(type) { 65 | case string: 66 | return v 67 | case []byte: 68 | return string(v) 69 | case int: 70 | return strconv.Itoa(v) 71 | case MethodType: 72 | return string(v) 73 | case bool: 74 | return strconv.FormatBool(v) 75 | default: 76 | bytes, _ := json.Marshal(v) 77 | return string(bytes) 78 | } 79 | return "" 80 | } -------------------------------------------------------------------------------- /ddk_cms.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import "encoding/json" 4 | 5 | type CMSPromUrl struct { 6 | Url string `json:"url"` 7 | ShortUrl string `json:"short_url"` 8 | MobileUrl string `json:"mobile_url"` 9 | MobileShortUrl string `json:"mobile_short_url"` 10 | WeAppWebViewUrl string `json:"we_app_web_view_url"` 11 | WeAppWebViewShortUrl string `json:"we_app_web_view_short_url"` 12 | MultiGroupUrl string `json:"multi_group_url"` // 红包推广多人团链接 13 | MultiGroupShortUrl string `json:"multi_group_short_url"` // 红包推广多人团短链接 14 | MultiGroupMobileUrl string `json:"multi_group_mobile_url"` // 红包推广多人团移动链接 15 | MultiGroupMobileShortUrl string `json:"multi_group_mobile_short_url"` // 红包推广多人团移动短链接 16 | } 17 | 18 | type ChannelType int 19 | 20 | const ( 21 | PackageMail19 ChannelType = 0 // 1.9 包邮 22 | TodayHotStyle ChannelType = 1 // 今日爆款 23 | BrandClearance ChannelType = 2 // 品牌清仓 24 | PCTerminalMall ChannelType = 4 // PC端专属商城 25 | RaiseBabyCash ChannelType = 5 // 养宝宝兑现金 26 | ) 27 | 28 | func (d *DDK) CMSPromUrlGen(pids []string, channelType ChannelType, generateWeappWebview, weAppWebViewShortUrl, weAppWebViewUrl bool, notMustparams ...Params) (res *CMSPromUrl, err error) { 29 | res = new(CMSPromUrl) 30 | params := NewParamsWithType(DDK_CMSPromUrlGenerate, notMustparams...) 31 | params.Set("p_id_list", TransformPids(pids)) 32 | params.Set("channel_type", int(channelType)) 33 | params.Set("generate_weapp_webview", generateWeappWebview) 34 | params.Set("we_app_web_view_url", weAppWebViewShortUrl) 35 | params.Set("we_app_web_view_short_url", weAppWebViewUrl) 36 | 37 | r, err := Call(d.Context, params) 38 | if err != nil { 39 | return 40 | } 41 | bytes, err := GetResponseArrayIndexBytes(r, 0, "cms_promotion_url_generate_response", "url_list") 42 | err = json.Unmarshal(bytes, &res) 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= 2 | github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= 3 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 4 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 5 | github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a h1:A4wNiqeKqU56ZhtnzJCTyPZ1+cyu8jKtIchQ3TtxHgw= 6 | github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 7 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 8 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 9 | github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3 h1:sHsPfNMAG70QAvKbddQ0uScZCHQoZsT5NykGRCeeeIs= 10 | github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= 11 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 12 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 13 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 14 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 15 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 16 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 17 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 18 | github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= 19 | github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= 20 | github.com/parnurzeal/gorequest v0.2.15 h1:oPjDCsF5IkD4gUk6vIgsxYNaSgvAnIh1EJeROn3HdJU= 21 | github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= 22 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 23 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 24 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 25 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 26 | github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys= 27 | github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= 28 | -------------------------------------------------------------------------------- /ddk.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type DDK struct { 8 | Context *Context 9 | } 10 | 11 | func NewDDK(cfg *Config) *DDK { 12 | return &DDK{Context: NewContext(cfg)} 13 | } 14 | 15 | func newDDKWithContext(ctx *Context) *DDK { 16 | return &DDK{Context: ctx} 17 | } 18 | 19 | type RedPackageUrl struct { 20 | Url string `json:"url"` 21 | ShortUrl string `json:"short_url"` 22 | MobileUrl string `json:"mobile_url"` 23 | MobileShortUrl string `json:"mobile_short_url"` 24 | WeAppWebViewUrl string `json:"we_app_web_view_url"` 25 | WeAppWebViewShortUrl string `json:"we_app_web_view_short_url"` 26 | MultiGroupUrl string `json:"multi_group_url"` // 红包推广多人团链接 27 | MultiGroupShortUrl string `json:"multi_group_short_url"` // 红包推广多人团短链接 28 | MultiGroupMobileUrl string `json:"multi_group_mobile_url"` // 红包推广多人团移动链接 29 | MultiGroupMobileShortUrl string `json:"multi_group_mobile_short_url"` // 红包推广多人团移动短链接 30 | } 31 | 32 | func (d *DDK) RPPromUrlGenerate(pids []string, generateWeappWebview bool, notMustparams ...Params) (res *RedPackageUrl, err error) { 33 | res = new(RedPackageUrl) 34 | params := NewParamsWithType(DDK_RPPromUrlGenerate, notMustparams...) 35 | params.Set("p_id_list", TransformPids(pids)) 36 | params.Set("generate_weapp_webview", generateWeappWebview) 37 | 38 | r, err := Call(d.Context, params) 39 | if err != nil { 40 | return 41 | } 42 | bytes, err := GetResponseArrayIndexBytes(r, 0, "rp_promotion_url_generate_response", "url_list") 43 | err = json.Unmarshal(bytes, &res) 44 | return 45 | } 46 | 47 | type LotteryUrl struct { 48 | Url string `json:"url"` 49 | ShortUrl string `json:"short_url"` 50 | MobileUrl string `json:"mobile_url"` 51 | MobileShortUrl string `json:"mobile_short_url"` 52 | WeAppWebViewUrl string `json:"we_app_web_view_url"` 53 | WeAppWebViewShortUrl string `json:"we_app_web_view_short_url"` 54 | MultiGroupUrl string `json:"multi_group_url"` // 红包推广多人团链接 55 | MultiGroupShortUrl string `json:"multi_group_short_url"` // 红包推广多人团短链接 56 | MultiGroupMobileUrl string `json:"multi_group_mobile_url"` // 红包推广多人团移动链接 57 | MultiGroupMobileShortUrl string `json:"multi_group_mobile_short_url"` // 红包推广多人团移动短链接 58 | } 59 | 60 | // lottery custom_parameters 61 | func (d *DDK) LotteryUrlGen(pids []string, generateWeappWebview bool, notMustparams ...Params) (res *LotteryUrl, err error) { 62 | res = new(LotteryUrl) 63 | params := NewParamsWithType(DDK_LotteryUrlGen, notMustparams...) 64 | params.Set("pid_list", TransformPids(pids)) 65 | params.Set("generate_weapp_webview", generateWeappWebview) 66 | 67 | r, err := Call(d.Context, params) 68 | if err != nil { 69 | return 70 | } 71 | bytes, err := GetResponseArrayIndexBytes(r, 0,"lottery_url_response", "url_list") 72 | err = json.Unmarshal(bytes, &res) 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /ddk_order.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type OrderListResponse struct { 8 | OrderList []*Order `json:"order_list"` 9 | TotalCount int `json:"total_count"` 10 | } 11 | 12 | type Order struct { 13 | OrderSn string `json:"order_sn"` 14 | GoodsId int `json:"goods_id"` 15 | GoodsName string `json:"goods_name"` 16 | GoodsThumbnailUrl string `json:"goods_thumbnail_url"` 17 | GoodsQuantity int `json:"goods_quantity"` 18 | GoodsPrice int `json:"goods_price"` 19 | OrderAmount int `json:"order_amount"` 20 | OrderCreateTime int `json:"order_create_time"` 21 | OrderSettleTime int `json:"order_settle_time"` // 结算时间 22 | OrderVerifyTime int `json:"order_verify_time"` // 审核时间 23 | OrderReceiveTime int `json:"order_receive_time"` // 收货时间 24 | OrderPayTime int `json:"order_pay_time"` 25 | PromotionRate int `json:"promotion_rate"` 26 | PromotionAmount int `json:"promotion_amount"` 27 | BatchNo string `json:"batch_no"` 28 | OrderID string `json:"order_id"` 29 | OrderStatus int `json:"order_status"` 30 | OrderStatusDesc string `json:"order_status_desc"` 31 | VerifyTime int `json:"verify_time"` 32 | OrderGroupSuccessTime int `json:"order_group_success_time"` 33 | OrderModifyAt int `json:"order_modify_at"` 34 | Type int `json:"type"` 35 | GroupId int `json:"group_id"` 36 | AuthDuoId int `json:"auth_duo_id"` 37 | ZsDuoId int `json:"zs_duo_id"` 38 | CustomParameters string `json:"custom_parameters"` 39 | Pid string `json:"pid"` // common use pid 40 | PId string `json:"p_id"` 41 | MatchChannel int `json:"match_channel"` 42 | DuoCouponAmount int `json:"duo_coupon_amount"` 43 | } 44 | 45 | // page_size default 100 46 | // page default 1 47 | func (d *DDK) OrderListIncrementGet(startUpdateTime, endUpdateTime int64, notMustparams ...Params) (res *OrderListResponse, err error) { 48 | res = new(OrderListResponse) 49 | 50 | params := NewParamsWithType(DDK_OrderListIncrementGet, notMustparams...) 51 | params.Set("start_update_time", startUpdateTime) 52 | params.Set("end_update_time", endUpdateTime) 53 | 54 | r, err := Call(d.Context, params) 55 | if err != nil { 56 | return 57 | } 58 | bytes, err := GetResponseBytes(r, "order_list_get_response") 59 | err = json.Unmarshal(bytes, res) 60 | 61 | for _, ord := range res.OrderList { 62 | if ord.Pid == "" { 63 | ord.Pid = ord.PId 64 | } 65 | } 66 | return 67 | } 68 | 69 | func (d *DDK) ColorOrderIncrementGet(startUpdateTime, endUpdateTime int64, notMustparams ...Params) (res *OrderListResponse, err error) { 70 | params := NewParamsWithType(DDK_ColorOrderIncrementGet, notMustparams...) 71 | params.Set("start_update_time", startUpdateTime) 72 | params.Set("end_update_time", endUpdateTime) 73 | 74 | r, err := Call(d.Context, params) 75 | if err != nil { 76 | return 77 | } 78 | bytes, err := GetResponseBytes(r, "order_list_get_response") 79 | res = new(OrderListResponse) 80 | err = json.Unmarshal(bytes, res) 81 | 82 | for _, ord := range res.OrderList { 83 | if ord.Pid == "" { 84 | ord.Pid = ord.PId 85 | } 86 | } 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | 本项目为 `go` 语言实现的拼多多开放平台 SDK,调用方式简单粗暴。 4 | 5 | 对于未实现的接口,欢迎 pr 交流。 6 | 7 | `go get github.com/liunian1004/pdd` 8 | 9 | ```go 10 | import github.com/liunian1004/pdd 11 | 12 | p := pdd.NewPdd(&pdd.Config{ 13 | ClientId: "your client id", 14 | ClientSecret: "your client secret", 15 | RetryTimes: 3, // 设置接口调用失败重试次数 16 | }) 17 | 18 | // 初始化多多客相关 API 调用 19 | d := p.GetDDK() 20 | 21 | // 或者 22 | 23 | d := ddk.NewDDK(&pdd.Config{ 24 | ClientId: "your client id", 25 | ClientSecret: "your client secret", 26 | RetryTimes: 3, // 设置接口调用失败重试次数 27 | }) 28 | 29 | // 获取主题列表 30 | r, err := d.ThemeListGet(1, 20) 31 | 32 | // 初始化商品 API 33 | g := p.GetGoodsAPI() 34 | ``` 35 | ## 非必须参数 36 | 37 | 通过自定义 Params 定制非必须参数,在函数的最后一个参数传入 Params 对象。 38 | 39 | ```go 40 | d := NewDDK(p) 41 | 42 | params := pdd.NewParams() 43 | // 设置非必传参数 44 | params.Set("custom_parameters", "test") 45 | params.Set("generate_short_url", true) 46 | s, err := d.RPPromUrlGenerate([]string{"pid"}, true, params) 47 | ``` 48 | 49 | ## Todo 50 | 51 | - [] 实现 ddk 多多客 API 相关接口 52 | - [x] `OrderListIncrementGet()` pdd.ddk.order.list.increment.get 最后更新时间段增量同步推广订单信息 53 | - [x] `GoodsDetail()` pdd.ddk.goods.detail 多多进宝商品详情查询 54 | - [x] `GoodsSearch()` pdd.ddk.goods.search 多多进宝商品查询 55 | - [x] `GoodsPidQuery()` pdd.ddk.goods.pid.query 查询已经生成的推广位信息 56 | - [x] `GoodsPidGenerate()` pdd.ddk.goods.pid.generate 创建多多进宝推广位 57 | - [x] `GoodsPromotionUrlGenerate()` pdd.ddk.goods.promotion.url.generate 多多进宝推广链接生成 58 | - [x] `TopGoodsListQuery()` pdd.ddk.top.goods.list.query 获取热销商品列表 59 | - [x] `RPPromUrlGenerate()` pdd.ddk.rp.prom.url.generate 生成红包推广链接 (**需要对应权限**) 60 | - [x] `CMSPromUrlGen()` pdd.ddk.cms.prom.url.generate 生成商城-频道推广链接 61 | - [x] `ThemeListGet()` pdd.ddk.theme.list.get 多多进宝主题列表查询 62 | - [] pdd.ddk.theme.goods.search 多多进宝主题商品查询 63 | - [] pdd.ddk.theme.prom.url.generate 多多进宝主题推广链接生成 64 | - [] pdd.ddk.app.new.bill.list.get 多多客拉新账单 65 | - [] pdd.ddk.direct.goods.query 定向推广商品查询接口 66 | - [x] `GoodsZsURLGen()` pdd.ddk.goods.zs.unit.url.gen 多多进宝转链接口 67 | - [] pdd.ddk.zs.unit.goods.query 查询招商推广计划商品 68 | - [] pdd.ddk.weapp.qrcode.url.gen 多多客生成单品推广小程序二维码 url 69 | - [] pdd.ddk.goods.basic.info.get 获取商品基本信息接口 70 | - [] pdd.ddk.goods.recommend.get 运营频道商品查询 API 71 | - [] pdd.ddk.order.detail.get 查询订单详情 72 | - [] pdd.ddk.mall.goods.list.get 查询店铺商品 73 | - [] pdd.ddk.mall.url.gen 多多客生成店铺推广链接 API 74 | - [x] `LotteryUrlGen()` pdd.ddk.lottery.url.gen 多多客生成转盘抽免单 url (**需要对应权限**) 75 | - [] pdd.ddk.lottery.new.list.get 多多客查询转盘拉新订单列表 76 | - [] pdd.ddk.resource.url.gen 生成多多进宝频道推广 77 | - [] pdd.ddk.merchant.list.get 多多客查店铺列表接口 78 | - 商品 API 79 | - [x] `GoodsCatGet()` pdd.goods.cats.get 拼多多标准商品类目信息 80 | - [x] `GoodsOptGet()` pdd.goods.opt.get 拼多多商品标签列表 81 | 82 | ## 多多客工具 API 83 | 84 | 提供给第三方开发者为多多进宝推广者提供第三方工具的 API。 85 | 86 | - pdd.ddk.oauth.goods.pid.generate(多多进宝推广位创建接口) 87 | - pdd.ddk.oauth.goods.pid.query(多多客已生成推广位信息查询) 88 | - pdd.ddk.oauth.goods.prom.url.generate(生成多多进宝推广链接) 89 | - pdd.ddk.oauth.order.list.increment.get(按照更新时间段增量同步推广订单信息) 90 | - pdd.ddk.oauth.order.list.range.get(按照时间段获取多多进宝推广订单信息) 91 | 92 | ## 注意事项 93 | 94 | 1. 多多客工具API需要多多客授权才能使用,授权服务URL为 http://jinbao.pinduoduo.com/open.html ,根据接入指南提示组装合法的 URL,示例:http://jinbao.pinduoduo.com/open.html?client_id=89daeb71b2e546318fbb53cd04d0329c&redirect_uri=www.pinduoduo.com&response_type=code,之后引导多多客授权获取code即可。 95 | 2. code、access_token、refresh_token 失效时间与商家授权一致,分别为10分钟、24小时、30天,刷新token后分别延长access_token和refresh_token的失效时间,延长时间等同于授权时长,获取token教程详见:http://open.pinduoduo.com/#/document 96 | 3. 调用多多客工具 API 时,必须入参 access_token 方可正常调用 97 | 4. 多多客工具 API 可以通过创建多多客联盟应用获取权限 -------------------------------------------------------------------------------- /ddk_goods.go: -------------------------------------------------------------------------------- 1 | package pdd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/jinzhu/copier" 8 | ) 9 | 10 | type Range struct { 11 | RangeId int `json:"range_id"` 12 | RangeFrom int `json:"range_from"` 13 | RangeTo int `json:"range_to"` 14 | } 15 | 16 | type GoodsListResponse struct { 17 | GoodsList []*Goods `json:"goods_list"` 18 | TotalCount int `json:"total_count"` 19 | } 20 | 21 | type Goods struct { 22 | CreateAt int `json:"create_at"` // 创建时间 23 | GoodsId int `json:"goods_id"` // 商品id 24 | GoodsSign string `json:"goods_sign"` // 商品sign 25 | GoodsName string `json:"goods_name"` // 商品名称 26 | GoodsDesc string `json:"goods_desc"` // 商品描述 27 | GoodsThumbnailUrl string `json:"goods_thumbnail_url"` // 商品缩略图 28 | GoodsImageUrl string `json:"goods_image_url"` // 商品主图 29 | GoodsGalleryUrls []string `json:"goods_gallery_urls"` // 商品轮播图 30 | SoldQuantity int `json:"sold_quantity"` // 已售卖件数 31 | MinGroupPrice int `json:"min_group_price"` // 最小拼团价(单位为分) 最低价 sku 的拼团价,单位为分 32 | MinNormalPrice int `json:"min_normal_price"` // 最小单买价格(单位为分) 33 | MallId int `json:"mall_id"` // 店铺 id 34 | MallName string `json:"mall_name"` // 店铺名字 35 | MerchantType int `json:"merchant_type"` // 店铺类型,1-个人,2-企业,3-旗舰店,4-专卖店,5-专营店,6-普通店 36 | MallCps int `json:"mall_cps"` // 该商品所在店铺是否参与全店推广,0:否,1:是 37 | MallRate int `json:"mall_rate"` // 全店推广佣金比率? 千分比 38 | CategoryId int `json:"category_id"` // 商品类目 id 39 | CategoryName string `json:"category_name"` // 商品类目名称 40 | OptId int `json:"opt_id"` // 商品标签 id,使用 pdd.goods.opts.get 接口获取 41 | OptName string `json:"opt_name"` // 商品标签名 42 | OptIds []int `json:"opt_ids"` // 商品标签id 43 | CatIds []int `json:"cat_ids"` // 商品类目 id 列表 44 | CatId int `json:"cat_id"` // 可能为 null 45 | HasCoupon bool `json:"has_coupon"` // 是否有优惠券 46 | CouponMinOrderAmount int `json:"coupon_min_order_amount"` // 优惠券门槛价格,单位为分 47 | CouponDiscount int `json:"coupon_discount"` // 优惠券面额,单位为分 48 | CouponTotalQuantity int `json:"coupon_total_quantity"` // 优惠券总数量 49 | CouponRemainQuantity int `json:"coupon_remain_quantity"` // 优惠券剩余数量 50 | CouponStartTime int `json:"coupon_start_time"` // 优惠券生效时间,UNIX时间戳 51 | CouponEndTime int `json:"coupon_end_time"` // 优惠券失效时间,UNIX时间戳 52 | PromotionRate int `json:"promotion_rate"` // 佣金比例,千分比 300 53 | GoodsEvalScore float32 `json:"goods_eval_score"` // 商品评价分 4.81 54 | GoodsEvalCount int `json:"goods_eval_count"` // 商品评价数量 55 | AvgDesc int `json:"avg_desc"` // 描述评分 56 | AvgLgst int `json:"avg_lgst"` // 物流评分 57 | AvgServ int `json:"avg_serv"` // 服务评分 58 | DescPct float32 `json:"desc_pct"` // 描述分击败同类店铺百分比 59 | LgstPct float32 `json:"lgst_pct"` // 物流分击败同类店铺百分比 60 | ServPct float32 `json:"serv_pct"` // 服务分击败同类店铺百分比 61 | } 62 | 63 | func (g *Goods) MarshalGoodsGalleryUrls() string { 64 | r, _ := json.Marshal(g.GoodsGalleryUrls) 65 | return string(r) 66 | } 67 | 68 | func (g *Goods) MarshalOptIds() string { 69 | r, _ := json.Marshal(g.OptIds) 70 | return string(r) 71 | } 72 | 73 | func (g *Goods) MarshalCatIds() string { 74 | r, _ := json.Marshal(g.CatIds) 75 | return string(r) 76 | } 77 | 78 | // GoodsModel for gorm 79 | type GoodsModel struct { 80 | CreateAt int `json:"create_at"` 81 | GoodsId int `json:"goods_id" gorm:"type:bigint;not null;unique"` 82 | GoodsName string `json:"goods_name"` 83 | GoodsDesc string `json:"goods_desc" gorm:"type:text"` 84 | GoodsThumbnailUrl string `json:"goods_thumbnail_url"` 85 | GoodsImageUrl string `json:"goods_image_url"` 86 | GoodsGalleryUrls string `json:"goods_gallery_urls" gorm:"type:text"` 87 | SoldQuantity int `json:"sold_quantity"` 88 | MinGroupPrice int `json:"min_group_price"` 89 | MinNormalPrice int `json:"min_normal_price"` 90 | MallId int `json:"mall_id"` 91 | MallName string `json:"mall_name"` 92 | MerchantType int `json:"merchant_type"` 93 | MallCps int `json:"mall_cps"` 94 | CategoryId int `json:"category_id"` 95 | CategoryName string `json:"category_name"` 96 | OptId int `json:"opt_id"` 97 | OptName string `json:"opt_name"` 98 | OptIds string `json:"opt_ids"` 99 | CatIds string `json:"cat_ids"` 100 | CatId int `json:"cat_id"` 101 | HasCoupon bool `json:"has_coupon"` 102 | CouponMinOrderAmount int `json:"coupon_min_order_amount"` 103 | CouponDiscount int `json:"coupon_discount"` 104 | CouponTotalQuantity int `json:"coupon_total_quantity"` 105 | CouponRemainQuantity int `json:"coupon_remain_quantity"` 106 | CouponStartTime int `json:"coupon_start_time"` 107 | CouponEndTime int `json:"coupon_end_time"` 108 | PromotionRate int `json:"promotion_rate"` 109 | GoodsEvalScore float32 `json:"goods_eval_score"` 110 | GoodsEvalCount int `json:"goods_eval_count"` 111 | AvgDesc int `json:"avg_desc"` 112 | AvgLgst int `json:"avg_lgst"` 113 | AvgServ int `json:"avg_serv"` 114 | DescPct float32 `json:"desc_pct"` 115 | LgstPct float32 `json:"lgst_pct"` 116 | ServPct float32 `json:"serv_pct"` 117 | } 118 | 119 | func (m *GoodsModel) SetFields(g *Goods) { 120 | copier.Copy(m, g) 121 | m.GoodsGalleryUrls = g.MarshalGoodsGalleryUrls() 122 | m.OptIds = g.MarshalOptIds() 123 | m.CatIds = g.MarshalCatIds() 124 | } 125 | 126 | func GoodsToGoodsModel(g *Goods) (r *GoodsModel) { 127 | r = new(GoodsModel) 128 | copier.Copy(r, g) 129 | r.GoodsGalleryUrls = g.MarshalGoodsGalleryUrls() 130 | r.OptIds = g.MarshalOptIds() 131 | r.CatIds = g.MarshalCatIds() 132 | return 133 | } 134 | 135 | func GoodsListToGoodsModelList(gs []*Goods) (gms []*GoodsModel) { 136 | for _, goods := range gs { 137 | gms = append(gms, GoodsToGoodsModel(goods)) 138 | } 139 | return 140 | } 141 | 142 | // search interface with more params 143 | func (d *DDK) GoodsSearch(notMustparams ...Params) (res *GoodsListResponse, err error) { 144 | res = new(GoodsListResponse) 145 | params := NewParamsWithType(DDK_GoodsSearch, notMustparams...) 146 | 147 | r, err := Call(d.Context, params) 148 | if err != nil { 149 | return 150 | } 151 | bytes, err := GetResponseBytes(r, "goods_search_response") 152 | err = json.Unmarshal(bytes, res) 153 | return 154 | } 155 | 156 | // 获取指定个数的商品 157 | func (d *DDK) GoodsSearchWithNumber(number int, params Params) (res []*Goods, err error) { 158 | page := 1 159 | pageSize := 100 160 | if pageSize > number { 161 | pageSize = number 162 | params["page"] = page 163 | params["page_size"] = number 164 | r, err1 := d.GoodsSearch(params) 165 | if err1 != nil { 166 | err = err1 167 | return 168 | } 169 | res = append(res, r.GoodsList...) 170 | return 171 | } 172 | 173 | for (page * pageSize) < number { 174 | params["page"] = page 175 | params["page_size"] = pageSize 176 | r, err1 := d.GoodsSearch(params) 177 | if err1 != nil { 178 | err = err1 179 | return 180 | } 181 | res = append(res, r.GoodsList...) 182 | page++ 183 | } 184 | 185 | if number <= (page * pageSize) { 186 | params["page"] = page 187 | params["page_size"] = number - ((page - 1) * pageSize) 188 | r, err1 := d.GoodsSearch(params) 189 | if err1 != nil { 190 | err = err1 191 | return 192 | } 193 | res = append(res, r.GoodsList...) 194 | } 195 | return 196 | } 197 | 198 | // 获取未下架的商品 199 | func (d *DDK) GetExistGoods(ids []int) (res []*Goods, err error) { 200 | length := len(ids) 201 | start := 0 202 | pageSize := 100 203 | 204 | if length < pageSize { 205 | res, err = d.getGoodsByIds(ids[start:length]) 206 | if err != nil { 207 | return 208 | } 209 | return 210 | } 211 | 212 | for length > pageSize { 213 | r, err1 := d.getGoodsByIds(ids[start:pageSize]) 214 | if err1 != nil { 215 | err = err1 216 | return 217 | } 218 | res = append(res, r...) 219 | start += 100 220 | pageSize += 100 221 | } 222 | 223 | r, err := d.getGoodsByIds(ids[start:length]) 224 | if err != nil { 225 | return 226 | } 227 | res = append(res, r...) 228 | return 229 | } 230 | 231 | func (d *DDK) getGoodsByIds(ids []int) (rsp []*Goods, err error) { 232 | idstr, _ := json.Marshal(ids) 233 | params := NewParams() 234 | params.Set("goods_id_list", string(idstr)) 235 | r, err := d.GoodsSearch(params) 236 | rsp = r.GoodsList 237 | return 238 | } 239 | 240 | // detail 接口查询返回的商品详情比搜索返回的多出一些信息: 241 | // goods_gallery_urls 242 | // goods_desc 243 | // sold_quantity 244 | func (d *DDK) GoodsDetail(goodsId int) (res *Goods, err error) { 245 | res = new(Goods) 246 | params := NewParamsWithType(DDK_GoodsDetail) 247 | params.Set("goods_id_list", fmt.Sprintf("[%d]", goodsId)) 248 | 249 | r, err := Call(d.Context, params) 250 | if err != nil { 251 | return 252 | } 253 | bytes, err := GetResponseArrayIndexBytes(r, 0, "goods_detail_response", "goods_details") 254 | err = json.Unmarshal(bytes, res) 255 | return 256 | } 257 | 258 | type GoodsPromotionUrl struct { 259 | Url string `json:"url"` 260 | ShortUrl string `json:"short_url"` 261 | MobileUrl string `json:"mobile_url"` 262 | MobileShortUrl string `json:"mobile_short_url"` 263 | WeAppWebViewUrl string `json:"we_app_web_view_url"` 264 | WeAppWebViewShortUrl string `json:"we_app_web_view_short_url"` 265 | } 266 | 267 | // create promotion url 268 | func (d *DDK) GoodsPromotionUrlGenerate(pid string, goodsSign string, notMustparams ...Params) (res *GoodsPromotionUrl, err error) { 269 | params := NewParamsWithType(DDK_GoodsPromotionUrlGenerate, notMustparams...) 270 | params.Set("p_id", pid) 271 | params.Set("goods_sign_list", fmt.Sprintf("[\"%s\"]", goodsSign)) 272 | 273 | r, err := Call(d.Context, params) 274 | if err != nil { 275 | return 276 | } 277 | bytes, err := GetResponseBytes(r, "goods_promotion_url_generate_response", "goods_promotion_url_list") 278 | var urls []*GoodsPromotionUrl 279 | err = json.Unmarshal(bytes, &urls) 280 | res = urls[0] 281 | return 282 | } 283 | 284 | // 爆款排行商品列表 285 | type TopGoodsListResponse struct { 286 | GoodsList []*Goods `json:"list"` 287 | Total int64 `json:"total"` 288 | } 289 | 290 | // 多多客获取爆款排行商品接口 291 | func (d *DDK) TopGoodsListQuery(notMustparams ...Params) (res *TopGoodsListResponse, err error) { 292 | res = new(TopGoodsListResponse) 293 | params := NewParamsWithType(DDK_TopGoodsListQuery, notMustparams...) 294 | 295 | r, err := Call(d.Context, params) 296 | if err != nil { 297 | return 298 | } 299 | bytes, err := GetResponseBytes(r, "top_goods_list_get_response") 300 | err = json.Unmarshal(bytes, &res) 301 | return 302 | } 303 | 304 | // GoodsZsURL 多多进宝转链 305 | type GoodsZsURL struct { 306 | Url string `json:"url"` //单人团推广长链接 307 | ShortUrl string `json:"short_url"` //单人团推广短链接 308 | MobileUrl string `json:"mobile_url"` //推广长链接(唤起拼多多app) 309 | MobileShortUrl string `json:"mobile_short_url"` //推广短链接(可唤起拼多多app) 310 | WeAppWebViewUrl string `json:"we_app_web_view_url"` 311 | WeAppWebViewShortUrl string `json:"we_app_web_view_short_url"` 312 | MultiGroupUrl string `json:"multi_group_url"` //双人团推广长链接 313 | MultiGroupShortUrl string `json:"multi_group_short_url"` //双人团推广短链接 314 | MultiGroupMobileUrl string `json:"multi_group_mobile_url"` //推广长链接(可唤起拼多多app) 315 | MultiGroupMobileShortUrl string `json:"multi_group_mobile_short_url"` //推广短链接(唤起拼多多app) 316 | } 317 | 318 | //GoodsZsURLGen 多多进宝转链接口 319 | func (d *DDK) GoodsZsURLGen(sourceURL, pid string) (res *GoodsZsURL, err error) { 320 | res = new(GoodsZsURL) 321 | params := NewParamsWithType(DDK_GoodsZsUnitUrlGen) 322 | params.Set("source_url", sourceURL) 323 | params.Set("pid", pid) 324 | 325 | r, err := Call(d.Context, params) 326 | if err != nil { 327 | return 328 | } 329 | 330 | bytes, err := GetResponseBytes(r, "goods_zs_unit_generate_response") 331 | err = json.Unmarshal(bytes, &res) 332 | return 333 | } 334 | --------------------------------------------------------------------------------