package comm import ( "bytes" "crypto/md5" "crypto/tls" "encoding/hex" "encoding/json" "errors" "io" "io/ioutil" "net/http" "net/url" "sort" "strconv" "strings" "time" "github.com/bitly/go-simplejson" "github.com/nilorg/sdk/convert" ) var ( /**公用方法**/ // Session 用户登录授权成功后,TOP颁发给应用的授权信息。当此API的标签上注明:“需要授权”,则此参数必传;“不需要授权”,则此参数不需要传;“可选授权”,则此参数为可选 Session string // Timeout ... Timeout time.Duration // CacheExpiration 缓存过期时间 CacheExpiration = time.Hour // GetCache 获取缓存 GetCache getCacheFunc // SetCache 设置缓存 SetCache setCacheFunc /**淘宝平台信息**/ TaobaoAppKey string TaobaoAppSecret string TaobaoRouter string TaobaoVersion = "2.0" /**京东平台信息**/ JDAppKey string JDAppSecret string JDRouter string JDSdkRouter string JDVersion = "2.0" /**拼多多平台信息**/ PDDAppKey string PDDAppSecret string PDDRouter string PDDVersion = "v1.0" /**考拉海购平台信息**/ KaolaAppKey string KaolaAppSecret string KaolaRouter string KaolaVersion = "1.0" ) // Arg 参数 type Arg map[string]interface{} // copyArg 复制参数 func copyArg(srcArgs Arg) Arg { newArgs := make(Arg) for key, value := range srcArgs { newArgs[key] = value } return newArgs } // newCacheKey 创建缓存Key func newCacheKey(p Arg) string { cpArgs := copyArg(p) delete(cpArgs, "session") delete(cpArgs, "timestamp") delete(cpArgs, "sign") keys := []string{} for k := range cpArgs { keys = append(keys, k) } // 排序asc sort.Strings(keys) // 把所有参数名和参数值串在一起 cacheKeyBuf := new(bytes.Buffer) for _, k := range keys { cacheKeyBuf.WriteString(k) cacheKeyBuf.WriteString("=") cacheKeyBuf.WriteString(interfaceToString(cpArgs[k])) } h := md5.New() io.Copy(h, cacheKeyBuf) return hex.EncodeToString(h.Sum(nil)) } // execute 执行API接口 func execute(p Arg, router string) (bytes []byte, err error) { err = checkConfig() if err != nil { return } var req *http.Request req, err = http.NewRequest("POST", router, strings.NewReader(p.getReqData())) if err != nil { return } tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8") httpClient := &http.Client{Transport: tr} httpClient.Timeout = Timeout var resp *http.Response resp, err = httpClient.Do(req) if err != nil { return } if resp.StatusCode != 200 { err = errors.New("请求错误:" + strconv.Itoa(resp.StatusCode)) return } defer resp.Body.Close() bytes, err = ioutil.ReadAll(resp.Body) // fmt.Printf("\n\n\nreq: %v\n\nresp: %v\n\n\n", p.getReqData(), string(bytes)) return } // Execute 执行API接口 func Execute(method string, p Arg) (res *simplejson.Json, err error) { p, r := setReqData(p, method) var bodyBytes []byte bodyBytes, err = execute(p, r) if err != nil { return } return bytesToResult(bodyBytes) } func bytesToResult(bytes []byte) (res *simplejson.Json, err error) { res, err = simplejson.NewJson(bytes) if err != nil { return } if responseError, ok := res.CheckGet("error_response"); ok { if subMsg, subOk := responseError.CheckGet("sub_msg"); subOk { err = errors.New(subMsg.MustString()) } else if zhDesc, descOk := responseError.CheckGet("zh_desc"); descOk { err = errors.New(zhDesc.MustString()) } else { err = errors.New(responseError.Get("msg").MustString()) } res = nil } return } // ExecuteCache 执行API接口,缓存 func ExecuteCache(method string, param Arg) (res *simplejson.Json, err error) { p, r := setReqData(param, method) cacheKey := newCacheKey(p) // 获取缓存 if GetCache != nil { cacheBytes := GetCache(cacheKey) if len(cacheBytes) > 0 { res, err = simplejson.NewJson(cacheBytes) if err == nil && res != nil { return } } } var bodyBytes []byte bodyBytes, err = execute(param, r) if err != nil { return } res, err = bytesToResult(bodyBytes) if err != nil { return } ejsonBody, _ := res.MarshalJSON() // 设置缓存 if SetCache != nil { go SetCache(cacheKey, ejsonBody, CacheExpiration) } return } // 检查配置 func checkConfig() error { if TaobaoAppKey == "" && JDAppKey == "" && KaolaAppKey == "" && PDDAppKey == "" { return errors.New("至少需要设置一个平台参数") } return nil } //组装参数及添加公共参数 func setReqData(p Arg, method string) (Arg, string) { platform := strings.Split(method, ".")[0] router := "" hh, _ := time.ParseDuration("8h") loc := time.Now().UTC().Add(hh) if platform == "taobao" { //淘宝 p["timestamp"] = strconv.FormatInt(loc.Unix(), 10) p["partner_id"] = "Blant" p["app_key"] = TaobaoAppKey p["v"] = TaobaoVersion if Session != "" { p["session"] = Session } p["method"] = method p["format"] = "json" p["sign_method"] = "md5" // 设置签名 p["sign"] = getSign(p, TaobaoAppSecret) router = TaobaoRouter } else if platform == "jdSdk" || platform == "jd" { //京东 param := p p = Arg{} p["param_json"] = param p["app_key"] = JDAppKey p["v"] = JDVersion p["timestamp"] = loc.Format("2006-01-02 15:04:05") p["method"] = method p["format"] = "json" p["sign_method"] = "md5" // 设置签名 p["sign"] = getSign(p, JDAppSecret) router = JDSdkRouter if platform == "jd" { router = JDRouter } } else if platform == "pdd" { //拼多多 p["type"] = method p["data_type"] = "json" p["version"] = PDDVersion p["client_id"] = PDDAppKey p["timestamp"] = strconv.FormatInt(loc.Unix(), 10) // 设置签名 p["sign"] = getSign(p, PDDAppSecret) router = PDDRouter } else if platform == "kaola" { //考拉海购 p["method"] = method p["v"] = KaolaVersion p["signMethod"] = "md5" p["unionId"] = KaolaAppKey p["timestamp"] = loc.Format("2006-01-02 15:04:05") // 设置签名 p["sign"] = getSign(p, KaolaAppSecret) router = KaolaRouter } else if platform == "suning" { // TODO 苏宁 } else if platform == "vip" { // TODO 唯品会 } return p, router } // 获取请求数据 func (p Arg) getReqData() string { // 公共参数 args := url.Values{} // 请求参数 for key, val := range p { args.Set(key, interfaceToString(val)) } return args.Encode() } // 获取签名 func getSign(p Arg, secret string) string { // 获取Key var keys []string for k := range p { keys = append(keys, k) } // 排序asc sort.Strings(keys) // 把所有参数名和参数值串在一起 query := bytes.NewBufferString(secret) for _, k := range keys { query.WriteString(k) query.WriteString(interfaceToString(p[k])) } query.WriteString(secret) // 使用MD5加密 h := md5.New() _, _ = io.Copy(h, query) // 把二进制转化为大写的十六进制 return strings.ToUpper(hex.EncodeToString(h.Sum(nil))) } func interfaceToString(src interface{}) string { if src == nil { panic(ErrTypeIsNil) } switch src.(type) { case string: return src.(string) case int, int8, int32, int64: case uint8, uint16, uint32, uint64: case float32, float64: return convert.ToString(src) } data, err := json.Marshal(src) if err != nil { panic(err) } return string(data) }