From ac4dff47136784255b14142057bb68b842fbb267 Mon Sep 17 00:00:00 2001 From: wukesheng Date: Mon, 13 May 2024 23:18:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BB=B4=E6=BB=B4=E8=81=94=E7=9B=9F=E7=9A=84ap?= =?UTF-8?q?i=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 4 + index.go | 6 + platform/didi-union/api.go | 88 ++++++++++++ platform/didi-union/api_test.go | 37 +++++ platform/didi-union/client.go | 34 +++++ platform/didi-union/consts.go | 14 ++ platform/didi-union/types.go | 40 ++++++ sdk/dunion-go-sdk/README.md | 47 +++++++ sdk/dunion-go-sdk/client/client.go | 202 ++++++++++++++++++++++++++++ sdk/dunion-go-sdk/const/const.go | 32 +++++ sdk/dunion-go-sdk/model/link.go | 17 +++ sdk/dunion-go-sdk/model/option.go | 7 + sdk/dunion-go-sdk/model/order.go | 138 +++++++++++++++++++ sdk/dunion-go-sdk/model/poster.go | 21 +++ sdk/dunion-go-sdk/model/response.go | 13 ++ sdk/dunion-go-sdk/util/auth.go | 42 ++++++ sdk/dunion-go-sdk/util/log.go | 46 +++++++ sdk/dunion-go-sdk/util/request.go | 115 ++++++++++++++++ sdk/dunion-go-sdk/util/uuid.go | 9 ++ 19 files changed, 912 insertions(+) create mode 100644 platform/didi-union/api.go create mode 100644 platform/didi-union/api_test.go create mode 100644 platform/didi-union/client.go create mode 100644 platform/didi-union/consts.go create mode 100644 platform/didi-union/types.go create mode 100644 sdk/dunion-go-sdk/README.md create mode 100644 sdk/dunion-go-sdk/client/client.go create mode 100644 sdk/dunion-go-sdk/const/const.go create mode 100644 sdk/dunion-go-sdk/model/link.go create mode 100644 sdk/dunion-go-sdk/model/option.go create mode 100644 sdk/dunion-go-sdk/model/order.go create mode 100644 sdk/dunion-go-sdk/model/poster.go create mode 100644 sdk/dunion-go-sdk/model/response.go create mode 100644 sdk/dunion-go-sdk/util/auth.go create mode 100644 sdk/dunion-go-sdk/util/log.go create mode 100644 sdk/dunion-go-sdk/util/request.go create mode 100644 sdk/dunion-go-sdk/util/uuid.go diff --git a/go.mod b/go.mod index 5a861eb..21c2864 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,10 @@ require ( github.com/stretchr/testify v1.8.4 github.com/zeromicro/go-zero v1.5.5 github.com/zywaited/xcopy v1.1.0 + github.com/BurntSushi/toml v1.0.0 // indirect + github.com/google/uuid v1.3.0 + go.uber.org/zap v1.21.0 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) require ( diff --git a/index.go b/index.go index 184b6f0..fd03b39 100644 --- a/index.go +++ b/index.go @@ -3,6 +3,7 @@ package third_platform_sdk import ( "github.com/zeromicro/go-zero/core/logx" + didiunion "gitee.com/chengdu-lenntc/third-platform-sdk/platform/didi-union" elemeunion "gitee.com/chengdu-lenntc/third-platform-sdk/platform/eleme-union" meituancsr "gitee.com/chengdu-lenntc/third-platform-sdk/platform/meituan-csr" meituanunion "gitee.com/chengdu-lenntc/third-platform-sdk/platform/meituan-union" @@ -22,3 +23,8 @@ func NewMeituanCsrApi(log logx.Logger, conf meituancsr.AuthConfig) meituancsr.Me func NewMeituanUnionApi(log logx.Logger, conf meituanunion.AuthConfig) meituanunion.MeituanUnionApi { return meituanunion.NewApiClient(log, conf) } + +// NewDidiUnionApi 滴滴联盟 +func NewDidiUnionApi(log logx.Logger, conf didiunion.AuthConfig) didiunion.DidiUnionApi { + return didiunion.NewApiClient(log, conf) +} diff --git a/platform/didi-union/api.go b/platform/didi-union/api.go new file mode 100644 index 0000000..fc6ee53 --- /dev/null +++ b/platform/didi-union/api.go @@ -0,0 +1,88 @@ +package didi_union + +import ( + "context" + "fmt" + "time" + + "github.com/zeromicro/go-zero/core/logx" + + "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/model" + sdkutil "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/util" +) + +// DidiUnionApi 调用第三方平台的api +// Api defines the interface of eleme_union api +type DidiUnionApi interface { + // Sign 签名 + Sign(data map[string]interface{}) string + // GenerateH5Link 生成h5推广链接 + GenerateH5Link(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*model.LinkResponse, error) + // GenerateMiniLink 生成小程序页面推广路径 + GenerateMiniLink(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*model.LinkResponse, error) + // GenerateH5Code 生成h5二维码,需先取链得到dsi + GenerateH5Code(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*model.QrcodeResponse, error) + // GenerateMiniCode 生成小程序太阳码,需先取链得到dsi + GenerateMiniCode(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*model.QrcodeResponse, error) + // GeneratePoster 生成推广海报,需先取链得到dsi + GeneratePoster(ctx context.Context, req GeneratePosterRequest, opt model.Option) (*model.PosterResponse, error) + // QueryOrderList 查询订单列表 + QueryOrderList(ctx context.Context, req QueryOrderListRequest, opt model.Option) (*model.OrderResponse, error) + // SelfQueryOrder 订单归因问题自查询 + SelfQueryOrder(ctx context.Context, req SelfQueryOrderRequest, opt model.Option) (*model.SelfQueryResponse, error) +} + +type didiUnionApiImpl struct { + log logx.Logger + client *Client +} + +func newDidiUnionApiImpl(log logx.Logger, client *Client) DidiUnionApi { + return &didiUnionApiImpl{ + log: log, + client: client, + } +} + +// Sign 签名 +func (d *didiUnionApiImpl) Sign(params map[string]any) string { + params[sdkutil.AppKey] = d.client.authConfig.AppKey + params[sdkutil.Timestamp] = fmt.Sprintf("%d", time.Now().Unix()) + sign := sdkutil.GetSign(params, d.client.authConfig.AppSecret) + return sign +} + +// GenerateH5Link 生成h5推广链接 +func (d *didiUnionApiImpl) GenerateH5Link(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*model.LinkResponse, error) { + return d.client.clt.GenerateH5Link(ctx, req.ActivityID, req.PromotionID, req.SourceID, opt) +} + +// GenerateMiniLink 生成小程序页面推广路径 +func (d *didiUnionApiImpl) GenerateMiniLink(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*model.LinkResponse, error) { + return d.client.clt.GenerateMiniLink(ctx, req.ActivityID, req.PromotionID, req.SourceID, opt) +} + +// GenerateH5Code 生成h5二维码,需先取链得到dsi +func (d *didiUnionApiImpl) GenerateH5Code(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*model.QrcodeResponse, error) { + return d.client.clt.GenerateH5Code(ctx, req.Dsi, req.SourceID, opt) +} + +// GenerateMiniCode 生成小程序太阳码,需先取链得到dsi +func (d *didiUnionApiImpl) GenerateMiniCode(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*model.QrcodeResponse, error) { + return d.client.clt.GenerateMiniCode(ctx, req.Dsi, req.SourceID, opt) +} + +// GeneratePoster 生成推广海报,需先取链得到dsi +func (d *didiUnionApiImpl) GeneratePoster(ctx context.Context, req GeneratePosterRequest, opt model.Option) (*model.PosterResponse, error) { + return d.client.clt.GeneratePoster(ctx, req.Dsi, req.SourceID, opt) +} + +// QueryOrderList 查询订单列表 +func (d *didiUnionApiImpl) QueryOrderList(ctx context.Context, req QueryOrderListRequest, opt model.Option) (*model.OrderResponse, error) { + return d.client.clt.QueryOrderList(ctx, req.StartTime, req.EndTime, string(req.Typ), req.Page, req.Size, opt) +} + +// SelfQueryOrder 订单归因问题自查询 +func (d *didiUnionApiImpl) SelfQueryOrder(ctx context.Context, req SelfQueryOrderRequest, opt model.Option) (*model.SelfQueryResponse, error) { + return d.client.clt.SelfQueryOrder(ctx, req.OrderID, opt) +} diff --git a/platform/didi-union/api_test.go b/platform/didi-union/api_test.go new file mode 100644 index 0000000..b8efa82 --- /dev/null +++ b/platform/didi-union/api_test.go @@ -0,0 +1,37 @@ +package didi_union + +import ( + "context" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/zeromicro/go-zero/core/logx" +) + +// api-单元测试 +type apiClientSuite struct { + suite.Suite + api DidiUnionApi +} + +func TestApiClient(t *testing.T) { + suite.Run(t, new(apiClientSuite)) +} + +func (a *apiClientSuite) SetupSuite() { + log := logx.WithContext(context.Background()) + apiClient := NewApiClient(log, AuthConfig{ + AppKey: "2M0QUa0o6ER8nuX1", + AppSecret: "obvJ5mmV45ZWA3YpO95njR1xH62JT50h", + }) + a.api = apiClient +} + +func (a *apiClientSuite) Test_Sign() { + data := map[string]interface{}{ + "method": "test", + } + + sign := a.api.Sign(data) + a.T().Logf("=====[TestSign] sign: %s", sign) +} diff --git a/platform/didi-union/client.go b/platform/didi-union/client.go new file mode 100644 index 0000000..06e5fd0 --- /dev/null +++ b/platform/didi-union/client.go @@ -0,0 +1,34 @@ +package didi_union + +import ( + "github.com/zeromicro/go-zero/core/logx" + + "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/client" +) + +// AuthConfig api鉴权参数 +type AuthConfig struct { + AppKey string // 应用key + AppSecret string // 应用秘钥 +} + +// 连接第三方平台的client +type Client struct { + clt client.UnionClient + log logx.Logger + authConfig AuthConfig +} + +func NewApiClient(log logx.Logger, conf AuthConfig) DidiUnionApi { + clt := newClient(log, conf) + return newDidiUnionApiImpl(log, clt) +} + +func newClient(log logx.Logger, conf AuthConfig) *Client { + uc := client.NewUnionClient(conf.AppKey, conf.AppSecret) + return &Client{ + clt: uc, + log: log, + authConfig: conf, + } +} diff --git a/platform/didi-union/consts.go b/platform/didi-union/consts.go new file mode 100644 index 0000000..ba00bf1 --- /dev/null +++ b/platform/didi-union/consts.go @@ -0,0 +1,14 @@ +package didi_union + +// OrderType 订单类型 +type OrderType string + +// 订单查询type枚举值 +const ( + OrderTypeAll OrderType = "" //全部 + OrderTypeEnergy OrderType = "energy" //滴滴加油 + OrderTypeCar OrderType = "online_car" //网约车 + OrderTypeFreight OrderType = "freight" //货运 + OrderTypeHxz OrderType = "king_flower" //花小猪 + OrderTypeDaijia OrderType = "daijia" //代驾 +) diff --git a/platform/didi-union/types.go b/platform/didi-union/types.go new file mode 100644 index 0000000..b9d231d --- /dev/null +++ b/platform/didi-union/types.go @@ -0,0 +1,40 @@ +package didi_union + +import "time" + +// GenerateLinkRequest 生成推广链接的请求 +type GenerateLinkRequest struct { + ActivityID int64 // 活动id + PromotionID int64 // 推广位id + SourceID string // 来源id (用于标明订单来源) +} + +// 生成推广链接的响应 +type GenerateLinkResponse struct { +} + +// GenerateCodeRequest 生成二维码的请求 +type GenerateCodeRequest struct { + Dsi string // 活动ID+推广位ID决定一个DSI (需先取链得到dsi) + SourceID string // 来源id (用于标明订单来源) +} + +// GeneratePosterRequest 生成海报的请求 +type GeneratePosterRequest struct { + Dsi string // 活动ID+推广位ID决定一个DSI (需先取链得到dsi) + SourceID string // 来源id (用于标明订单来源) +} + +// QueryOrderListRequest 订单列表请求 +type QueryOrderListRequest struct { + StartTime time.Time // 开始时间 + EndTime time.Time // 结束时间 + Typ OrderType // 订单类型,枚举值见 OrderType + Page int // 页码 (最大值为100) + Size int // 每页数量(最大值为100) +} + +// SelfQueryOrderRequest 自查询订单请求 +type SelfQueryOrderRequest struct { + OrderID string // 订单ID +} diff --git a/sdk/dunion-go-sdk/README.md b/sdk/dunion-go-sdk/README.md new file mode 100644 index 0000000..571101b --- /dev/null +++ b/sdk/dunion-go-sdk/README.md @@ -0,0 +1,47 @@ +# 滴滴联盟 openAPI go-sdk + +引入mod +``` +go get gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk@master +``` +使用方法 +``` +c := client.NewUnionClient("appkey", "accesskey") +//日志可选,将在指定目录生成日志 +util.InitLogger("./log/union.log") + +//或者使用日志注入的方式,需实现两个接口函数: +//Infof(template string, args ...interface{}) +//Errorf(template string, args ...interface{}) +//然后调用 +//util.SetLogger(yourLogger) + +//设置全局超时时间 +//util.SetTimeoutDuration(2*time.Second) + +//或者设置单个接口的超时时间 +//link, err := c.GenerateH5Link(context.Background(), 6133, 6834408369283047676, "d", model.Option{Timeout: 2*time.Second}) + +link, err := c.GenerateH5Link(context.Background(), 6133, 6834408369283047676, "d") +if err != nil { + fmt.Println(err) + return +} +``` + +函数一览 + +| 函数原型 | 用途 | +| ---- | ---- | +| GenerateH5Link(activityID, promotionID int64, sourceID string) (*model.LinkResponse, error) | 生成h5推广链接 | +| GenerateMiniLink(activityID, promotionID int64, sourceID string) (*model.LinkResponse, error) | 生成小程序页面推广路径| +| GenerateH5Code(dsi, sourceID string) (*model.QrcodeResponse, error)|生成h5二维码,需先取链得到dsi| +| GenerateMiniCode(dsi, sourceID string) (*model.QrcodeResponse, error)|生成小程序太阳码,需先取链得到dsi| +| GeneratePoster(dsi, sourceID string) (*model.PosterResponse, error)|生成推广海报,需先取链得到dsi| +| QueryOrderList(startTime, endTime time.Time, type_ string, page, size int) (*model.OrderResponse, error)|查询订单列表,type_可用枚举见 const.OrderTypeEnergy等| +| MockOrderCallback(dsi string, sourceID string, type_ int) (*model.OrderCallbackResponse, error)|模拟订单回调,需先取链得到 dsi,type_ 可取 consts.MockPay 或 consts.MockRefund; 需在后台配置回调地址| +| GenerateH5CodeDirectly(activityID, promotionID int64, sourceID string) (*model.QrcodeResponse, error)|直接生成h5推广二维码,会内置请求一次取链接口| +| GenerateMiniCodeDirectly(activityID, promotionID int64, sourceID string) (*model.QrcodeResponse, error)|直接生成小程序推广太阳码,会内置请求一次取链接口| +| GeneratePosterDirectly(activityID, promotionID int64, sourceID string) (*model.PosterResponse, error)|直接生成推广海报,会内置请求一次取链接口| +| SelfQueryOrder(orderID string)(*model.SelfQueryResponse, error)| 订单归因问题自查询| + diff --git a/sdk/dunion-go-sdk/client/client.go b/sdk/dunion-go-sdk/client/client.go new file mode 100644 index 0000000..c897316 --- /dev/null +++ b/sdk/dunion-go-sdk/client/client.go @@ -0,0 +1,202 @@ +package client + +import ( + "context" + "encoding/json" + "errors" + "time" + + "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/const" + "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/model" + "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/util" +) + +type client struct { + AppKey string + AccessKey string +} + +type UnionClient interface { + GenerateH5Link(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.LinkResponse, error) + GenerateMiniLink(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.LinkResponse, error) + GenerateH5Code(ctx context.Context, dsi, sourceID string, opt ...model.Option) (*model.QrcodeResponse, error) + GenerateMiniCode(ctx context.Context, dsi, sourceID string, opt ...model.Option) (*model.QrcodeResponse, error) + GeneratePoster(ctx context.Context, dsi, sourceID string, opt ...model.Option) (*model.PosterResponse, error) + QueryOrderList(ctx context.Context, startTime, endTime time.Time, type_ string, page, size int, opt ...model.Option) (*model.OrderResponse, error) + GenerateH5CodeDirectly(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.QrcodeResponse, error) + MockOrderCallback(ctx context.Context, dsi string, sourceID string, type_ int, opt ...model.Option) (*model.OrderCallbackResponse, error) + GenerateMiniCodeDirectly(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.QrcodeResponse, error) + GeneratePosterDirectly(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.PosterResponse, error) + SelfQueryOrder(ctx context.Context, orderID string, opt ...model.Option) (*model.SelfQueryResponse, error) +} + +func NewUnionClient(appKey string, accessKey string) UnionClient { + return &client{ + AppKey: appKey, + AccessKey: accessKey, + } +} + +// GenerateH5Link 生成h5推广链接 +func (s client) GenerateH5Link(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.LinkResponse, error) { + body := map[string]interface{}{ + "activity_id": activityID, + "link_type": "h5", + "promotion_id": promotionID, + "source_id": sourceID, + } + response, err := util.Post(ctx, s.AppKey, s.AccessKey, consts.GenerateLinkUrl, body, opt...) + if err != nil { + return nil, err + } + result := &model.LinkResponse{} + err = json.Unmarshal(response, result) + return result, err +} + +// GenerateMiniLink 生成小程序页面推广路径 +func (s client) GenerateMiniLink(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.LinkResponse, error) { + body := map[string]interface{}{ + "activity_id": activityID, + "link_type": "mini", + "promotion_id": promotionID, + "source_id": sourceID, + } + response, err := util.Post(ctx, s.AppKey, s.AccessKey, consts.GenerateLinkUrl, body, opt...) + if err != nil { + return nil, err + } + result := &model.LinkResponse{} + err = json.Unmarshal(response, result) + return result, err +} + +// GenerateH5Code 生成h5二维码,需先取链得到dsi +func (s client) GenerateH5Code(ctx context.Context, dsi, sourceID string, opt ...model.Option) (*model.QrcodeResponse, error) { + param := map[string]interface{}{ + "dsi": dsi, + "source_id": sourceID, + "type": "h5", + } + response, err := util.Get(ctx, s.AppKey, s.AccessKey, consts.GenerateQrCodeUrl, param, opt...) + if err != nil { + return nil, err + } + result := &model.QrcodeResponse{} + err = json.Unmarshal(response, result) + return result, err +} + +// GenerateMiniCode 生成小程序太阳码,需先取链得到dsi +func (s client) GenerateMiniCode(ctx context.Context, dsi, sourceID string, opt ...model.Option) (*model.QrcodeResponse, error) { + param := map[string]interface{}{ + "dsi": dsi, + "source_id": sourceID, + "type": "mini", + } + response, err := util.Get(ctx, s.AppKey, s.AccessKey, consts.GenerateQrCodeUrl, param, opt...) + if err != nil { + return nil, err + } + result := &model.QrcodeResponse{} + err = json.Unmarshal(response, result) + return result, err +} + +// GeneratePoster 生成推广海报,需先取链得到dsi +func (s client) GeneratePoster(ctx context.Context, dsi, sourceID string, opt ...model.Option) (*model.PosterResponse, error) { + param := map[string]interface{}{ + "dsi": dsi, + "source_id": sourceID, + } + response, err := util.Get(ctx, s.AppKey, s.AccessKey, consts.GeneratePosterUrl, param, opt...) + if err != nil { + return nil, err + } + result := &model.PosterResponse{} + err = json.Unmarshal(response, result) + return result, err +} + +// QueryOrderList 查询订单列表,type_可用枚举见 const.OrderTypeEnergy等 +func (s client) QueryOrderList(ctx context.Context, startTime, endTime time.Time, type_ string, page, size int, opt ...model.Option) (*model.OrderResponse, error) { + if page < 0 || page > 100 || size < 0 || size > 100 { + return nil, errors.New("分页参数不合法") + } + param := map[string]interface{}{ + "pay_start_time": startTime.Unix(), + "pay_end_time": endTime.Unix(), + "page": page, + "size": size, + } + if len(type_) > 0 { + param["type"] = type_ + } + response, err := util.Get(ctx, s.AppKey, s.AccessKey, consts.QueryOrderUrl, param, opt...) + if err != nil { + return nil, err + } + result := &model.OrderResponse{} + err = json.Unmarshal(response, result) + return result, err +} + +// MockOrderCallback 模拟订单回调,需先取链得到 dsi,type_ 可取 consts.MockPay 或 consts.MockRefund; 需在后台配置回调地址 +func (s client) MockOrderCallback(ctx context.Context, dsi string, sourceID string, type_ int, opt ...model.Option) (*model.OrderCallbackResponse, error) { + param := map[string]interface{}{ + "dsi": dsi, + "source_id": sourceID, + "type": type_, + } + response, err := util.Get(ctx, s.AppKey, s.AccessKey, consts.MockOrderUrl, param, opt...) + if err != nil { + return nil, err + } + result := &model.OrderCallbackResponse{} + err = json.Unmarshal(response, result) + return result, err +} + +// GenerateH5CodeDirectly 直接生成h5推广二维码,会内置请求一次取链接口 +func (s client) GenerateH5CodeDirectly(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.QrcodeResponse, error) { + link, err := s.GenerateMiniLink(ctx, activityID, promotionID, sourceID, opt...) + if err != nil { + return nil, err + } + dsi := link.Data.DSI + return s.GenerateH5Code(ctx, dsi, sourceID, opt...) +} + +// GenerateMiniCodeDirectly 直接生成小程序推广太阳码,会内置请求一次取链接口 +func (s client) GenerateMiniCodeDirectly(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.QrcodeResponse, error) { + link, err := s.GenerateMiniLink(ctx, activityID, promotionID, sourceID, opt...) + if err != nil { + return nil, err + } + dsi := link.Data.DSI + return s.GenerateMiniCode(ctx, dsi, sourceID, opt...) +} + +// GeneratePosterDirectly 直接生成推广海报,会内置请求一次取链接口 +func (s client) GeneratePosterDirectly(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.PosterResponse, error) { + link, err := s.GenerateMiniLink(ctx, activityID, promotionID, sourceID, opt...) + if err != nil { + return nil, err + } + dsi := link.Data.DSI + return s.GeneratePoster(ctx, dsi, sourceID, opt...) +} + +// SelfQueryOrder 订单归因问题自查询 +func (s client) SelfQueryOrder(ctx context.Context, orderID string, opt ...model.Option) (*model.SelfQueryResponse, error) { + param := map[string]interface{}{ + "order_id": orderID, + } + response, err := util.Get(ctx, s.AppKey, s.AccessKey, consts.SelfQueryUrl, param, opt...) + if err != nil { + return nil, err + } + result := &model.SelfQueryResponse{} + err = json.Unmarshal(response, result) + return result, err +} diff --git a/sdk/dunion-go-sdk/const/const.go b/sdk/dunion-go-sdk/const/const.go new file mode 100644 index 0000000..8497c88 --- /dev/null +++ b/sdk/dunion-go-sdk/const/const.go @@ -0,0 +1,32 @@ +package consts + +const ( + GenerateLinkUrl = "https://union.didi.cn/openapi/v1.0/link/generate" + GenerateQrCodeUrl = "https://union.didi.cn/openapi/v1.0/code/generate" + GeneratePosterUrl = "https://union.didi.cn/openapi/v1.0/poster/generate" + QueryOrderUrl = "https://union.didi.cn/openapi/v1.0/order/list" + MockOrderUrl = "https://union.didi.cn/openapi/v1.0/orderMock/callback" + SelfQueryUrl = "https://union.didi.cn/openapi/v1.0/order/selfQuery" +) + +// 订单查询type枚举值 +const ( + OrderTypeAll = "" //全部 + OrderTypeEnergy = "energy" //滴滴加油 + OrderTypeCar = "online_car" //网约车 + OrderTypeFreight = "freight" //货运 + OrderTypeHxz = "king_flower" //花小猪 + OrderTypeDaijia = "daijia" //代驾 +) + +// mock订单回调类型枚举值 +const ( + MockPay = 0 //支付 + MockRefund = 1 //退款 +) + +const ( + UserAgent = "User-Agent" + SDKVersion = "dunion-go-openapi-sdk-1.0" + TraceID = "Didi-Header-Rid" +) diff --git a/sdk/dunion-go-sdk/model/link.go b/sdk/dunion-go-sdk/model/link.go new file mode 100644 index 0000000..665f713 --- /dev/null +++ b/sdk/dunion-go-sdk/model/link.go @@ -0,0 +1,17 @@ +package model + +//swagger:model +type LinkResponse struct { + Response + Data struct { + //生成的链接 + //example: https://v.didi.cn/p/abcd + Link string `json:"link"` + //实例ID,可通过此ID去生成海报或者二维码 + DSI string `json:"dsi"` + //小程序appid + AppId string `json:"app_id,omitempty"` + //小程序原始ID + AppSource string `json:"app_source,omitempty"` + } `json:"data"` +} diff --git a/sdk/dunion-go-sdk/model/option.go b/sdk/dunion-go-sdk/model/option.go new file mode 100644 index 0000000..bca8d6c --- /dev/null +++ b/sdk/dunion-go-sdk/model/option.go @@ -0,0 +1,7 @@ +package model + +import "time" + +type Option struct { + Timeout time.Duration +} diff --git a/sdk/dunion-go-sdk/model/order.go b/sdk/dunion-go-sdk/model/order.go new file mode 100644 index 0000000..14e77fa --- /dev/null +++ b/sdk/dunion-go-sdk/model/order.go @@ -0,0 +1,138 @@ +package model + +// swagger:model +type ResponseOrderItem struct { + // 标题 + Title string `json:"title"` + // 订单id + OrderId string `json:"order_id"` + // 业务线 `159`: 滴滴加油
+ // `210`: 滴滴网约车
+ // `393`: 滴滴货运
+ // `500`: 花小猪
+ // `120`: 滴滴代驾 + ProductId string `json:"product_id"` + // 支付时间 + PayTime int64 `json:"pay_time"` + // 支付金额,单位:分 + PayPrice int64 `json:"pay_price"` + // CPA类型
+ // `cpa_normal`: 普通CPA(新用户奖励) + RefundPrice int64 `json:"refund_price"` + // 退款时间,秒级时间戳 + RefundTime int64 `json:"refund_time"` + // CPS返佣金额,单位:分 + CpsProfit int64 `json:"cps_profit"` + // CPA返佣金额,单位:分 + CpaProfit int64 `json:"cpa_profit"` + // CPA类型 + CpaType string `json:"cpa_type"` + // 推送状态: 1.已预估归因 2.预估订单已推送 3.预估订单推送失败 4.结算已提交 5.结算提交中 6.结算取消 7.结算成功 8.结算失败 + Status int `json:"status"` + // 推广位ID + PromotionId int `json:"promotion_id"` + // 来源ID + SourceId string `json:"source_id"` + // 是否被风控 + IsRisk int `json:"is_risk"` + // 下单用户openUID + // example:ecca7d66c984706aa94a15b656db2538 + OpenUID string `json:"open_uid" structs:"open_uid"` + // 订单状态 `2`:已付款 + // `8`:已退款 + // example:2 + OrderStatus int `json:"order_status" structs:"order_status"` +} + +//swagger:model +type OrderResponse struct { + Response + Data struct { + // 总数量 + // example: 1 + Total int `json:"total"` + OrderList []*ResponseOrderItem `json:"order_list"` + } `json:"data"` +} + +//swagger:model +type OrderCallbackResponse struct { + Response + Data struct { + // 标题 + Title string `json:"title"` + // 订单id + OrderId string `json:"order_id"` + // 业务线 `159`: 滴滴加油
+ // `210`: 滴滴网约车
+ // `393`: 滴滴货运
+ // `500`: 花小猪
+ // `120`: 滴滴代驾 + ProductId string `json:"product_id"` + // 支付时间 + PayTime int64 `json:"pay_time"` + // 支付金额,单位:分 + PayPrice int64 `json:"pay_price"` + // 退款金额,单位:分 + RefundPrice int64 `json:"refund_price"` + // 退款时间,秒级时间戳 + RefundTime int64 `json:"refund_time"` + // CPS返佣金额,单位:分 + CpsProfit int64 `json:"cps_profit"` + // CPA返佣金额,单位:分 + CpaProfit int64 `json:"cpa_profit"` + // CPA类型 + CpaType string `json:"cpa_type"` + // 推送状态: 1.已预估归因 2.预估订单已推送 3.预估订单推送失败 4.结算已提交 5.结算提交中 6.结算取消 7.结算成功 8.结算失败 + Status int `json:"status"` + // 推广位ID + PromotionId string `json:"promotion_id"` + // 来源ID + SourceId string `json:"source_id"` + // 是否被风控 + IsRisk int `json:"is_risk"` + } `json:"data"` +} + +type SelfQueryResponse struct { + Response + Data ResponseOrderSelfQuery `json:"data"` +} + +//swagger:model +type ResponseOrderSelfQuery struct { + // 推广成功列表 + EstimateSuccessList []*EstimateSuccessData `json:"estimate_success_list,omitempty" structs:"estimate_success_list"` + // 推广失败列表 + EstimateFailList []*EstimateFailData `json:"estimate_fail_list,omitempty" structs:"estimate_fail_list"` +} + +//swagger:model +type EstimateSuccessData struct { + // 推广成功时间 + // example:2022-01-10 10:30:00 + EstimateTime string `json:"estimate_time" structs:"estimate_time"` + // 推广渠道 + // example:当前登陆账号注册的公司名 + EstimateChannel string `json:"estimate_channel" structs:"estimate_channel"` + // 领券状态 `1`:成功 + // `2`:失败 + // example:1 + ReceiveStatus int `json:"receive_status" structs:"receive_status"` + // 领券时间 + // example:2022-01-10 10:00:00 + ReceiveTime string `json:"receive_time" structs:"receive_time"` + // 业务线名称 + // example:网约车 + SceneName string `json:"scene_name" structs:"scene_name"` +} + +//swagger:model +type EstimateFailData struct { + // 失败原因 + // example:未查询到有效绑定关系 + FailReason string `json:"fail_reason" structs:"fail_reason"` + // 业务线名称 + // example:网约车 + SceneName string `json:"scene_name" structs:"scene_name"` +} diff --git a/sdk/dunion-go-sdk/model/poster.go b/sdk/dunion-go-sdk/model/poster.go new file mode 100644 index 0000000..de265e1 --- /dev/null +++ b/sdk/dunion-go-sdk/model/poster.go @@ -0,0 +1,21 @@ +package model + +// swagger:model +type QrcodeResponse struct { + Response + Data struct { + //生成的二维码链接 + //example: https://example.com/img.jpg + CodeLink string `json:"code_link"` + } `json:"data"` +} + +// swagger:model +type PosterResponse struct { + Response + Data struct { + //生成的海报链接 + //example: https://example.com/img.jpg + PosterLink string `json:"poster_link"` + } `json:"data"` +} diff --git a/sdk/dunion-go-sdk/model/response.go b/sdk/dunion-go-sdk/model/response.go new file mode 100644 index 0000000..4634ef7 --- /dev/null +++ b/sdk/dunion-go-sdk/model/response.go @@ -0,0 +1,13 @@ +package model + +import "fmt" + +type Response struct { + Errno int64 `json:"errno"` + ErrMsg string `json:"errmsg"` + TraceID string `json:"traceid"` +} + +func (r Response) ErrorMsg() string { + return fmt.Sprintf("错误码:%d, 错误信息: %s, traceID: %s", r.Errno, r.ErrMsg, r.TraceID) +} diff --git a/sdk/dunion-go-sdk/util/auth.go b/sdk/dunion-go-sdk/util/auth.go new file mode 100644 index 0000000..496fc6e --- /dev/null +++ b/sdk/dunion-go-sdk/util/auth.go @@ -0,0 +1,42 @@ +package util + +import ( + "crypto/sha1" + "encoding/base64" + "encoding/hex" + "fmt" + "io" + "net/url" + "sort" + "strings" +) + +func GetSign(params map[string]interface{}, accessKey string) string { + // key排序 + arr := sort.StringSlice{} + for k := range params { + if k != "sign" { + arr = append(arr, k) + } + } + arr.Sort() + // 参数拼接 + var build strings.Builder + for idx, k := range arr { + if idx != 0 { + build.WriteString("&") + } + build.WriteString(fmt.Sprintf("%s=%v", k, params[k])) + } + build.WriteString(accessKey) + // URL encode + sourceStr := url.QueryEscape(build.String()) + // sha1加密 + h := sha1.New() + _, _ = io.WriteString(h, sourceStr) + shaStr := hex.EncodeToString(h.Sum([]byte(""))) + // 返回base64字符串 + b64Str := base64.StdEncoding.EncodeToString([]byte(shaStr)) + // base64字符串含有=和/,再一次URL encode + return url.QueryEscape(b64Str) +} diff --git a/sdk/dunion-go-sdk/util/log.go b/sdk/dunion-go-sdk/util/log.go new file mode 100644 index 0000000..f21e45c --- /dev/null +++ b/sdk/dunion-go-sdk/util/log.go @@ -0,0 +1,46 @@ +package util + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +type DunionLogger interface { + Infof(template string, args ...interface{}) + Errorf(template string, args ...interface{}) +} + +// var unionLogger *zap.SugaredLogger +var unionLogger DunionLogger + +func SetLogger(logger DunionLogger) { + unionLogger = logger +} + +func InitLogger(logPath string) { + writeSyncer := getLogWriter(logPath) + encoder := getEncoder() + core := zapcore.NewCore(encoder, writeSyncer, zapcore.InfoLevel) + + logger := zap.New(core, zap.AddCaller()) + unionLogger = logger.Sugar() +} + +func getEncoder() zapcore.Encoder { + encoderConfig := zap.NewProductionEncoderConfig() + encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder + return zapcore.NewConsoleEncoder(encoderConfig) +} + +func getLogWriter(logPath string) zapcore.WriteSyncer { + lumberJackLogger := &lumberjack.Logger{ + Filename: logPath, + MaxSize: 500, + MaxBackups: 5, + MaxAge: 30, + Compress: false, + } + return zapcore.AddSync(lumberJackLogger) +} diff --git a/sdk/dunion-go-sdk/util/request.go b/sdk/dunion-go-sdk/util/request.go new file mode 100644 index 0000000..5a56cd3 --- /dev/null +++ b/sdk/dunion-go-sdk/util/request.go @@ -0,0 +1,115 @@ +package util + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + netUrl "net/url" + "time" + + consts "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/const" + "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/model" +) + +const ( + AppKey = "App-Key" + Timestamp = "Timestamp" + Sign = "Sign" +) + +var globalTimeoutDuration = 2 * time.Second + +func SetTimeoutDuration(timeout time.Duration) { + globalTimeoutDuration = timeout +} + +func Post(ctx context.Context, appKey, accessKey, url string, body map[string]interface{}, opt ...model.Option) ([]byte, error) { + header := map[string]string{ + AppKey: appKey, + Timestamp: fmt.Sprintf("%d", time.Now().Unix()), + } + params := make(map[string]interface{}) + for k, v := range body { + params[k] = v + } + for k, v := range header { + params[k] = v + } + header[Sign] = GetSign(params, accessKey) + + bodyBytes, _ := json.Marshal(body) + reqReader := bytes.NewReader(bodyBytes) + req, _ := http.NewRequest("POST", url, reqReader) + traceID := uuid4() + req.Header.Set("Content-Type", "application/json") + req.Header.Set(consts.TraceID, traceID) + req.Header.Set(consts.UserAgent, consts.SDKVersion) + if unionLogger != nil { + unionLogger.Infof("url=%s||headers=%v||data=%v", url, req.Header, body) + } + for key, value := range header { + req.Header.Set(key, value) + } + timeout := globalTimeoutDuration + if len(opt) > 0 { + timeout = opt[0].Timeout + } + client := &http.Client{Timeout: timeout} + response, err := client.Do(req) + if err != nil { + if unionLogger != nil { + unionLogger.Errorf("url=%s||headers=%v||data=%v||err=%v", url, req.Header, body, err) + } + return nil, err + } + return ioutil.ReadAll(response.Body) +} + +func Get(ctx context.Context, appKey, accessKey, url string, param map[string]interface{}, opt ...model.Option) ([]byte, error) { + header := map[string]string{ + AppKey: appKey, + Timestamp: fmt.Sprintf("%d", time.Now().Unix()), + } + params := make(map[string]interface{}) + for k, v := range param { + params[k] = v + } + for k, v := range header { + params[k] = v + } + header[Sign] = GetSign(params, accessKey) + query := netUrl.Values{} + for k, v := range params { + query.Add(k, fmt.Sprintf("%v", v)) + } + if query.Encode() != "" { + url = url + "?" + query.Encode() + } + + req, _ := http.NewRequest("GET", url, nil) + traceID := uuid4() + req.Header.Set(consts.TraceID, traceID) + req.Header.Set(consts.UserAgent, consts.SDKVersion) + if unionLogger != nil { + unionLogger.Infof("url=%s||headers=%v||data=%v", url, req.Header, param) + } + for key, value := range header { + req.Header.Set(key, value) + } + timeout := globalTimeoutDuration + if len(opt) > 0 { + timeout = opt[0].Timeout + } + client := &http.Client{Timeout: timeout} + response, err := client.Do(req) + if err != nil { + if unionLogger != nil { + unionLogger.Errorf("url=%s||headers=%v||data=%v||err=%v", url, req.Header, param, err) + } + return nil, err + } + return ioutil.ReadAll(response.Body) +} diff --git a/sdk/dunion-go-sdk/util/uuid.go b/sdk/dunion-go-sdk/util/uuid.go new file mode 100644 index 0000000..4143920 --- /dev/null +++ b/sdk/dunion-go-sdk/util/uuid.go @@ -0,0 +1,9 @@ +package util + +import ( + "github.com/google/uuid" +) + +func uuid4() string { + return uuid.New().String() +}