Compare commits
23 Commits
Author | SHA1 | Date |
---|---|---|
李子铭 | e0b1bf4b43 | |
李子铭 | 84a6cb208f | |
李子铭 | b71aa93e65 | |
李子铭 | 85034cfb35 | |
李子铭 | 50ab391039 | |
李子铭 | fe8e56420f | |
李子铭 | 35ccf00466 | |
李子铭 | 8102c3ffcd | |
李子铭 | eb9a1323e5 | |
李子铭 | 1b3162df5d | |
李子铭 | 990030fff3 | |
李子铭 | 0823da7786 | |
李子铭 | 3069c47115 | |
李子铭 | 5e71fabbf9 | |
李子铭 | e0985d04af | |
李子铭 | c9c91d9146 | |
李子铭 | 307205cdfd | |
李子铭 | 0ea434699a | |
李子铭 | 7cc22209f5 | |
李子铭 | aa83189b01 | |
李子铭 | 48907c4653 | |
李子铭 | a241f05370 | |
李子铭 | 83c54628dd |
|
@ -13,13 +13,14 @@ func TestToken_Client(t *testing.T) {
|
|||
MchCertPath: "/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt",
|
||||
RootCertPath: "/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt",
|
||||
}
|
||||
resp, err := d.Client(context.Background(), "349feb5fb58e455da1b00a7748ceMD68")
|
||||
resp, err := d.Client(context.Background(), "ecfb8b3f91e44ca6b94b8830fcd5BF88")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
if resp.IsSuccess() {
|
||||
t.Logf("Response: %+v", resp.Response)
|
||||
} else {
|
||||
t.Errorf("resp: %+v", resp)
|
||||
t.Errorf("ErrorResponse: %+v", resp.ErrorResponse)
|
||||
t.Errorf("ErrorResponse Msg: %+v", resp.ErrorResponse.Msg)
|
||||
t.Errorf("ErrorResponse SubCode: %+v", resp.ErrorResponse.SubCode)
|
||||
|
|
|
@ -22,19 +22,20 @@ type Param struct {
|
|||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type AlipayTokenSuccessResponse struct {
|
||||
UserId string `json:"user_id"`
|
||||
OpenId string `json:"open_id"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn string `json:"expires_in"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ReExpiresIn string `json:"re_expires_in"`
|
||||
ReExpiresIn int `json:"re_expires_in"`
|
||||
AuthStart string `json:"auth_start"`
|
||||
}
|
||||
|
||||
type AlipayTokenResponse struct {
|
||||
ErrorResponse *alipay.ErrorResponse `json:"error_response"`
|
||||
Response *AlipayTokenSuccessResponse `json:"alipay_system_oauth_token_response"`
|
||||
ErrorResponse *alipay.ErrorResponse `json:"error_response,omitempty"`
|
||||
Response *AlipayTokenSuccessResponse `json:"alipay_system_oauth_token_response,omitempty"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ func GetCert(mchCertPath, rootCertPath, PublicKeyPath, appId string) (*CertConfi
|
|||
}
|
||||
c := getCertConfig(appId)
|
||||
if c != nil {
|
||||
return nil, fmt.Errorf("appId %s already exists", appId)
|
||||
return c, nil
|
||||
}
|
||||
mchCertSN, err := getMchCertSN(mchCertPath)
|
||||
if err != nil {
|
||||
|
|
|
@ -23,6 +23,7 @@ func hex2dec(hex string) string {
|
|||
return fmt.Sprintf("%d", dec)
|
||||
}
|
||||
|
||||
// getCert 从证书数据中提取证书
|
||||
func getCert(certData []byte) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode(certData)
|
||||
if block == nil {
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package card
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"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
|
||||
}
|
||||
|
||||
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, *CardCode, error) {
|
||||
result, err := c.Request(ctx, request, queryPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var response *QueryResp
|
||||
if err = json.Unmarshal(result, &response); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cardCode, err := response.Decode(c.Config.AppKey)
|
||||
|
||||
return response, cardCode, nil
|
||||
}
|
||||
|
||||
func (c *Card) Notify(_ context.Context, httpRequest *http.Request) (*Notify, *CardCode, error) {
|
||||
body, err := ioutil.ReadAll(httpRequest.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err = c.VerifyDctW(httpRequest.Header.Get(core.SignKeyName), body); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var response *Notify
|
||||
if err = json.Unmarshal(body, &response); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cardCode, err := response.Decode(c.Config.AppKey)
|
||||
|
||||
return response, cardCode, err
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package card
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/dctw/v1/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCard_Order(t *testing.T) {
|
||||
server, err := core.NewDctWServer(
|
||||
&core.DctWConfig{
|
||||
AppId: "1",
|
||||
AppKey: "1e2bf7a04b8b1e6be5dc78d04e8639c9",
|
||||
BaseUri: "http://test.openapi.1688sup.cn",
|
||||
},
|
||||
core.WithDebug(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a := &Card{DctWServer: server}
|
||||
req := &Order{
|
||||
Number: 1,
|
||||
MerchantId: "23329",
|
||||
OutTradeNo: "test_card_zltx_2",
|
||||
ProductId: "222",
|
||||
Mobile: "18666666666",
|
||||
NotifyUrl: "https://gateway.dev.cdlsxd.cn/yxh5api/v1/order/direct/notify",
|
||||
Version: "1.0",
|
||||
}
|
||||
resp, err := a.Order(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !resp.GetCode().IsSuccess() {
|
||||
t.Error(resp.Message)
|
||||
return
|
||||
}
|
||||
t.Logf("%+v", 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: "23329",
|
||||
OutTradeNo: "test_card_zltx_2",
|
||||
Version: "1.0",
|
||||
}
|
||||
resp, cardCode, err := a.Query(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !resp.GetCode().IsSuccess() {
|
||||
t.Error(resp.Message)
|
||||
return
|
||||
}
|
||||
t.Logf("%+v", resp)
|
||||
t.Logf("%+v", cardCode)
|
||||
assert.Equal(t, resp.Status.IsSuccess(), true, "充值是否成功")
|
||||
}
|
||||
|
||||
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":"test_card_zltx_2","status":"01"}`)
|
||||
httpHeaderBytes := []byte(`{"Accept-Encoding":["gzip, deflate, br"],"Authorization":["MD5 appid=1,sign=6B48E95EAEE20EE4525BEF0112493711"],"Connection":["close"],"Content-Length":["68"],"Content-Type":["application/json"],"Cookie":[""],"User-Agent":["GuzzleHttp/6.5.5 curl/7.69.1 PHP/7.2.34"],"X-Remoteaddr":["172.21.0.1"]}`)
|
||||
|
||||
httpHeaders := make(http.Header)
|
||||
if err = json.Unmarshal(httpHeaderBytes, &httpHeaders); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var httpRequest = &http.Request{
|
||||
Header: httpHeaders,
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(body)),
|
||||
}
|
||||
resp, cardCode, err := a.Notify(context.Background(), httpRequest)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
t.Logf("%+v", resp)
|
||||
t.Logf("%+v", cardCode)
|
||||
assert.Equal(t, resp.Status.IsSuccess(), true, "充值是否成功")
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package card
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type Code json.Number
|
||||
|
||||
const (
|
||||
CodeSuccess Code = "2000"
|
||||
CodeSuccess2 Code = "0000"
|
||||
)
|
||||
|
||||
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 || c == CodeSuccess2
|
||||
}
|
||||
|
||||
func (c Code) GetMessage() string {
|
||||
if message, ok := ResMessageMap[c]; ok {
|
||||
return message
|
||||
}
|
||||
return "未知错误,请联系平台"
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
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,
|
||||
}
|
||||
|
||||
var orderStatusTextMap = map[OrderStatus]string{
|
||||
OrderSuccess: "充值成功",
|
||||
OrderPending: "充值处理中",
|
||||
OrderFail: "充值失败",
|
||||
OrderAbnormal: "充值异常,处理中",
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (o OrderStatus) GetOrderStatusText() string {
|
||||
if text, ok := orderStatusTextMap[o]; ok {
|
||||
return text
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package card
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/utils"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 json.Number `json:"code"` // 不能直接定义值对象,因为单独定义类型type code json.Number类型无法jsonUnmarshal
|
||||
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 json.Number `json:"code"`
|
||||
Status OrderStatus `json:"status"`
|
||||
Message string `json:"message"`
|
||||
OutTradeNo string `json:"outTradeNo"`
|
||||
CardCode string `json:"cardCode"`
|
||||
}
|
||||
|
||||
type Notify struct {
|
||||
MerchantId string `json:"merchantId"`
|
||||
OutTradeNo string `json:"outTradeNo"`
|
||||
TradeNo string `json:"tradeNo"`
|
||||
RechargeAccount string `json:"rechargeAccount"`
|
||||
Status OrderStatus `json:"status"`
|
||||
CardCode string `json:"cardCode"`
|
||||
}
|
||||
|
||||
type CardCode struct {
|
||||
Number string `json:"number"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (o *Notify) Decode(appKey string) (*CardCode, error) {
|
||||
car := &CardCode{}
|
||||
return car.Decode(o.CardCode, appKey)
|
||||
}
|
||||
|
||||
func (o *CardCode) Json() ([]byte, error) {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (o *CardCode) Decode(carCode, appKey string) (*CardCode, error) {
|
||||
if carCode == "" {
|
||||
return o, nil
|
||||
}
|
||||
s, err := utils.AesDecode(carCode, []byte(appKey))
|
||||
if err != nil {
|
||||
return o, nil
|
||||
}
|
||||
parts := strings.Split(s, "_")
|
||||
if len(parts) > 1 {
|
||||
o.Number = parts[0]
|
||||
o.Password = parts[1]
|
||||
} else {
|
||||
o.Password = s
|
||||
}
|
||||
return o, err
|
||||
}
|
||||
|
||||
func (o *OrderResp) GetCode() Code {
|
||||
return Code(o.Code)
|
||||
}
|
||||
|
||||
func (o *QueryResp) GetCode() Code {
|
||||
return Code(o.Code)
|
||||
}
|
||||
|
||||
func (o *QueryResp) Decode(appKey string) (*CardCode, error) {
|
||||
car := &CardCode{}
|
||||
return car.Decode(o.CardCode, appKey)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package direct
|
||||
|
||||
import (
|
||||
"gitea.cdlsxd.cn/sdk/plugin/utils"
|
||||
)
|
||||
|
||||
type AccountType int
|
||||
|
||||
const (
|
||||
AccountTypeDefault AccountType = iota
|
||||
AccountTypePhone
|
||||
AccountTypeQQ
|
||||
)
|
||||
|
||||
// AccountType 账号类型(1:手机号 2:QQ号 其他:0)
|
||||
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
|
||||
} else if utils.IsEmail(account) {
|
||||
return AccountTypeDefault
|
||||
}
|
||||
return AccountTypeDefault
|
||||
}
|
||||
|
||||
func (o AccountType) Value() int {
|
||||
return int(o)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package direct
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type Code json.Number
|
||||
|
||||
const (
|
||||
CodeSuccess Code = "2000"
|
||||
CodeSuccess2 Code = "0000"
|
||||
)
|
||||
|
||||
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 || c == CodeSuccess2
|
||||
}
|
||||
|
||||
func (c Code) GetMessage() string {
|
||||
if message, ok := ResMessageMap[c]; ok {
|
||||
return message
|
||||
}
|
||||
return "未知错误,请联系平台"
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package direct
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package direct
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"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",
|
||||
},
|
||||
core.WithDebug(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a := &Direct{DctWServer: server}
|
||||
req := &Order{
|
||||
Number: 1,
|
||||
MerchantId: "25537",
|
||||
OutTradeNo: "test_zltx_direct_2",
|
||||
ProductId: "101",
|
||||
AccountType: 1,
|
||||
RechargeAccount: "18666666666",
|
||||
NotifyUrl: "https://gateway.dev.cdlsxd.cn/yxh5api/v1/order/direct/notify",
|
||||
Version: "1.0",
|
||||
}
|
||||
resp, err := a.Order(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !resp.GetCode().IsSuccess() {
|
||||
t.Error(resp.Message)
|
||||
return
|
||||
}
|
||||
t.Logf("%+v", 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: "25537",
|
||||
OutTradeNo: "test_zltx_direct_2",
|
||||
Version: "1.0",
|
||||
}
|
||||
resp, err := a.Query(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !resp.GetCode().IsSuccess() {
|
||||
t.Error(resp.Message)
|
||||
return
|
||||
}
|
||||
t.Logf("%+v", 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":"25537","outTradeNo":"test_zltx_direct_2","status":"03","rechargeAccount":"18666666666"}`)
|
||||
httpHeaderBytes := []byte(`{"Accept-Encoding":["gzip, deflate, br"],"Authorization":["MD5 appid=1,sign=642F82644ABB88E73C04F7881B93E6FA"],"Connection":["close"],"Content-Length":["102"],"Content-Type":["application/json"],"Cookie":[""],"User-Agent":["GuzzleHttp/6.5.5 curl/7.69.1 PHP/7.2.34"],"X-Remoteaddr":["172.21.0.1"]}`)
|
||||
|
||||
httpHeaders := make(http.Header)
|
||||
if err = json.Unmarshal(httpHeaderBytes, &httpHeaders); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var httpRequest = &http.Request{
|
||||
Header: httpHeaders,
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(body)),
|
||||
}
|
||||
resp, err := a.Notify(context.Background(), httpRequest)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
t.Logf("%+v", resp)
|
||||
assert.Equal(t, resp.Status.IsSuccess(), true, "充值是否成功")
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
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,
|
||||
}
|
||||
|
||||
var orderStatusTextMap = map[OrderStatus]string{
|
||||
OrderSuccess: "充值成功",
|
||||
OrderPending: "充值处理中",
|
||||
OrderFail: "充值失败",
|
||||
OrderAbnormal: "充值异常,处理中",
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (o OrderStatus) GetOrderStatusText() string {
|
||||
if text, ok := orderStatusTextMap[o]; ok {
|
||||
return text
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
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"`
|
||||
RechargeAccount string `validate:"required" json:"rechargeAccount"`
|
||||
NotifyUrl string `validate:"required" json:"notifyUrl"`
|
||||
Version string `validate:"required" json:"version"`
|
||||
}
|
||||
|
||||
type OrderResp struct {
|
||||
Code json.Number `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 json.Number `json:"code"`
|
||||
Status OrderStatus `json:"status"`
|
||||
Message string `json:"message"`
|
||||
OutTradeNo string `json:"outTradeNo"`
|
||||
CardCode string `json:"cardCode"`
|
||||
}
|
||||
|
||||
type Notify struct {
|
||||
MerchantId string `json:"merchantId"`
|
||||
OutTradeNo string `json:"outTradeNo"`
|
||||
RechargeAccount string `json:"rechargeAccount"`
|
||||
TradeNo string `json:"tradeNo"`
|
||||
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
|
||||
}
|
||||
|
||||
func (o *OrderResp) GetCode() Code {
|
||||
return Code(o.Code)
|
||||
}
|
||||
|
||||
func (o *QueryResp) GetCode() Code {
|
||||
return Code(o.Code)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package api
|
||||
|
||||
import "gitea.cdlsxd.cn/sdk/plugin/dctw/v1/core"
|
||||
|
||||
type Service struct {
|
||||
*core.DctWServer
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
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("时间戳获取失败")
|
||||
}
|
||||
if sign == c.Sign(string(body), path, timestamp) {
|
||||
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)
|
||||
if sign == utils.Md5([]byte(signStr)) {
|
||||
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)
|
||||
|
||||
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("request.url:%s\n", url)
|
||||
fmt.Printf("request.body:%s\n", string(body))
|
||||
fmt.Printf("request.signStr:%s\n", signStr)
|
||||
fmt.Printf("request.headers:%+v\n", req.Header)
|
||||
}
|
||||
|
||||
resp, err := c.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("resp.body:%s\n", string(bodyBytes))
|
||||
fmt.Printf("resp.headers:%+v\n", resp.Header)
|
||||
}
|
||||
|
||||
return bodyBytes, nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
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",
|
||||
},
|
||||
WithDebug(true),
|
||||
)
|
||||
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("验签成功")
|
||||
}
|
4
go.mod
4
go.mod
|
@ -7,11 +7,13 @@ require (
|
|||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/hashicorp/go-plugin v1.6.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
google.golang.org/grpc v1.64.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
|
@ -24,9 +26,11 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -62,5 +62,7 @@ google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
|||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
package manage
|
||||
|
||||
var m *pluginManage
|
||||
|
||||
func init() {
|
||||
m = &pluginManage{
|
||||
plugins: make(map[string]*PluginInfo),
|
||||
}
|
||||
}
|
||||
|
||||
func Load(p []*Config) error {
|
||||
for _, c := range p {
|
||||
err := Add(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Add(c *Config) error {
|
||||
err := c.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.add(c)
|
||||
}
|
||||
|
||||
func Update(c *Config) error {
|
||||
err := c.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = Remove(c.Tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.add(c)
|
||||
}
|
||||
|
||||
func Get(tag string) (*PluginInfo, error) {
|
||||
p, err := m.get(tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func Remove(tag string) error {
|
||||
return m.remove(tag)
|
||||
}
|
||||
|
||||
func Close() {
|
||||
m.close()
|
||||
m = nil
|
||||
}
|
|
@ -1,64 +1,60 @@
|
|||
package manage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/shared"
|
||||
)
|
||||
var m *pluginManage
|
||||
|
||||
type PluginInfo struct {
|
||||
Tag string
|
||||
Impl shared.PluginService
|
||||
cleanup func()
|
||||
func init() {
|
||||
m = &pluginManage{
|
||||
plugins: make(map[string]*PluginInfo),
|
||||
}
|
||||
}
|
||||
|
||||
type pluginManage struct {
|
||||
plugins map[string]*PluginInfo
|
||||
}
|
||||
|
||||
func (m *pluginManage) exists(tag string) bool {
|
||||
if _, ok := m.plugins[tag]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *pluginManage) add(c *Config) error {
|
||||
if m.exists(c.Tag) {
|
||||
return fmt.Errorf("插件已存在[%s]", c.Tag)
|
||||
}
|
||||
impl, cleanup, err := newClient(c)
|
||||
func Load(p []*Config) error {
|
||||
for _, c := range p {
|
||||
err := Add(c)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return err
|
||||
}
|
||||
m.plugins[c.Tag] = &PluginInfo{
|
||||
Tag: c.Tag,
|
||||
Impl: impl,
|
||||
cleanup: cleanup,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pluginManage) remove(tag string) error {
|
||||
if m.exists(tag) {
|
||||
m.plugins[tag].cleanup()
|
||||
delete(m.plugins, tag)
|
||||
return nil
|
||||
func Add(c *Config) error {
|
||||
err := c.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("插件不存在[%s]", tag)
|
||||
return m.add(c)
|
||||
}
|
||||
|
||||
func (m *pluginManage) get(tag string) (*PluginInfo, error) {
|
||||
if m.exists(tag) {
|
||||
return m.plugins[tag], nil
|
||||
func Update(c *Config) error {
|
||||
err := c.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil, fmt.Errorf("插件不存在[%s]", tag)
|
||||
err = Remove(c.Tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.add(c)
|
||||
}
|
||||
|
||||
func (m *pluginManage) close() {
|
||||
for _, info := range m.plugins {
|
||||
if info.cleanup != nil {
|
||||
info.cleanup()
|
||||
func Get(tag string) (*PluginInfo, error) {
|
||||
p, err := m.get(tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func GetAll() map[string]*PluginInfo {
|
||||
return m.plugins
|
||||
}
|
||||
|
||||
func Remove(tag string) error {
|
||||
return m.remove(tag)
|
||||
}
|
||||
|
||||
func Close() {
|
||||
m.close()
|
||||
m = nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package manage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.cdlsxd.cn/sdk/plugin/shared"
|
||||
)
|
||||
|
||||
type PluginInfo struct {
|
||||
Tag string
|
||||
Impl shared.PluginService
|
||||
cleanup func()
|
||||
}
|
||||
|
||||
type pluginManage struct {
|
||||
plugins map[string]*PluginInfo
|
||||
}
|
||||
|
||||
func (m *pluginManage) exists(tag string) bool {
|
||||
if _, ok := m.plugins[tag]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *pluginManage) add(c *Config) error {
|
||||
if m.exists(c.Tag) {
|
||||
return fmt.Errorf("插件已存在[%s]", c.Tag)
|
||||
}
|
||||
impl, cleanup, err := newClient(c)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return err
|
||||
}
|
||||
m.plugins[c.Tag] = &PluginInfo{
|
||||
Tag: c.Tag,
|
||||
Impl: impl,
|
||||
cleanup: cleanup,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pluginManage) remove(tag string) error {
|
||||
if m.exists(tag) {
|
||||
m.plugins[tag].cleanup()
|
||||
delete(m.plugins, tag)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("插件不存在[%s]", tag)
|
||||
}
|
||||
|
||||
func (m *pluginManage) get(tag string) (*PluginInfo, error) {
|
||||
if p, ok := m.plugins[tag]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return nil, fmt.Errorf("插件不存在[%s]", tag)
|
||||
}
|
||||
|
||||
func (m *pluginManage) close() {
|
||||
for _, info := range m.plugins {
|
||||
if info.cleanup != nil {
|
||||
info.cleanup()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -434,6 +434,7 @@ type NotifyResponse struct {
|
|||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Result *Result `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"`
|
||||
Return string `protobuf:"bytes,2,opt,name=return,proto3" json:"return,omitempty"`
|
||||
}
|
||||
|
||||
func (x *NotifyResponse) Reset() {
|
||||
|
@ -475,6 +476,13 @@ func (x *NotifyResponse) GetResult() *Result {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *NotifyResponse) GetReturn() string {
|
||||
if x != nil {
|
||||
return x.Return
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type OrderRequest_Order struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -764,23 +772,24 @@ var file_proto_plugin_proto_rawDesc = []byte{
|
|||
0x72, 0x69, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f,
|
||||
0x64, 0x79, 0x22, 0x37, 0x0a, 0x0e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70,
|
||||
0x64, 0x79, 0x22, 0x4f, 0x0a, 0x0e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73,
|
||||
0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0xad, 0x01, 0x0a, 0x06,
|
||||
0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12,
|
||||
0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x72, 0x64,
|
||||
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x05,
|
||||
0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x51, 0x75,
|
||||
0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x12, 0x37, 0x0a, 0x06, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x14, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66,
|
||||
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x70,
|
||||
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72,
|
||||
0x65, 0x74, 0x75, 0x72, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x74,
|
||||
0x75, 0x72, 0x6e, 0x32, 0xad, 0x01, 0x0a, 0x06, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x34,
|
||||
0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
|
||||
0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x13, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x06, 0x4e, 0x6f,
|
||||
0x74, 0x69, 0x66, 0x79, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74,
|
||||
0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
@ -949,6 +949,8 @@ func (m *NotifyResponse) validate(all bool) error {
|
|||
}
|
||||
}
|
||||
|
||||
// no validation rules for Return
|
||||
|
||||
if len(errors) > 0 {
|
||||
return NotifyResponseMultiError(errors)
|
||||
}
|
||||
|
|
|
@ -66,4 +66,5 @@ message NotifyRequest{
|
|||
}
|
||||
message NotifyResponse{
|
||||
Result result = 1;
|
||||
string return = 2;
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Load 获取插件目录中的文件信息
|
||||
|
@ -21,6 +25,29 @@ func Load(dir string) ([]string, error) {
|
|||
files = append(files, fmt.Sprintf("%s/%s", dir, info.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue