From c9c91d9146da4e4b386ceab45934d8372898c7c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=AD=90=E9=93=AD?= Date: Thu, 14 Nov 2024 18:33:56 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=B4=E8=BF=9E=E5=A4=A9=E4=B8=8B=EF=BC=8Csd?= =?UTF-8?q?k=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dctw/v1/api/card/card.go | 62 ++++++++++ dctw/v1/api/card/card_test.go | 89 ++++++++++++++ dctw/v1/api/card/code.go | 38 ++++++ dctw/v1/api/card/status.go | 43 +++++++ dctw/v1/api/card/trans.go | 99 +++++++++++++++ dctw/v1/api/direct/code.go | 38 ++++++ dctw/v1/api/direct/direct.go | 61 ++++++++++ dctw/v1/api/direct/direct_test.go | 97 +++++++++++++++ dctw/v1/api/direct/status.go | 43 +++++++ dctw/v1/api/direct/trans.go | 99 +++++++++++++++ dctw/v1/api/direct/vo.go | 28 +++++ dctw/v1/api/service.go | 7 ++ dctw/v1/core/core.go | 196 ++++++++++++++++++++++++++++++ dctw/v1/core/core_test.go | 28 +++++ utils/aes.go | 32 +++++ utils/utils.go | 28 +++++ utils/utils_test.go | 13 +- 17 files changed, 1000 insertions(+), 1 deletion(-) create mode 100644 dctw/v1/api/card/card.go create mode 100644 dctw/v1/api/card/card_test.go create mode 100644 dctw/v1/api/card/code.go create mode 100644 dctw/v1/api/card/status.go create mode 100644 dctw/v1/api/card/trans.go create mode 100644 dctw/v1/api/direct/code.go create mode 100644 dctw/v1/api/direct/direct.go create mode 100644 dctw/v1/api/direct/direct_test.go create mode 100644 dctw/v1/api/direct/status.go create mode 100644 dctw/v1/api/direct/trans.go create mode 100644 dctw/v1/api/direct/vo.go create mode 100644 dctw/v1/api/service.go create mode 100644 dctw/v1/core/core.go create mode 100644 dctw/v1/core/core_test.go create mode 100644 utils/aes.go diff --git a/dctw/v1/api/card/card.go b/dctw/v1/api/card/card.go new file mode 100644 index 0000000..80eece0 --- /dev/null +++ b/dctw/v1/api/card/card.go @@ -0,0 +1,62 @@ +package card + +import ( + "context" + "encoding/json" + "fmt" + "gitea.cdlsxd.cn/sdk/plugin/dctw/v1/api" + "gitea.cdlsxd.cn/sdk/plugin/dctw/v1/core" + "io/ioutil" + "net/http" +) + +type Card api.Service + +const ( + orderPath = "/internal/v1/card/order" + queryPath = "/internal/v1/card/query" +) + +func (c *Card) Order(ctx context.Context, request *Order) (*OrderResp, error) { + result, err := c.Request(ctx, request, orderPath) + if err != nil { + return nil, err + } + fmt.Println("result:", string(result)) + var response *OrderResp + if err = json.Unmarshal(result, &response); err != nil { + return nil, err + } + return response, nil +} + +func (c *Card) Query(ctx context.Context, request *Query) (*QueryResp, error) { + result, err := c.Request(ctx, request, queryPath) + if err != nil { + return nil, err + } + fmt.Println("result:", string(result)) + var response *QueryResp + if err = json.Unmarshal(result, &response); err != nil { + return nil, err + } + return response, nil +} + +func (c *Card) Notify(_ context.Context, httpRequest *http.Request) (*Notify, error) { + body, err := ioutil.ReadAll(httpRequest.Body) + if err != nil { + return nil, err + } + + if err = c.VerifyDctW(httpRequest.Header.Get(core.SignKeyName), body); err != nil { + return nil, err + } + + var response *Notify + if err = json.Unmarshal(body, &response); err != nil { + return nil, err + } + + return response, err +} diff --git a/dctw/v1/api/card/card_test.go b/dctw/v1/api/card/card_test.go new file mode 100644 index 0000000..aabe51f --- /dev/null +++ b/dctw/v1/api/card/card_test.go @@ -0,0 +1,89 @@ +package card + +import ( + "bytes" + "context" + "gitea.cdlsxd.cn/sdk/plugin/dctw/v1/core" + "io/ioutil" + "net/http" + "testing" +) + +func TestCardOrder(t *testing.T) { + server, err := core.NewDctWServer(&core.DctWConfig{ + AppId: "1", + AppKey: "1e2bf7a04b8b1e6be5dc78d04e8639c9", + BaseUri: "http://test.openapi.1688sup.cn", + }) + if err != nil { + t.Fatal(err) + } + a := &Card{DctWServer: server} + req := &Order{ + Number: 1, + MerchantId: "101", + OutTradeNo: "test_1_zltx_4", + ProductId: "106", + Mobile: "18666666666", + NotifyUrl: "http://test.openapi.1688sup.cn", + Version: "1.0", + } + resp, err := a.Order(context.Background(), req) + if err != nil { + t.Error(err) + return + } + t.Log(resp) +} + +func TestCard_Query(t *testing.T) { + server, err := core.NewDctWServer(&core.DctWConfig{ + AppId: "1", + AppKey: "1e2bf7a04b8b1e6be5dc78d04e8639c9", + BaseUri: "http://test.openapi.1688sup.cn", + }) + if err != nil { + t.Fatal(err) + } + a := &Card{DctWServer: server} + req := &Query{ + MerchantId: "101", + OutTradeNo: "test_1_zltx_4", + Version: "1.0", + } + resp, err := a.Query(context.Background(), req) + if err != nil { + t.Error(err) + return + } + t.Log(resp) +} + +func TestCard_Notify(t *testing.T) { + server, err := core.NewDctWServer(&core.DctWConfig{ + AppId: "1", + AppKey: "1e2bf7a04b8b1e6be5dc78d04e8639c9", + BaseUri: "http://test.openapi.1688sup.cn", + }) + if err != nil { + t.Fatal(err) + } + a := &Card{DctWServer: server} + + body := []byte(`{"merchantId":23329,"outTradeNo":"202409111714224026320002","rechargeAccount":"18512869479","sign":"474ACB521DEE99551153B6CE108FD06D","status":"01"}`) + + headers := make(http.Header) + headers.Set(core.SignKeyName, "MD5 appid=101,sign=292e21f3683219369cf68dede0d45730") + headers.Set("Content-Type", core.ApplicationJSON) + + var httpRequest = &http.Request{ + Header: http.Header{}, + Body: ioutil.NopCloser(bytes.NewBuffer(body)), + } + resp, err := a.Notify(context.Background(), httpRequest) + if err != nil { + t.Error(err) + return + } + t.Log(resp) +} diff --git a/dctw/v1/api/card/code.go b/dctw/v1/api/card/code.go new file mode 100644 index 0000000..317f82b --- /dev/null +++ b/dctw/v1/api/card/code.go @@ -0,0 +1,38 @@ +package card + +type Code string + +const CodeSuccess Code = "0000" +const RechargeCodeSuccess Code = "2000" + +var ResMessageMap = map[Code]string{ + "1000": "Ip limit(ip未绑定或绑定失败)", + "1001": "Missing parameters(参数异常)", + "1002": "Invalid merchant(无效商户信息)", + "1003": "Invalid signature(签名校验失败)", + "1004": "Request expiration(请求时间过期)", + "1005": "Order repeat(订单重复)", + "1006": "Invalid item(商品未开通)", + "1007": "Item price invalid(商品价格无效)", + "1008": "Insufficient Balance(余额不足)", + "1009": "Interface adjustment(商品映射无效)", + "1010": "Interface price adjustment(映射价格无效)", + "1011": "Account format matching(充值账号格式不匹配)", + "1012": "no order(无订单信息)", + "1999": "unknown error(异常错误,建议人工处理或查询订单状态)", +} + +func (c Code) IsRechargeSuccess() bool { + return c == RechargeCodeSuccess +} + +func (c Code) GetMessage() string { + if message, ok := ResMessageMap[c]; ok { + return message + } + return "" +} + +func (c Code) IsSuccess() bool { + return c == CodeSuccess +} diff --git a/dctw/v1/api/card/status.go b/dctw/v1/api/card/status.go new file mode 100644 index 0000000..a1deb2c --- /dev/null +++ b/dctw/v1/api/card/status.go @@ -0,0 +1,43 @@ +package card + +import ( + "gitea.cdlsxd.cn/sdk/plugin/proto" +) + +type OrderStatus string + +const ( + OrderSuccess OrderStatus = "01" // 充值成功 + OrderPending OrderStatus = "02" // 充值处理中 + OrderFail OrderStatus = "03" // 充值失败 + OrderAbnormal OrderStatus = "04" // 充值异常,处理中 +) + +var orderStatusMap = map[OrderStatus]proto.Status{ + OrderSuccess: proto.Status_SUCCESS, + OrderPending: proto.Status_ING, + OrderFail: proto.Status_FAIL, +} + +func (o OrderStatus) IsSuccess() bool { + return o == OrderSuccess +} + +func (o OrderStatus) IsPending() bool { + return o == OrderPending +} + +func (o OrderStatus) IsFail() bool { + return o == OrderFail +} + +func (o OrderStatus) IsAbnormal() bool { + return o == OrderAbnormal +} + +func (o OrderStatus) GetOrderStatus() proto.Status { + if resultStatus, ok := orderStatusMap[o]; ok { + return resultStatus + } + return proto.Status_ING +} diff --git a/dctw/v1/api/card/trans.go b/dctw/v1/api/card/trans.go new file mode 100644 index 0000000..5a1743a --- /dev/null +++ b/dctw/v1/api/card/trans.go @@ -0,0 +1,99 @@ +package card + +import ( + "encoding/json" + "fmt" + "gitea.cdlsxd.cn/sdk/plugin/alipay/vo" + "github.com/go-playground/validator/v10" +) + +type Order struct { + Number uint32 `validate:"required" json:"number"` + MerchantId string `validate:"required" json:"merchantId"` + OutTradeNo string `validate:"required" json:"outTradeNo"` + ProductId string `validate:"required" json:"productId"` + Mobile string `json:"mobile"` + NotifyUrl string `validate:"required" json:"notifyUrl"` + Version string `validate:"required" json:"version"` +} + +type OrderResp struct { + Code vo.Code `json:"code"` + Message string `json:"message"` +} + +type Query struct { + MerchantId string `validate:"required" json:"merchantId"` + OutTradeNo string `validate:"required" json:"outTradeNo"` + Version string `validate:"required" json:"version"` +} + +type QueryResp struct { + Code vo.Code `json:"code"` + Status OrderStatus `json:"status"` + Message string `json:"message"` + OutTradeNo string `json:"outTradeNo"` + CardCode string `json:"cardCode"` +} + +type Notify struct { + MerchantId int `json:"merchantId"` + OutTradeNo string `json:"outTradeNo"` + RechargeAccount string `json:"rechargeAccount"` + Status OrderStatus `json:"status"` + CardCode string `json:"cardCode"` +} + +func (o *Order) Json() ([]byte, error) { + b, err := json.Marshal(o) + if err != nil { + return nil, err + } + return b, nil +} + +func (o *Order) Validate() error { + err := validator.New().Struct(o) + if err != nil { + for _, err = range err.(validator.ValidationErrors) { + return fmt.Errorf("参数有误:" + err.Error()) + } + } + return nil +} + +func (o *Query) Json() ([]byte, error) { + b, err := json.Marshal(o) + if err != nil { + return nil, err + } + return b, nil +} + +func (o *Query) Validate() error { + err := validator.New().Struct(o) + if err != nil { + for _, err = range err.(validator.ValidationErrors) { + return fmt.Errorf("参数有误:" + err.Error()) + } + } + return nil +} + +func (o *Notify) Json() ([]byte, error) { + b, err := json.Marshal(o) + if err != nil { + return nil, err + } + return b, nil +} + +func (o *Notify) Validate() error { + err := validator.New().Struct(o) + if err != nil { + for _, err = range err.(validator.ValidationErrors) { + return fmt.Errorf("参数有误:" + err.Error()) + } + } + return nil +} diff --git a/dctw/v1/api/direct/code.go b/dctw/v1/api/direct/code.go new file mode 100644 index 0000000..c6e4cd8 --- /dev/null +++ b/dctw/v1/api/direct/code.go @@ -0,0 +1,38 @@ +package direct + +import "encoding/json" + +type Code json.Number + +const ( + CodeSuccess Code = "2000" +) + +var ResMessageMap = map[Code]string{ + CodeSuccess: "成功", + "1000": "Ip limit(ip未绑定或绑定失败)", + "1001": "Missing parameters(参数异常)", + "1002": "Invalid merchant(无效商户信息)", + "1003": "Invalid signature(签名校验失败)", + "1004": "Request expiration(请求时间过期)", + "1005": "Order repeat(订单重复)", + "1006": "Invalid item(商品未开通)", + "1007": "Item price invalid(商品价格无效)", + "1008": "Insufficient Balance(余额不足)", + "1009": "Interface adjustment(商品映射无效)", + "1010": "Interface price adjustment(映射价格无效)", + "1011": "Account format matching(充值账号格式不匹配)", + "1012": "no order(无订单信息)", + "1999": "unknown error(异常错误,建议人工处理或查询订单状态)", +} + +func (c Code) IsSuccess() bool { + return c == CodeSuccess +} + +func (c Code) GetMessage() string { + if message, ok := ResMessageMap[c]; ok { + return message + } + return "未知错误,请联系平台" +} diff --git a/dctw/v1/api/direct/direct.go b/dctw/v1/api/direct/direct.go new file mode 100644 index 0000000..493faec --- /dev/null +++ b/dctw/v1/api/direct/direct.go @@ -0,0 +1,61 @@ +package direct + +import ( + "context" + "encoding/json" + "fmt" + "gitea.cdlsxd.cn/sdk/plugin/dctw/v1/api" + "gitea.cdlsxd.cn/sdk/plugin/dctw/v1/core" + "io/ioutil" + "net/http" +) + +type Direct api.Service + +const ( + orderPath = "/internal/v1/recharge/create" + queryPath = "/internal/v1/recharge/query" +) + +func (c *Direct) Order(ctx context.Context, request *Order) (*OrderResp, error) { + result, err := c.Request(ctx, request, orderPath) + if err != nil { + return nil, err + } + fmt.Printf("result: %s\n", string(result)) + var response *OrderResp + if err = json.Unmarshal(result, &response); err != nil { + return nil, err + } + return response, nil +} + +func (c *Direct) Query(ctx context.Context, request *Query) (*QueryResp, error) { + result, err := c.Request(ctx, request, queryPath) + if err != nil { + return nil, err + } + var response *QueryResp + if err = json.Unmarshal(result, &response); err != nil { + return nil, err + } + return response, nil +} + +func (c *Direct) Notify(_ context.Context, httpRequest *http.Request) (*Notify, error) { + body, err := ioutil.ReadAll(httpRequest.Body) + if err != nil { + return nil, err + } + + if err = c.VerifyDctW(httpRequest.Header.Get(core.SignKeyName), body); err != nil { + return nil, err + } + + var response *Notify + if err = json.Unmarshal(body, &response); err != nil { + return nil, err + } + + return response, err +} diff --git a/dctw/v1/api/direct/direct_test.go b/dctw/v1/api/direct/direct_test.go new file mode 100644 index 0000000..22f7194 --- /dev/null +++ b/dctw/v1/api/direct/direct_test.go @@ -0,0 +1,97 @@ +package direct + +import ( + "bytes" + "context" + "gitea.cdlsxd.cn/sdk/plugin/dctw/v1/core" + "github.com/stretchr/testify/assert" + "io/ioutil" + "net/http" + "testing" +) + +func TestDirect_Order(t *testing.T) { + server, err := core.NewDctWServer(&core.DctWConfig{ + AppId: "1", + AppKey: "1e2bf7a04b8b1e6be5dc78d04e8639c9", + BaseUri: "http://test.openapi.1688sup.cn", + }) + if err != nil { + t.Fatal(err) + } + a := &Direct{DctWServer: server} + req := &Order{ + Number: 1, + MerchantId: "23329", + OutTradeNo: "test_1_zltx_6", + ProductId: "106", + AccountType: 1, + RechargeAccount: "18666666666", + NotifyUrl: "http://test.openapi.1688sup.cn", + Version: "1.0", + } + resp, err := a.Order(context.Background(), req) + if err != nil { + t.Error(err) + return + } + if !resp.Code.IsSuccess() { + t.Error(resp.Message) + return + } + t.Log(resp) +} + +func TestDirect_Query(t *testing.T) { + server, err := core.NewDctWServer(&core.DctWConfig{ + AppId: "1", + AppKey: "1e2bf7a04b8b1e6be5dc78d04e8639c9", + BaseUri: "http://test.openapi.1688sup.cn", + }) + if err != nil { + t.Fatal(err) + } + a := &Direct{DctWServer: server} + req := &Query{ + MerchantId: "106", + OutTradeNo: "test_1_zltx_4", + Version: "1.0", + } + resp, err := a.Query(context.Background(), req) + if err != nil { + t.Error(err) + return + } + t.Log(resp) + assert.Equal(t, resp.Status.IsSuccess(), true, "充值是否成功") +} + +func TestDirect_Notify(t *testing.T) { + server, err := core.NewDctWServer(&core.DctWConfig{ + AppId: "1", + AppKey: "1e2bf7a04b8b1e6be5dc78d04e8639c9", + BaseUri: "http://test.openapi.1688sup.cn", + }) + if err != nil { + t.Fatal(err) + } + a := &Direct{DctWServer: server} + + body := []byte(`{"merchantId":23329,"outTradeNo":"202409111714224026320002","rechargeAccount":"18512869479","sign":"474ACB521DEE99551153B6CE108FD06D","status":"01"}`) + + headers := make(http.Header) + headers.Set(core.SignKeyName, "MD5 appid=101,sign=292e21f3683219369cf68dede0d45730") + headers.Set("Content-Type", core.ApplicationJSON) + + var httpRequest = &http.Request{ + Header: http.Header{}, + Body: ioutil.NopCloser(bytes.NewBuffer(body)), + } + resp, err := a.Notify(context.Background(), httpRequest) + if err != nil { + t.Error(err) + return + } + t.Log(resp) + assert.Equal(t, resp.Status.IsSuccess(), true, "充值是否成功") +} diff --git a/dctw/v1/api/direct/status.go b/dctw/v1/api/direct/status.go new file mode 100644 index 0000000..9a1e0ce --- /dev/null +++ b/dctw/v1/api/direct/status.go @@ -0,0 +1,43 @@ +package direct + +import ( + "gitea.cdlsxd.cn/sdk/plugin/proto" +) + +type OrderStatus string + +const ( + OrderSuccess OrderStatus = "01" // 充值成功 + OrderPending OrderStatus = "02" // 充值处理中 + OrderFail OrderStatus = "03" // 充值失败 + OrderAbnormal OrderStatus = "04" // 充值异常,处理中 +) + +var orderStatusMap = map[OrderStatus]proto.Status{ + OrderSuccess: proto.Status_SUCCESS, + OrderPending: proto.Status_ING, + OrderFail: proto.Status_FAIL, +} + +func (o OrderStatus) IsSuccess() bool { + return o == OrderSuccess +} + +func (o OrderStatus) IsPending() bool { + return o == OrderPending +} + +func (o OrderStatus) IsFail() bool { + return o == OrderFail +} + +func (o OrderStatus) IsAbnormal() bool { + return o == OrderAbnormal +} + +func (o OrderStatus) GetOrderStatus() proto.Status { + if resultStatus, ok := orderStatusMap[o]; ok { + return resultStatus + } + return proto.Status_ING +} diff --git a/dctw/v1/api/direct/trans.go b/dctw/v1/api/direct/trans.go new file mode 100644 index 0000000..43b7cd4 --- /dev/null +++ b/dctw/v1/api/direct/trans.go @@ -0,0 +1,99 @@ +package direct + +import ( + "encoding/json" + "fmt" + "github.com/go-playground/validator/v10" +) + +type Order struct { + Number uint32 `validate:"required" json:"number"` + MerchantId string `validate:"required" json:"merchantId"` + OutTradeNo string `validate:"required" json:"outTradeNo"` + ProductId string `validate:"required" json:"productId"` + AccountType AccountType `validate:"required" json:"accountType"` // 账号类型(1:手机号 2:QQ号 其他:0) + RechargeAccount string `validate:"required" json:"rechargeAccount"` + NotifyUrl string `validate:"required" json:"notifyUrl"` + Version string `validate:"required" json:"version"` +} + +type OrderResp struct { + Code Code `json:"code"` + Message string `json:"message"` + TradeNo string `json:"tradeNo"` +} + +type Query struct { + MerchantId string `validate:"required" json:"merchantId"` + OutTradeNo string `validate:"required" json:"outTradeNo"` + Version string `validate:"required" json:"version"` +} + +type QueryResp struct { + Code Code `json:"code"` + Status OrderStatus `json:"status"` + Message string `json:"message"` + OutTradeNo string `json:"outTradeNo"` + CardCode string `json:"cardCode"` +} + +type Notify struct { + MerchantId int `json:"merchantId"` + OutTradeNo string `json:"outTradeNo"` + RechargeAccount string `json:"rechargeAccount"` + Status OrderStatus `json:"status"` +} + +func (o *Order) Json() ([]byte, error) { + b, err := json.Marshal(o) + if err != nil { + return nil, err + } + return b, nil +} + +func (o *Order) Validate() error { + err := validator.New().Struct(o) + if err != nil { + for _, err = range err.(validator.ValidationErrors) { + return fmt.Errorf("参数有误:" + err.Error()) + } + } + return nil +} + +func (o *Query) Json() ([]byte, error) { + b, err := json.Marshal(o) + if err != nil { + return nil, err + } + return b, nil +} + +func (o *Query) Validate() error { + err := validator.New().Struct(o) + if err != nil { + for _, err = range err.(validator.ValidationErrors) { + return fmt.Errorf("参数有误:" + err.Error()) + } + } + return nil +} + +func (o *Notify) Json() ([]byte, error) { + b, err := json.Marshal(o) + if err != nil { + return nil, err + } + return b, nil +} + +func (o *Notify) Validate() error { + err := validator.New().Struct(o) + if err != nil { + for _, err = range err.(validator.ValidationErrors) { + return fmt.Errorf("参数有误:" + err.Error()) + } + } + return nil +} diff --git a/dctw/v1/api/direct/vo.go b/dctw/v1/api/direct/vo.go new file mode 100644 index 0000000..cf552cd --- /dev/null +++ b/dctw/v1/api/direct/vo.go @@ -0,0 +1,28 @@ +package direct + +import ( + "gitea.cdlsxd.cn/sdk/plugin/utils" +) + +type AccountType int + +const ( + AccountTypeDefault AccountType = iota + AccountTypePhone + AccountTypeQQ +) + +func (o AccountType) AccountType(account string) AccountType { + if account == "" { + return AccountTypeDefault + } else if utils.IsPhoneNumber(account) { + return AccountTypePhone + } else if utils.IsValidQQ(account) { + return AccountTypeQQ + } + return AccountTypeDefault +} + +func (o AccountType) Value() int { + return int(o) +} diff --git a/dctw/v1/api/service.go b/dctw/v1/api/service.go new file mode 100644 index 0000000..11a3d6a --- /dev/null +++ b/dctw/v1/api/service.go @@ -0,0 +1,7 @@ +package api + +import "gitea.cdlsxd.cn/sdk/plugin/dctw/v1/core" + +type Service struct { + *core.DctWServer +} diff --git a/dctw/v1/core/core.go b/dctw/v1/core/core.go new file mode 100644 index 0000000..b3ddceb --- /dev/null +++ b/dctw/v1/core/core.go @@ -0,0 +1,196 @@ +package core + +import ( + "bytes" + "context" + "fmt" + "gitea.cdlsxd.cn/sdk/plugin/utils" + "github.com/go-playground/validator/v10" + "io/ioutil" + "net/http" + "strings" + "time" +) + +const ( + DeBug = true + SignType = "MD5" + SignKeyName = "Authorization" + ApplicationJSON = "application/json" +) + +// DctWRequest 直连天下 Direct connection The world +type DctWRequest interface { + Json() ([]byte, error) + Validate() error +} + +type DctWConfig struct { + AppId string `json:"app_id" validate:"required"` + AppKey string `json:"app_key" validate:"required"` + BaseUri string `json:"base_uri" validate:"required"` +} + +func (c *DctWConfig) Validate() error { + if err := validator.New().Struct(c); err != nil { + for _, err = range err.(validator.ValidationErrors) { + return err + } + } + return nil +} + +type DctWServer struct { + DeBug bool + Config *DctWConfig + HttpClient *http.Client +} + +type Option func(*DctWServer) + +func WithHttpClient(client *http.Client) Option { + return func(s *DctWServer) { + s.HttpClient = client + } +} + +func WithDebug(debug bool) Option { + return func(s *DctWServer) { + s.DeBug = debug + } +} + +func NewDctWServer(config *DctWConfig, o ...Option) (*DctWServer, error) { + if err := config.Validate(); err != nil { + return nil, err + } + + httpClient := &http.Client{ + Timeout: 15 * time.Second, + } + server := &DctWServer{ + DeBug: DeBug, + Config: config, + HttpClient: httpClient, + } + for _, f := range o { + f(server) + } + + return server, nil +} + +func (c *DctWServer) Sign(body, path, timestamp string) string { + str := c.Config.AppId + body + path + timestamp + c.Config.AppKey + return utils.Md5([]byte(str)) +} + +func (c *DctWServer) BuildAuth(signStr, timestamp string) string { + return fmt.Sprintf("%s appid=%s,timestamp=%s,sign=%s", SignType, c.Config.AppId, timestamp, signStr) +} + +func (c *DctWServer) Headers(signStr, timestamp string) http.Header { + h := make(http.Header) + h.Add("Content-type", ApplicationJSON) + h.Add(SignKeyName, c.BuildAuth(signStr, timestamp)) + return h +} + +func (c *DctWServer) verify(authorization, path string, body []byte) error { + trimmedAuth := strings.TrimPrefix(authorization, "MD5 ") + + authData := make(map[string]string) + parts := strings.Split(trimmedAuth, ",") + for _, part := range parts { + keyValue := strings.Split(part, "=") + if len(keyValue) != 2 { + return fmt.Errorf("授权信息格式错误: %s", part) + } + authData[keyValue[0]] = keyValue[1] + } + + sign, ok := authData["sign"] + if !ok { + return fmt.Errorf("签名数据获取失败") + } + timestamp, ok := authData["timestamp"] + if !ok { + return fmt.Errorf("时间戳获取失败") + } + calculatedSign := c.Sign(string(body), path, timestamp) + if sign == calculatedSign { + return nil + } + + return fmt.Errorf("验签失败") +} + +func (c *DctWServer) VerifyDctW(authorization string, body []byte) error { + trimmedAuth := strings.TrimPrefix(authorization, "MD5 ") + + authData := make(map[string]string) + parts := strings.Split(trimmedAuth, ",") + for _, part := range parts { + keyValue := strings.Split(part, "=") + if len(keyValue) != 2 { + return fmt.Errorf("授权信息格式错误: %s", part) + } + authData[keyValue[0]] = keyValue[1] + } + + sign, ok := authData["sign"] + if !ok { + return fmt.Errorf("签名数据获取失败") + } + + signStr := fmt.Sprintf("%s%s%s", c.Config.AppId, string(body), c.Config.AppKey) + calculatedSign := utils.Md5([]byte(signStr)) + if sign == calculatedSign { + return nil + } + + return fmt.Errorf("验签失败") +} + +func (c *DctWServer) Request(_ context.Context, request DctWRequest, path string) ([]byte, error) { + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + + body, err := request.Json() + if err != nil { + return nil, err + } + signStr := c.Sign(string(body), path, timestamp) + url := fmt.Sprintf("%s%s", c.Config.BaseUri, path) + httpClient := &http.Client{ + Timeout: 15 * time.Second, + } + req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + + req.Header = c.Headers(signStr, timestamp) + + if c.DeBug { + fmt.Printf("url:%s\n", url) + fmt.Printf("body:%s\n", string(body)) + fmt.Printf("signStr:%s\n", signStr) + fmt.Printf("Header:%+v\n", req.Header) + } + + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if c.DeBug { + fmt.Printf("resp.Status:%s\n", resp.Status) + fmt.Printf("bodyStr:%s\n", string(bodyBytes)) + } + + return bodyBytes, nil +} diff --git a/dctw/v1/core/core_test.go b/dctw/v1/core/core_test.go new file mode 100644 index 0000000..4f32d1a --- /dev/null +++ b/dctw/v1/core/core_test.go @@ -0,0 +1,28 @@ +package core + +import ( + "testing" +) + +func TestZc_DctWServer(t *testing.T) { + server, err := NewDctWServer(&DctWConfig{ + AppId: "101", + AppKey: "5k8bJV6axIukpkwz5vdte8QCw5etlC+txi+Nu69nIhOMN4VhmcNgf/THdllpt0jO", + BaseUri: "http://openapi.1688sup.cn", + }) + if err != nil { + t.Fatal(err) + } + + timestamp := "1731566046566" + body := "xx=1" + path := "/xx/xx" + + str := server.Sign(body, path, timestamp) + authorization := server.BuildAuth(str, timestamp) + + if err = server.verify(authorization, path, []byte(body)); err != nil { + t.Fatal(err) + } + t.Log("验签成功") +} diff --git a/utils/aes.go b/utils/aes.go new file mode 100644 index 0000000..ce6164b --- /dev/null +++ b/utils/aes.go @@ -0,0 +1,32 @@ +package utils + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "fmt" +) + +func AesDecode(data string, key []byte) (string, error) { + decoded, err := base64.StdEncoding.DecodeString(data) + if err != nil { + return "", fmt.Errorf("base64 decode error: %v", err) + } + block, err := aes.NewCipher(key) + if err != nil { + return "", fmt.Errorf("aes new cipher error: %v", err) + } + + if len(decoded) < aes.BlockSize { + return "", fmt.Errorf("ciphertext too short") + } + + decrypted := make([]byte, len(decoded)) + mode := cipher.NewCBCDecrypter(block, make([]byte, aes.BlockSize)) + mode.CryptBlocks(decrypted, decoded) + + padding := decrypted[len(decrypted)-1] + decrypted = decrypted[:len(decrypted)-int(padding)] + + return string(decrypted), nil +} diff --git a/utils/utils.go b/utils/utils.go index 8e298f1..40d7670 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,8 +1,12 @@ package utils import ( + "crypto/md5" + "encoding/hex" "fmt" "os" + "regexp" + "strings" ) // Load 获取插件目录中的文件信息 @@ -24,3 +28,27 @@ func Load(dir string) ([]string, error) { return files, nil } + +func Md5(values []byte) string { + hash := md5.Sum(values) + return strings.ToUpper(hex.EncodeToString(hash[:])) +} + +// IsPhoneNumber 检查给定的字符串是否为有效的手机号码 +func IsPhoneNumber(phoneNumber string) bool { + phoneRegex := `^1[34578]\d{9}$` + return regexp.MustCompile(phoneRegex).MatchString(phoneNumber) +} + +// IsEmail 检查给定的字符串是否为有效的电子邮箱地址 +func IsEmail(email string) bool { + var emailRegex = regexp.MustCompile(`[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) + return emailRegex.MatchString(email) +} + +// IsValidQQ 检查给定的字符串是否为有效的 QQ 号 +func IsValidQQ(qq string) bool { + // QQ号正则表达式:5到11位数字,且开头不为0的情况 + re := regexp.MustCompile(`^(?!0)[0-9]{5,11}$`) + return re.MatchString(qq) +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 152c926..ea85921 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func Test_load(t *testing.T) { +func Test_Load(t *testing.T) { got, err := Load("../../pkg") if err != nil { t.Errorf("load() error = %v", err) @@ -28,3 +28,14 @@ func Test_BuildPem(t *testing.T) { puk2 := NewPublic() t.Logf("\n%s", puk2.Build("puk key")) } + +func Test_AesDecode(t *testing.T) { + key := "837c7a898810160ce5aab4c42bf22bc4" + encStr := "" + str, err := AesDecode(encStr, []byte(key)) + if err != nil { + t.Errorf("AesDecode() error = %v", err) + return + } + t.Logf("encStr: %s, str: %s", encStr, str) +}