From 4a40f28e7aa4ada821959ee9e0d4e41d0e9514e2 Mon Sep 17 00:00:00 2001 From: wukesheng Date: Wed, 15 May 2024 22:01:40 +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=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=94=AF=E4=BB=98=E5=AE=9D?= =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 8 +- platform/didi-union/api.go | 165 +++++++++++++++++++++++++---- platform/didi-union/api_test.go | 20 ++++ platform/didi-union/types.go | 47 +++++++- platform/eleme-union/api_test.go | 8 +- sdk/dunion-go-sdk/client/client.go | 18 ++++ sdk/dunion-go-sdk/model/order.go | 2 +- util/xerror/error.go | 1 + 8 files changed, 241 insertions(+), 28 deletions(-) create mode 100644 util/xerror/error.go diff --git a/go.mod b/go.mod index 21c2864..b493a18 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module gitee.com/chengdu-lenntc/third-platform-sdk go 1.19 require ( + github.com/BurntSushi/toml v1.0.0 // indirect + github.com/google/uuid v1.3.0 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 + go.uber.org/zap v1.24.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) @@ -22,7 +22,9 @@ require ( github.com/spaolacci/murmur3 v1.1.0 // indirect go.opentelemetry.io/otel v1.14.0 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect + go.uber.org/atomic v1.10.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/sys v0.11.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/platform/didi-union/api.go b/platform/didi-union/api.go index fc6ee53..1837e83 100644 --- a/platform/didi-union/api.go +++ b/platform/didi-union/api.go @@ -2,34 +2,40 @@ package didi_union import ( "context" + "errors" "fmt" "time" "github.com/zeromicro/go-zero/core/logx" + "github.com/zywaited/xcopy" "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" ) +// todo:: 定义统一的返回错误结构 + // 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) + GenerateH5Link(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*GenerateLinkResponse, error) // GenerateMiniLink 生成小程序页面推广路径 - GenerateMiniLink(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*model.LinkResponse, error) + GenerateMiniLink(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*GenerateLinkResponse, error) + // GenerateAlipayMiniLink 生成支付宝小程序页面推广路径 + GenerateAlipayMiniLink(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*GenerateLinkResponse, error) // GenerateH5Code 生成h5二维码,需先取链得到dsi - GenerateH5Code(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*model.QrcodeResponse, error) + GenerateH5Code(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*GenerateCodeResponse, error) // GenerateMiniCode 生成小程序太阳码,需先取链得到dsi - GenerateMiniCode(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*model.QrcodeResponse, error) + GenerateMiniCode(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*GenerateCodeResponse, error) // GeneratePoster 生成推广海报,需先取链得到dsi - GeneratePoster(ctx context.Context, req GeneratePosterRequest, opt model.Option) (*model.PosterResponse, error) + GeneratePoster(ctx context.Context, req GeneratePosterRequest, opt model.Option) (*GeneratePosterResponse, error) // QueryOrderList 查询订单列表 - QueryOrderList(ctx context.Context, req QueryOrderListRequest, opt model.Option) (*model.OrderResponse, error) + QueryOrderList(ctx context.Context, req QueryOrderListRequest, opt model.Option) (*QueryOrderListResponse, error) // SelfQueryOrder 订单归因问题自查询 - SelfQueryOrder(ctx context.Context, req SelfQueryOrderRequest, opt model.Option) (*model.SelfQueryResponse, error) + SelfQueryOrder(ctx context.Context, req SelfQueryOrderRequest, opt model.Option) (*SelfQueryOrderResponse, error) } type didiUnionApiImpl struct { @@ -53,36 +59,153 @@ func (d *didiUnionApiImpl) Sign(params map[string]any) string { } // 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) +func (d *didiUnionApiImpl) GenerateH5Link(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*GenerateLinkResponse, error) { + resp, err := d.client.clt.GenerateH5Link(ctx, req.ActivityID, req.PromotionID, req.SourceID, opt) + if err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateH5Link] get link http request error, error: %v", err) + return nil, err + } + if resp.Errno > 0 { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateH5Link] get link error, error: %v", resp.ErrorMsg()) + return nil, errors.New(resp.ErrMsg) + } + var result *GenerateLinkResponse + if err := xcopy.Copy(&result, resp.Data); err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateH5Link] copy resp to result error, error: %v", err) + return nil, errors.New(resp.ErrMsg) + } + return result, nil } // 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) +func (d *didiUnionApiImpl) GenerateMiniLink(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*GenerateLinkResponse, error) { + resp, err := d.client.clt.GenerateMiniLink(ctx, req.ActivityID, req.PromotionID, req.SourceID, opt) + if err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateMiniLink] http request error, error: %v", err) + return nil, err + } + if resp.Errno > 0 { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateMiniLink] get link error, error: %v", resp.ErrorMsg()) + return nil, errors.New(resp.ErrMsg) + } + var result *GenerateLinkResponse + if err := xcopy.Copy(&result, resp.Data); err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateMiniLink] copy resp to result error, error: %v", err) + return nil, errors.New(resp.ErrMsg) + } + return result, nil +} + +// GenerateAlipayMiniLink 生成小程序页面推广路径 +func (d *didiUnionApiImpl) GenerateAlipayMiniLink(ctx context.Context, req GenerateLinkRequest, opt model.Option) (*GenerateLinkResponse, error) { + resp, err := d.client.clt.GenerateAlipayMiniLink(ctx, req.ActivityID, req.PromotionID, req.SourceID, opt) + if err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateAlipayMiniLink] http request error, error: %v", err) + return nil, err + } + if resp.Errno > 0 { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateAlipayMiniLink] get link error, error: %v", resp.ErrorMsg()) + return nil, errors.New(resp.ErrMsg) + } + var result *GenerateLinkResponse + if err := xcopy.Copy(&result, resp.Data); err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateAlipayMiniLink] copy resp to result error, error: %v", err) + return nil, errors.New(resp.ErrMsg) + } + return result, nil } // 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) +func (d *didiUnionApiImpl) GenerateH5Code(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*GenerateCodeResponse, error) { + resp, err := d.client.clt.GenerateH5Code(ctx, req.Dsi, req.SourceID, opt) + if err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateH5Code] http request error, error: %v", err) + return nil, err + } + if resp.Errno > 0 { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateH5Code] get h5 code error, error: %v", resp.ErrorMsg()) + return nil, errors.New(resp.ErrMsg) + } + var result *GenerateCodeResponse + if err := xcopy.Copy(&result, resp.Data); err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateH5Code] copy resp to result error, error: %v", err) + return nil, errors.New(resp.ErrMsg) + } + return result, nil } // 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) +func (d *didiUnionApiImpl) GenerateMiniCode(ctx context.Context, req GenerateCodeRequest, opt model.Option) (*GenerateCodeResponse, error) { + resp, err := d.client.clt.GenerateMiniCode(ctx, req.Dsi, req.SourceID, opt) + if err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateMiniCode] http request error, error: %v", err) + return nil, err + } + if resp.Errno > 0 { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateMiniCode] get mini code error, error: %v", resp.ErrorMsg()) + return nil, errors.New(resp.ErrMsg) + } + var result *GenerateCodeResponse + if err := xcopy.Copy(&result, resp.Data); err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GenerateMiniCode] copy resp to result error, error: %v", err) + return nil, errors.New(resp.ErrMsg) + } + return result, nil } // 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) +func (d *didiUnionApiImpl) GeneratePoster(ctx context.Context, req GeneratePosterRequest, opt model.Option) (*GeneratePosterResponse, error) { + resp, err := d.client.clt.GeneratePoster(ctx, req.Dsi, req.SourceID, opt) + if err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GeneratePoster] http request error, error: %v", err) + return nil, err + } + if resp.Errno > 0 { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GeneratePoster] generate poster error, error: %v", resp.ErrorMsg()) + return nil, errors.New(resp.ErrMsg) + } + var result *GeneratePosterResponse + if err := xcopy.Copy(&result, resp.Data); err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][GeneratePoster] copy resp to result error, error: %v", err) + return nil, errors.New(resp.ErrMsg) + } + return result, nil } // 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) +func (d *didiUnionApiImpl) QueryOrderList(ctx context.Context, req QueryOrderListRequest, opt model.Option) (*QueryOrderListResponse, error) { + resp, err := d.client.clt.QueryOrderList(ctx, req.StartTime, req.EndTime, string(req.Typ), req.Page, req.Size, opt) + if err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][QueryOrderList] http request error, error: %v", err) + return nil, err + } + if resp.Errno > 0 { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][QueryOrderList] get order list error, error: %v", resp.ErrorMsg()) + return nil, errors.New(resp.ErrMsg) + } + var result *QueryOrderListResponse + if err := xcopy.Copy(&result, resp.Data); err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][QueryOrderList] copy resp to result error, error: %v", err) + return nil, errors.New(resp.ErrMsg) + } + return result, nil } // 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) +func (d *didiUnionApiImpl) SelfQueryOrder(ctx context.Context, req SelfQueryOrderRequest, opt model.Option) (*SelfQueryOrderResponse, error) { + resp, err := d.client.clt.SelfQueryOrder(ctx, req.OrderID, opt) + if err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][SelfQueryOrder] http request error, error: %v", err) + return nil, err + } + if resp.Errno > 0 { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][SelfQueryOrder] query self order error, error: %v", resp.ErrorMsg()) + return nil, errors.New(resp.ErrMsg) + } + var result *SelfQueryOrderResponse + if err := xcopy.Copy(&result, resp.Data); err != nil { + d.log.WithFields(logx.LogField{Key: "req", Value: req}).Errorf("[didiUnionApiImpl][SelfQueryOrder] copy resp to result error, error: %v", err) + return nil, errors.New(resp.ErrMsg) + } + return result, nil } diff --git a/platform/didi-union/api_test.go b/platform/didi-union/api_test.go index b8efa82..57746f4 100644 --- a/platform/didi-union/api_test.go +++ b/platform/didi-union/api_test.go @@ -3,9 +3,12 @@ package didi_union import ( "context" "testing" + "time" "github.com/stretchr/testify/suite" "github.com/zeromicro/go-zero/core/logx" + + "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/model" ) // api-单元测试 @@ -35,3 +38,20 @@ func (a *apiClientSuite) Test_Sign() { sign := a.api.Sign(data) a.T().Logf("=====[TestSign] sign: %s", sign) } + +func (a *apiClientSuite) Test_GenerateH5Link() { + ctx := context.Background() + req := GenerateLinkRequest{ + ActivityID: 207811824611, + PromotionID: 7193964476899539205, + SourceID: "test", + } + opt := model.Option{ + Timeout: time.Second * 10, + } + resp, err := a.api.GenerateH5Link(ctx, req, opt) + if !a.NoError(err) { + a.T().Errorf("=====[TestGenerateH5Link] err: %v", err) + } + a.T().Logf("=====[TestGenerateH5Link] resp: %+v, err: %v", resp, err) +} diff --git a/platform/didi-union/types.go b/platform/didi-union/types.go index b9d231d..5c6317a 100644 --- a/platform/didi-union/types.go +++ b/platform/didi-union/types.go @@ -1,6 +1,10 @@ package didi_union -import "time" +import ( + "time" + + "gitee.com/chengdu-lenntc/third-platform-sdk/sdk/dunion-go-sdk/model" +) // GenerateLinkRequest 生成推广链接的请求 type GenerateLinkRequest struct { @@ -9,8 +13,17 @@ type GenerateLinkRequest struct { SourceID string // 来源id (用于标明订单来源) } -// 生成推广链接的响应 +// GenerateLinkResponse 生成推广链接的响应 type GenerateLinkResponse 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"` } // GenerateCodeRequest 生成二维码的请求 @@ -19,12 +32,26 @@ type GenerateCodeRequest struct { SourceID string // 来源id (用于标明订单来源) } +// GenerateCodeResponse 生成二维码的响应 +type GenerateCodeResponse struct { + //生成的二维码链接 + //example: https://example.com/img.jpg + CodeLink string `json:"code_link"` +} + // GeneratePosterRequest 生成海报的请求 type GeneratePosterRequest struct { Dsi string // 活动ID+推广位ID决定一个DSI (需先取链得到dsi) SourceID string // 来源id (用于标明订单来源) } +// GeneratePosterResponse 生成海报的响应 +type GeneratePosterResponse struct { + //生成的海报链接 + //example: https://example.com/img.jpg + PosterLink string `json:"poster_link"` +} + // QueryOrderListRequest 订单列表请求 type QueryOrderListRequest struct { StartTime time.Time // 开始时间 @@ -34,7 +61,23 @@ type QueryOrderListRequest struct { Size int // 每页数量(最大值为100) } +// QueryOrderListResponse 订单列表响应 +type QueryOrderListResponse struct { + // 总数量 + // example: 1 + Total int `json:"total"` + OrderList []*model.ResponseOrderItem `json:"order_list"` +} + // SelfQueryOrderRequest 自查询订单请求 type SelfQueryOrderRequest struct { OrderID string // 订单ID } + +// SelfQueryOrderResponse 订单归因问题自查询响应 +type SelfQueryOrderResponse struct { + // 推广成功列表 + EstimateSuccessList []*model.EstimateSuccessData `json:"estimate_success_list,omitempty" structs:"estimate_success_list"` + // 推广失败列表 + EstimateFailList []*model.EstimateFailData `json:"estimate_fail_list,omitempty" structs:"estimate_fail_list"` +} diff --git a/platform/eleme-union/api_test.go b/platform/eleme-union/api_test.go index 4c7182d..690002e 100644 --- a/platform/eleme-union/api_test.go +++ b/platform/eleme-union/api_test.go @@ -2,6 +2,7 @@ package eleme_union import ( "context" + "encoding/json" "testing" "time" @@ -80,5 +81,10 @@ func (a *apiClientSuite) Test_KbcpxPositiveOrderGet() { a.T().Errorf("=====[Test_GetOrders] err: %v", err) return } - a.T().Logf("=====[Test_GetOrders] resp: %+v", resp) + respStr, err := json.Marshal(resp) + if err != nil { + a.T().Errorf("=====[Test_GetOrders] err: %v", err) + return + } + a.T().Logf("=====[Test_GetOrders] resp: %+v", string(respStr)) } diff --git a/sdk/dunion-go-sdk/client/client.go b/sdk/dunion-go-sdk/client/client.go index c897316..c0726a3 100644 --- a/sdk/dunion-go-sdk/client/client.go +++ b/sdk/dunion-go-sdk/client/client.go @@ -19,6 +19,7 @@ type client struct { 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) + GenerateAlipayMiniLink(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) @@ -71,6 +72,23 @@ func (s client) GenerateMiniLink(ctx context.Context, activityID, promotionID in return result, err } +// GenerateAlipayMiniLink 生成支付宝小程序页面推广路径 +func (s client) GenerateAlipayMiniLink(ctx context.Context, activityID, promotionID int64, sourceID string, opt ...model.Option) (*model.LinkResponse, error) { + body := map[string]interface{}{ + "activity_id": activityID, + "link_type": "alipay_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{}{ diff --git a/sdk/dunion-go-sdk/model/order.go b/sdk/dunion-go-sdk/model/order.go index 14e77fa..61dbb38 100644 --- a/sdk/dunion-go-sdk/model/order.go +++ b/sdk/dunion-go-sdk/model/order.go @@ -25,7 +25,7 @@ type ResponseOrderItem struct { CpsProfit int64 `json:"cps_profit"` // CPA返佣金额,单位:分 CpaProfit int64 `json:"cpa_profit"` - // CPA类型 + // CPA类型:cpa_normal-普通CPA(新用户奖励),order_cnt_eq-冲单奖 CpaType string `json:"cpa_type"` // 推送状态: 1.已预估归因 2.预估订单已推送 3.预估订单推送失败 4.结算已提交 5.结算提交中 6.结算取消 7.结算成功 8.结算失败 Status int `json:"status"` diff --git a/util/xerror/error.go b/util/xerror/error.go new file mode 100644 index 0000000..b26b559 --- /dev/null +++ b/util/xerror/error.go @@ -0,0 +1 @@ +package xerror