├── errors.go ├── .gitignore ├── go.mod ├── context.go ├── .vscode └── launch.json ├── go.sum ├── cmd └── main.go ├── README.md ├── response.go ├── LICENSE ├── util.go ├── options.go └── client.go /errors.go: -------------------------------------------------------------------------------- 1 | package opentaobao 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrTypeIsNil = errors.New("类型为Nil") 7 | ErrTypeUnknown = errors.New("未处理到的数据类型") 8 | ) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .env -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nilorg/go-opentaobao/v2 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/nilorg/sdk v0.0.0-20230418035736-8bd62607dfd8 7 | github.com/tidwall/gjson v1.14.4 8 | ) 9 | 10 | require ( 11 | github.com/tidwall/match v1.1.1 // indirect 12 | github.com/tidwall/pretty v1.2.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package opentaobao 2 | 3 | import "context" 4 | 5 | type sessionKey struct{} 6 | 7 | func NewSessionContext(parent context.Context, session string) context.Context { 8 | return context.WithValue(parent, sessionKey{}, session) 9 | } 10 | 11 | func FromSessionContext(ctx context.Context) (session string, ok bool) { 12 | session, ok = ctx.Value(sessionKey{}).(string) 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceFolder}/cmd/main.go", 13 | "envFile": "${workspaceFolder}/.env", 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/nilorg/sdk v0.0.0-20230418035736-8bd62607dfd8 h1:wX29EPNhFYoCOR9O+9Wx0lUVkd0YaxLfqwtjMr4J9xc= 2 | github.com/nilorg/sdk v0.0.0-20230418035736-8bd62607dfd8/go.mod h1:X1swpPdqguAZaBDoEPyEWHSsJii0YQ1o+3piMv6W3JU= 3 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 4 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 5 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 6 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 7 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 8 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 9 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | 8 | "github.com/nilorg/go-opentaobao/v2" 9 | ) 10 | 11 | func main() { 12 | client := opentaobao.NewClient( 13 | opentaobao.WithAppKey(os.Getenv("APP_KEY")), 14 | opentaobao.WithAppSecret(os.Getenv("APP_SECRET")), 15 | ) 16 | ctx := context.Background() 17 | // EXP: 使用session 18 | // ctx = opentaobao.NewSessionContext(ctx, "session") 19 | result, err := client.Execute(ctx, "taobao.tbk.dg.material.optional", opentaobao.Parameter{ 20 | "q": "鸿星尔克男鞋板鞋", 21 | "adzone_id": os.Getenv("ADZONE_ID"), 22 | "platform": "2", 23 | }) 24 | 25 | if err != nil { 26 | log.Printf("execute error:%s\n", err) 27 | return 28 | } 29 | log.Printf("result:%s\n", result.String()) 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # taobaogo 2 | 淘宝Api、淘宝开放平台Api请求基础SDK 3 | 4 | # 淘宝API 5 | 6 | [sign算法](http://open.taobao.com/doc.htm?docId=101617&docType=1) 7 | 8 | [淘宝Session](https://oauth.taobao.com/authorize?response_type=token&client_id=24840730) 9 | 10 | # Example 🌰 11 | ```go 12 | package main 13 | 14 | import ( 15 | "context" 16 | "log" 17 | "os" 18 | 19 | "github.com/nilorg/go-opentaobao/v2" 20 | ) 21 | 22 | func main() { 23 | client := opentaobao.NewClient( 24 | opentaobao.WithAppKey(os.Getenv("APP_KEY")), 25 | opentaobao.WithAppSecret(os.Getenv("APP_SECRET")), 26 | ) 27 | ctx := context.Background() 28 | // EXP: 使用session 29 | // ctx = opentaobao.NewSessionContext(ctx, "session") 30 | result, err := client.Execute(ctx, "taobao.tbk.dg.material.optional", opentaobao.Parameter{ 31 | "q": "鸿星尔克男鞋板鞋", 32 | "adzone_id": os.Getenv("ADZONE_ID"), 33 | "platform": "2", 34 | }) 35 | 36 | if err != nil { 37 | log.Printf("execute error:%s\n", err) 38 | return 39 | } 40 | log.Printf("result:%s\n", result.String()) 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package opentaobao 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | func RespDecode(httpResp *http.Response, v any) error { 11 | resp := &Response{ 12 | Data: v, 13 | } 14 | err := json.NewDecoder(httpResp.Body).Decode(resp) 15 | if err != nil { 16 | return err 17 | } 18 | fmt.Printf("resp: %+v\n", resp) 19 | fmt.Printf("resp.Data: %+v\n", resp.Data) 20 | if resp.IsError() { 21 | return resp.Error() 22 | } 23 | if httpResp.StatusCode != http.StatusOK { 24 | return fmt.Errorf("http status code: %d", httpResp.StatusCode) 25 | } 26 | return nil 27 | } 28 | 29 | // Error HTTP响应错误项 30 | type Error struct { 31 | Code int `json:"code" swaggo:"true,错误码"` 32 | Message string `json:"message" swaggo:"true,错误信息"` 33 | } 34 | 35 | type Response struct { 36 | Err Error `json:"error" swaggo:"true,错误项"` 37 | Data any `json:"data"` 38 | } 39 | 40 | func (e *Response) IsError() bool { 41 | return e.Err.Code != 200 && e.Err.Code != 0 42 | } 43 | 44 | func (e *Response) Error() error { 45 | return errors.New(e.Err.Message) 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nil Org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package opentaobao 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "encoding/hex" 7 | "encoding/json" 8 | "io" 9 | "sort" 10 | "strings" 11 | 12 | "github.com/nilorg/sdk/convert" 13 | ) 14 | 15 | // 获取签名 16 | func Sign(params Parameter, appSecret string) string { 17 | // 获取Key 18 | keys := []string{} 19 | for k := range params { 20 | keys = append(keys, k) 21 | } 22 | // 排序asc 23 | sort.Strings(keys) 24 | // 把所有参数名和参数值串在一起 25 | query := bytes.NewBufferString(appSecret) 26 | for _, k := range keys { 27 | query.WriteString(k) 28 | query.WriteString(interfaceToString(params[k])) 29 | } 30 | query.WriteString(appSecret) 31 | // 使用MD5加密 32 | h := md5.New() 33 | io.Copy(h, query) 34 | // 把二进制转化为大写的十六进制 35 | return strings.ToUpper(hex.EncodeToString(h.Sum(nil))) 36 | } 37 | 38 | func interfaceToString(src interface{}) string { 39 | if src == nil { 40 | panic(ErrTypeIsNil) 41 | } 42 | switch v := src.(type) { 43 | case string: 44 | return v 45 | case uint8, uint16, uint32, uint64, int, int8, int32, int64, float32, float64: 46 | return convert.ToString(v) 47 | } 48 | data, err := json.Marshal(src) 49 | if err != nil { 50 | panic(err) 51 | } 52 | return string(data) 53 | } 54 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package opentaobao 2 | 3 | import "net/http" 4 | 5 | // Options 可选参数列表 6 | type Options struct { 7 | Router string // 环境请求地址 8 | AppKey string // 应用Key 9 | AppSecret string // 秘密 10 | JSONSimplify bool // 是否开启JSON返回值的简化模式 11 | httpClient *http.Client 12 | } 13 | 14 | // Option 为可选参数赋值的函数 15 | type Option func(*Options) 16 | 17 | // NewOptions 创建可选参数 18 | func NewOptions(opts ...Option) Options { 19 | opt := Options{ 20 | Router: "http://gw.api.taobao.com/router/rest", 21 | JSONSimplify: true, 22 | httpClient: &http.Client{}, 23 | } 24 | for _, o := range opts { 25 | o(&opt) 26 | } 27 | return opt 28 | } 29 | 30 | // WithRouter 设置环境请求地址 31 | func WithRouter(router string) Option { 32 | return func(o *Options) { 33 | o.Router = router 34 | } 35 | } 36 | 37 | // WithAppKey 设置应用Key 38 | func WithAppKey(appKey string) Option { 39 | return func(o *Options) { 40 | o.AppKey = appKey 41 | } 42 | } 43 | 44 | // WithAppSecret 设置秘密 45 | func WithAppSecret(appSecret string) Option { 46 | return func(o *Options) { 47 | o.AppSecret = appSecret 48 | } 49 | } 50 | 51 | // WithJSONSimplify 设置是否开启JSON返回值的简化模式 52 | func WithJSONSimplify(jsonSimplify bool) Option { 53 | return func(o *Options) { 54 | o.JSONSimplify = jsonSimplify 55 | } 56 | } 57 | 58 | // WithHTTPClient 设置HTTP客户端 59 | func WithHTTPClient(httpClient *http.Client) Option { 60 | return func(o *Options) { 61 | o.httpClient = httpClient 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package opentaobao 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | "time" 12 | 13 | "github.com/tidwall/gjson" 14 | ) 15 | 16 | type Client struct { 17 | opts Options 18 | } 19 | 20 | func NewClient(opts ...Option) *Client { 21 | client := new(Client) 22 | client.opts = NewOptions(opts...) 23 | return client 24 | } 25 | 26 | func (c *Client) do(req *http.Request) (*http.Response, error) { 27 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8") 28 | return c.opts.httpClient.Do(req) 29 | } 30 | 31 | // 检查配置 32 | func (c *Client) checkConfig() error { 33 | if c.opts.AppKey == "" { 34 | return errors.New("AppKey不能为空") 35 | } 36 | if c.opts.AppSecret == "" { 37 | return errors.New("AppSecret不能为空") 38 | } 39 | if c.opts.Router == "" { 40 | return errors.New("Router不能为空") 41 | } 42 | return nil 43 | } 44 | 45 | // execute 执行API接口 46 | func (c *Client) execute(ctx context.Context, param Parameter) (bytes []byte, err error) { 47 | var req *http.Request 48 | req, err = http.NewRequestWithContext(ctx, "POST", c.opts.Router, strings.NewReader(param.getRequestData())) 49 | if err != nil { 50 | return 51 | } 52 | var httpResp *http.Response 53 | httpResp, err = c.do(req) 54 | if err != nil { 55 | return 56 | } 57 | defer httpResp.Body.Close() 58 | 59 | if httpResp.StatusCode != 200 { 60 | err = fmt.Errorf("http status code: %d", httpResp.StatusCode) 61 | return 62 | } 63 | bytes, err = io.ReadAll(httpResp.Body) 64 | return 65 | } 66 | 67 | // Execute 执行API接口 68 | func (c *Client) Execute(ctx context.Context, method string, param Parameter) (res gjson.Result, err error) { 69 | err = c.checkConfig() 70 | if err != nil { 71 | return 72 | } 73 | if c.opts.JSONSimplify { 74 | param["simplify"] = "true" 75 | } 76 | param["method"] = method 77 | param.setRequestData(ctx, c.opts.AppKey, c.opts.AppSecret) 78 | 79 | var bodyBytes []byte 80 | bodyBytes, err = c.execute(ctx, param) 81 | if err != nil { 82 | return 83 | } 84 | return bytesToResult(bodyBytes) 85 | } 86 | 87 | // Parameter 参数 88 | type Parameter map[string]any 89 | 90 | func bytesToResult(bytes []byte) (res gjson.Result, err error) { 91 | res = gjson.ParseBytes(bytes) 92 | responseError := res.Get("error_response") 93 | if ok := responseError.Exists(); ok { 94 | subMsg := responseError.Get("sub_msg") 95 | if ok := subMsg.Exists(); ok { 96 | err = errors.New(subMsg.String()) 97 | } else { 98 | err = errors.New(responseError.Get("msg").String()) 99 | } 100 | } 101 | return 102 | } 103 | 104 | func (p Parameter) setRequestData(ctx context.Context, appKey string, appSecret string) { 105 | p["timestamp"] = time.Now().Format("2006-01-02 15:04:05") 106 | p["format"] = "json" 107 | p["app_key"] = appKey 108 | p["v"] = "2.0" 109 | p["sign_method"] = "md5" 110 | if session, ok := FromSessionContext(ctx); ok { 111 | p["session"] = session 112 | } 113 | // 设置签名 114 | p["sign"] = Sign(p, appSecret) 115 | } 116 | 117 | // 获取请求数据 118 | func (p Parameter) getRequestData() string { 119 | // 公共参数 120 | args := url.Values{} 121 | // 请求参数 122 | for key, val := range p { 123 | args.Set(key, interfaceToString(val)) 124 | } 125 | return args.Encode() 126 | } 127 | --------------------------------------------------------------------------------