first commit

This commit is contained in:
ziming 2025-10-10 18:52:33 +08:00
commit 763be7adbb
45 changed files with 3011 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
# Test binary, built with `go test -c`
*.test
# Go workspace file
go.work
go.work.sum
# OS General
Thumbs.db
.DS_Store
.vscode/
.idea/
pem

246
README.md Normal file
View File

@ -0,0 +1,246 @@
## 功能介绍
1. 接口 SDK。详见 [接口介绍](services)。
2. HTTP 客户端,支持请求签名和应答验签。如果 SDK 未支持你需要的接口,请用此客户端发起请求。
3. 回调通知处理库,支持回调通知的验签。详见 [回调通知验签](#回调通知的验签)。
4. 密钥生成下载、[敏感信息加解密](#敏感信息加解密) 等辅助能力。
#### 名词解释
+ **商户 API 公钥**,是用来证实商户身份的
+ **商户 API 私钥**。是用来证实商户身份的
+ **商户 API 密钥**。是商户用来加密请求参数的密钥,为加强数据安全,使用的对称加密密钥。
> :warning: 不要把私钥文件暴露在公共场合,如上传到 Github写在客户端代码等。
## 快速开始
### 安装
#### 1、使用 Go Modules 管理你的项目
如果你的项目还不是使用 Go Modules 做依赖管理,在项目根目录下执行:
```shell
go mod init
```
#### 2、在项目目录中执行
```shell
go get -u github.com/sleepinggodoflove/lansexiongdi-marketing-sdk
```
来添加依赖,完成 `go.mod` 修改与 SDK 下载。
## 示例
#### [获取key码](https://tvd8jq9lqkp.feishu.cn/wiki/PVq3wtanPicDu0kyfpLc0McMnAc?from=from_copylink)
```go
package main
import (
"context"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/key"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"log"
)
func main() {
c, err := core.NewCore(&core.Config{
AppID: "123",
PrivateKey: "私钥",
PublicKey: "验签公钥",
Key: "业务参数密钥key",
SignType: "签名类型",
BaseURL: "请求地址:https://api.lansexiongdi.com",
})
if err != nil {
log.Fatalf("new core err:%v", err)
}
a := &key.Key{c}
_,_,r, err := a.Order(context.Background(), &key.OrderRequest{
OutBizNo: "123456",
ActivityNo: "123456",
Number: 1,
})
if err != nil {
log.Fatalf("key get err:%v", err)
}
log.Printf(r)
}
```
#### [查询key码](https://tvd8jq9lqkp.feishu.cn/wiki/GvRswEDyfiXGUUkkDCYc8xg4nVX?from=from_copylink)
```go
package main
import (
"context"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/key"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"log"
)
func main() {
c, err := core.NewCore(&core.Config{
AppID: "123",
PrivateKey: "私钥",
PublicKey: "验签公钥",
Key: "业务参数密钥key",
SignType: "签名类型",
BaseURL: "请求地址",
})
if err != nil {
log.Fatalf("new core err:%v", err)
}
a := &key.Key{c}
_,_,r, err := a.Query(context.Background(), &key.QueryRequest{
OutBizNo: "123456",
trade_no: "123456",
})
if err != nil {
log.Fatalf("key query err:%v", err)
}
log.Printf(r)
}
```
#### [作废key码](https://tvd8jq9lqkp.feishu.cn/wiki/R9NMw96eIiXLiRkOi7icANkynbb?from=from_copylink)
```go
package main
import (
"context"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/key"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"log"
)
func main() {
c, err := core.NewCore(&core.Config{
AppID: "123",
PrivateKey: "私钥",
PublicKey: "验签公钥",
Key: "业务参数密钥key",
SignType: "签名类型",
BaseURL: "请求地址",
})
if err != nil {
log.Fatalf("new core err:%v", err)
}
a := &key.Key{c}
_,_,r, err := a.Discard(context.Background(), &key.DiscardRequest{
OutBizNo: "123456",
trade_no: "123456",
})
if err != nil {
log.Fatalf("key query err:%v", err)
}
log.Printf(r)
}
```
#### [回调通知](https://alidocs.dingtalk.com/i/nodes/N7dx2rn0Jb6A1wvLixErNlLkJMGjLRb3?utm_scene=team_space)
```go
package main
import (
"context"
"net/http"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/key"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"log"
)
func main() {
c, err := core.NewCore(&core.Config{
AppID: "123",
PrivateKey: "私钥",
PublicKey: "验签公钥",
Key: "业务参数密钥key",
SignType: "签名类型",
BaseURL: "请求地址",
})
if err != nil {
log.Fatalf("new core err:%v", err)
}
a := &key.Key{c}
req := &http.Request{
Header: nil, // 请求头
Body: nil, // 请求体
}
r, err := a.CallBack(context.Background(), req)
if err != nil {
log.Fatalf("key callBack err:%v", err)
}
if err != nil {
log.Fatalf("key notify err:%v", err)
}
log.Printf(r)
}
```
#### [其它调用](https://alidocs.dingtalk.com/i/nodes/N7dx2rn0Jb6A1wvLixErNlLkJMGjLRb3?utm_scene=team_space)
```go
package main
import (
"context"
"encoding/json"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/anyapi"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"log"
)
func main() {
c, err := core.NewCore(&core.Config{
AppID: "appid",
PrivateKey: "私钥",
PublicKey: "验签公钥",
Key: "业务参数密钥key",
SignType: "签名类型",
BaseURL: "请求地址",
})
if err != nil {
log.Fatalf("new core err:%v", err)
}
bizContent := struct {
Source string `json:"source"` // 来源
AppId string `json:"app_id"` // 应用Id
MchPublicKey string `json:"mch_public_key"` // 客户公钥
NotifyUrl string `json:"notify_url"` // 事件通知地址,可为空
}{
Source: "来源",
AppId: "123",
MchPublicKey: "123",
NotifyUrl: "https://xx.com/xx",
}
a := &anyapi.AnyApi{c}
method := "/openapi/v1/xxx"
_,_, r, err := a.AnyApi(context.Background(), method, bizContent)
if err != nil {
log.Fatalf("call err:%v", err)
}
if !r.IsSuccess() {
log.Fatalf("err:%s", r.Message)
}
var bizDataContent = struct {
Ciphertext string `json:"ciphertext,omitempty"`
}{}
_ = json.Unmarshal(r.Data, &bizDataContent)
bizJsonContent, _ := c.CryptographySuite.Cipher.Decode(bizDataContent.Ciphertext)
log.Printf("bizJsonContent=%s", bizJsonContent)
}
```

9
api/service.go Normal file
View File

@ -0,0 +1,9 @@
package api
import (
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
)
type Service struct {
*core.Core
}

41
api/v1/anyapi/anyapi.go Normal file
View File

@ -0,0 +1,41 @@
package anyapi
import (
"context"
"encoding/json"
"fmt"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"net/http"
)
type AnyApi api.Service
func (a *AnyApi) AnyApi(ctx context.Context, method string, bizContent any) (http.Header, *http.Response, *core.Response, error) {
p, err := a.BuildAnyApiParams(bizContent)
if err != nil {
return nil, nil, nil, err
}
reqBodyBytes, err := json.Marshal(p)
if err != nil {
return nil, nil, nil, err
}
h := a.GetHeaders(p)
url := fmt.Sprintf("%s%s", a.Config.BaseURL, method)
httpResponse, bodyBytes, err := a.Request(ctx, h, http.MethodPost, url, reqBodyBytes)
if err != nil {
return nil, nil, nil, err
}
res, err := core.BuildResponse(bodyBytes)
if err != nil {
return h, httpResponse, nil, err
}
return h, httpResponse, res, nil
}

View File

@ -0,0 +1,80 @@
package anyapi
import (
"context"
"encoding/json"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"testing"
)
var (
appId = "OP001"
privateKey = ""
publicKey = ""
key = ""
baseURL = "http://127.0.0.1:9000"
signType = core.SignRSA
)
func newCore() *core.Core {
c, err := core.NewCore(&core.Config{
AppID: appId,
PrivateKey: privateKey,
PublicKey: publicKey,
Key: key,
SignType: signType,
BaseURL: baseURL,
})
if err != nil {
panic(err)
}
return c
}
func Test_AnyApi(t *testing.T) {
c := newCore()
bizContent := struct {
Source string `json:"source"` // 来源
AppId string `json:"app_id"` // 应用Id
MchPublicKey string `json:"mch_public_key"` // 客户公钥
NotifyUrl string `json:"notify_url"` // 事件通知地址,可为空
}{
Source: "来源",
AppId: "OP002",
MchPublicKey: "",
NotifyUrl: "https://utils.85938.cn/utils/v1/wechat/notify",
}
a := &AnyApi{c}
method := "/openapi/v1/merchant/appSet"
h, _, r, err := a.AnyApi(context.Background(), method, bizContent)
if err != nil {
t.Error(err)
return
}
if !r.IsSuccess() {
t.Error(r.Message)
return
}
t.Logf("data=%s", string(r.Data))
var bizDataContent = struct {
Ciphertext string `json:"ciphertext,omitempty"`
}{}
_ = json.Unmarshal(r.Data, &bizDataContent)
bizJsonContent, _ := c.CryptographySuite.Cipher.Decode(bizDataContent.Ciphertext)
t.Logf("header=%+v", h)
t.Logf("bizJsonContent=%s", bizJsonContent)
}

125
api/v1/key/key.go Normal file
View File

@ -0,0 +1,125 @@
package key
import (
"context"
"encoding/json"
"fmt"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"io"
"net/http"
)
const (
orderMethod = "/openapi/v1/key/order"
queryMethod = "/openapi/v1/key/query"
discardMethod = "/openapi/v1/key/discard"
)
type Key api.Service
func (k *Key) Order(ctx context.Context, request *OrderRequest) (http.Header, *http.Response, *core.Response, error) {
h, httpResponse, bodyBytes, err := k.Post(ctx, orderMethod, request)
if err != nil {
return nil, nil, nil, err
}
res, err := core.BuildResponse(bodyBytes)
if err != nil {
return h, httpResponse, nil, err
}
return h, httpResponse, res, nil
}
func (k *Key) Query(ctx context.Context, request *QueryRequest) (http.Header, *http.Response, *core.Response, error) {
h, httpResponse, bodyBytes, err := k.Post(ctx, queryMethod, request)
if err != nil {
return nil, nil, nil, err
}
res, err := core.BuildResponse(bodyBytes)
if err != nil {
return h, httpResponse, nil, err
}
return h, httpResponse, res, nil
}
func (k *Key) Discard(ctx context.Context, request *DiscardRequest) (http.Header, *http.Response, *core.Response, error) {
h, httpResponse, bodyBytes, err := k.Post(ctx, discardMethod, request)
if err != nil {
return nil, nil, nil, err
}
res, err := core.BuildResponse(bodyBytes)
if err != nil {
return h, httpResponse, nil, err
}
return h, httpResponse, res, nil
}
func (k *Key) Notify(_ context.Context, n *Notify) (*NotifyData, error) {
if !k.CryptographySuite.Verifier.Verify(n.SignString(), n.Sign) {
return nil, fmt.Errorf("verify sign fail")
}
return &n.Data, nil
}
func (k *Key) CallBack(ctx context.Context, req *http.Request) (*NotifyData, error) {
body, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
var n *Notify
if err = json.Unmarshal(body, &n); err != nil {
return nil, err
}
sign := req.Header.Get("Sign")
if sign == "" {
return k.Notify(ctx, n)
}
timestamp := req.Header.Get("Timestamp")
if timestamp == "" {
return nil, fmt.Errorf("timestamp is empty")
}
appid := req.Header.Get("Appid")
if appid == "" {
return nil, fmt.Errorf("appid is empty")
}
signType := req.Header.Get("Sign-Type")
if signType == "" {
return nil, fmt.Errorf("sign-type is empty")
}
if appid != k.Config.AppID {
return nil, fmt.Errorf("appid is invalid")
}
if signType != string(k.Config.SignType) {
return nil, fmt.Errorf("sign-type is invalid")
}
ciphertext, err := k.GetCiphertext(&n.Data)
if err != nil {
return nil, err
}
if !k.Verify(timestamp, ciphertext, sign) {
return nil, fmt.Errorf("call back verify sign fail")
}
return &n.Data, nil
}

150
api/v1/key/key_test.go Normal file
View File

@ -0,0 +1,150 @@
package key
import (
"bytes"
"context"
"encoding/json"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"io"
"net/http"
"testing"
)
var (
appId = "lzm"
privateKey = "MIIEowIBAAKCAQEA7a2I4l8OOdW4weVFvj4u/mBqP3aZhJ0mOTKl4MCW4Pf6gNAlZa5dZYOS/BocmG872+pd10BiI73qiAWsuVaPwCL0A37lQbCXlG0fDAfCLogXuF1qVNRZgkYKrx/5Gppo2PNed7E5YyCUkMUKVPbuwuZteMZJH8d1o6Uojbb/xJQvAGOlx5Y04VZWp/6p2GjhW0srwgbpVegMyyn2Qblx1Lo+Uq5zG8um7FTpbtb/L/itpBFEDSZGIIKDfn4FPyt+jQ0SW5TDYQClSvWHK4V3RkWOVkD1nHeBpZyp7JNehK+7kBfO6G4NJabkyoWqFEiZcTy38ZWQdqJ9N4LZuY37NwIDAQABAoIBAGs2u6e5z1YBda1pehN+Q36WCXeFTW0H4qUslq0S0zy6P/L5cdUzWYggWR6FvN56Vts2Foyxy1NqKTCgtrCIPqIiYkZtaIdAXLAkpTutCEgrNeABq6SGgbYFWG51Es6QVrl+1t9RP5zaponDiIyZM00R2tH/SB8gv41JREjhAvEuNIwPyaoVVt+U/kAdhJgiMKsDpoGaMfsJk76sORu6qQqBkBN8cglN94xC0QtROytW3EY8SnZmgGZHcY3YTXM74CWM8yBg7rNuKv0982f9hKvUDHKMFYly1PzYiSgplkT7RYCMjo2FFf1lt7k7N61+4nalS/EM6324m2poisTRFAkCgYEA+3oXvtwrTim0kXG7V7w5PS8u5dU0sAAH4ACSyzy8nEdKEk4ipoaTGm6km8ko0O+9E70SwZQgK/eAnmsYf5WtMzvItweKUUVsBCm01qSHlu5vzGO3H1ndi7hg+tH9VrOQH3+odQJP9FqC4BkncMszHM4nLglWSTixTTvGIovQLy0CgYEA8fPnu0tWqhfQV6svaA5kt4h6cL52ARKlubRuYkI4hGuikKYpd2A3WuVtD1LkuPQSjwID9730HAqLc7ZMwONjQ8NANi9ZoJR6A+Vzba9zDPQmSc80Ax7Kkjc03D1Y7yiP6P8bWnhCCbRcMy+dcobvBZc2zaWzSNjZwPOSV9xaMnMCgYAclz354hg+U7mGy7JsACdV0HZ5hOrvk6FRk18dIjOjZOuD90QzQJua5rdqSs2MK6WIh/eI8KlTtlj2KeDoKIE/kO15+a59HPJx6rf3q08LFuK5DyEzvEjW6MiF27f80n9xRVdGrlOeyWeVyOZWCZQvEzUbI86eloZ57HDTXqf1pQKBgF6T6xeJgZ0Hpgc/AU75oWEk1kfQC6yrr2CCKUv7esA4mtlUOo1RbRH48MK2snWh4sdIEGj9NbjoXk6jCim0OQ85+ZW0uKJOp8tyG8baeGyt23GqrzgxBxpUvjMBQAxsnKSFZBnfPGEywX+4syEbob9btq54gTaOncAQ9jmmBxQFAoGBAIpPbq2lYwOhgoUJ2BR34xjpmNOiOAF5AVLPGTH44a+iGMJ4tbF9AvfL4xsCWK9zMi3ExaKVN0lNn0cWx2lpXxwO6B+l4L//eczmHx4h1eLJd6ZWyTj7lq+RBOOUgHLKEssZfJ11RYTZjSD7s75JZteM2OFw7BVRRNgw387A3mj6"
publicKey = "MIIBCgKCAQEA0w4XGS1eEO9gAtWoB0E1vi1QH3xZAiHnkzZMhZRJOKeZhNUb9nmPzrGtCFD1c+to9/hxKnWZnRi1dklRGI4uXaB7PKuDhifHarBTTPBzW/8m+YqKEwjT2XWYvnG1Zeek4a45xze5cHhLA7Ow1Lwgy0u1rhalvz8GbCa9A7ZHKvZtIJJzfPSIV6gZIz5b7+v7rXZzMNNxvC7m+cwtvvERPjhJoj3O7ithcgdiT3JkZd1fZxkA6HCJx1I+TElt4qA9WnV+rqQwjka1gxBO497c0MUq+4Tx+lLGKpb61RPja4+9wiLFvEiS80WyZYkptWlA0Z5mhsxURs/OqaMyVXzbhwIDAQAB"
key = "5f42e758a38cc003c5da7cee814ddfd5"
baseURL = "https://gateway.dev.cdlsxd.cn"
signType = core.SignRSA
)
func newCore() *Key {
c, _ := core.NewCore(&core.Config{
AppID: appId,
PrivateKey: privateKey,
PublicKey: publicKey,
Key: key,
SignType: signType,
BaseURL: baseURL,
})
k := Key{c}
return &k
}
func TestOrder(t *testing.T) {
c := newCore()
h, _, r, err := c.Order(context.Background(), &OrderRequest{
OutBizNo: "cs002",
ActivityNo: "lzmcs",
Number: 1,
NotifyUrl: "",
Account: "",
Extra: "",
})
if err != nil {
t.Error(err)
return
}
if !r.IsSuccess() {
t.Errorf("获取key失败:%s", r.Message)
return
}
t.Logf("data=%s", string(r.Data))
t.Logf("headers=%+v", h)
}
func TestQuery(t *testing.T) {
c := newCore()
h, _, r, err := c.Query(context.Background(), &QueryRequest{
OutBizNo: "cs001",
TradeNo: "",
})
if err != nil {
t.Error(err)
return
}
if !r.IsSuccess() {
t.Errorf("查询失败:%s", r.Message)
return
}
t.Logf("data=%s", string(r.Data))
t.Logf("headers=%+v", h)
//t.Log(result.Status.IsNormal())
}
func TestDiscard(t *testing.T) {
c := newCore()
h, _, r, err := c.Discard(context.Background(), &DiscardRequest{
OutBizNo: "N123456003",
TradeNo: "",
Reason: "正常作废",
})
if err != nil {
t.Error(err)
return
}
if !r.IsSuccess() {
t.Errorf(r.Message)
return
}
t.Logf("headers=%+v", h)
t.Logf("respons=%+v", r)
}
func TestResponse(t *testing.T) {
jsonBytes := []byte(`{"code":200,"data":{},"message":"成功"}`)
resp, err := core.BuildResponse(jsonBytes)
if err != nil {
t.Error(err)
return
}
result, err := ConvertData(resp.Data)
if err != nil {
t.Error(err)
return
}
t.Logf("%+v", result)
}
func TestCallBack(t *testing.T) {
reqStr := `{"data": {"url": "https://gateway.dev.cdlsxd.cn/yxh5/dpK5ly6oVVE2AM0W", "status": 2, "account": "18479266021", "trade_no": "794167617429315585", "notify_id": "7345294954732199936", "usage_num": 0, "out_biz_no": "0627002001", "usable_num": 2, "usage_time": "2025-06-30 11:39:46", "valid_end_time": "2026-06-30 23:59:59", "settlement_price": 2, "valid_begin_time": "2025-06-24 11:00:08"}, "sign": "XLwRQ12EBXSGOSVzUMXwjSlKP88P4Odhe6c9MrfaszKLe+3HtPTeB6QWvyAmXGeIvsy02P0YtcOYV4xQHlWo3Uh5FZc6IJU/+KN+xVnn/DlFLpc+DhCKw6o4hYv+eLLyshjFZPZYVUU2I2YmkI1ZlwBaufsB+N9ds8gBz5+hELn17/qcFcbO6pYOd2te7xmJSGKOAMn0q2c2DSvTLvyQXhKUlDZfUZZGBOc1LGChy9CHc7Z/0E8/p2YYTlMPnvk0VHjEjV5sJxDnXwhSZqE7f3mRx0IN3au3VtZnXJsgl/whxdTyab9dYpfIxK75bS0mjncdqxGf1hLdhYJhTx8bog==", "app_id": "lzm", "sign_type": "RSA", "timestamp": "2025-06-30 11:39:46"}`
headerStr := `{"Sign": ["Boj6IrOOrRATJt0IBE+z5Ie/g4mo3MZk+JpJ4bLYoBbDfMqvgTBhxqiC8CheRm/nEF9iFFJCvq9S0dL25fLexQ1k5AxE3cX1+qR5fCRdaiZvqWG4jaXOjUUW8K7fQ9g5ii6T4b3cWp71FBHiG3ZH5XohM9JuLo3W17MxrizsLLD0euGROAY3bXcakVustto07V3i0g59+ajsCTTdxF/gNcrsO5a3eTJ8CTSDnMgpwqMbU+E9YMX1zGFH/+m/RtL9s8tLRf8j4T/t8g6b94JfvBv+Fu1wV4eMUO7H4Iv0LJ1TL8qMBkWul5BbwGxSdEGQoWU0CIAehYTfR5meKxTOTQ=="], "Appid": ["lzm"], "Version": ["1.0"], "Sign-Type": ["RSA"], "Timestamp": ["2025-06-30 11:39:46"], "Content-Type": ["application/json"]}`
var headers http.Header
if err := json.Unmarshal([]byte(headerStr), &headers); err != nil {
t.Error(err)
return
}
c := newCore()
request := &http.Request{
Header: headers,
Body: io.NopCloser(bytes.NewBuffer([]byte(reqStr))),
}
r, err := c.CallBack(context.Background(), request)
if err != nil {
t.Error(err)
return
}
t.Logf("response=%+v", r)
}

212
api/v1/key/models.go Normal file
View File

@ -0,0 +1,212 @@
package key
import (
"encoding/json"
"fmt"
"github.com/go-playground/validator/v10"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
)
var _ core.Request = (*OrderRequest)(nil)
var _ core.Request = (*DiscardRequest)(nil)
var _ core.Request = (*QueryRequest)(nil)
var _ core.Request = (*NotifyData)(nil)
type OrderRequest struct {
OutBizNo string `validate:"required,alphanum,min=2,max=32" json:"out_biz_no"` // 同一商户应用下不可重复
ActivityNo string `validate:"required,min=2,max=32" json:"activity_no"`
Number int32 `validate:"required,eq=1" json:"number"` // v1只支持1若要多个请异步v2接口批量生产
NotifyUrl string `json:"notify_url,omitempty"` // 回调地址,可为空
Account string `json:"account,omitempty"` // 可兑换账号
Extra string `json:"extra,omitempty"` // 拓展参数,备用
}
func (a *OrderRequest) String() (string, error) {
b, err := json.Marshal(a)
if err != nil {
return "", err
}
return string(b), nil
}
func (c *OrderRequest) Validate() error {
if err := validator.New().Struct(c); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return fmt.Errorf(err.Error())
}
}
return nil
}
type QueryRequest struct {
OutBizNo string `json:"out_biz_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 同一商户应用下不可重复
TradeNo string `json:"trade_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 若不为空,则优先使用
}
func (a *QueryRequest) String() (string, error) {
b, err := json.Marshal(a)
if err != nil {
return "", err
}
return string(b), nil
}
func (q *QueryRequest) Validate() error {
if q.OutBizNo == "" && q.TradeNo == "" {
return fmt.Errorf("参数错误,out_biz_no/trade_no 二选一")
}
if err := validator.New().Struct(q); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return fmt.Errorf(err.Error())
}
}
return nil
}
type DiscardRequest struct {
OutBizNo string `json:"out_biz_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 同一商户应用下不可重复
TradeNo string `json:"trade_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 若不为空,则优先使用
Reason string `json:"reason,omitempty" validate:"omitempty,min=1,max=50"` // 可为空
}
func (a *DiscardRequest) String() (string, error) {
b, err := json.Marshal(a)
if err != nil {
return "", err
}
return string(b), nil
}
func (d *DiscardRequest) Validate() error {
if d.OutBizNo == "" && d.TradeNo == "" {
return fmt.Errorf("out_biz_no/trade_no 二选一")
}
if err := validator.New().Struct(d); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return fmt.Errorf(err.Error())
}
}
return nil
}
type NotifyData struct {
NotifyId string `json:"notify_id" validate:"required,alphanum,min=2,max=32"`
OutBizNo string `json:"out_biz_no" validate:"required,alphanum,min=2,max=32"`
TradeNo string `json:"trade_no" validate:"required,alphanum,min=2,max=32"`
Key string `json:"key,omitempty"`
UsableNum uint32 `json:"usable_num"`
UsageNum uint32 `json:"usage_num"`
Status Status `json:"status" validate:"required"`
Url string `json:"url,omitempty"`
Amount float32 `json:"amount,omitempty"`
PayAmount float32 `json:"pay_amount,omitempty"`
PayTime string `json:"pay_time,omitempty"`
SettlementPrice float32 `json:"settlement_price,omitempty"`
ValidBeginTime string `json:"valid_begin_time,omitempty"`
ValidEndTime string `json:"valid_end_time,omitempty"`
UsageTime string `json:"usage_time,omitempty"`
DiscardTime string `json:"discard_time,omitempty"`
Account string `json:"account,omitempty"` // 可兑换账号
}
type Notify struct {
AppId string `json:"app_id" validate:"required"`
SignType string `json:"sign_type" validate:"required"`
Timestamp string `json:"timestamp" validate:"required"`
Sign string `json:"sign" validate:"required"`
Data NotifyData `json:"data"`
}
func (d *NotifyData) Validate() error {
if err := validator.New().Struct(d); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return fmt.Errorf(err.Error())
}
}
return nil
}
func (a *NotifyData) String() (string, error) {
b, err := json.Marshal(a)
if err != nil {
return "", err
}
return string(b), nil
}
func (d *Notify) Validate() error {
if err := validator.New().Struct(d); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return fmt.Errorf(err.Error())
}
}
return nil
}
func (a *Notify) String() string {
b, err := json.Marshal(a)
if err != nil {
return ""
}
return string(b)
}
func (a *Notify) SignString() string {
b, err := json.Marshal(a.Data)
if err != nil {
return ""
}
return a.AppId + a.Timestamp + string(b)
}
type Data struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Key string `json:"key"`
UsableNum uint32 `json:"usable_num"`
UsageNum uint32 `json:"usage_num"`
Status Status `json:"status"`
Url string `json:"url"`
Amount float32 `json:"amount,omitempty"`
PayAmount float32 `json:"pay_amount,omitempty"`
PayTime string `json:"pay_time,omitempty"`
SettlementPrice float32 `json:"settlement_price,omitempty"`
ValidBeginTime string `json:"valid_begin_time,omitempty"`
ValidEndTime string `json:"valid_end_time,omitempty"`
UsageTime string `json:"usage_time,omitempty"`
DiscardTime string `json:"discard_time,omitempty"`
Account string `json:"account,omitempty"` // 可兑换账号
}
func ConvertData(b []byte) (*Data, error) {
var data *Data
if err := json.Unmarshal(b, &data); err != nil {
return nil, err
}
return data, nil
}

