meituan-union平台api对接完成

This commit is contained in:
wukesheng 2024-04-30 17:57:27 +08:00
parent 15bcb2ed31
commit d31b52d38a
23 changed files with 809 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.idea/
vendor
.vscode
go.sum

20
api/api.go Normal file
View File

@ -0,0 +1,20 @@
package api
type PromotionSdk interface {
Sign(data map[string]interface{}) string //签名
GetLink(r *GetLinkRequest) (*GetLinkResponse, error) //推广取链
GetOrder(r *GetOrderRequest) (*GetOrderResponse, error) //拉取订单信息
}
type PromotionConf struct {
AppKey string
AppSecret string
Ext1 string
Ext2 string
Ext3 string
Ext4 string
}
func NewPromotionSdk(sdk PromotionSdk) PromotionSdk {
return sdk
}

51
api/entity.go Normal file
View File

@ -0,0 +1,51 @@
package api
type GetLinkRequest struct {
ActivityId string `json:"activity_id"` //活动ID
Position string `json:"position"` //推广位ID
}
type GetLinkResponse struct {
H5 string `json:"h5"`
ShortLink string `json:"shortLink"`
DeepLink string `json:"deepLink"`
EvokeLink string `json:"evokeLink"`
WechatApplet string `json:"wechatApplet"`
WechatAppletQrcode string `json:"wechatAppletQrcode"`
AlipayApplet string `json:"alipayApplet"`
Tkl string `json:"tkl"`
Poster string `json:"poster"`
}
type GetOrderRequest struct {
OrderNo string `json:"orderNo"`
ActId string `json:"actId"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
Page int64 `json:"page"`
PageSize int64 `json:"pageSize"`
}
type OrderItem struct {
OrderNo string `json:"orderNo"` //订单号
ActId string `json:"actId"` //活动ID
ActName string `json:"actName"` //活动名称
Sid string `json:"sid"` //Sid
OrderTitle string `json:"orderTitle"` //订标题
Commission string `json:"commission"` //佣金
CommissionRate string `json:"commissionRate"` //佣金比率
OrderTime int64 `json:"orderTime"` //订单时间
PayTime int64 `json:"payTime"` //支付时间
ModifiedTime int64 `json:"modifiedTime"` //最后一次更新时间
OrderPrice string `json:"orderPrice"` //订单金额
PayPrice string `json:"payPrice"` //支付金额
RefundPrice string `json:"refundPrice"` //退款金额
RefundReason string `json:"refundReason"` //退款原因
Status string `json:"status"` //状态
Quantity int64 `json:"quantity"` //商品数量
AppKey string `json:"appKey"`
Ext string `json:"ext"` //拓展参数
}
type GetOrderResponse struct {
Total int64 `json:"total"`
List []*OrderItem `json:"list"`
}

106
client/client.go Normal file
View File

@ -0,0 +1,106 @@
package client
import (
"errors"
"fmt"
"net/http"
"github.com/zeromicro/go-zero/core/logx"
"chengdu-lenntc/third-platform-sdk/util"
)
// HttpRequest http请求参数
type HttpRequest struct {
Headers map[string]string // 请求header参数
QueryArgs map[string]any // 请求query参数
BodyArgs map[string]any // 请求body参数
}
// HttpResponse http响应结果
type HttpResponse struct {
Result any // 响应的body数据结构, 必须为指针类型
RespHeader *http.Header // 响应header
}
// ThirdClient 第三方平台的client
type ThirdClient interface {
// HttpGet GET请求
HttpGet(url string, req *HttpRequest, resp *HttpResponse) error
// HttpPost POST请求
HttpPost(url string, req *HttpRequest, resp *HttpResponse) error
// HttpPut PUT请求
HttpPut(url string, req *HttpRequest, resp *HttpResponse) error
// HttpDelete DELETE请求
HttpDelete(url string, req *HttpRequest, resp *HttpResponse) error
// DoHttp 发起http请求
DoHttp(method string, url string, req *HttpRequest, resp *HttpResponse) error
}
type ThirdClientImpl struct {
log logx.Logger
}
func NewThirdClient(log logx.Logger) *ThirdClientImpl {
return &ThirdClientImpl{
log: log,
}
}
func (c *ThirdClientImpl) HttpGet(url string, req *HttpRequest, resp *HttpResponse) error {
return c.DoHttp(http.MethodGet, url, req, resp)
}
func (c *ThirdClientImpl) HttpPost(url string, req *HttpRequest, resp *HttpResponse) error {
return c.DoHttp(http.MethodPost, url, req, resp)
}
func (c *ThirdClientImpl) HttpPut(url string, req *HttpRequest, resp *HttpResponse) error {
return c.DoHttp(http.MethodPut, url, req, resp)
}
func (c *ThirdClientImpl) HttpDelete(url string, req *HttpRequest, resp *HttpResponse) error {
return c.DoHttp(http.MethodDelete, url, req, resp)
}
func (c *ThirdClientImpl) DoHttp(method string, url string, req *HttpRequest, resp *HttpResponse) error {
// 发起请求
reqConfig := &util.ReqConfig{
Headers: req.Headers,
QueryArgs: req.QueryArgs,
BodyArgs: req.BodyArgs,
}
hc := util.NewHttpClient(c.log).NewRequest(method, url, reqConfig).Do()
if hc.Error != nil {
return hc.Error
}
var responseHeader *http.Header
if hc.Response != nil {
responseHeader = &hc.Response.Header
}
// 检查http响应错误
if err := c.checkResponseError(hc.Response); err != nil {
c.log.WithFields([]logx.LogField{{Key: "method", Value: method}, {Key: "url", Value: url}, {Key: "reqConfig", Value: reqConfig}}...).
Errorf("[ThirdClientImpl][DoHttp] checkResponseError err:%s", err)
return err
}
// 获取响应结果
if resp == nil {
return nil
}
result := resp.Result
if err := hc.Result(result).Error; err != nil {
return err
}
resp.Result = result
resp.RespHeader = responseHeader
return nil
}
// 检查http响应错误
func (c *ThirdClientImpl) checkResponseError(r *util.Response) error {
if r.StatusCode >= 200 && r.StatusCode < 300 {
return nil
}
return errors.New(fmt.Sprintf("http response error, status code: %d, status: %s", r.StatusCode, r.Status))
}

7
go.mod Normal file
View File

@ -0,0 +1,7 @@
module chengdu-lenntc/third-platform-sdk
go 1.19
require (
github.com/zeromicro/go-zero v1.5.5
)

View File

@ -0,0 +1,41 @@
package eleme_union
import (
"github.com/zeromicro/go-zero/core/logx"
)
// todo:: 调用第三方平台的api
// Api defines the interface of eleme_union api
type Api interface {
Sign(data map[string]interface{}) string
GetLink() error
GetOrder() error
}
type ApiImpl struct {
log logx.Logger
client *Client
}
func NewApiImpl(log logx.Logger, client *Client) Api {
return &ApiImpl{
log: log,
client: client,
}
}
// todo::
func (a *ApiImpl) Sign(data map[string]interface{}) string {
return ""
}
// todo::
func (a *ApiImpl) GetLink() error {
return nil
}
// todo::
func (a *ApiImpl) GetOrder() error {
return nil
}

View File

@ -0,0 +1,54 @@
package eleme_union
import (
"net/http"
"github.com/zeromicro/go-zero/core/logx"
"chengdu-lenntc/third-platform-sdk/client"
"chengdu-lenntc/third-platform-sdk/util"
)
// AuthConfig api鉴权参数
type AuthConfig struct {
}
// todo:: 连接第三方平台的client
type Client struct {
client.ThirdClient
log logx.Logger
authConfig AuthConfig
}
func NewClient(log logx.Logger, conf AuthConfig) *Client {
// todo:: 请求第三方平台的配置参数
return &Client{
ThirdClient: client.NewThirdClient(log),
log: log,
authConfig: conf,
}
}
// todo:: DoHttp 发起http请求
func (c *Client) DoHttp(method string, url string, req *client.HttpRequest, resp *client.HttpResponse) error {
// todo:: api请求频率限制
c.apiRateLimit()
// 发起请求
err := c.ThirdClient.DoHttp(method, url, req, resp)
return err
}
// todo:: 请求api的频率限制
func (c *Client) apiRateLimit() {
}
// todo:: 检查api请求频率限制
func (c *Client) checkRateLimit(header *http.Header) error {
return nil
}
// todo:: 检查http响应错误
func (c *Client) checkResponseError(r *util.Response) error {
return nil
}

View File

@ -0,0 +1 @@
package eleme_union

View File

@ -0,0 +1 @@
package eleme_union

View File

@ -0,0 +1,3 @@
package eleme_union
// todo:: 定义第三方平台api接口的结构体

View File

@ -0,0 +1,28 @@
package meituan_csr
import (
"fmt"
"sort"
"strings"
"github.com/zeromicro/go-zero/core/logx"
"chengdu-lenntc/third-platform-sdk/client"
"chengdu-lenntc/third-platform-sdk/util"
)
// Api 调用第三方平台的api
type MeituanCsrApi interface {
}
type meituanApiImpl struct {
log logx.Logger
client *Client
}
func newMeituanApiImpl(log logx.Logger, client *Client) MeituanCsrApi {
return &meituanApiImpl{
log: log,
client: client,
}
}

View File

@ -0,0 +1 @@
package meituan_csr

View File

@ -0,0 +1 @@
package meituan_csr

View File

@ -0,0 +1 @@
package meituan_csr

View File

@ -0,0 +1,107 @@
package meituan_union
import (
"fmt"
"sort"
"strings"
"github.com/zeromicro/go-zero/core/logx"
"chengdu-lenntc/third-platform-sdk/client"
"chengdu-lenntc/third-platform-sdk/util"
)
// Api 调用第三方平台的api
type MeituanUnionApi interface {
GetLink(params GenerateLinkRequest) (*GenerateLinkResponse, error)
MiniCode(params MiniCodeRequest) (*MimiCodeResponse, error)
GetOrderBySinge(params GetOrderBySingeRequest) (*GetOrderBySingeResponse, error)
GetOrderByBatch(params GetOrderByBatchRequest) (*GetOrderByBatchResponse, error)
}
type meituanApiImpl struct {
log logx.Logger
client *Client
}
func newMeituanApiImpl(log logx.Logger, client *Client) MeituanUnionApi {
return &meituanApiImpl{
log: log,
client: client,
}
}
func (a *meituanApiImpl) GetLink(params GenerateLinkRequest) (*GenerateLinkResponse, error) {
params.Sign = a.sign(util.StructToMap(params))
queryArgs := util.StructToMap(params)
req := &client.HttpRequest{Headers: a.client.Headers, QueryArgs: queryArgs}
response := new(GenerateLinkResponse)
if err := a.client.HttpGet(GetLinkUrl, req, &client.HttpResponse{Result: response}); err != nil {
return nil, err
}
return response, nil
}
func (a *meituanApiImpl) MiniCode(params MiniCodeRequest) (*MimiCodeResponse, error) {
params.Sign = a.sign(util.StructToMap(params))
queryArgs := util.StructToMap(params)
req := &client.HttpRequest{Headers: a.client.Headers, QueryArgs: queryArgs}
response := new(MimiCodeResponse)
if err := a.client.HttpGet(GetMiniCode, req, &client.HttpResponse{Result: response}); err != nil {
return nil, err
}
return response, nil
}
func (a *meituanApiImpl) GetOrderBySinge(params GetOrderBySingeRequest) (*GetOrderBySingeResponse, error) {
params.Sign = a.sign(util.StructToMap(params))
queryArgs := util.StructToMap(params)
req := &client.HttpRequest{Headers: a.client.Headers, QueryArgs: queryArgs}
response := new(GetOrderBySingeResponse)
if err := a.client.HttpGet(GetOrderSinge, req, &client.HttpResponse{Result: response}); err != nil {
return nil, err
}
return response, nil
}
func (a *meituanApiImpl) GetOrderByBatch(params GetOrderByBatchRequest) (*GetOrderByBatchResponse, error) {
params.Sign = a.sign(util.StructToMap(params))
queryArgs := util.StructToMap(params)
req := &client.HttpRequest{Headers: a.client.Headers, QueryArgs: queryArgs}
response := new(GetOrderByBatchResponse)
if err := a.client.HttpGet(GetOrderBatch, req, &client.HttpResponse{Result: response}); err != nil {
return nil, err
}
return response, nil
}
func (a *meituanApiImpl) sign(params map[string]interface{}) string {
kvPairs := a.getSignStr(params)
sort.Strings(kvPairs)
paramStr := strings.Join(kvPairs, "")
return util.Md5String(a.client.authConfig.SignKey + paramStr + a.client.authConfig.SignKey)
}
func (a *meituanApiImpl) notifySign(params map[string]interface{}) string {
kvPairs := a.getSignStr(params)
sort.Strings(kvPairs)
paramStr := strings.Join(kvPairs, "")
return util.Md5String(a.client.authConfig.NotifyKey + paramStr + a.client.authConfig.NotifyKey)
}
func (a *meituanApiImpl) getSignStr(dataMap map[string]any) []string {
var kvPairs []string
for k, v := range dataMap {
key := a.lowerFirstLetter(k)
if key == "sign" || key == "Sign" {
continue
}
kvPairs = append(kvPairs, fmt.Sprintf("%s%v", key, v))
}
return kvPairs
}
func (a *meituanApiImpl) lowerFirstLetter(s string) string {
if len(s) == 0 {
return s
}
return strings.ToLower(string(s[0])) + s[1:]
}

View File

@ -0,0 +1,35 @@
package meituan_union
import (
"github.com/zeromicro/go-zero/core/logx"
"chengdu-lenntc/third-platform-sdk/client"
)
// AuthConfig api鉴权参数
type AuthConfig struct {
SignKey string //签名秘钥
NotifyKey string //回调秘钥
}
// 连接第三方平台的client
type Client struct {
client.ThirdClient
log logx.Logger
authConfig AuthConfig
Headers map[string]string
}
func newClient(log logx.Logger, conf AuthConfig) *Client {
// todo:: 请求第三方平台的配置参数
return &Client{
ThirdClient: client.NewThirdClient(log),
log: log,
authConfig: conf,
}
}
func NewApiClient(log logx.Logger, conf AuthConfig) MeituanUnionApi {
clt := newClient(log, conf)
return newMeituanApiImpl(log, clt)
}

View File

@ -0,0 +1,9 @@
package meituan_union
const (
Domain = "https://openapi.meituan.com"
GetLinkUrl = Domain + "/api/generateLink"
GetMiniCode = Domain + "/api/miniCode"
GetOrderSinge = Domain + "/api/order"
GetOrderBatch = Domain + "/api/orderList"
)

View File

@ -0,0 +1 @@
package meituan_union

View File

@ -0,0 +1,87 @@
package meituan_union
type GenerateLinkRequest struct {
ActId int64 `form:"actId"`
Appkey string `form:"appkey"`
LinkType int64 `form:"linkType"`
ShortLink int64 `form:"shortLink"`
Sid string `form:"sid"`
Sign string `form:"sign"`
}
type MiniCodeRequest struct {
ActId int64 `form:"actId"`
Appkey string `form:"appkey"`
Sid string `form:"sid"`
Sign string `form:"sign"`
LinkType int64 `form:"linkType"`
}
type MimiCodeResponse struct {
}
type GenerateLinkResponse struct {
Status int64 `json:"status"`
Des string `json:"des"`
Data string `json:"data"`
}
type GetOrderBySingeRequest struct {
Appkey string `form:"appkey"`
ActId int64 `form:"actId"`
Full int64 `form:"full"`
Sign string `form:"sign"`
OrderId string `form:"orderId"`
}
type GetOrderBySingeResponse struct {
Status int64 `json:"status"`
Des string `json:"des"`
Data OrderSinge `json:"data"`
}
type OrderSinge struct {
ActId int64 `json:"actId"`
Quantity int64 `json:"quantity"`
OrderId string `json:"orderId"`
PayTime string `json:"paytime"`
ModTime string `json:"modTime"`
PayPrice string `json:"payprice"`
Profit string `json:"profit"`
CpaProfit string `json:"cpaProfit"`
Sid string `json:"sid"`
AppKey string `json:"appkey"`
SmsTitle string `json:"smstitle"`
Status int64 `json:"status"`
RiskOrder int64 `json:"riskOrder"`
RefundProfit string `json:"refundProfit"`
CpaRefundProfit string `json:"cpaRefundProfit"`
}
type GetOrderByBatchRequest struct {
Appkey string `form:"appkey"`
Ts string `form:"ts"`
ActId string `form:"actId"`
StartTime string `form:"startTime"`
EndTime string `form:"endTime"`
Sign string `form:"sign"`
Page string `form:"page"`
Limit string `form:"limit"`
}
type GetOrderByBatchResponse struct {
Total int64 `json:"total"`
DataList []OrderBatch `json:"dataList"`
}
type OrderBatch struct {
ActId int64 `json:"actId"`
OrderId string `json:"orderId"`
PayTime string `json:"paytime"`
ModTime string `json:"modTime"`
PayPrice string `json:"payprice"`
Profit string `json:"profit"`
CpaProfit string `json:"cpaProfit"`
Sid string `json:"sid"`
AppKey string `json:"appkey"`
SmsTitle string `json:"smstitle"`
Status int64 `json:"status"`
RiskOrder int64 `json:"riskOrder"`
RefundProfit string `json:"refundProfit"`
CpaRefundProfit string `json:"cpaRefundProfit"`
}

202
util/http.go Normal file
View File

@ -0,0 +1,202 @@
package util
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
netUrl "net/url"
"strings"
"time"
"github.com/zeromicro/go-zero/core/logx"
)
const (
TimeOut = time.Duration(30) * time.Second // http请求超时时间, 默认30s
)
type HttpClient struct {
log logx.Logger
ReqConfig *ReqConfig // 请求参数
TimeOut time.Duration // 超时时间
Request *http.Request // 请求request
Response *Response // 响应结果
Error error
}
// ReqConfig 请求参数
type ReqConfig struct {
Headers map[string]string
QueryArgs map[string]any
BodyArgs map[string]any
}
type Response struct {
Status string // e.g. "200 OK"
StatusCode int // e.g. 200
Header http.Header
Body []byte // body实体即io.ReadAll(resp.body)后的结果
}
func NewHttpClient(log logx.Logger) *HttpClient {
return &HttpClient{
log: log,
}
}
// Get GET请求
// @param url 请求的http地址
// @param reqConfig 中header、query、body等参数
func (h *HttpClient) Get(url string, reqConfig *ReqConfig) *HttpClient {
return h.NewRequest(http.MethodGet, url, reqConfig)
}
// Post POST请求
func (h *HttpClient) Post(url string, reqConfig *ReqConfig) *HttpClient {
return h.NewRequest(http.MethodPost, url, reqConfig)
}
// Put PUT请求
func (h *HttpClient) Put(url string, reqConfig *ReqConfig) *HttpClient {
return h.NewRequest(http.MethodPut, url, reqConfig)
}
// Delete DELETE请求
func (h *HttpClient) Delete(url string, reqConfig *ReqConfig) *HttpClient {
return h.NewRequest(http.MethodDelete, url, reqConfig)
}
// NewRequest 组装请求参数
func (h *HttpClient) NewRequest(method, url string, reqConfig *ReqConfig) (hc *HttpClient) {
if h.Error != nil {
return h
}
h.ReqConfig = reqConfig
hc = h
// 请求
req, err := http.NewRequest(method, url, nil)
if err != nil {
h.log.WithFields([]logx.LogField{{Key: "method", Value: method}, {Key: "url", Value: url}, {Key: "reqConfig", Value: reqConfig}}...).
Errorf("[HttpClient][NewRequest] http new request error", err)
hc.Error = err
return
}
hc.Request = req
// 请求参数
err = hc.requestConfig()
if err != nil {
hc.Error = err
return
}
return
}
// Do http请求及响应处理
func (h *HttpClient) Do() (hc *HttpClient) {
if h.Error != nil {
return h
}
hc = h
// http请求
timeOut := hc.TimeOut
if hc.TimeOut == 0 {
timeOut = TimeOut
}
client := &http.Client{}
client.Timeout = timeOut
// 开始请求
resp, err := client.Do(hc.Request)
if err != nil {
h.log.WithFields(logx.LogField{Key: "req", Value: hc.Request}).
Errorf("[HttpClient][Do] http client do error", err)
hc.Error = err
return
}
defer resp.Body.Close()
content, err := io.ReadAll(resp.Body)
if err != nil {
h.log.WithFields(logx.LogField{Key: "req", Value: hc.Request}).
Errorf("[HttpClient][Do] http response ReadAll error", err)
hc.Error = err
return
}
hc.Response = &Response{
Status: resp.Status,
StatusCode: resp.StatusCode,
Header: resp.Header,
Body: content,
}
return
}
// Result 获取http的响应结果
// @param result 为结构体指针类型解析http请求返回数据json.Unmarshal后的结构
func (h *HttpClient) Result(result any) (hc *HttpClient) {
if h.Error != nil {
return h
}
hc = h
if result == nil {
return
}
if hc.Response == nil || len(hc.Response.Body) == 0 {
return
}
err := json.Unmarshal(hc.Response.Body, &result)
if err != nil {
h.log.WithFields(logx.LogField{Key: "HttpClient", Value: hc}, logx.LogField{Key: "result", Value: result}).
Errorf("[HttpClient][Result] http response body json unmarshal error", err)
hc.Error = err
return
}
return
}
// 请求参数
func (h *HttpClient) requestConfig() error {
if h.Request == nil {
return errors.New("http request is nil")
}
if h.ReqConfig == nil {
return nil
}
// header参数
if h.ReqConfig.Headers != nil {
for key, value := range h.ReqConfig.Headers {
h.Request.Header.Set(key, value)
}
}
// 请求query参数
queryParams := h.Request.URL.Query()
if h.ReqConfig.QueryArgs != nil {
for key, value := range h.ReqConfig.QueryArgs {
queryParams.Add(key, fmt.Sprint(value))
}
}
h.Request.URL.RawQuery = queryParams.Encode()
// 请求body参数
if h.ReqConfig.BodyArgs != nil {
switch h.Request.Header.Get("Content-Type") {
case "application/x-www-form-urlencoded":
form := make(netUrl.Values)
for k, v := range h.ReqConfig.BodyArgs {
form.Add(k, fmt.Sprint(v))
}
h.Request.Body = io.NopCloser(strings.NewReader(form.Encode()))
case "application/json":
data, err := json.Marshal(h.ReqConfig.BodyArgs)
if err != nil {
h.Error = err
return err
}
h.Request.Body = io.NopCloser(strings.NewReader(string(data)))
}
}
return nil
}

19
util/json.go Normal file
View File

@ -0,0 +1,19 @@
package util
//// StructToMap 将struct转为map
//func StructToMap(info any) (map[string]any, error) {
// result := make(map[string]any)
// data, err := json.Marshal(info)
// if err != nil {
// return nil, err
// }
//
// d := json.NewDecoder(bytes.NewReader(data))
// d.UseNumber()
// err = d.Decode(&result)
// //err = json.Unmarshal(data, &result)
// if err != nil {
// return nil, err
// }
// return result, nil
//}

13
util/md5.go Normal file
View File

@ -0,0 +1,13 @@
package util
import (
"crypto/md5"
"encoding/hex"
)
// Md5String 字符串计算md5
func Md5String(v string) string {
h := md5.New()
h.Write([]byte(v))
return hex.EncodeToString(h.Sum(nil))
}

17
util/util.go Normal file
View File

@ -0,0 +1,17 @@
package util
import "reflect"
func StructToMap(obj interface{}) map[string]any {
objValue := reflect.ValueOf(obj)
objType := objValue.Type()
data := make(map[string]any)
for i := 0; i < objValue.NumField(); i++ {
field := objValue.Field(i)
key := objType.Field(i).Name
value := field.Interface()
data[key] = value
}
return data
}