From afc205d9ac9e19e0823687857be4563e14476946 Mon Sep 17 00:00:00 2001 From: zhuxianglong <56494565@qq.com> Date: Thu, 16 Jan 2025 16:36:11 +0800 Subject: [PATCH] =?UTF-8?q?=E9=AA=8C=E7=AD=BE=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sdk/README.md | 223 ++++++++++++++++++++++++++++++++++++++--------------- sdk/order_sign.go | 106 +++++++++++++++++++++++++ sdk/refund_sign.go | 63 +++++++++++++++ sdk/sign.go | 113 --------------------------- test/sdk_test.go | 20 ++++- utils/md5.go | 12 +++ 6 files changed, 361 insertions(+), 176 deletions(-) create mode 100644 sdk/order_sign.go create mode 100644 sdk/refund_sign.go delete mode 100644 sdk/sign.go create mode 100644 utils/md5.go diff --git a/sdk/README.md b/sdk/README.md index 1d30a41..eb6d477 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -1,118 +1,219 @@ -# SDK 使用文档 +### SDK 调用文档 -## 概述 +本文档详细说明了如何使用两个SDK文件中的功能:一个是用于订单支付签名生成的SDK,另一个是用于订单退款签名生成的SDK。我们将分别介绍这两个SDK的使用方法。 -本SDK 提供了生成签名的功能,主要用于订单相关操作的签名验证。 +--- -## 安装 +#### 1. 订单支付SDK (`sdk_payment.go`) -在Go项目中导入本SDK: +##### 1.1 结构体定义 -```bash -go get gitea.weitiangame.com/sdk/wt-game +```go +type Order struct { + Uid string // 用户ID + BsTradeNo string // 交易编号 + Role string // 用户角色 + RoleId string // 角色ID + ServerId string // 服务器ID + GoodsName string // 商品名称 + OutTradeNo string // 外部交易编号 + Body string // 商品描述 + CpExtraInfo string // 扩展信息 + TradeState string // 交易状态 + TotalFee string // 总费用 + PayFee string // 支付费用 + PayTime string // 支付时间 +} ``` -请将 `"gitea.weitiangame.com/sdk/wt-game"` 替换为实际的导入路径。 +##### 1.2 SDK 结构体 -## 初始化 +```go +type SDK struct { + SecretKey string // 秘钥 +} +``` -通过 `New` 函数创建SDK实例,并传入秘钥: +##### 1.3 单例实例化 ```go -import ( - "fmt" +func NewOrderPayment(secretKey string) *SDK +``` - "gitea.weitiangame.com/sdk/wt-game/sdk" -) +- **功能**: 初始化并返回一个SDK单例实例。 +- **参数**: + - `secretKey` (string): 用于签名的密钥。 +- **返回值**: `*SDK` 类型的单例实例。 -func main() { - secretKey := "your_secret_key" - newSdk := sdk.New(secretKey) - // 继续使用SDK -} +##### 1.4 生成签名 + +```go +func (s *SDK) SignParam(order *Order) (string, error) ``` -## 生成签名 +- **功能**: 为提供的 `Order` 对象生成签名。 +- **参数**: + - `order` (*Order): 包含订单信息的结构体指针。 +- **返回值**: + - `string`: 生成的签名字符串。 + - `error`: 可能返回的错误,当前实现中总是返回 `nil`。 + +##### 1.5 内部方法 + +- `createParamMap(order *Order) map[string]string` + - **功能**: 创建一个包含订单参数的映射,排除 `sign` 和 `-` 键。 + - **返回值**: `map[string]string` 类型的参数映射。 + +- `BuildSignString(params map[string]string) string` + - **功能**: 按字典顺序排序参数并生成签名字符串。 + - **参数**: `params` (map[string]string): 参数映射。 + - **返回值**: 拼接后的签名字符串。 + +- `getSortedKeys(params map[string]string) []string` + - **功能**: 获取排序后的参数键列表,排除 `sign` 和 `-` 键。 + - **参数**: `params` (map[string]string): 参数映射。 + - **返回值**: `[]string` 类型的排序键列表。 + +--- + +#### 2. 订单退款SDK (`sdk_refund.go`) -创建一个 `Order` 对象并填充相关字段,然后调用 `SignParam` 方法生成签名: +##### 2.1 结构体定义 ```go -order := &sdk.Order{ - Uid: "12345", - BsTradeNo: "TRADE001", - Role: "Player", - RoleId: "ROLE001", - ServerId: "SERVER01", - GoodsName: "Game Coin", - OutTradeNo: "OUT001", - Body: "Purchase Game Coin", - CpExtraInfo: "extra_info", - TradeState: "SUCCESS", - TotalFee: "100", - PayFee: "95", - PayTime: "2023-10-05 14:30:00", +type OrderRefund struct { + OutTradeNo string // 订单号 + RoleId string // 角色ID + Status string // 订单状态 + RefundFee string // 退款金额 + Timestamp string // 时间戳 } +``` + +##### 2.2 SDK 结构体 -sign, err := newSdk.SignParam(order) -if err != nil { - fmt.Println("生成签名失败:", err) - return +```go +type RefundSDK struct { + SecretKey string // 密钥 } -fmt.Println("签名:", sign) ``` -## 示例代码 +##### 2.3 单例实例化 + +```go +func NewRefundSDK(secretKey string) *RefundSDK +``` + +- **功能**: 初始化并返回一个 RefundSDK 单例实例。 +- **参数**: + - `secretKey` (string): 用于签名的密钥。 +- **返回值**: `*RefundSDK` 类型的单例实例。 -完整的示例代码如下: +##### 2.4 生成签名 + +```go +func (sdk *RefundSDK) SignParam(order *OrderRefund) string +``` + +- **功能**: 为提供的 `OrderRefund` 对象生成签名。 +- **参数**: + - `order` (*OrderRefund): 包含退款信息的结构体指针。 +- **返回值**: `string` 类型的签名字符串(大写形式)。 + +##### 2.5 内部方法 + +- `createFormUrl(order *OrderRefund) string` + - **功能**: 生成用于签名的参数字符串。 + - **参数**: `order` (*OrderRefund): 包含退款信息的结构体指针。 + - **返回值**: 拼接后的参数字符串。 + +--- + +### 示例代码 + +#### 1. 订单支付SDK 使用示例 ```go package main import ( "fmt" - "gitea.weitiangame.com/sdk/wt-game/sdk" ) func main() { - secretKey := "your_secret_key" - newSdk := sdk.New(secretKey) + // 初始化SDK实例 + sdkInstance := sdk.NewOrderPayment("your_secret_key") + // 创建订单对象 order := &sdk.Order{ Uid: "12345", - BsTradeNo: "TRADE001", + BsTradeNo: "BT12345", Role: "Player", - RoleId: "ROLE001", - ServerId: "SERVER01", + RoleId: "R001", + ServerId: "S001", GoodsName: "Game Coin", - OutTradeNo: "OUT001", + OutTradeNo: "OT12345", Body: "Purchase Game Coin", CpExtraInfo: "extra_info", TradeState: "SUCCESS", TotalFee: "100", PayFee: "95", - PayTime: "2023-10-05 14:30:00", + PayTime: "20231030120000", } - sign, err := newSdk.SignParam(order) + // 生成签名 + sign, err := sdkInstance.SignParam(order) if err != nil { - fmt.Println("生成签名失败:", err) + fmt.Println("签名生成失败:", err) return } - fmt.Println("签名:", sign) + fmt.Println("生成的签名:", sign) +} +``` + +#### 2. 订单退款SDK 使用示例 + +```go +package main + +import ( + "fmt" + "gitea.weitiangame.com/sdk/wt-game/sdk" +) + +func main() { + // 初始化SDK实例 + refundSdk := sdk.NewRefundSDK("your_secret_key") + + // 创建退款订单对象 + refundOrder := &sdk.OrderRefund{ + OutTradeNo: "OT12345", + RoleId: "R001", + Status: "REFUNDED", + RefundFee: "95", + Timestamp: "20231030120000", + } + + // 生成签名 + sign := refundSdk.SignParam(refundOrder) + fmt.Println("生成的签名:", sign) } ``` -## 注意事项 +--- -- **秘钥安全**:秘钥是生成签名的重要参数,务必妥善保管,防止泄露。 -- **签名生成**:签名生成过程中,参数按字典序排序,并且排除`sign`和`-`字段。 -- **时间格式**:`PayTime`字段请使用`YYYY-MM-DD HH:MM:SS`格式,以保证签名的一致性。 +### 注意事项 -## 常见问题 +1. **单例模式**: 两个SDK都使用了单例模式,确保在应用程序中只有一个实例。 +2. **线程安全**: 初始化SDK实例时使用了 `sync.Once`,确保线程安全。 +3. **签名生成**: 签名生成过程中要注意参数的顺序和拼接方式,确保与服务端一致。 +4. **密钥保管**: 保证密钥的安全,不要硬编码在代码中,建议从配置文件或环境变量中读取。 -- **签名不匹配**:请检查参数是否正确,秘钥是否一致,以及时间格式是否正确。 +--- -## 联系方式 +### 参考资料 -如有任何问题,请联系开发人员或查阅相关文档。 \ No newline at end of file +- [Go 语言 sync.Once 包](https://golang.org/pkg/sync/#Once) +- [Go 语言字符串拼接](https://golang.org/pkg/strings/) +- [Go 语言排序](https://golang.org/pkg/sort/) \ No newline at end of file diff --git a/sdk/order_sign.go b/sdk/order_sign.go new file mode 100644 index 0000000..8724e82 --- /dev/null +++ b/sdk/order_sign.go @@ -0,0 +1,106 @@ +package sdk + +import ( + "gitea.weitiangame.com/sdk/wt-game/utils" + "sort" + "strings" + "sync" +) + +// Order 表示订单对象,用于签名参数 +type Order struct { + Uid string // 用户ID + BsTradeNo string // 交易编号 + Role string // 用户角色 + RoleId string // 角色ID + ServerId string // 服务器ID + GoodsName string // 商品名称 + OutTradeNo string // 外部交易编号 + Body string // 商品描述 + CpExtraInfo string // 扩展信息 + TradeState string // 交易状态 + TotalFee string // 总费用 + PayFee string // 支付费用 + PayTime string // 支付时间 +} + +// SDK结构体封装了SDK的配置和工具函数 +type SDK struct { + SecretKey string // 秘钥 +} + +// 单例实例和互斥锁,确保线程安全 +var instance *SDK +var once sync.Once + +// NewOrderPayment 返回一个SDK单例实例,初始化时传入秘钥 +func NewOrderPayment(secretKey string) *SDK { + once.Do(func() { + instance = &SDK{ + SecretKey: secretKey, + } + }) + return instance // 返回单例实例 +} + +// SignParam 为提供的Order对象生成签名 +func (s *SDK) SignParam(order *Order) (string, error) { + // 创建待签名参数映射 + paramMap := s.createParamMap(order) + + // 按照字典顺序排序并生成待签名字符串 + signStr := s.BuildSignString(paramMap) + + // 计算最终签名并返回 + return utils.Md5String(signStr), nil +} + +// createParamMap 构建参数映射,跳过 "sign" 和 "-" 键 +func (s *SDK) createParamMap(order *Order) map[string]string { + return map[string]string{ + "uid": order.Uid, + "bs_trade_no": order.BsTradeNo, + "role": order.Role, + "role_id": order.RoleId, + "server_id": order.ServerId, + "goods_name": order.GoodsName, + "out_trade_no": order.OutTradeNo, + "body": order.Body, + "extra_info": order.CpExtraInfo, + "order_status": order.TradeState, + "total_fee": order.TotalFee, + "pay_fee": order.PayFee, + "pay_time": order.PayTime, + "sign": "", // 初始化签名为空 + } +} + +// buildSignString 按照字典顺序排序参数并拼接成签名字符串 +func (s *SDK) BuildSignString(params map[string]string) string { + var builder strings.Builder + keys := s.getSortedKeys(params) // 获取排序后的键 + + // 拼接所有键对应的值 + for _, k := range keys { + builder.WriteString(params[k]) + } + + // 将秘钥追加到签名字符串后 + builder.WriteString(s.SecretKey) + + return builder.String() +} + +// getSortedKeys 返回排除 "sign" 和 "-" 键后的排序键列表 +func (s *SDK) getSortedKeys(params map[string]string) []string { + var keys []string + for k := range params { + // 跳过 "sign" 和 "-" 键 + if k == "sign" || k == "-" { + continue + } + keys = append(keys, k) + } + sort.Strings(keys) // 排序键 + return keys +} diff --git a/sdk/refund_sign.go b/sdk/refund_sign.go new file mode 100644 index 0000000..952128d --- /dev/null +++ b/sdk/refund_sign.go @@ -0,0 +1,63 @@ +package sdk + +import ( + "gitea.weitiangame.com/sdk/wt-game/utils" + "strings" + "sync" +) + +// OrderRefund 表示一个订单退款对象,用于生成签名参数 +type OrderRefund struct { + OutTradeNo string // 订单号 + RoleId string // 角色ID + Status string // 订单状态 + RefundFee string // 退款金额 + Timestamp string // 时间戳 +} + +// RefundSDK 封装了SDK配置和工具方法 +type RefundSDK struct { + SecretKey string // 密钥 +} + +// 全局变量,用于保存单例实例和线程安全控制 +var ( + instance2 *RefundSDK + once2 sync.Once // 确保单例实例只初始化一次 +) + +// NewRefundSDK 返回一个使用密钥初始化的 RefundSDK 单例实例 +func NewRefundSDK(secretKey string) *RefundSDK { + // 使用 sync.Once 确保实例只初始化一次 + once2.Do(func() { + instance2 = &RefundSDK{ + SecretKey: secretKey, + } + }) + return instance2 +} + +// SignParam 为指定的 OrderRefund 对象生成签名 +func (sdk *RefundSDK) SignParam(order *OrderRefund) string { + // 创建用于签名的参数字符串 + paramStr := sdk.createFormUrl(order) + // 生成MD5签名 + md5Str := utils.Md5String(paramStr) + // 返回大写的签名 + return strings.ToUpper(md5Str) +} + +// createFormUrl 生成用于签名的参数字符串 +func (sdk *RefundSDK) createFormUrl(order *OrderRefund) string { + var builder strings.Builder + // 按顺序拼接所有参数 + builder.WriteString(order.OutTradeNo) + builder.WriteString(order.RoleId) + builder.WriteString(order.Status) + builder.WriteString(order.RefundFee) + builder.WriteString(order.Timestamp) + // 拼接密钥 + builder.WriteString(sdk.SecretKey) + // 返回拼接后的字符串 + return builder.String() +} diff --git a/sdk/sign.go b/sdk/sign.go deleted file mode 100644 index aa517bc..0000000 --- a/sdk/sign.go +++ /dev/null @@ -1,113 +0,0 @@ -package sdk - -import ( - "crypto/md5" - "encoding/hex" - "sort" - "strings" - "sync" -) - -// Order 表示订单对象,用于签名参数 -type Order struct { - Uid string // 用户ID - BsTradeNo string // 交易编号 - Role string // 用户角色 - RoleId string // 角色ID - ServerId string // 服务器ID - GoodsName string // 商品名称 - OutTradeNo string // 外部交易编号 - Body string // 商品描述 - CpExtraInfo string // 扩展信息 - TradeState string // 交易状态 - TotalFee string // 总费用 - PayFee string // 支付费用 - PayTime string // 支付时间 -} - -// SDK结构体封装了SDK的配置和工具函数 -type SDK struct { - SecretKey string // 秘钥 -} - -// 单例实例和互斥锁,确保线程安全 -var instance *SDK -var once sync.Once - -// New 返回一个SDK单例实例,初始化时传入秘钥 -func New(secretKey string) *SDK { - once.Do(func() { - instance = &SDK{ - SecretKey: secretKey, - } - }) - return instance // 返回单例实例 -} - -// Md5String 生成给定字符串的MD5哈希值并返回十六进制字符串 -func (s *SDK) Md5String(str string) string { - hash := md5.Sum([]byte(str)) // 计算MD5哈希 - return hex.EncodeToString(hash[:]) // 转为十六进制字符串并返回 -} - -// SignParam 为提供的Order对象生成签名 -func (s *SDK) SignParam(order *Order) (string, error) { - // 创建待签名参数映射 - paramMap := s.createParamMap(order) - - // 按照字典顺序排序并生成待签名字符串 - signStr := s.BuildSignString(paramMap) - - // 计算最终签名并返回 - return s.Md5String(signStr), nil -} - -// createParamMap 构建参数映射,跳过 "sign" 和 "-" 键 -func (s *SDK) createParamMap(order *Order) map[string]string { - return map[string]string{ - "uid": order.Uid, - "bs_trade_no": order.BsTradeNo, - "role": order.Role, - "role_id": order.RoleId, - "server_id": order.ServerId, - "goods_name": order.GoodsName, - "out_trade_no": order.OutTradeNo, - "body": order.Body, - "extra_info": order.CpExtraInfo, - "order_status": order.TradeState, - "total_fee": order.TotalFee, - "pay_fee": order.PayFee, - "pay_time": order.PayTime, - "sign": "", // 初始化签名为空 - } -} - -// buildSignString 按照字典顺序排序参数并拼接成签名字符串 -func (s *SDK) BuildSignString(params map[string]string) string { - var builder strings.Builder - keys := s.getSortedKeys(params) // 获取排序后的键 - - // 拼接所有键对应的值 - for _, k := range keys { - builder.WriteString(params[k]) - } - - // 将秘钥追加到签名字符串后 - builder.WriteString(s.SecretKey) - - return builder.String() -} - -// getSortedKeys 返回排除 "sign" 和 "-" 键后的排序键列表 -func (s *SDK) getSortedKeys(params map[string]string) []string { - var keys []string - for k := range params { - // 跳过 "sign" 和 "-" 键 - if k == "sign" || k == "-" { - continue - } - keys = append(keys, k) - } - sort.Strings(keys) // 排序键 - return keys -} diff --git a/test/sdk_test.go b/test/sdk_test.go index a1402af..aa89535 100644 --- a/test/sdk_test.go +++ b/test/sdk_test.go @@ -6,11 +6,11 @@ import ( "testing" ) -var newSdk = sdk.New("your-secret-key") +var orderSdk = sdk.NewOrderPayment("your-secret-key") func TestOrderSign(t *testing.T) { // Generate signed parameters - signature, err := newSdk.SignParam(&sdk.Order{ + signature, err := orderSdk.SignParam(&sdk.Order{ Uid: "1", BsTradeNo: "trade123", Role: "user", @@ -32,3 +32,19 @@ func TestOrderSign(t *testing.T) { // Example output fmt.Println(signature) } + +var refundSdk = sdk.NewRefundSDK("your-secret-key") + +func TestRefundSign(t *testing.T) { + // Generate signed parameters + signature := refundSdk.SignParam(&sdk.OrderRefund{ + OutTradeNo: "trade123", + RoleId: "1234", + Status: "1", + RefundFee: "100", + Timestamp: "1234567890", + }) + + // Example output + fmt.Println(signature) +} diff --git a/utils/md5.go b/utils/md5.go new file mode 100644 index 0000000..e62a0db --- /dev/null +++ b/utils/md5.go @@ -0,0 +1,12 @@ +package utils + +import ( + "crypto/md5" + "encoding/hex" +) + +// Md5String 生成给定字符串的MD5哈希值并返回十六进制字符串 +func Md5String(str string) string { + hash := md5.Sum([]byte(str)) // 计算MD5哈希 + return hex.EncodeToString(hash[:]) // 转为十六进制字符串并返回 +}