45
api/v1/key/vo.go Normal file
View File

@ -0,0 +1,45 @@
package key
type Status uint8
const (
Normal Status = iota + 1
Used
Discard
Expire
)
var statusMap = map[Status]string{
Normal: "正常",
Used: "已核销",
Discard: "已作废",
Expire: "已过期",
}
func (s Status) Value() uint8 {
return uint8(s)
}
func (s Status) GetText() string {
t, ok := statusMap[s]
if !ok {
return ""
}
return t
}
func (s Status) IsNormal() bool {
return s == Normal
}
func (s Status) IsUsed() bool {
return s == Used
}
func (s Status) IsDiscard() bool {
return s == Discard
}
func (s Status) IsExpire() bool {
return s == Expire
}

45
api/v2/key.go Normal file
View File

@ -0,0 +1,45 @@
package v2
import (
"context"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"net/http"
)
const (
orderMethod = "/openapi/v2/key/order"
queryMethod = "/openapi/v2/key/query"
)
type Key api.Service
func (k *Key) Order(ctx context.Context, request *OrderRequest) (http.Header, *http.Response, *core.Response, error) {
h, httpResponse, bodyBytes, err := k.Post(ctx, orderMethod, request)
if err != nil {
return nil, nil, nil, err
}
res, err := core.BuildResponse(bodyBytes)
if err != nil {
return h, httpResponse, nil, err
}
return h, httpResponse, res, nil
}
func (k *Key) Query(ctx context.Context, request *QueryRequest) (http.Header, *http.Response, *core.Response, error) {
h, httpResponse, bodyBytes, err := k.Post(ctx, queryMethod, request)
if err != nil {
return nil, nil, nil, err
}
res, err := core.BuildResponse(bodyBytes)
if err != nil {
return h, httpResponse, nil, err
}
return h, httpResponse, res, nil
}

130
api/v2/key_test.go Normal file
View File

@ -0,0 +1,130 @@
package v2
import (
"context"
"encoding/json"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
"testing"
)
var (
appId = ""
privateKey = ""
publicKey = ""
key = ""
baseURL = ""
signType = core.SignRSA
)
func newCore() (*core.Core, error) {
return core.NewCore(&core.Config{
AppID: appId,
PrivateKey: privateKey,
PublicKey: publicKey,
Key: key,
SignType: signType,
BaseURL: baseURL,
})
}
func TestOrder(t *testing.T) {
c, err := newCore()
if err != nil {
t.Error(err)
return
}
a := &Key{c}
_, r, err := a.Order(context.Background(), &OrderRequest{
OutBizNo: "b202412270z8q7r1f704",
ActivityNo: "2024070901134",
Number: 1,
})
if err != nil {
t.Error(err)
return
}
t.Logf("response=%+v", r)
if !r.IsSuccess() {
t.Errorf("获取key失败:%s", r.Message)
return
}
}
func TestQuery(t *testing.T) {
c, err := newCore()
if err != nil {
t.Error(err)
return
}
a := &Key{c}
_, r, err := a.Query(context.Background(), &QueryRequest{
OutBizNo: "006",
TradeNo: "",
})
if err != nil {
t.Error(err)
return
}
t.Logf("response=%+v", r)
if !r.IsSuccess() {
t.Errorf("查询失败:%s", r.Message)
return
}
}
func TestNotify(t *testing.T) {
c, err := newCore()
if err != nil {
t.Error(err)
return
}
n := &Notify{
AppId: "",
SignType: "",
Timestamp: "",
Sign: "",
Data: NotifyData{
Event: "",
NotifyId: "",
OutBizNo: "",
TradeNo: "",
ActivityNo: "",
Number: 0,
Status: 0,
KeyMapCiphertext: "",
},
}
str, err := n.SignString()
if err != nil {
t.Error(err)
return
}
sign, err := c.CryptographySuite.Signer.Sign(str)
if err != nil {
t.Error(err)
return
}
n.Sign = sign
b := c.CryptographySuite.Verifier.Verify(str, sign)
if !b {
t.Error("验签失败")
return
}
keyMapDecode, err := c.CryptographySuite.Cipher.Decode(n.Data.KeyMapCiphertext)
if err != nil {
t.Error(err)
return
}
keyMap := make([]*KeyInfo, 0, n.Data.Number)
if err = json.Unmarshal([]byte(keyMapDecode), &keyMap); err != nil {
t.Error(err)
return
}
}

166
api/v2/models.go Normal file
View File

@ -0,0 +1,166 @@
package v2
import (
"encoding/json"
"fmt"
"github.com/go-playground/validator/v10"
)
type OrderRequest struct {
OutBizNo string `validate:"required,alphanum,min=2,max=32" json:"out_biz_no"` // 同一商户应用下不可重复
ActivityNo string `validate:"required,min=2,max=32" json:"activity_no"`
Number int32 `validate:"required,min=1,max=10000" json:"number"`
NotifyUrl string `json:"notify_url,omitempty"` // 回调地址,为空则使用客户应用设置地址
Extra string `json:"extra,omitempty"` // 拓展参数,备用
}
type OrderResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
}
func (a *OrderRequest) String() (string, error) {
b, err := json.Marshal(a)
if err != nil {
return "", err
}
return string(b), nil
}
func (c *OrderRequest) Validate() error {
if err := validator.New().Struct(c); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return fmt.Errorf(err.Error())
}
}
return nil
}
type QueryRequest struct {
OutBizNo string `json:"out_biz_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 同一商户应用下不可重复
TradeNo string `json:"trade_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 若不为空,则优先使用
}
type QueryResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Status Status `json:"status"`
Number int32 `json:"number"`
KeyMapCiphertext string `json:"ciphertext" validate:"required"`
}
type KeyInfo struct {
Key string `json:"key,omitempty"` // key码
Url string `json:"url,omitempty"` // 短链接
UsableNum uint32 `json:"usable_num"` // 可兑换次数
UsageNum uint32 `json:"usage_num"` // 已核销次数
Status KeyStatus `json:"status"` // 状态
BeginTime string `json:"begin_time"` // 开始时间
EndTime string `json:"end_time"` // 结束时间
Amount float32 `json:"amount,omitempty"` // 金额
PayAmount float32 `json:"pay_amount,omitempty"` // 支付金额
PayTime string `json:"pay_time,omitempty"` // 支付时间
SettlementPrice float32 `json:"settlement_price,omitempty"` // 结算价
UsageTime string `json:"usage_time,omitempty"` // 最后一次核销时间
DiscardTime string `json:"discard_time,omitempty"` // 作废时间
}
func (a *QueryRequest) String() (string, error) {
b, err := json.Marshal(a)
if err != nil {
return "", err
}
return string(b), nil
}
func (q *QueryRequest) Validate() error {
if q.OutBizNo == "" && q.TradeNo == "" {
return fmt.Errorf("参数错误,out_biz_no/trade_no 二选一")
}
if err := validator.New().Struct(q); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return fmt.Errorf(err.Error())
}
}
return nil
}
type Notify struct {
AppId string `json:"app_id" validate:"required"`
SignType string `json:"sign_type" validate:"required"`
Timestamp string `json:"timestamp" validate:"required"`
Sign string `json:"sign" validate:"required"`
Data NotifyData `json:"data" validate:"required"`
}
type NotifyData struct {
Event NotifyEvent `json:"event" validate:"required"`
NotifyId string `json:"notify_id" validate:"required"`
OutBizNo string `json:"out_biz_no" validate:"required"`
TradeNo string `json:"trade_no" validate:"required"`
ActivityNo string `json:"activity_no" validate:"required"`
Number int32 `json:"number" validate:"required"`
Status Status `json:"status" validate:"required"`
KeyMapCiphertext string `json:"ciphertext" validate:"required"`
}
func (d *Notify) Validate() error {
if err := validator.New().Struct(d); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return fmt.Errorf(err.Error())
}
}
return nil
}
func (a *Notify) String() (string, error) {
b, err := json.Marshal(a)
if err != nil {
return "", err
}
return string(b), nil
}
func (d *NotifyData) Validate() error {
if err := validator.New().Struct(d); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return fmt.Errorf(err.Error())
}
}
return nil
}
func (a *NotifyData) String() (string, error) {
b, err := json.Marshal(a)
if err != nil {
return "", err
}
return string(b), nil
}
func (a *Notify) SignString() (string, error) {
b, err := a.Data.String()
if err != nil {
return "", nil
}
return a.AppId + a.Timestamp + b, nil
}

97
api/v2/vo.go Normal file
View File

@ -0,0 +1,97 @@
package v2
const SuccessCode = 200
type Status uint8
const (
StatusIng Status = iota + 1
StatusSuccess
StatusFailed
)
var statusMap = map[Status]string{
StatusIng: "生成中",
StatusSuccess: "生成完成",
StatusFailed: "生成失败",
}
func (s Status) Value() uint8 {
return uint8(s)
}
func (s Status) GetText() string {
t, ok := statusMap[s]
if !ok {
return ""
}
return t
}
type NotifyEvent string
const (
NotifyEventKeyCreate NotifyEvent = "key.create"
NotifyEventKeyUsage NotifyEvent = "key.usage"
NotifyEventKeyDiscard NotifyEvent = "key.discard"
)
func (s NotifyEvent) Value() string {
return string(s)
}
func (s NotifyEvent) IsKeyCreate() bool {
return NotifyEventKeyCreate == s
}
func (s NotifyEvent) IsKeyUsage() bool {
return NotifyEventKeyUsage == s
}
func (s NotifyEvent) IsKeyDiscard() bool {
return NotifyEventKeyDiscard == s
}
type KeyStatus uint8
const (
KeyNormal KeyStatus = iota + 1
KeyUsed
KeyDiscard
KeyExpire
)
var keyStatusMap = map[KeyStatus]string{
KeyNormal: "正常",
KeyUsed: "已核销",
KeyDiscard: "已作废",
KeyExpire: "已过期",
}
func (s KeyStatus) Value() uint8 {
return uint8(s)
}
func (s KeyStatus) GetText() string {
t, ok := keyStatusMap[s]
if !ok {
return ""
}
return t
}
func (s KeyStatus) IsNormal() bool {
return s == KeyNormal
}
func (s KeyStatus) IsUsed() bool {
return s == KeyUsed
}
func (s KeyStatus) IsDiscard() bool {
return s == KeyDiscard
}
func (s KeyStatus) IsExpire() bool {
return s == KeyExpire
}

19
cmd/rsa/main.go Normal file
View File

@ -0,0 +1,19 @@
package main
import (
"fmt"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/rsa"
)
func main() {
n := rsa.NewGenerateKey()
err := n.SavePem("../../pem")
if err != nil {
panic(err)
}
privateKeyStr, publicKeyStr := n.GetKey()
fmt.Println("privateKeyStr=", privateKeyStr)
fmt.Println("publicKeyStr=", publicKeyStr)
fmt.Println("aesKey=", rsa.GenerateAesKey())
}

20
cmd/sm/main.go Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"fmt"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/sm"
)
func main() {
priK, pubK, err := sm.GenerateKey()
if err != nil {
panic(err)
}
fmt.Println("privateKeyStr=", priK)
fmt.Println("publicKeyStr=", pubK)
sm4key, err := sm.GenerateSM4Key()
if err != nil {
panic(err)
}
fmt.Println("sm4key=", sm4key)
}

90
core/config.go Normal file
View File

@ -0,0 +1,90 @@
package core
import (
"fmt"
"github.com/go-playground/validator/v10"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/rsa"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/sm"
)
type SignType string
const (
SignRSA SignType = "RSA"
SignSM SignType = "SM"
)
// Config merchant app Config
type Config struct {
AppID string `validate:"required"`
PrivateKey string `validate:"required"`
PublicKey string `validate:"required"`
Key string `validate:"required"`
SignType SignType `validate:"required"`
BaseURL string `validate:"required,url"`
}
func (c *Config) Validate() error {
if err := validator.New().Struct(c); err != nil {
for _, err = range err.(validator.ValidationErrors) {
return err
}
}
return nil
}
func (s SignType) IsRSA() bool {
return s == SignRSA
}
func (s SignType) IsSM() bool {
return s == SignSM
}
func (c *Config) CryptographySuite() (*CryptographySuite, error) {
if c.SignType.IsRSA() {
return c.CryptographySuiteRSA()
}
if c.SignType.IsSM() {
return c.CryptographySuiteSM()
}
return nil, fmt.Errorf("[%s] invalid sign type", c.SignType)
}
func (c *Config) CryptographySuiteRSA() (*CryptographySuite, error) {
prk, err := rsa.PrivateKeyRSA(c.PrivateKey)
if err != nil {
return nil, err
}
puk, err := rsa.PublicKeyRSA(c.PublicKey)
if err != nil {
return nil, err
}
return &CryptographySuite{
Signer: &RsaSigner{privateKey: prk},
Verifier: &RsaVerifier{publicKey: puk},
Cipher: &RsaEncodeDecode{key: c.Key},
}, nil
}
func (c *Config) CryptographySuiteSM() (*CryptographySuite, error) {
prk, err := sm.PrivateKeySM(c.PrivateKey)
if err != nil {
return nil, err
}
puk, err := sm.PublicKeySM(c.PublicKey)
if err != nil {
return nil, err
}
return &CryptographySuite{
Signer: &SmSigner{privateKey: prk},
Verifier: &SmVerifier{publicKey: puk},
Cipher: &SmEncodeDecode{key: c.Key},
}, nil
}

249
core/core.go Normal file
View File

@ -0,0 +1,249 @@
package core
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils"
"io"
"net/http"
"time"
)
// Params request params
type Params struct {
// AppId app id
AppId string `json:"app_id"`
// SignType sign type
SignType SignType `json:"sign_type"`
// Timestamp 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss"
Timestamp string `json:"timestamp"`
// Sign
Sign string `json:"sign"`
// Ciphertext
Ciphertext string `json:"ciphertext"`
}
// Core structure
type Core struct {
// HttpClient http client
HttpClient *http.Client
// Config config
Config *Config
// CryptographySuite
CryptographySuite *CryptographySuite
}
type Option func(*Core)
// WithHttpClient sets the http client
func WithHttpClient(client *http.Client) Option {
return func(s *Core) {
s.HttpClient = client
}
}
// NewCore creates a new Core instance
func NewCore(c *Config, o ...Option) (*Core, error) {
if err := c.Validate(); err != nil {
return nil, err
}
core := &Core{
HttpClient: http.DefaultClient,
Config: c,
}
for _, f := range o {
f(core)
}
crs, err := c.CryptographySuite()
if err != nil {
return nil, err
}
core.CryptographySuite = crs
return core, nil
}
// GetBizSignStr gets the biz sign str Go 版本是 1.15 或更高版本
func (c *Core) GetBizSignStr(request any) (plaintext string, err error) {
kvs := utils.SortStruct(request)
kvm := make(map[string]any, len(kvs))
var order []string
for _, kv := range kvs {
kvm[kv.Key] = kv.Value
order = append(order, kv.Key)
}
orderedMap := make(map[string]any)
for _, key := range order {
orderedMap[key] = kvm[key]
}
// 将 orderedMap 转换成 JSON 字符串,保持顺序
kvmBytes, err := json.Marshal(orderedMap)
if err != nil {
return "", err
}
return string(kvmBytes), nil
}
// GetCiphertext gets the ciphertext
func (c *Core) GetCiphertext(request any) (string, error) {
plaintext, err := c.GetBizSignStr(request)
if err != nil {
return "", err
}
ciphertext, err := c.CryptographySuite.Cipher.Encode(plaintext)
if err != nil {
return "", err
}
return ciphertext, nil
}
// BuildParams gets the params
func (c *Core) BuildParams(request Request) (*Params, error) {
if err := request.Validate(); err != nil {
return nil, err
}
ciphertext, err := c.GetCiphertext(request)
if err != nil {
return nil, err
}
timestamps := time.Now().Format(time.DateTime)
dataToSign := c.Config.AppID + timestamps + ciphertext
signature, err := c.CryptographySuite.Signer.Sign(dataToSign)
if err != nil {
return nil, err
}
return &Params{
AppId: c.Config.AppID,
SignType: c.Config.SignType,
Timestamp: timestamps,
Sign: signature,
Ciphertext: ciphertext,
}, nil
}
// BuildAnyApiParams gets the params
func (c *Core) BuildAnyApiParams(bizContent any) (*Params, error) {
ciphertext, err := c.GetCiphertext(bizContent)
if err != nil {
return nil, err
}
timestamps := time.Now().Format(time.DateTime)
dataToSign := c.Config.AppID + timestamps + ciphertext
signature, err := c.CryptographySuite.Signer.Sign(dataToSign)
if err != nil {
return nil, err
}
return &Params{
AppId: c.Config.AppID,
SignType: c.Config.SignType,
Timestamp: timestamps,
Sign: signature,
Ciphertext: ciphertext,
}, nil
}
// Verify verifies the params
func (c *Core) Verify(timestamp, ciphertext, sign string) bool {
dataToSign := c.Config.AppID + timestamp + ciphertext
return c.CryptographySuite.Verifier.Verify(dataToSign, sign)
}
func (c *Core) GetHeaders(p *Params) http.Header {
h := http.Header{}
h.Set("Content-Type", "application/json")
h.Set("Version", "1.0")
h.Set("Appid", c.Config.AppID)
h.Set("Sign-Type", string(c.Config.SignType))
h.Set("Timestamp", p.Timestamp)
h.Set("Sign", p.Sign)
return h
}
// GetRequestBody gets the request body
func (c *Core) GetRequestBody(_ context.Context, request Request) (http.Header, []byte, error) {
p, err := c.BuildParams(request)
if err != nil {
return nil, nil, err
}
h := c.GetHeaders(p)
reqBodyBytes, err := json.Marshal(p)
if err != nil {
return nil, nil, err
}
return h, reqBodyBytes, nil
}
// Post sends the request and Analysis the response
func (c *Core) Post(ctx context.Context, method string, request Request) (http.Header, *http.Response, []byte, error) {
h, reqBodyBytes, err := c.GetRequestBody(ctx, request)
if err != nil {
return nil, nil, nil, err
}
httpResponse, body, err := c.Request(ctx, h, http.MethodPost, c.Config.BaseURL+method, reqBodyBytes)
if err != nil {
return nil, nil, nil, err
}
return h, httpResponse, body, nil
}
// Request sends the request and Analysis the response
func (c *Core) Request(ctx context.Context, h http.Header, method, url string, body []byte) (*http.Response, []byte, error) {
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(body))
if err != nil {
return nil, nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
req.Header = h
resp, err := c.HttpClient.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("sending HTTP request failed: %w", err)
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("HTTP status code: %d", resp.StatusCode)
}
return resp, bodyBytes, nil
}

63
core/core_test.go Normal file
View File

@ -0,0 +1,63 @@
package core
import (
"net/http"
"testing"
"time"
)
func TestRSASignVerify(t *testing.T) {
c := Config{
AppID: "",
PrivateKey: "",
PublicKey: "",
Key: "",
SignType: SignRSA,
BaseURL: "http://127.0.0.1:9000",
}
httpClient := &http.Client{
Timeout: time.Second * 10,
}
core, err := NewCore(&c, WithHttpClient(httpClient))
if err != nil {
t.Error(err)
return
}
signStr := "123456{}测试"
signature, err := core.CryptographySuite.Signer.Sign(signStr)
if err != nil {
t.Error(err)
return
}
b := core.CryptographySuite.Verifier.Verify(signStr, signature)
if !b {
t.Error("验签失败")
}
}
func TestSMSignVerify(t *testing.T) {
c := Config{
AppID: "123456",
PrivateKey: "zJRUcwPpKFf4nWiN9wqSO9gpGFx5BP4WviqnPsrhkpc=",
PublicKey: "BKbxGVVlJGWK/ScU0ebKSe4Jr4LvcBGgvt/HHBk+ODVCYnJYvvmX8cDNpf3TVYuRdz/RUH6UDgcoVpz02jXNfrM=",
Key: "t+VxHnp+K9huhtNT84Pk7A==",
SignType: SignSM,
BaseURL: "http://127.0.0.1:9000",
}
core, err := NewCore(&c)
if err != nil {
t.Error(err)
return
}
signStr := "123456{}测试"
signature, err := core.CryptographySuite.Signer.Sign(signStr)
if err != nil {
t.Error(err)
return
}
b := core.CryptographySuite.Verifier.Verify(signStr, signature)
if !b {
t.Error("验签失败")
}
}

30
core/interface.go Normal file
View File

@ -0,0 +1,30 @@
package core
// Signer interfaces for signing data
type Signer interface {
Sign(data string) (string, error)
}
// Verifier interfaces for verifying signatures
type Verifier interface {
Verify(data, signature string) bool
}
// Cipher interfaces for Encode or Decode request
type Cipher interface {
Encode(plaintext string) (string, error)
Decode(ciphertext string) (string, error)
}
// CryptographySuite .
type CryptographySuite struct {
Signer Signer
Verifier Verifier
Cipher Cipher
}
// Request interfaces for request
type Request interface {
String() (string, error)
Validate() error
}

28
core/response.go Normal file
View File

