package wps import ( "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "net/url" "reflect" "sort" "strings" "time" "github.com/pkg/errors" ) const ( DevHost = "https://uat.api.weipinshang.net/" ReleaseHost = "https://api.weipinshang.net/" ) const ( GetGoodBrandURL = "mcang/Mcang/goodBrand" //查询商品的品牌 GetGoodsClassifyURL = "mcang/Mcang/getGoodsClassify" //查询商品分类 GetGoodsListURL = "mcang/Mcang/getGoodsList" //获取全部商品列表接口 GetGoodsdeptURL = "mcang/Mcang/getGoodsdept" //(推荐使用)获取后台已选商品列表接口 GetDetailsGoodsUrl = "mcang/Mcang/getDetailsGoods" //获取批量商品详情接口 GetGoodsDetailsURL = "mcang/Mcang/getGoodsDetails" //获取单个商品详情接口 GetGoodsStockURL = "mcang/Mcang/getGoodsStock" //获取单个商品详情接口 ) // Config 客户端配置 type Config struct { ChannelType string Key string BaseURL string Timeout time.Duration } // WeiPinShangClient 客户端结构体 type WeiPinShangClient struct { ChannelType string Key string BaseURL string HttpClient *http.Client } // NewWeiPinShangClient 创建客户端 func NewWeiPinShangClient(cfg Config) *WeiPinShangClient { if cfg.ChannelType == "" { cfg.ChannelType = "AILEHUI" } if cfg.BaseURL == "" { cfg.BaseURL = ReleaseHost } if cfg.Key == "" { cfg.Key = "f654ea5bde7635c3f46191191e5c4c8e" } if cfg.Timeout == 0 { cfg.Timeout = 10 * time.Second } return &WeiPinShangClient{ ChannelType: cfg.ChannelType, Key: cfg.Key, BaseURL: cfg.BaseURL, HttpClient: &http.Client{Timeout: cfg.Timeout}, } } // sign 生成签名 func (c *WeiPinShangClient) sign(params map[string]string) string { keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) } sort.Strings(keys) var sb strings.Builder for _, k := range keys { v := params[k] if v == "" { continue } sb.WriteString(k) sb.WriteString("=") sb.WriteString(v) sb.WriteString("&") } signStr := strings.TrimRight(sb.String(), "&") + c.ChannelType + c.Key hash := md5.Sum([]byte(signStr)) return hex.EncodeToString(hash[:]) } // buildHeaders 构建请求头 func (c *WeiPinShangClient) buildHeaders(params map[string]string) http.Header { headers := http.Header{} headers.Set("channelType", c.ChannelType) headers.Set("md5", c.sign(params)) headers.Set("Content-Type", "application/x-www-form-urlencoded") return headers } // buildRequest 构造请求对象 func (c *WeiPinShangClient) buildRequest(method, endpoint string, params map[string]string) (*http.Request, error) { var ( req *http.Request err error ) if method == http.MethodPost { form := url.Values{} for k, v := range params { form.Set(k, v) } req, err = http.NewRequest(method, c.BaseURL+endpoint, strings.NewReader(form.Encode())) } else { query := url.Values{} for k, v := range params { query.Set(k, v) } req, err = http.NewRequest(method, fmt.Sprintf("%s%s?%s", c.BaseURL, endpoint, query.Encode()), nil) } if err != nil { return nil, fmt.Errorf("构建请求失败,地址:%s,err:%v", endpoint, err.Error()) } req.Header = c.buildHeaders(params) return req, nil } // doRequest 执行请求并返回字符串响应 func (c *WeiPinShangClient) doRequest(req *http.Request) (string, error) { resp, err := c.HttpClient.Do(req) if err != nil { return "", errors.Wrap(err, "HTTP request failed") } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("HTTP status error: %s", resp.Status) } body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("读取response body失败。err:%v", err.Error()) } return string(body), nil } // Post 发起 POST 请求 func (c *WeiPinShangClient) Post(endpoint string, params map[string]string) (string, error) { req, err := c.buildRequest(http.MethodPost, endpoint, params) if err != nil { return "", err } return c.doRequest(req) } // Get 发起 GET 请求 func (c *WeiPinShangClient) Get(endpoint string, params map[string]string) (string, error) { req, err := c.buildRequest(http.MethodGet, endpoint, params) if err != nil { return "", err } return c.doRequest(req) } // GetAndParse 发起 GET 请求并解析 JSON 响应 func (c *WeiPinShangClient) GetAndParse(endpoint string, params map[string]string, out interface{}) error { respStr, err := c.Get(endpoint, params) if err != nil { return err } return json.Unmarshal([]byte(respStr), out) } // PostAndParse 发起 POST 请求并解析 JSON 响应 func (c *WeiPinShangClient) PostAndParse(endpoint string, params map[string]string, out interface{}) error { respStr, err := c.Post(endpoint, params) if err != nil { return err } return json.Unmarshal([]byte(respStr), out) } func StructToMapString(obj interface{}) map[string]string { result := make(map[string]string) v := reflect.ValueOf(obj) t := reflect.TypeOf(obj) // 支持结构体指针 if v.Kind() == reflect.Ptr { v = v.Elem() t = t.Elem() } for i := 0; i < v.NumField(); i++ { field := t.Field(i) valueField := v.Field(i) // 只导出可访问的字段 if !valueField.CanInterface() { continue } // 判断是否为零值 if reflect.DeepEqual(valueField.Interface(), reflect.Zero(valueField.Type()).Interface()) { continue } key := field.Tag.Get("json") if key == "" { key = field.Name } result[key] = fmt.Sprintf("%v", valueField.Interface()) } return result }