@ -0,0 +1,28 @@
package core
import (
"encoding/json"
)
const SuccessCode = 200
type Response struct {
Code int32 `json:"code"`
Message string `json:"message"`
Reason string `json:"reason,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
func BuildResponse(b []byte) (*Response, error) {
var resp Response
if err := json.Unmarshal(b, &resp); err != nil {
return nil, err
}
return &resp, nil
}
func (a *Response) IsSuccess() bool {
return a.Code == SuccessCode
}

41
core/rsa.go Normal file
View File

@ -0,0 +1,41 @@
package core
import (
"crypto/rsa"
sdkrsa "github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/rsa"
)
// RsaSigner for RSA signing
type RsaSigner struct {
privateKey *rsa.PrivateKey
}
// RsaVerifier for RSA verification
type RsaVerifier struct {
publicKey *rsa.PublicKey
}
// RsaEncodeDecode .
type RsaEncodeDecode struct {
key string
}
func (r *RsaSigner) Sign(data string) (string, error) {
return sdkrsa.Sign(data, r.privateKey)
}
func (r *RsaVerifier) Verify(data, signature string) bool {
b, err := sdkrsa.Verify(data, signature, r.publicKey)
if err != nil {
return false
}
return b
}
func (r *RsaEncodeDecode) Encode(plaintext string) (string, error) {
return sdkrsa.Encode(r.key, plaintext), nil
}
func (r *RsaEncodeDecode) Decode(data string) (string, error) {
return sdkrsa.Decode(r.key, data), nil
}

41
core/sm.go Normal file
View File

@ -0,0 +1,41 @@
package core
import (
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/sm"
"github.com/tjfoc/gmsm/sm2"
)
// SmSigner for SM signing (国密)
type SmSigner struct {
privateKey *sm2.PrivateKey
}
// SmVerifier for SM verification (国密)
type SmVerifier struct {
publicKey *sm2.PublicKey
}
// SmEncodeDecode .
type SmEncodeDecode struct {
key string
}
func (s *SmSigner) Sign(data string) (string, error) {
return sm.Sign(data, s.privateKey)
}
func (s *SmVerifier) Verify(data, signature string) bool {
b, err := sm.Verify(data, signature, s.publicKey)
if err != nil {
return false
}
return b
}
func (s *SmEncodeDecode) Encode(plaintext string) (string, error) {
return sm.Encode(s.key, []byte(plaintext))
}
func (s *SmEncodeDecode) Decode(ciphertext string) (string, error) {
return sm.Decode(s.key, ciphertext)
}

23
go.mod Normal file
View File

@ -0,0 +1,23 @@
module github.com/sleepinggodoflove/lansexiongdi-marketing-sdk
go 1.22.2
require (
github.com/go-playground/validator/v10 v10.22.1
github.com/stretchr/testify v1.9.0
github.com/tjfoc/gmsm v1.4.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

101
go.sum Normal file
View File

@ -0,0 +1,101 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

122
utils/rsa/aes.go Normal file
View File

@ -0,0 +1,122 @@
package rsa
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/hex"
)
func GenerateAesKey() string {
key := make([]byte, 16)
if _, err := rand.Read(key); err != nil {
panic(err)
}
return hex.EncodeToString(key)
}
// Encode 加密函数
func Encode(key, plaintext string) string {
// 创建AES加密器
block, err := aes.NewCipher([]byte(key))
if err != nil {
return ""
}
// PKCS7填充
blockSize := block.BlockSize()
padding := blockSize - len(plaintext)%blockSize
padText := string(byte(padding))
for i := 0; i < padding; i++ {
plaintext += padText
}
// 创建ECB模式加密器
mode := NewECEncrypted(block)
// 计算加密后数据的长度
encrypted := make([]byte, len(plaintext))
mode.CryptBlocks(encrypted, []byte(plaintext))
// Base64编码
return base64.StdEncoding.EncodeToString(encrypted)
}
// Decode 解密函数
func Decode(key, code string) string {
// Base64解码
encryptString, err := base64.StdEncoding.DecodeString(code)
if err != nil {
return ""
}
// 创建AES解密器
block, err := aes.NewCipher([]byte(key))
if err != nil {
return ""
}
// 创建ECB模式解密器
mode := NewECDecrypted(block)
// 解密
decrypted := make([]byte, len(encryptString))
mode.CryptBlocks(decrypted, encryptString)
// 去除填充
padding := decrypted[len(decrypted)-1]
return string(decrypted[:len(decrypted)-int(padding)])
}
// ecEncrypted ECB加密器
type ecEncrypted struct {
b cipher.Block
blockSize int
}
func NewECEncrypted(b cipher.Block) cipher.BlockMode {
return &ecEncrypted{b, b.BlockSize()}
}
func (x *ecEncrypted) BlockSize() int { return x.blockSize }
func (x *ecEncrypted) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("src not full blocks")
}
if len(dst) < len(src) {
panic("output smaller than input")
}
for len(src) > 0 {
x.b.Encrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
// ECB解密器
type ecDecrypted struct {
b cipher.Block
blockSize int
}
func NewECDecrypted(b cipher.Block) cipher.BlockMode {
return &ecDecrypted{b, b.BlockSize()}
}
func (x *ecDecrypted) BlockSize() int { return x.blockSize }
func (x *ecDecrypted) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("src not full blocks")
}
if len(dst) < len(src) {
panic("output smaller than input")
}
for len(src) > 0 {
x.b.Decrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}

31
utils/rsa/aes_test.go Normal file
View File

@ -0,0 +1,31 @@
package rsa
import "testing"
func TestGenerateAesKey(t *testing.T) {
s := GenerateAesKey()
t.Log(s)
}
func TestEncode(t *testing.T) {
key := "870abfc720f86ce2c5e4d3345741d48d"
str := "123yie一二三"
e := Encode(key, str)
t.Log(e)
}
func TestDecode(t *testing.T) {
key := "870abfc720f86ce2c5e4d3345741d48d"
e := "xxxx"
d := Decode(key, e)
t.Log(d)
}
func TestEncodeDecode(t *testing.T) {
key := "870abfc720f86ce2c5e4d3345741d48d"
str := "123yie一二三"
e := Encode(key, str)
t.Log(e)
d := Decode(key, e)
t.Log(d)
}

30
utils/rsa/cipher.go Normal file
View File

@ -0,0 +1,30 @@
package rsa
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"fmt"
)
func Cipher(publicKey *rsa.PublicKey, plaintext []byte) (string, error) {
ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, plaintext, nil)
if err != nil {
return "", fmt.Errorf("error encrypting data: %v", err)
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
func Plain(privateKey *rsa.PrivateKey, ciphertext string) (string, error) {
ciphertextBytes, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", fmt.Errorf("error decoding base64: %v", err)
}
decryptedData, err := privateKey.Decrypt(nil, ciphertextBytes, &rsa.OAEPOptions{Hash: crypto.SHA256})
if err != nil {
return "", fmt.Errorf("error decrypting data: %v", err)
}
return string(decryptedData), nil
}

43
utils/rsa/cipher_test.go Normal file
View File

@ -0,0 +1,43 @@
package rsa
import (
"testing"
)
func TestCipher(t *testing.T) {
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
pukRsa, err := PublicKeyRSA(publicKeyStr)
if err != nil {
t.Fatal(err)
}
plaintext := []byte("123一二三{\"key\":\"value\"}")
cipherStr, err := Cipher(pukRsa, plaintext)
if err != nil {
t.Fatal(err)
}
t.Log(cipherStr)
}
func TestPlain(t *testing.T) {
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
pukRsa, err := PublicKeyRSA(publicKeyStr)
if err != nil {
t.Fatal(err)
}
plaintext := []byte("123一二三{\"key\":\"value\"}")
cipherStr, err := Cipher(pukRsa, plaintext)
if err != nil {
t.Fatal(err)
}
t.Log(cipherStr)
rsaPrivateKey := "MIIEpQIBAAKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQABAoIBAQCQkaXi3y8YWrdWvCwkUN0/fWkJmLtExn2Dmpu/wsEf9iQurVvo1SheY9JG+fUQa7bsAQuXRntNF/GgpsGsT+HXezwckog4Q7gSk066LZY8IKZUsKXXkeH4H5hbKUFsrcGIf4n+GoCKNglGmPUkjsq68kVmEn8Y1FF6rpU5n2P40xEKAieKxlM2JNwR22DQYRw3iw4PcMAD88nKx9OBUwGig8MQnUka7OCZk9fNLdwBT0VfgCzRdvyBCDieif4vB7TnMmvYlr6wWOMi2Ad9ccY2wTlVOUyHoC6BZ72FgOYyfnAmEZbDChCNTEDNNj0m056slCTMO+nIVUqMip4imgiBAoGBAOWI84+4dRA+gm6xynXsp9TAto43/DmbohHrUWRE2tSGqPevBz2i0c5AOaNUdxtzoEWOj260Zdzf3gRhv/iH3Mgp2+R2+cJ1QYoaX/2auJ50dPJf1SHZrKVIYhYqqlIWc8jQo8XBK/Ys/Lf+N2We2EsMLMtUUMH9OZ+20XtlxQBZAoGBAPAYGVOzJnqFuSqxFzAA7VXP6p9WKxGEzbDUCxFaLj751DnvogI8FczAE6ADBxNYVkeQYtvqzMb9nTollOL6+/T9MUJn3DXTT8/St+REVdWvmO9Az9nGVDkLGjz0CH0iMjN49iSMFsdmo6L2528kUOj20dPh5IkzyWykYqni+fwlAoGBAMa1A51U40rXwpTPp2TlJfnBh4ihIOJCQFDg9YonLYY0uUwKousR7C1wXjVuJtqGA6aTnsoIs/I9f3ctpEIkY9aInksvUFKurbk/0f+7FL5gNOmqWtk+Fv7TJc7oyp/bvgqHzG+jJkqscW9bTVvU4ow9kv3HFU6KyHriioEX/i6pAoGBAJs5yW4e3lrKh/u9ANPNVaRsRzF64V9zMBUKEpnGZy3aEcbfUiwFssZszINgUbvFGgsso22xcXGZ2IQWdhsFz84FwEpBodK+6tPfVXrkX2ZHICZXDcqrehpjPjR4ReC5MiGrK+BXHgcPKe6bmOd3YEQuB1zop/u4mpp98TgLAjptAoGAF1lgcmwnHDduvWErwSmMR3w45FppV0pOdSEmv/6DU6iglzleSuuCm4fhXAEgSahJMKiflNx1ufl84IGir0r/we8GNfs4ceSPlyKDYnaBG19f5jWLLNFTWfwVhZqa85pd9b2dcqVWMazHq8psmyt0opmctRcM9s4lNq2OmvB7vM4="
prkRsa, err := PrivateKeyRSA(rsaPrivateKey)
if err != nil {
t.Fatal(err)
}
plainStr, err := Plain(prkRsa, cipherStr)
if err != nil {
t.Fatal(err)
}
t.Log(plainStr)
}

59
utils/rsa/generate.go Normal file
View File

@ -0,0 +1,59 @@
package rsa
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"log"
"os"
)
type Generate struct {
publicKeyBytes []byte
privateKeyBytes []byte
}
func NewGenerateKey() *Generate {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}
return &Generate{
publicKeyBytes: x509.MarshalPKCS1PublicKey(&privateKey.PublicKey),
privateKeyBytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
}
func (g *Generate) SavePem(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
if err = os.MkdirAll(path, os.ModePerm); err != nil {
return err
}
}
privateKeyFile, err := os.Create(path + "/private.pem")
if err != nil {
return err
}
defer privateKeyFile.Close()
if err = pem.Encode(privateKeyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: g.privateKeyBytes}); err != nil {
return err
}
publicKeyFile, err := os.Create(path + "/public.pem")
if err != nil {
return err
}
defer publicKeyFile.Close()
return pem.Encode(publicKeyFile, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: g.publicKeyBytes})
}
func (g *Generate) GetKey() (privateKeyStr, publicKeyStr string) {
privateKeyStr = base64.StdEncoding.EncodeToString(g.privateKeyBytes)
publicKeyStr = base64.StdEncoding.EncodeToString(g.publicKeyBytes)
return
}

View File

@ -0,0 +1,29 @@
package rsa
import "testing"
func TestGenerate(t *testing.T) {
n := NewGenerateKey()
err := n.SavePem("../../pem")
if err != nil {
t.Fatal(err)
}
privateKeyStr, publicKeyStr := n.GetKey()
t.Log("privateKeyStr=", privateKeyStr)
t.Log("publicKeyStr=", publicKeyStr)
}
func TestGenerateSavePem(t *testing.T) {
n := NewGenerateKey()
err := n.SavePem("../../pem")
if err != nil {
t.Fatal(err)
}
}
func TestGenerateGetKey(t *testing.T) {
n := NewGenerateKey()
privateKeyStr, publicKeyStr := n.GetKey()
t.Log("privateKeyStr=", privateKeyStr)
t.Log("publicKeyStr=", publicKeyStr)
}

75
utils/rsa/pem.go Normal file
View File

@ -0,0 +1,75 @@
package rsa
import (
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"log"
)
func PrivateKeyRSA(privateKeyStr string) (*rsa.PrivateKey, error) {
privateKeyBytes, err := base64.StdEncoding.DecodeString(privateKeyStr)
if err != nil {
return nil, fmt.Errorf("解码 Base64 编码的 RSA 私钥字符串: %v", err)
}
privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBytes)
if err != nil {
return nil, fmt.Errorf("解析 RSA 私钥: %v", err)
}
return privateKey, nil
}
func PrivateKeyPem(privateKeyStr string) (string, error) {
privateKeyBytes, err := base64.StdEncoding.DecodeString(privateKeyStr)
if err != nil {
return "", fmt.Errorf("解码 Base64 编码的 RSA 私钥字符串: %v", err)
}
privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBytes)
if err != nil {
return "", fmt.Errorf("解析 RSA 私钥: %v", err)
}
privateKeyPEM := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
return string(pem.EncodeToMemory(privateKeyPEM)), nil
}
func PublicKeyRSA(publicKeyStr string) (*rsa.PublicKey, error) {
publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKeyStr)
if err != nil {
return nil, fmt.Errorf("解码 Base64 编码的 RSA 公钥字符串: %v", err)
}
publicKey, err := x509.ParsePKCS1PublicKey(publicKeyBytes)
if err != nil {
return nil, fmt.Errorf("解析 RSA 公钥: %v", err)
}
return publicKey, nil
}
func PublicKeyPem(publicKeyStr string) (string, error) {
publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKeyStr)
if err != nil {
return "", fmt.Errorf("解码 Base64 编码的 RSA 公钥字符串: %v", err)
}
publicKey, err := x509.ParsePKCS1PublicKey(publicKeyBytes)
if err != nil {
log.Fatal("解析 RSA 公钥:", err)
}
publicKeyPEM := &pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(publicKey),
}
return string(pem.EncodeToMemory(publicKeyPEM)), nil
}

21
utils/rsa/pem_test.go Normal file
View File

@ -0,0 +1,21 @@
package rsa
import "testing"
func TestPrivateKeyPem(t *testing.T) {
privateKeyStr := "MIIEpQIBAAKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQABAoIBAQCQkaXi3y8YWrdWvCwkUN0/fWkJmLtExn2Dmpu/wsEf9iQurVvo1SheY9JG+fUQa7bsAQuXRntNF/GgpsGsT+HXezwckog4Q7gSk066LZY8IKZUsKXXkeH4H5hbKUFsrcGIf4n+GoCKNglGmPUkjsq68kVmEn8Y1FF6rpU5n2P40xEKAieKxlM2JNwR22DQYRw3iw4PcMAD88nKx9OBUwGig8MQnUka7OCZk9fNLdwBT0VfgCzRdvyBCDieif4vB7TnMmvYlr6wWOMi2Ad9ccY2wTlVOUyHoC6BZ72FgOYyfnAmEZbDChCNTEDNNj0m056slCTMO+nIVUqMip4imgiBAoGBAOWI84+4dRA+gm6xynXsp9TAto43/DmbohHrUWRE2tSGqPevBz2i0c5AOaNUdxtzoEWOj260Zdzf3gRhv/iH3Mgp2+R2+cJ1QYoaX/2auJ50dPJf1SHZrKVIYhYqqlIWc8jQo8XBK/Ys/Lf+N2We2EsMLMtUUMH9OZ+20XtlxQBZAoGBAPAYGVOzJnqFuSqxFzAA7VXP6p9WKxGEzbDUCxFaLj751DnvogI8FczAE6ADBxNYVkeQYtvqzMb9nTollOL6+/T9MUJn3DXTT8/St+REVdWvmO9Az9nGVDkLGjz0CH0iMjN49iSMFsdmo6L2528kUOj20dPh5IkzyWykYqni+fwlAoGBAMa1A51U40rXwpTPp2TlJfnBh4ihIOJCQFDg9YonLYY0uUwKousR7C1wXjVuJtqGA6aTnsoIs/I9f3ctpEIkY9aInksvUFKurbk/0f+7FL5gNOmqWtk+Fv7TJc7oyp/bvgqHzG+jJkqscW9bTVvU4ow9kv3HFU6KyHriioEX/i6pAoGBAJs5yW4e3lrKh/u9ANPNVaRsRzF64V9zMBUKEpnGZy3aEcbfUiwFssZszINgUbvFGgsso22xcXGZ2IQWdhsFz84FwEpBodK+6tPfVXrkX2ZHICZXDcqrehpjPjR4ReC5MiGrK+BXHgcPKe6bmOd3YEQuB1zop/u4mpp98TgLAjptAoGAF1lgcmwnHDduvWErwSmMR3w45FppV0pOdSEmv/6DU6iglzleSuuCm4fhXAEgSahJMKiflNx1ufl84IGir0r/we8GNfs4ceSPlyKDYnaBG19f5jWLLNFTWfwVhZqa85pd9b2dcqVWMazHq8psmyt0opmctRcM9s4lNq2OmvB7vM4="
prkPem, err := PrivateKeyPem(privateKeyStr)
if err != nil {
t.Fatal(err)
}
t.Log(prkPem)
}
func TestPublicKeyPem(t *testing.T) {
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
pukPem, err := PublicKeyPem(publicKeyStr)
if err != nil {
t.Fatal(err)
}
t.Log(pukPem)
}

31
utils/rsa/sign.go Normal file
View File

@ -0,0 +1,31 @@
package rsa
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
)
func Sign(data string, privateKey *rsa.PrivateKey) (string, error) {
hashed := sha256.Sum256([]byte(data))
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:])
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(signature), nil
}
func Verify(data string, signature string, publicKey *rsa.PublicKey) (bool, error) {
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return false, err
}
hashed := sha256.Sum256([]byte(data))
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], signatureBytes)
if err != nil {
return false, nil
}
return true, nil
}

63
utils/rsa/sign_test.go Normal file
View File

@ -0,0 +1,63 @@
package rsa
import (
"testing"
)
func Test_Sign(t *testing.T) {
rsaPrivateKey := "MIIEpQIBAAKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQABAoIBAQCQkaXi3y8YWrdWvCwkUN0/fWkJmLtExn2Dmpu/wsEf9iQurVvo1SheY9JG+fUQa7bsAQuXRntNF/GgpsGsT+HXezwckog4Q7gSk066LZY8IKZUsKXXkeH4H5hbKUFsrcGIf4n+GoCKNglGmPUkjsq68kVmEn8Y1FF6rpU5n2P40xEKAieKxlM2JNwR22DQYRw3iw4PcMAD88nKx9OBUwGig8MQnUka7OCZk9fNLdwBT0VfgCzRdvyBCDieif4vB7TnMmvYlr6wWOMi2Ad9ccY2wTlVOUyHoC6BZ72FgOYyfnAmEZbDChCNTEDNNj0m056slCTMO+nIVUqMip4imgiBAoGBAOWI84+4dRA+gm6xynXsp9TAto43/DmbohHrUWRE2tSGqPevBz2i0c5AOaNUdxtzoEWOj260Zdzf3gRhv/iH3Mgp2+R2+cJ1QYoaX/2auJ50dPJf1SHZrKVIYhYqqlIWc8jQo8XBK/Ys/Lf+N2We2EsMLMtUUMH9OZ+20XtlxQBZAoGBAPAYGVOzJnqFuSqxFzAA7VXP6p9WKxGEzbDUCxFaLj751DnvogI8FczAE6ADBxNYVkeQYtvqzMb9nTollOL6+/T9MUJn3DXTT8/St+REVdWvmO9Az9nGVDkLGjz0CH0iMjN49iSMFsdmo6L2528kUOj20dPh5IkzyWykYqni+fwlAoGBAMa1A51U40rXwpTPp2TlJfnBh4ihIOJCQFDg9YonLYY0uUwKousR7C1wXjVuJtqGA6aTnsoIs/I9f3ctpEIkY9aInksvUFKurbk/0f+7FL5gNOmqWtk+Fv7TJc7oyp/bvgqHzG+jJkqscW9bTVvU4ow9kv3HFU6KyHriioEX/i6pAoGBAJs5yW4e3lrKh/u9ANPNVaRsRzF64V9zMBUKEpnGZy3aEcbfUiwFssZszINgUbvFGgsso22xcXGZ2IQWdhsFz84FwEpBodK+6tPfVXrkX2ZHICZXDcqrehpjPjR4ReC5MiGrK+BXHgcPKe6bmOd3YEQuB1zop/u4mpp98TgLAjptAoGAF1lgcmwnHDduvWErwSmMR3w45FppV0pOdSEmv/6DU6iglzleSuuCm4fhXAEgSahJMKiflNx1ufl84IGir0r/we8GNfs4ceSPlyKDYnaBG19f5jWLLNFTWfwVhZqa85pd9b2dcqVWMazHq8psmyt0opmctRcM9s4lNq2OmvB7vM4="
signStr := "123一二三{\"key\":\"value\"}"
prkRsa, err := PrivateKeyRSA(rsaPrivateKey)
if err != nil {
t.Fatal(err)
}
got, err := Sign(signStr, prkRsa)
if err != nil {
t.Fatalf("Sign() error = %v", err)
}
t.Log(got)
}
func Test_Verify(t *testing.T) {
signStr := "123一二三{\"key\":\"value\"}"
signature := "0vQLV1LKzcExH4Nc7U37OwOoq+3o2Sdz8dfV9mME99L9YiaM5LN9dv9CaxQI4NSTxDTbJ9iTrScHE5TyquKRcyeJCW1qBEYySoYhiAdF9TxhVcyXYgbz4oMfjTF0J0C78hKZUb+mZeoeq7hgUsOhMQwmbumjoxKM6Y/rsZHfJQSwyty7Z4jc6BkHr8IZzPUvDlQkmwcnk4EWwx0au47fKVGxdW8dD2Gf0vstYiDN0MSy7BZnWh1/RY0g3EnjmmO7NvSJFxdlOqLwA9HR2ch2Fot/dxot2nSPK2+pj8k1+vZ1+ga/Ee2hmrRG5gSVmesBiI79NVzgPsrGLAscupz2Mg=="
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
pukRsa, err := PublicKeyRSA(publicKeyStr)
if err != nil {
t.Fatal(err)
}
got, err := Verify(signStr, signature, pukRsa)
if err != nil {
t.Fatal(err)
}
if !got {
t.Fatal("Verify() error = false")
}
}
func Test_SignVerify(t *testing.T) {
rsaPrivateKey := "MIIEpQIBAAKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQABAoIBAQCQkaXi3y8YWrdWvCwkUN0/fWkJmLtExn2Dmpu/wsEf9iQurVvo1SheY9JG+fUQa7bsAQuXRntNF/GgpsGsT+HXezwckog4Q7gSk066LZY8IKZUsKXXkeH4H5hbKUFsrcGIf4n+GoCKNglGmPUkjsq68kVmEn8Y1FF6rpU5n2P40xEKAieKxlM2JNwR22DQYRw3iw4PcMAD88nKx9OBUwGig8MQnUka7OCZk9fNLdwBT0VfgCzRdvyBCDieif4vB7TnMmvYlr6wWOMi2Ad9ccY2wTlVOUyHoC6BZ72FgOYyfnAmEZbDChCNTEDNNj0m056slCTMO+nIVUqMip4imgiBAoGBAOWI84+4dRA+gm6xynXsp9TAto43/DmbohHrUWRE2tSGqPevBz2i0c5AOaNUdxtzoEWOj260Zdzf3gRhv/iH3Mgp2+R2+cJ1QYoaX/2auJ50dPJf1SHZrKVIYhYqqlIWc8jQo8XBK/Ys/Lf+N2We2EsMLMtUUMH9OZ+20XtlxQBZAoGBAPAYGVOzJnqFuSqxFzAA7VXP6p9WKxGEzbDUCxFaLj751DnvogI8FczAE6ADBxNYVkeQYtvqzMb9nTollOL6+/T9MUJn3DXTT8/St+REVdWvmO9Az9nGVDkLGjz0CH0iMjN49iSMFsdmo6L2528kUOj20dPh5IkzyWykYqni+fwlAoGBAMa1A51U40rXwpTPp2TlJfnBh4ihIOJCQFDg9YonLYY0uUwKousR7C1wXjVuJtqGA6aTnsoIs/I9f3ctpEIkY9aInksvUFKurbk/0f+7FL5gNOmqWtk+Fv7TJc7oyp/bvgqHzG+jJkqscW9bTVvU4ow9kv3HFU6KyHriioEX/i6pAoGBAJs5yW4e3lrKh/u9ANPNVaRsRzF64V9zMBUKEpnGZy3aEcbfUiwFssZszINgUbvFGgsso22xcXGZ2IQWdhsFz84FwEpBodK+6tPfVXrkX2ZHICZXDcqrehpjPjR4ReC5MiGrK+BXHgcPKe6bmOd3YEQuB1zop/u4mpp98TgLAjptAoGAF1lgcmwnHDduvWErwSmMR3w45FppV0pOdSEmv/6DU6iglzleSuuCm4fhXAEgSahJMKiflNx1ufl84IGir0r/we8GNfs4ceSPlyKDYnaBG19f5jWLLNFTWfwVhZqa85pd9b2dcqVWMazHq8psmyt0opmctRcM9s4lNq2OmvB7vM4="
signStr := "123一二三{\"key\":\"value\"}"
prkPem, err := PrivateKeyRSA(rsaPrivateKey)
if err != nil {
t.Fatal(err)
}
signature, err := Sign(signStr, prkPem)
if err != nil {
t.Fatalf("Sign() error = %v", err)
}
t.Log(signature)
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
pukRsa, err := PublicKeyRSA(publicKeyStr)
if err != nil {
t.Fatal(err)
}
got, err := Verify(signStr, signature, pukRsa)
if err != nil {
t.Fatal(err)
}
if !got {
t.Fatal("Verify() error = false")
}
}

29
utils/sm/cipher.go Normal file
View File

@ -0,0 +1,29 @@
package sm
import (
"crypto/rand"
"encoding/base64"
"github.com/tjfoc/gmsm/sm2"
)
// Cipher 使用公钥加密数据
func Cipher(publicKey *sm2.PublicKey, plaintext []byte) (string, error) {
ciphertext, err := sm2.Encrypt(publicKey, plaintext, rand.Reader, sm2.C1C2C3)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// Plain 使用私钥解密数据
func Plain(privateKey *sm2.PrivateKey, ciphertext string) (string, error) {
ciphertextBytes, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", err
}
decryptedText, err := sm2.Decrypt(privateKey, ciphertextBytes, sm2.C1C2C3)
if err != nil {
return "", err
}
return string(decryptedText), nil
}

33
utils/sm/cipher_test.go Normal file
View File

@ -0,0 +1,33 @@
package sm
import (
"testing"
)
func TestCipher(t *testing.T) {
}
func TestPlain(t *testing.T) {
data := "123456{}测试"
prkStr := "zJRUcwPpKFf4nWiN9wqSO9gpGFx5BP4WviqnPsrhkpc="
pukStr := "BKbxGVVlJGWK/ScU0ebKSe4Jr4LvcBGgvt/HHBk+ODVCYnJYvvmX8cDNpf3TVYuRdz/RUH6UDgcoVpz02jXNfrM="
prk, err := PrivateKeySM(prkStr)
if err != nil {
t.Fatal(err)
}
puk, err := PublicKeySM(pukStr)
if err != nil {
t.Fatal(err)
}
cs, err := Cipher(puk, []byte(data))
if err != nil {
t.Fatal(err)
}
t.Log(cs)
ps, err := Plain(prk, cs)
if err != nil {
t.Fatal(err)
}
t.Log(ps)
}

46
utils/sm/generate.go Normal file
View File

@ -0,0 +1,46 @@
package sm
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/tjfoc/gmsm/sm2"
"github.com/tjfoc/gmsm/x509"
)
// GenerateKey 生成公钥和私钥
func GenerateKey() (pri, puk string, err error) {
privateKey, err := sm2.GenerateKey(rand.Reader)
if err != nil {
return pri, puk, fmt.Errorf("秘钥生成失败:%v", err)
}
priKeyBytes, err := hex.DecodeString(x509.WritePrivateKeyToHex(privateKey))
pri = base64.StdEncoding.EncodeToString(priKeyBytes)
publicKeyBytes, err := hex.DecodeString(x509.WritePublicKeyToHex(&privateKey.PublicKey))
puk = base64.StdEncoding.EncodeToString(publicKeyBytes)
return
}
// GetPukByPrK 根据私钥得到公钥
func GetPukByPrK(prk string) (string, error) {
privateKeyBytes, err := base64.StdEncoding.DecodeString(prk)
if err != nil {
return "", fmt.Errorf("私钥base64解码失败:%v", err)
}
privateKey, err := x509.ReadPrivateKeyFromHex(hex.EncodeToString(privateKeyBytes))
if err != nil {
return "", fmt.Errorf("私钥hex解码失败:%v", err)
}
publicKeyBytes, err := hex.DecodeString(x509.WritePublicKeyToHex(&privateKey.PublicKey))
if err != nil {
return "", fmt.Errorf("公钥hex解码失败:%v", err)
}
return base64.StdEncoding.EncodeToString(publicKeyBytes), nil
}

28
utils/sm/generate_test.go Normal file
View File

@ -0,0 +1,28 @@
package sm
import (
"fmt"
"github.com/stretchr/testify/assert"
"testing"
)
func Test_GenerateKey(t *testing.T) {
priK, pubK, err := GenerateKey()
if err != nil {
t.Fatal(err)
}
t.Logf("\n私钥=%s\n公钥=%s\n", priK, pubK)
}
// Test_GetPukByPrK 根据私钥获取公钥
func Test_GetPukByPriK(t *testing.T) {
prk := "ZvMCTiG67qUyPN65fcgg+EHhy2W/fN+9ixBudcmfbuU="
puk, err := GetPukByPrK(prk)
if err != nil {
t.Fatal(err)
}
want := "BL6PopnKr/hhPkxgn700Li1hPGx2/J5y2dQ4BDPLKDXe1sS4JeIG8/W1B8AO7hBzi0bKTArti0E/HJJcR9WcH/I="
fmt.Printf("prk: %s \n", prk)
fmt.Printf("puk: %s \n", puk)
assert.Equal(t, puk, want)
}

37
utils/sm/pem.go Normal file
View File

@ -0,0 +1,37 @@
package sm
import (
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/tjfoc/gmsm/sm2"
"github.com/tjfoc/gmsm/x509"
)
func PrivateKeySM(privateKeyStr string) (*sm2.PrivateKey, error) {
keyBytes, err := base64.StdEncoding.DecodeString(privateKeyStr)
if err != nil {
return nil, fmt.Errorf("privateKey base64 decode failed: %v", err)
}
privateKey, err := x509.ReadPrivateKeyFromHex(hex.EncodeToString(keyBytes))
if err != nil {
return nil, fmt.Errorf("read private key from hex failed: %v", err)
}
return privateKey, nil
}
func PublicKeySM(publicKeyStr string) (*sm2.PublicKey, error) {
keyBytes, err := base64.StdEncoding.DecodeString(publicKeyStr)
if err != nil {
return nil, fmt.Errorf("publicKeyStr base64 decode failed: %v", err)
}
publicKey, err := x509.ReadPublicKeyFromHex(hex.EncodeToString(keyBytes))
if err != nil {
return nil, fmt.Errorf("read public key from hex failed: %v", err)
}
return publicKey, nil
}

28
utils/sm/sign.go Normal file
View File

@ -0,0 +1,28 @@
package sm
import (
"crypto/rand"
"encoding/base64"
"fmt"
"github.com/tjfoc/gmsm/sm2"
)
func Sign(data string, privateKey *sm2.PrivateKey) (string, error) {
signatureBytes, err := privateKey.Sign(rand.Reader, []byte(data), nil)
if err != nil {
return "", fmt.Errorf("sign failed: %v", err)
}
return base64.StdEncoding.EncodeToString(signatureBytes), nil
}
func Verify(data string, signature string, publicKey *sm2.PublicKey) (bool, error) {
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return false, fmt.Errorf("signature base64 decode failed: %v", err)
}
if !publicKey.Verify([]byte(data), signatureBytes) {
fmt.Println("Signature verification failed")
return false, nil
}
return true, nil
}

49
utils/sm/sign_test.go Normal file
View File

@ -0,0 +1,49 @@
package sm
import (
"github.com/stretchr/testify/assert"
"testing"
)
func Test_Sign(t *testing.T) {
data := "123456{}测试"
prkStr := "zJRUcwPpKFf4nWiN9wqSO9gpGFx5BP4WviqnPsrhkpc="
prk, err := PrivateKeySM(prkStr)
if err != nil {
t.Fatal(err)
}
signature, err := Sign(data, prk)
if err != nil {
t.Fatal(err)
}
t.Logf("signature=%s\n", signature)
}
func Test_Verify(t *testing.T) {
data := "123456{}测试"
prkStr := "zJRUcwPpKFf4nWiN9wqSO9gpGFx5BP4WviqnPsrhkpc="
pukStr := "BKbxGVVlJGWK/ScU0ebKSe4Jr4LvcBGgvt/HHBk+ODVCYnJYvvmX8cDNpf3TVYuRdz/RUH6UDgcoVpz02jXNfrM="
prk, err := PrivateKeySM(prkStr)
if err != nil {
t.Fatal(err)
}
puk, err := PublicKeySM(pukStr)
if err != nil {
t.Fatal(err)
}
signature, err := Sign(data, prk)
if err != nil {
t.Fatal(err)
}
t.Logf("signature=%s\n", signature)
b, err := Verify(data, signature, puk)
if err != nil {
t.Fatal(err)
}
if assert.True(t, b) {
t.Logf("Test_sign 验签-成功 %t\n", b)
} else {
t.Errorf("Test_sign 验签-失败 %t\n", b)
}
}

77
utils/sm/sm4.go Normal file
View File

@ -0,0 +1,77 @@
package sm
import (
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"github.com/tjfoc/gmsm/sm4"
)
func GenerateSM4Key() (string, error) {
key := make([]byte, sm4.BlockSize)
_, err := rand.Read(key)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(key), nil
}
func Encode(key string, plaintextBytes []byte) (string, error) {
d, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return "", err
}
cipherBlock, err := sm4.NewCipher(d)
if err != nil {
return "", err
}
blockSize := cipherBlock.BlockSize()
iv := make([]byte, blockSize)
for i := 0; i < blockSize; i++ {
iv[i] = 0
}
blockMode := cipher.NewCBCEncrypter(cipherBlock, iv)
padding := blockSize - len(plaintextBytes)%blockSize
for i := 0; i < padding; i++ {
plaintextBytes = append(plaintextBytes, byte(padding))
}
cipherText := make([]byte, len(plaintextBytes))
blockMode.CryptBlocks(cipherText, plaintextBytes)
return base64.StdEncoding.EncodeToString(cipherText), nil
}
func Decode(key, ciphertext string) (string, error) {
d, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return "", err
}
cipherBlock, err := sm4.NewCipher(d)
if err != nil {
return "", err
}
blockSize := cipherBlock.BlockSize()
iv := make([]byte, blockSize)
for i := 0; i < blockSize; i++ {
iv[i] = 0
}
cipherTextBytes, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", err
}
plainText := make([]byte, len(cipherTextBytes))
blockMode := cipher.NewCBCDecrypter(cipherBlock, iv)
blockMode.CryptBlocks(plainText, cipherTextBytes)
plainTextLen := len(plainText)
padding := int(plainText[plainTextLen-1])
buff := plainText[:plainTextLen-padding]
return string(buff), nil
}

53
utils/sm/sm4_test.go Normal file
View File

@ -0,0 +1,53 @@
package sm
import (
"testing"
)
func TestSM4GenerateKey(t *testing.T) {
key, err := GenerateSM4Key()
if err != nil {
t.Error(err)
return
}
t.Log(key)
}
func TestSM4(t *testing.T) {
key := "t+VxHnp+K9huhtNT84Pk7A=="
plaintextBytes := []byte("BZjU223ZBM7A8586Tm7P")
enc, err := Encode(key, plaintextBytes)
if err != nil {
t.Error(err)
return
}
t.Log(enc)
dec, err := Decode(key, enc)
if err != nil {
t.Error(err)
return
}
t.Log(dec)
}
func TestSM4KeyEncrypt(t *testing.T) {
key := "t+VxHnp+K9huhtNT84Pk7A=="
plaintextBytes := []byte("BZjU223ZBM7A8586Tm7P")
enc, err := Encode(key, plaintextBytes)
if err != nil {
t.Error(err)
return
}
t.Log(enc)
}
func TestSM4KeyPassDecrypt(t *testing.T) {
key := "t+VxHnp+K9huhtNT84Pk7A=="
ciphertext := "NwANcXkjX79873jenLJRGhbEr39eYOwC5WQxZFXmLpw="
dec, err := Decode(key, ciphertext)
if err != nil {
t.Error(err)
return
}
t.Log(dec)
}

45
utils/sort_struct.go Normal file
View File

@ -0,0 +1,45 @@
package utils
import (
"reflect"
"sort"
"unicode"
)
type KeyValue struct {
Key string
Value any
}
func SortStruct(data any) []KeyValue {
v := reflect.ValueOf(data).Elem()
t := v.Type()
var kv []KeyValue
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
key := ToSnakeCase(t.Field(i).Name)
if field.IsZero() {
continue
}
kv = append(kv, KeyValue{Key: key, Value: field.Interface()})
}
sort.SliceStable(kv, func(i, j int) bool {
return kv[i].Key < kv[j].Key
})
return kv
}
func ToSnakeCase(s string) string {
var result []rune
for i, char := range s {
if unicode.IsUpper(char) {
if i > 0 {
result = append(result, '_')
}
result = append(result, unicode.ToLower(char))
} else {
result = append(result, char)
}
}
return string(result)
}

19
utils/sort_struct_test.go Normal file
View File

@ -0,0 +1,19 @@
package utils
import (
"testing"
)
func TestSortStruct(t *testing.T) {
data := &struct {
Name string `json:"name"`
Filed1 string `json:"filed1"`
Filed2 string `json:"filed2"`
}{
Name: "test",
Filed1: "filed1",
Filed2: "filed2",
}
kv := SortStruct(data)
t.Log(kv)
}