ymt_v3-20260723181356/ymt_v3/generate.md

65 KiB
Raw Permalink Blame History

现在我来生成所有 6 个文件。根据文档签名规则是拼接app_id + timestamp + ciphertext然后使用 RSA 私钥签名。业务参数加密使用 AES ECB 或 SM4 CBC。

让我生成完整的 SDK 代码。

// File: ymt_v3/go.mod

module ymt_v3

go 1.21

require github.com/tjfoc/gmsm v1.4.1

// File: ymt_v3/types.go

package ymt_v3

// CommonResponse 公共响应结构
type CommonResponse struct {
	Code    int32            `json:"code"`
	Message string           `json:"message"`
	Reason  string           `json:"reason,omitempty"`
	Data    *CommonData      `json:"data,omitempty"`
}

// CommonData 公共响应数据包含加密的ciphertext
type CommonData struct {
	Ciphertext string `json:"ciphertext"`
}

// KeyOrderRequest 获取券码请求业务参数
type KeyOrderRequest struct {
	OutBizNo   string `json:"out_biz_no"`
	ActivityNo string `json:"activity_no"`
	Account    string `json:"account,omitempty"`
	NotifyURL  string `json:"notify_url,omitempty"`
}

// KeyOrderResponse 获取券码/券码查询响应业务参数
type KeyOrderResponse struct {
	OutBizNo        string  `json:"out_biz_no"`
	TradeNo         string  `json:"trade_no"`
	Key             string  `json:"key,omitempty"`
	URL             string  `json:"url,omitempty"`
	ValidBeginTime  string  `json:"valid_begin_time,omitempty"`
	ValidEndTime    string  `json:"valid_end_time,omitempty"`
	UsableNum       uint32  `json:"usable_num"`
	UsageNum        uint32  `json:"usage_num"`
	Status          uint32  `json:"status"`
	SettlementPrice float64 `json:"settlement_price,omitempty"`
	Account         string  `json:"account,omitempty"`
}

// KeyQueryRequest 券码查询请求业务参数
type KeyQueryRequest struct {
	OutBizNo string `json:"out_biz_no,omitempty"`
	TradeNo  string `json:"trade_no,omitempty"`
}

// KeyDiscardRequest 券码作废请求业务参数
type KeyDiscardRequest struct {
	OutBizNo string `json:"out_biz_no,omitempty"`
	TradeNo  string `json:"trade_no,omitempty"`
}

// KeyDiscardResponse 券码作废响应业务参数
type KeyDiscardResponse struct {
	OutBizNo string `json:"out_biz_no"`
	TradeNo  string `json:"trade_no"`
	Status   uint32 `json:"status"`
}

// BatchOrderRequest 批量发卡请求业务参数
type BatchOrderRequest struct {
	OutBizNo   string `json:"out_biz_no"`
	ActivityNo string `json:"activity_no"`
	Number     int32  `json:"number"`
	NotifyURL  string `json:"notify_url,omitempty"`
}

// BatchOrderResponse 批量发卡响应业务参数
type BatchOrderResponse struct {
	OutBizNo string `json:"out_biz_no"`
	TradeNo  string `json:"trade_no"`
	Status   string `json:"status"`
}

// BatchQueryRequest 批量查询请求业务参数
type BatchQueryRequest struct {
	OutBizNo string `json:"out_biz_no,omitempty"`
	TradeNo  string `json:"trade_no,omitempty"`
}

// BatchQueryResponse 批量查询响应业务参数
type BatchQueryResponse struct {
	OutBizNo    string `json:"out_biz_no"`
	TradeNo     string `json:"trade_no"`
	Status      string `json:"status"`
	DownloadURL string `json:"download_url,omitempty"`
	ZipPassword string `json:"zip_password,omitempty"`
}

// NotifyPayload 回调通知的body结构
type NotifyPayload struct {
	Data *CommonData `json:"data"`
}

// NotifyHeader 回调通知的header
type NotifyHeader struct {
	Appid     string `json:"Appid"`
	Timestamp string `json:"Timestamp"`
	Sign      string `json:"Sign"`
}

// File: ymt_v3/errors.go

package ymt_v3

import "fmt"

// APIError 表示API返回的业务错误
type APIError struct {
	Code    int32  `json:"code"`
	Message string `json:"message"`
	Reason  string `json:"reason,omitempty"`
}

func (e *APIError) Error() string {
	return fmt.Sprintf("API error: code=%d, message=%s, reason=%s", e.Code, e.Message, e.Reason)
}

// IsSuccess 判断是否成功
func (e *APIError) IsSuccess() bool {
	return e.Code == 200
}

// NewAPIError 创建API错误
func NewAPIError(code int32, message, reason string) *APIError {
	return &APIError{
		Code:    code,
		Message: message,
		Reason:  reason,
	}
}

// SDKError SDK内部错误
type SDKError struct {
	Message string
	Err     error
}

func (e *SDKError) Error() string {
	if e.Err != nil {
		return fmt.Sprintf("SDK error: %s: %v", e.Message, e.Err)
	}
	return fmt.Sprintf("SDK error: %s", e.Message)
}

func (e *SDKError) Unwrap() error {
	return e.Err
}

// NewSDKError 创建SDK内部错误
func NewSDKError(msg string, err error) *SDKError {
	return &SDKError{Message: msg, Err: err}
}

// File: ymt_v3/crypto.go

package ymt_v3

import (
	"bytes"
	"crypto"
	"crypto/aes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/sha512"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"reflect"
	"sort"
	"strings"
	"time"

	"github.com/tjfoc/gmsm/sm4"
)

// EncryptType 加密类型
type EncryptType string

const (
	EncryptTypeAES EncryptType = "aes"
	EncryptTypeSM4 EncryptType = "sm4"
)

// ==================== RSA签名 ====================

// SignWithRSA 使用RSA私钥对数据进行签名
// 签名串拼接规则app_id + timestamp + ciphertext
func SignWithRSA(signStr string, privateKeyPEM string) (string, error) {
	block, _ := pem.Decode([]byte(privateKeyPEM))
	if block == nil {
		return "", fmt.Errorf("failed to decode PEM private key")
	}

	privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		// 尝试PKCS1格式
		privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
		if err != nil {
			return "", fmt.Errorf("failed to parse private key: %v", err)
		}
	}

	rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
	if !ok {
		return "", fmt.Errorf("not a RSA private key")
	}

	h := sha256.New()
	h.Write([]byte(signStr))
	hashed := h.Sum(nil)

	signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, crypto.SHA256, hashed)
	if err != nil {
		return "", err
	}

	return base64.StdEncoding.EncodeToString(signature), nil
}

// VerifyWithRSA 使用RSA公钥验证签名
// 验签串拼接规则app_id + timestamp + ciphertext
func VerifyWithRSA(signStr, signature string, publicKeyPEM string) error {
	block, _ := pem.Decode([]byte(publicKeyPEM))
	if block == nil {
		return fmt.Errorf("failed to decode PEM public key")
	}

	publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return fmt.Errorf("failed to parse public key: %v", err)
	}

	rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
	if !ok {
		return fmt.Errorf("not a RSA public key")
	}

	h := sha256.New()
	h.Write([]byte(signStr))
	hashed := h.Sum(nil)

	sigBytes, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return fmt.Errorf("failed to decode signature: %v", err)
	}

	return rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hashed, sigBytes)
}

// ==================== AES ECB加密 ====================

// AESECBEncrypt AES-ECB模式加密
func AESECBEncrypt(plaintext []byte, key []byte) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", fmt.Errorf("创建AES cipher失败: %v", err)
	}

	padded := pkcs7Padding(plaintext, aes.BlockSize)

	ciphertext := make([]byte, len(padded))
	for i := 0; i < len(padded); i += aes.BlockSize {
		block.Encrypt(ciphertext[i:i+aes.BlockSize], padded[i:i+aes.BlockSize])
	}

	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// AESECBDecrypt AES-ECB模式解密
func AESECBDecrypt(encryptedData string, key []byte) ([]byte, error) {
	ciphertext, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return nil, fmt.Errorf("解码失败: %v", err)
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("创建AES cipher失败: %v", err)
	}

	if len(ciphertext)%aes.BlockSize != 0 {
		return nil, fmt.Errorf("密文长度不是块大小的整数倍")
	}

	plaintext := make([]byte, len(ciphertext))
	for i := 0; i < len(ciphertext); i += aes.BlockSize {
		block.Decrypt(plaintext[i:i+aes.BlockSize], ciphertext[i:i+aes.BlockSize])
	}

	plaintext, err = pkcs7UnPadding(plaintext)
	if err != nil {
		return nil, fmt.Errorf("去除填充失败: %v", err)
	}

	return plaintext, nil
}

// ==================== SM4 CBC加密 ====================

// SM4CBCEncrypt SM4-CBC模式加密
func SM4CBCEncrypt(plaintext []byte, key []byte) (string, error) {
	if len(key) != 16 {
		return "", fmt.Errorf("SM4密钥长度必须为16字节")
	}

	block, err := sm4.NewCipher(key)
	if err != nil {
		return "", fmt.Errorf("创建SM4 cipher失败: %v", err)
	}

	padded := pkcs7Padding(plaintext, block.BlockSize())

	iv := make([]byte, block.BlockSize())
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return "", fmt.Errorf("生成IV失败: %v", err)
	}

	mode := NewCBCEncrypter(block, iv)
	ciphertext := make([]byte, len(padded))
	mode.CryptBlocks(ciphertext, padded)

	result := append(iv, ciphertext...)
	return base64.StdEncoding.EncodeToString(result), nil
}

// SM4CBCDecrypt SM4-CBC模式解密
func SM4CBCDecrypt(encryptedData string, key []byte) ([]byte, error) {
	if len(key) != 16 {
		return nil, fmt.Errorf("SM4密钥长度必须为16字节")
	}

	data, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return nil, fmt.Errorf("解码失败: %v", err)
	}

	block, err := sm4.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("创建SM4 cipher失败: %v", err)
	}

	blockSize := block.BlockSize()
	if len(data) < blockSize {
		return nil, fmt.Errorf("数据长度不足")
	}
	iv := data[:blockSize]
	ciphertext := data[blockSize:]

	mode := NewCBCDecrypter(block, iv)
	plaintext := make([]byte, len(ciphertext))
	mode.CryptBlocks(plaintext, ciphertext)

	plaintext, err = pkcs7UnPadding(plaintext)
	if err != nil {
		return nil, fmt.Errorf("去除填充失败: %v", err)
	}

	return plaintext, nil
}

// ==================== PKCS7填充 ====================

// pkcs7Padding PKCS7填充
func pkcs7Padding(data []byte, blockSize int) []byte {
	padding := blockSize - len(data)%blockSize
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padText...)
}

// pkcs7UnPadding 去除PKCS7填充
func pkcs7UnPadding(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, fmt.Errorf("数据为空")
	}
	padding := int(data[length-1])
	if padding > length || padding == 0 {
		return nil, fmt.Errorf("无效的填充")
	}
	for i := length - padding; i < length; i++ {
		if data[i] != byte(padding) {
			return nil, fmt.Errorf("无效的填充")
		}
	}
	return data[:length-padding], nil
}

// ==================== CBC模式实现用于SM4 ====================

// cbc 实现了CBC模式加密/解密
type cbc struct {
	b         cipherBlock
	blockSize int
	iv        []byte
}

type cipherBlock interface {
	BlockSize() int
	Encrypt(dst, src []byte)
	Decrypt(dst, src []byte)
}

// NewCBCEncrypter 创建CBC加密器
func NewCBCEncrypter(b cipherBlock, iv []byte) *cbc {
	return &cbc{
		b:         b,
		blockSize: b.BlockSize(),
		iv:        copyBytes(iv),
	}
}

// NewCBCDecrypter 创建CBC解密器
func NewCBCDecrypter(b cipherBlock, iv []byte) *cbc {
	return &cbc{
		b:         b,
		blockSize: b.BlockSize(),
		iv:        copyBytes(iv),
	}
}

func copyBytes(src []byte) []byte {
	dst := make([]byte, len(src))
	copy(dst, src)
	return dst
}

// CryptBlocks 加密/解密数据块
func (x *cbc) CryptBlocks(dst, src []byte) {
	if len(src)%x.blockSize != 0 {
		panic("crypto/cipher: input not full blocks")
	}
	if len(dst) < len(src) {
		panic("crypto/cipher: output smaller than input")
	}

	iv := x.iv

	for i := 0; i < len(src); i += x.blockSize {
		// 对于加密:先 XOR IV再加密
		// 对于解密:先解密,再 XOR IV
		// 这里我们通过判断是加密还是解密来区分
		// 简单起见,我们使用一个标志
	}

	// 重新实现:使用内部类型区分
	panic("use Encrypt or Decrypt specific methods")
}

// 使用标准库的cipher.BlockMode
// 由于sm4.NewCipher返回的是cipher.Block接口我们可以直接使用cipher.NewCBCEncrypter
// 但为了兼容性,我们使用标准库

// ==================== 业务参数加密/解密 ====================

// EncryptBizParams 加密业务参数
// 1. 将业务参数去掉"零"值的参数再由小到大按照字母排序转成json字符串得到plaintext
// 2. 使用应用key将plaintext字符串加密得到ciphertext
func EncryptBizParams(params interface{}, key []byte, encType EncryptType) (string, error) {
	// 去掉零值参数并按字母排序
	plaintext, err := marshalWithoutZero(params)
	if err != nil {
		return "", fmt.Errorf("序列化业务参数失败: %v", err)
	}

	switch encType {
	case EncryptTypeAES:
		return AESECBEncrypt([]byte(plaintext), key)
	case EncryptTypeSM4:
		return SM4CBCEncrypt([]byte(plaintext), key)
	default:
		return "", fmt.Errorf("不支持的加密类型: %s", encType)
	}
}

// DecryptBizParams 解密业务参数
func DecryptBizParams(ciphertext string, key []byte, encType EncryptType) ([]byte, error) {
	switch encType {
	case EncryptTypeAES:
		return AESECBDecrypt(ciphertext, key)
	case EncryptTypeSM4:
		return SM4CBCDecrypt(ciphertext, key)
	default:
		return nil, fmt.Errorf("不支持的加密类型: %s", encType)
	}
}

// marshalWithoutZero 将结构体序列化为JSON去掉零值字段并按字母排序
func marshalWithoutZero(v interface{}) (string, error) {
	val := reflect.ValueOf(v)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}

	if val.Kind() != reflect.Struct {
		// 如果是map直接排序后转json
		if val.Kind() == reflect.Map {
			return marshalMapSorted(v)
		}
		// 否则直接json序列化
		data, err := json.Marshal(v)
		return string(data), err
	}

	result := make(map[string]interface{})
	typ := val.Type()

	for i := 0; i < val.NumField(); i++ {
		field := val.Field(i)
		fieldType := typ.Field(i)

		// 获取json tag
		jsonTag := fieldType.Tag.Get("json")
		if jsonTag == "" || jsonTag == "-" {
			continue
		}
		// 处理omitempty
		name := strings.Split(jsonTag, ",")[0]

		// 检查是否是零值
		if isZeroValue(field) {
			continue
		}

		result[name] = field.Interface()
	}

	return marshalMapSorted(result)
}

// marshalMapSorted 将map按键排序后序列化为JSON
func marshalMapSorted(m map[string]interface{}) (string, error) {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var buf bytes.Buffer
	buf.WriteByte('{')
	for i, k := range keys {
		if i > 0 {
			buf.WriteByte(',')
		}
		// 写入key
		keyBytes, _ := json.Marshal(k)
		buf.Write(keyBytes)
		buf.WriteByte(':')
		// 写入value
		valBytes, err := json.Marshal(m[k])
		if err != nil {
			return "", err
		}
		buf.Write(valBytes)
	}
	buf.WriteByte('}')

	return buf.String(), nil
}

// isZeroValue 判断reflect.Value是否为零值
func isZeroValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.String:
		return v.String() == ""
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Slice, reflect.Map, reflect.Ptr:
		return v.IsNil()
	default:
		return false
	}
}

// ==================== 签名相关 ====================

// BuildSignStr 构建签名字符串
// 规则app_id + timestamp + ciphertext
func BuildSignStr(appID, timestamp, ciphertext string) string {
	return appID + timestamp + ciphertext
}

// GenerateTimestamp 生成时间戳,格式 yyyy-MM-dd HH:mm:ss
func GenerateTimestamp() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// ==================== 回调验签 ====================

// VerifyNotifySign 验证回调通知签名
// 1. 获取header头里面的签名信息
// 2. 获取body里面的业务参数data
// 3. 将业务参数data去掉"零"值的参数再由小到大按照字母排序转成json字符串得到plaintext
// 4. 使用应用key将plaintext字符串加密得到ciphertext
// 5. 拼接签名字符串app_id + timestamp + ciphertext
// 6. 使用应用公钥验签
func VerifyNotifySign(header *NotifyHeader, data interface{}, key []byte, publicKeyPEM string, encType EncryptType) error {
	// 3. 将业务参数data去掉零值参数排序转json
	plaintext, err := marshalWithoutZero(data)
	if err != nil {
		return fmt.Errorf("序列化回调数据失败: %v", err)
	}

	// 4. 加密得到ciphertext
	ciphertext, err := EncryptBizParamsRaw([]byte(plaintext), key, encType)
	if err != nil {
		return fmt.Errorf("加密回调数据失败: %v", err)
	}

	// 5. 拼接签名字符串
	signStr := BuildSignStr(header.Appid, header.Timestamp, ciphertext)

	// 6. 使用应用公钥验签
	return VerifyWithRSA(signStr, header.Sign, publicKeyPEM)
}

// EncryptBizParamsRaw 直接加密字节数据
func EncryptBizParamsRaw(plaintext []byte, key []byte, encType EncryptType) (string, error) {
	switch encType {
	case EncryptTypeAES:
		return AESECBEncrypt(plaintext, key)
	case EncryptTypeSM4:
		return SM4CBCEncrypt(plaintext, key)
	default:
		return "", fmt.Errorf("不支持的加密类型: %s", encType)
	}
}

// File: ymt_v3/client.go

package ymt_v3

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
)

// Env 环境
type Env string

const (
	EnvTest    Env = "test"
	EnvProduct Env = "product"
)

// 环境地址
const (
	testBaseURL    = "https://gateway.dev.cdlsxd.cn"
	productBaseURL = "https://market.api.86698.cn"
)

// ClientConfig 客户端配置
type ClientConfig struct {
	AppID       string      // 应用ID
	PrivateKey  string      // 应用私钥PEM格式
	PublicKey   string      // 平台公钥PEM格式
	Key         string      // 业务参数加密key
	EncryptType EncryptType // 加密类型aes 或 sm4
	Env         Env         // 环境test 或 product
	HTTPClient  *http.Client // HTTP客户端可选
}

// Client SDK客户端
type Client struct {
	config     *ClientConfig
	httpClient *http.Client
	baseURL    string
}

// NewClient 创建新的SDK客户端
func NewClient(config *ClientConfig) (*Client, error) {
	if config.AppID == "" {
		return nil, fmt.Errorf("AppID不能为空")
	}
	if config.PrivateKey == "" {
		return nil, fmt.Errorf("PrivateKey不能为空")
	}
	if config.PublicKey == "" {
		return nil, fmt.Errorf("PublicKey不能为空")
	}
	if config.Key == "" {
		return nil, fmt.Errorf("Key不能为空")
	}
	if config.EncryptType == "" {
		config.EncryptType = EncryptTypeAES
	}
	if config.Env == "" {
		config.Env = EnvTest
	}

	var baseURL string
	switch config.Env {
	case EnvTest:
		baseURL = testBaseURL
	case EnvProduct:
		baseURL = productBaseURL
	default:
		return nil, fmt.Errorf("不支持的环境: %s", config.Env)
	}

	httpClient := config.HTTPClient
	if httpClient == nil {
		httpClient = &http.Client{}
	}

	return &Client{
		config:     config,
		httpClient: httpClient,
		baseURL:    baseURL,
	}, nil
}

// doRequest 发送请求并处理响应
func (c *Client) doRequest(path string, bizParams interface{}, result interface{}) error {
	// 1. 加密业务参数
	ciphertext, err := EncryptBizParams(bizParams, []byte(c.config.Key), c.config.EncryptType)
	if err != nil {
		return NewSDKError("加密业务参数失败", err)
	}

	// 2. 生成时间戳
	timestamp := GenerateTimestamp()

	// 3. 构建签名字符串并签名
	signStr := BuildSignStr(c.config.AppID, timestamp, ciphertext)
	sign, err := SignWithRSA(signStr, c.config.PrivateKey)
	if err != nil {
		return NewSDKError("签名失败", err)
	}

	// 4. 构建请求体
	reqBody := map[string]string{
		"ciphertext": ciphertext,
	}
	reqBodyBytes, err := json.Marshal(reqBody)
	if err != nil {
		return NewSDKError("序列化请求体失败", err)
	}

	// 5. 创建HTTP请求
	url := c.baseURL + path
	req, err := http.NewRequest("POST", url, bytes.NewReader(reqBodyBytes))
	if err != nil {
		return NewSDKError("创建HTTP请求失败", err)
	}

	// 6. 设置Header
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Appid", c.config.AppID)
	req.Header.Set("Timestamp", timestamp)
	req.Header.Set("Sign", sign)

	// 7. 发送请求
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return NewSDKError("发送HTTP请求失败", err)
	}
	defer resp.Body.Close()

	// 8. 读取响应
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return NewSDKError("读取响应失败", err)
	}

	// 9. 解析公共响应
	var commonResp CommonResponse
	if err := json.Unmarshal(respBody, &commonResp); err != nil {
		return NewSDKError("解析响应JSON失败", err)
	}

	// 10. 检查业务错误
	if commonResp.Code != 200 {
		return NewAPIError(commonResp.Code, commonResp.Message, commonResp.Reason)
	}

	// 11. 检查是否有加密数据
	if commonResp.Data == nil || commonResp.Data.Ciphertext == "" {
		return NewSDKError("响应中没有加密数据", nil)
	}

	// 12. 解密响应数据
	decrypted, err := DecryptBizParams(commonResp.Data.Ciphertext, []byte(c.config.Key), c.config.EncryptType)
	if err != nil {
		return NewSDKError("解密响应数据失败", err)
	}

	// 13. 解析解密后的数据到结果
	if err := json.Unmarshal(decrypted, result); err != nil {
		return NewSDKError("解析解密后的响应数据失败", err)
	}

	return nil
}

// ==================== API方法 ====================

// KeyOrder 获取券码
// POST /openapi/v1/key/order
func (c *Client) KeyOrder(req *KeyOrderRequest) (*KeyOrderResponse, error) {
	result := &KeyOrderResponse{}
	if err := c.doRequest("/openapi/v1/key/order", req, result); err != nil {
		return nil, err
	}
	return result, nil
}

// KeyQuery 券码查询
// POST /openapi/v1/key/query
func (c *Client) KeyQuery(req *KeyQueryRequest) (*KeyOrderResponse, error) {
	result := &KeyOrderResponse{}
	if err := c.doRequest("/openapi/v1/key/query", req, result); err != nil {
		return nil, err
	}
	return result, nil
}

// KeyDiscard 券码作废
// POST /openapi/v1/key/discard
func (c *Client) KeyDiscard(req *KeyDiscardRequest) (*KeyDiscardResponse, error) {
	result := &KeyDiscardResponse{}
	if err := c.doRequest("/openapi/v1/key/discard", req, result); err != nil {
		return nil, err
	}
	return result, nil
}

// BatchOrder 批量发卡
// POST /openapi/v1/key/batch_order
func (c *Client) BatchOrder(req *BatchOrderRequest) (*BatchOrderResponse, error) {
	result := &BatchOrderResponse{}
	if err := c.doRequest("/openapi/v1/key/batch_order", req, result); err != nil {
		return nil, err
	}
	return result, nil
}

// BatchQuery 批量查询
// POST /openapi/v1/key/batch_query
func (c *Client) BatchQuery(req *BatchQueryRequest) (*BatchQueryResponse, error) {
	result := &BatchQueryResponse{}
	if err := c.doRequest("/openapi/v1/key/batch_query", req, result); err != nil {
		return nil, err
	}
	return result, nil
}

// VerifyNotify 验证回调通知
func (c *Client) VerifyNotify(header *NotifyHeader, data interface{}) error {
	return VerifyNotifySign(header, data, []byte(c.config.Key), c.config.PublicKey, c.config.EncryptType)
}

// GetBaseURL 获取当前环境的基础URL
func (c *Client) GetBaseURL() string {
	return c.baseURL
}

// GetAppID 获取应用ID
func (c *Client) GetAppID() string {
	return c.config.AppID
}

// String 返回客户端配置的字符串表示(隐藏敏感信息)
func (c *Client) String() string {
	return fmt.Sprintf("Client{AppID: %s, Env: %s, BaseURL: %s, EncryptType: %s}",
		c.config.AppID, c.config.Env, c.baseURL, c.config.EncryptType)
}

// File: ymt_v3/example_test.go

package ymt_v3

import (
	"encoding/json"
	"fmt"
	"testing"
)

// 测试参数(请替换为实际值)
const (
	testAppID      = "your_app_id"
	testPrivateKey = `-----BEGIN PRIVATE KEY-----
your_private_key_here
-----END PRIVATE KEY-----`
	testPublicKey = `-----BEGIN PUBLIC KEY-----
your_public_key_here
-----END PUBLIC KEY-----`
	testKey        = "your_encryption_key_16"
	testActivityNo = "your_activity_no"
)

func ExampleClient_KeyOrder() {
	// 创建客户端
	client, err := NewClient(&ClientConfig{
		AppID:       testAppID,
		PrivateKey:  testPrivateKey,
		PublicKey:   testPublicKey,
		Key:         testKey,
		EncryptType: EncryptTypeAES,
		Env:         EnvTest,
	})
	if err != nil {
		fmt.Printf("创建客户端失败: %v\n", err)
		return
	}

	// 获取券码
	resp, err := client.KeyOrder(&KeyOrderRequest{
		OutBizNo:   "order_001",
		ActivityNo: testActivityNo,
		Account:    "18666666666",
		NotifyURL:  "https://notify.example.com/openapi",
	})
	if err != nil {
		fmt.Printf("获取券码失败: %v\n", err)
		return
	}

	fmt.Printf("交易号: %s\n", resp.TradeNo)
	fmt.Printf("卡密: %s\n", resp.Key)
	fmt.Printf("状态: %d\n", resp.Status)

	// Output:
	// 交易号: 7251449503000383488
	// 卡密: aZKdU9BymzR6qGRzJM
	// 状态: 1
}

func ExampleClient_KeyQuery() {
	client, _ := NewClient(&ClientConfig{
		AppID:       testAppID,
		PrivateKey:  testPrivateKey,
		PublicKey:   testPublicKey,
		Key:         testKey,
		EncryptType: EncryptTypeAES,
		Env:         EnvTest,
	})

	// 按外部业务号查询
	resp, err := client.KeyQuery(&KeyQueryRequest{
		OutBizNo: "order_001",
	})
	if err != nil {
		fmt.Printf("查询失败: %v\n", err)
		return
	}

	fmt.Printf("交易号: %s\n", resp.TradeNo)
	fmt.Printf("状态: %d\n", resp.Status)
}

func ExampleClient_KeyDiscard() {
	client, _ := NewClient(&ClientConfig{
		AppID:       testAppID,
		PrivateKey:  testPrivateKey,
		PublicKey:   testPublicKey,
		Key:         testKey,
		EncryptType: EncryptTypeAES,
		Env:         EnvTest,
	})

	// 按交易号作废
	resp, err := client.KeyDiscard(&KeyDiscardRequest{
		TradeNo: "7251449503000383488",
	})
	if err != nil {
		fmt.Printf("作废失败: %v\n", err)
		return
	}

	fmt.Printf("交易号: %s\n", resp.TradeNo)
	fmt.Printf("状态: %d\n", resp.Status)
}

func ExampleClient_BatchOrder() {
	client, _ := NewClient(&ClientConfig{
		AppID:       testAppID,
		PrivateKey:  testPrivateKey,
		PublicKey:   testPublicKey,
		Key:         testKey,
		EncryptType: EncryptTypeAES,
		Env:         EnvTest,
	})

	// 批量发卡
	resp, err := client.BatchOrder(&BatchOrderRequest{
		OutBizNo:   "batch_001",
		ActivityNo: testActivityNo,
		Number:     100,
		NotifyURL:  "https://notify.example.com/openapi",
	})
	if err != nil {
		fmt.Printf("批量发卡失败: %v\n", err)
		return
	}

	fmt.Printf("交易号: %s\n", resp.TradeNo)
	fmt.Printf("状态: %s\n", resp.Status)
}

func ExampleClient_BatchQuery() {
	client, _ := NewClient(&ClientConfig{
		AppID:       testAppID,
		PrivateKey:  testPrivateKey,
		PublicKey:   testPublicKey,
		Key:         testKey,
		EncryptType: EncryptTypeAES,
		Env:         EnvTest,
	})

	// 查询批量任务
	resp, err := client.BatchQuery(&BatchQueryRequest{
		TradeNo: "7251449503000383499",
	})
	if err != nil {
		fmt.Printf("查询失败: %v\n", err)
		return
	}

	fmt.Printf("状态: %s\n", resp.Status)
	if resp.DownloadURL != "" {
		fmt.Printf("下载地址: %s\n", resp.DownloadURL)
	}
}

func TestMarshalWithoutZero(t *testing.T) {
	req := KeyOrderRequest{
		OutBizNo:   "order_001",
		ActivityNo: "ACT20260622001",
		// Account和NotifyURL为空应该被去掉
	}

	result, err := marshalWithoutZero(req)
	if err != nil {
		t.Fatalf("marshalWithoutZero失败: %v", err)
	}

	expected := `{"activity_no":"ACT20260622001","out_biz_no":"order_001"}`
	if result != expected {
		t.Errorf("期望 %s, 得到 %s", expected, result)
	}
}

func TestBuildSignStr(t *testing.T) {
	appID := "123456"
	timestamp := "2026-06-22 15:30:00"
	ciphertext := "encrypted_data"

	signStr := BuildSignStr(appID, timestamp, ciphertext)
	expected := "1234562026-06-22 15:30:00encrypted_data"
	if signStr != expected {
		t.Errorf("期望 %s, 得到 %s", expected, signStr)
	}
}

func TestGenerateTimestamp(t *testing.T) {
	ts := GenerateTimestamp()
	// 验证格式 yyyy-MM-dd HH:mm:ss
	if len(ts) != 19 {
		t.Errorf("时间戳格式错误: %s", ts)
	}
}

func TestEncryptDecryptAES(t *testing.T) {
	key := []byte("1234567890123456") // 16字节
	plaintext := `{"out_biz_no":"order_001","activity_no":"ACT001"}`

	ciphertext, err := AESECBEncrypt([]byte(plaintext), key)
	if err != nil {
		t.Fatalf("加密失败: %v", err)
	}

	decrypted, err := AESECBDecrypt(ciphertext, key)
	if err != nil {
		t.Fatalf("解密失败: %v", err)
	}

	if string(decrypted) != plaintext {
		t.Errorf("解密结果不匹配: 期望 %s, 得到 %s", plaintext, string(decrypted))
	}
}

func TestEncryptDecryptSM4(t *testing.T) {
	key := []byte("1234567890123456") // 16字节
	plaintext := `{"out_biz_no":"order_001","activity_no":"ACT001"}`

	ciphertext, err := SM4CBCEncrypt([]byte(plaintext), key)
	if err != nil {
		t.Fatalf("SM4加密失败: %v", err)
	}

	decrypted, err := SM4CBCDecrypt(ciphertext, key)
	if err != nil {
		t.Fatalf("SM4解密失败: %v", err)
	}

	if string(decrypted) != plaintext {
		t.Errorf("SM4解密结果不匹配: 期望 %s, 得到 %s", plaintext, string(decrypted))
	}
}

func TestEncryptBizParams(t *testing.T) {
	key := []byte("1234567890123456")
	req := KeyOrderRequest{
		OutBizNo:   "order_001",
		ActivityNo: "ACT20260622001",
	}

	ciphertext, err := EncryptBizParams(req, key, EncryptTypeAES)
	if err != nil {
		t.Fatalf("加密业务参数失败: %v", err)
	}

	if ciphertext == "" {
		t.Fatal("加密结果为空")
	}

	// 验证可以解密
	decrypted, err := DecryptBizParams(ciphertext, key, EncryptTypeAES)
	if err != nil {
		t.Fatalf("解密失败: %v", err)
	}

	var result KeyOrderRequest
	if err := json.Unmarshal(decrypted, &result); err != nil {
		t.Fatalf("解析失败: %v", err)
	}

	if result.OutBizNo != "order_001" {
		t.Errorf("OutBizNo不匹配: 期望 order_001, 得到 %s", result.OutBizNo)
	}
}

func TestNewClient(t *testing.T) {
	_, err := NewClient(&ClientConfig{
		AppID:       testAppID,
		PrivateKey:  testPrivateKey,
		PublicKey:   testPublicKey,
		Key:         testKey,
		EncryptType: EncryptTypeAES,
		Env:         EnvTest,
	})
	if err != nil {
		t.Fatalf("创建客户端失败: %v", err)
	}
}

func TestNewClientMissingFields(t *testing.T) {
	tests := []struct {
		name    string
		config  *ClientConfig
		wantErr bool
	}{
		{"空AppID", &ClientConfig{PrivateKey: "pk", PublicKey: "pub", Key: "key"}, true},
		{"空PrivateKey", &ClientConfig{AppID: "app", PublicKey: "pub", Key: "key"}, true},
		{"空PublicKey", &ClientConfig{AppID: "app", PrivateKey: "pk", Key: "key"}, true},
		{"空Key", &ClientConfig{AppID: "app", PrivateKey: "pk", PublicKey: "pub"}, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := NewClient(tt.config)
			if (err != nil) != tt.wantErr {
				t.Errorf("NewClient() error = %v, wantErr = %v", err, tt.wantErr)
			}
		})
	}
}

func TestAPIError(t *testing.T) {
	err := NewAPIError(401, "Signature verification failed", "INVALID_SIGNATURE")
	if err.Code != 401 {
		t.Errorf("Code不匹配: 期望 401, 得到 %d", err.Code)
	}
	if err.Message != "Signature verification failed" {
		t.Errorf("Message不匹配")
	}
	if err.Reason != "INVALID_SIGNATURE" {
		t.Errorf("Reason不匹配")
	}
	if err.Error() != "API error: code=401, message=Signature verification failed, reason=INVALID_SIGNATURE" {
		t.Errorf("Error()不匹配: %s", err.Error())
	}
	if err.IsSuccess() {
		t.Error("401应该不是成功")
	}
}

func TestSDKError(t *testing.T) {
	err := NewSDKError("测试错误", nil)
	if err.Error() != "SDK error: 测试错误" {
		t.Errorf("Error()不匹配: %s", err.Error())
	}
}

现在让我检查一下 crypto.go 中的 CBC 实现,我需要修复它,因为之前的实现有 panic。让我重新生成 crypto.go 文件,使用标准库的 cipher.BlockMode 接口。

实际上SM4 的 sm4.NewCipher 返回的是 cipher.Block 接口,所以我们可以直接使用 cipher.NewCBCEncryptercipher.NewCBCDecrypter。让我修复 crypto.go。

让我重新生成 crypto.go 文件:

// File: ymt_v3/crypto.go

package ymt_v3

import (
	"bytes"
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"reflect"
	"sort"
	"strings"
	"time"

	"github.com/tjfoc/gmsm/sm4"
)

// EncryptType 加密类型
type EncryptType string

const (
	EncryptTypeAES EncryptType = "aes"
	EncryptTypeSM4 EncryptType = "sm4"
)

// ==================== RSA签名 ====================

// SignWithRSA 使用RSA私钥对数据进行签名
func SignWithRSA(signStr string, privateKeyPEM string) (string, error) {
	block, _ := pem.Decode([]byte(privateKeyPEM))
	if block == nil {
		return "", fmt.Errorf("failed to decode PEM private key")
	}

	privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
		if err != nil {
			return "", fmt.Errorf("failed to parse private key: %v", err)
		}
	}

	rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
	if !ok {
		return "", fmt.Errorf("not a RSA private key")
	}

	h := sha256.New()
	h.Write([]byte(signStr))
	hashed := h.Sum(nil)

	signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, crypto.SHA256, hashed)
	if err != nil {
		return "", err
	}

	return base64.StdEncoding.EncodeToString(signature), nil
}

// VerifyWithRSA 使用RSA公钥验证签名
func VerifyWithRSA(signStr, signature string, publicKeyPEM string) error {
	block, _ := pem.Decode([]byte(publicKeyPEM))
	if block == nil {
		return fmt.Errorf("failed to decode PEM public key")
	}

	publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return fmt.Errorf("failed to parse public key: %v", err)
	}

	rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
	if !ok {
		return fmt.Errorf("not a RSA public key")
	}

	h := sha256.New()
	h.Write([]byte(signStr))
	hashed := h.Sum(nil)

	sigBytes, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return fmt.Errorf("failed to decode signature: %v", err)
	}

	return rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hashed, sigBytes)
}

// ==================== AES ECB加密 ====================

// AESECBEncrypt AES-ECB模式加密
func AESECBEncrypt(plaintext []byte, key []byte) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", fmt.Errorf("创建AES cipher失败: %v", err)
	}

	padded := pkcs7Padding(plaintext, aes.BlockSize)

	ciphertext := make([]byte, len(padded))
	for i := 0; i < len(padded); i += aes.BlockSize {
		block.Encrypt(ciphertext[i:i+aes.BlockSize], padded[i:i+aes.BlockSize])
	}

	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// AESECBDecrypt AES-ECB模式解密
func AESECBDecrypt(encryptedData string, key []byte) ([]byte, error) {
	ciphertext, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return nil, fmt.Errorf("解码失败: %v", err)
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("创建AES cipher失败: %v", err)
	}

	if len(ciphertext)%aes.BlockSize != 0 {
		return nil, fmt.Errorf("密文长度不是块大小的整数倍")
	}

	plaintext := make([]byte, len(ciphertext))
	for i := 0; i < len(ciphertext); i += aes.BlockSize {
		block.Decrypt(plaintext[i:i+aes.BlockSize], ciphertext[i:i+aes.BlockSize])
	}

	plaintext, err = pkcs7UnPadding(plaintext)
	if err != nil {
		return nil, fmt.Errorf("去除填充失败: %v", err)
	}

	return plaintext, nil
}

// ==================== SM4 CBC加密 ====================

// SM4CBCEncrypt SM4-CBC模式加密
func SM4CBCEncrypt(plaintext []byte, key []byte) (string, error) {
	if len(key) != 16 {
		return "", fmt.Errorf("SM4密钥长度必须为16字节")
	}

	block, err := sm4.NewCipher(key)
	if err != nil {
		return "", fmt.Errorf("创建SM4 cipher失败: %v", err)
	}

	padded := pkcs7Padding(plaintext, block.BlockSize())

	iv := make([]byte, block.BlockSize())
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return "", fmt.Errorf("生成IV失败: %v", err)
	}

	mode := cipher.NewCBCEncrypter(block, iv)
	ciphertext := make([]byte, len(padded))
	mode.CryptBlocks(ciphertext, padded)

	result := append(iv, ciphertext...)
	return base64.StdEncoding.EncodeToString(result), nil
}

// SM4CBCDecrypt SM4-CBC模式解密
func SM4CBCDecrypt(encryptedData string, key []byte) ([]byte, error) {
	if len(key) != 16 {
		return nil, fmt.Errorf("SM4密钥长度必须为16字节")
	}

	data, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return nil, fmt.Errorf("解码失败: %v", err)
	}

	block, err := sm4.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("创建SM4 cipher失败: %v", err)
	}

	blockSize := block.BlockSize()
	if len(data) < blockSize {
		return nil, fmt.Errorf("数据长度不足")
	}
	iv := data[:blockSize]
	ciphertext := data[blockSize:]

	mode := cipher.NewCBCDecrypter(block, iv)
	plaintext := make([]byte, len(ciphertext))
	mode.CryptBlocks(plaintext, ciphertext)

	plaintext, err = pkcs7UnPadding(plaintext)
	if err != nil {
		return nil, fmt.Errorf("去除填充失败: %v", err)
	}

	return plaintext, nil
}

// ==================== PKCS7填充 ====================

// pkcs7Padding PKCS7填充
func pkcs7Padding(data []byte, blockSize int) []byte {
	padding := blockSize - len(data)%blockSize
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padText...)
}

// pkcs7UnPadding 去除PKCS7填充
func pkcs7UnPadding(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, fmt.Errorf("数据为空")
	}
	padding := int(data[length-1])
	if padding > length || padding == 0 {
		return nil, fmt.Errorf("无效的填充")
	}
	for i := length - padding; i < length; i++ {
		if data[i] != byte(padding) {
			return nil, fmt.Errorf("无效的填充")
		}
	}
	return data[:length-padding], nil
}

// ==================== 业务参数加密/解密 ====================

// EncryptBizParams 加密业务参数
// 1. 将业务参数去掉"零"值的参数再由小到大按照字母排序转成json字符串得到plaintext
// 2. 使用应用key将plaintext字符串加密得到ciphertext
func EncryptBizParams(params interface{}, key []byte, encType EncryptType) (string, error) {
	plaintext, err := marshalWithoutZero(params)
	if err != nil {
		return "", fmt.Errorf("序列化业务参数失败: %v", err)
	}

	switch encType {
	case EncryptTypeAES:
		return AESECBEncrypt([]byte(plaintext), key)
	case EncryptTypeSM4:
		return SM4CBCEncrypt([]byte(plaintext), key)
	default:
		return "", fmt.Errorf("不支持的加密类型: %s", encType)
	}
}

// DecryptBizParams 解密业务参数
func DecryptBizParams(ciphertext string, key []byte, encType EncryptType) ([]byte, error) {
	switch encType {
	case EncryptTypeAES:
		return AESECBDecrypt(ciphertext, key)
	case EncryptTypeSM4:
		return SM4CBCDecrypt(ciphertext, key)
	default:
		return nil, fmt.Errorf("不支持的加密类型: %s", encType)
	}
}

// EncryptBizParamsRaw 直接加密字节数据
func EncryptBizParamsRaw(plaintext []byte, key []byte, encType EncryptType) (string, error) {
	switch encType {
	case EncryptTypeAES:
		return AESECBEncrypt(plaintext, key)
	case EncryptTypeSM4:
		return SM4CBCEncrypt(plaintext, key)
	default:
		return "", fmt.Errorf("不支持的加密类型: %s", encType)
	}
}

// marshalWithoutZero 将结构体序列化为JSON去掉零值字段并按字母排序
func marshalWithoutZero(v interface{}) (string, error) {
	val := reflect.ValueOf(v)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}

	if val.Kind() != reflect.Struct {
		if val.Kind() == reflect.Map {
			return marshalMapSorted(v)
		}
		data, err := json.Marshal(v)
		return string(data), err
	}

	result := make(map[string]interface{})
	typ := val.Type()

	for i := 0; i < val.NumField(); i++ {
		field := val.Field(i)
		fieldType := typ.Field(i)

		jsonTag := fieldType.Tag.Get("json")
		if jsonTag == "" || jsonTag == "-" {
			continue
		}
		name := strings.Split(jsonTag, ",")[0]

		if isZeroValue(field) {
			continue
		}

		result[name] = field.Interface()
	}

	return marshalMapSorted(result)
}

// marshalMapSorted 将map按键排序后序列化为JSON
func marshalMapSorted(m map[string]interface{}) (string, error) {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var buf bytes.Buffer
	buf.WriteByte('{')
	for i, k := range keys {
		if i > 0 {
			buf.WriteByte(',')
		}
		keyBytes, _ := json.Marshal(k)
		buf.Write(keyBytes)
		buf.WriteByte(':')
		valBytes, err := json.Marshal(m[k])
		if err != nil {
			return "", err
		}
		buf.Write(valBytes)
	}
	buf.WriteByte('}')

	return buf.String(), nil
}

// isZeroValue 判断reflect.Value是否为零值
func isZeroValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.String:
		return v.String() == ""
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Slice, reflect.Map, reflect.Ptr:
		return v.IsNil()
	default:
		return false
	}
}

// ==================== 签名相关 ====================

// BuildSignStr 构建签名字符串
// 规则app_id + timestamp + ciphertext
func BuildSignStr(appID, timestamp, ciphertext string) string {
	return appID + timestamp + ciphertext
}

// GenerateTimestamp 生成时间戳,格式 yyyy-MM-dd HH:mm:ss
func GenerateTimestamp() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// ==================== 回调验签 ====================

// VerifyNotifySign 验证回调通知签名
func VerifyNotifySign(header *NotifyHeader, data interface{}, key []byte, publicKeyPEM string, encType EncryptType) error {
	plaintext, err := marshalWithoutZero(data)
	if err != nil {
		return fmt.Errorf("序列化回调数据失败: %v", err)
	}

	ciphertext, err := EncryptBizParamsRaw([]byte(plaintext), key, encType)
	if err != nil {
		return fmt.Errorf("加密回调数据失败: %v", err)
	}

	signStr := BuildSignStr(header.Appid, header.Timestamp, ciphertext)

	return VerifyWithRSA(signStr, header.Sign, publicKeyPEM)
}

现在让我重新生成完整的 client.go 文件,确保 crypto.go 中的函数签名与 client.go 中的调用一致。

实际上,我注意到 crypto.go 中我使用了 cipher.NewCBCEncryptercipher.NewCBCDecrypter,这些是标准库 crypto/cipher 包中的函数,它们接受 cipher.Block 接口。而 sm4.NewCipher 返回的正是 cipher.Block 接口,所以可以直接使用。

让我检查一下所有文件的一致性,然后输出最终版本。

好的,所有文件已经生成完毕。让我输出最终版本。

实际上,让我重新审视一下 crypto.go 中的 CBC 实现。标准库的 cipher.NewCBCEncryptercipher.NewCBCDecrypter 接受 cipher.Block 接口,而 sm4.NewCipher 返回 *sm4.SM4Cipher 实现了 cipher.Block 接口,所以可以直接使用。

但是SM4 CBC 加密时IV 需要前置到密文中,解密时从密文中提取 IV。这个逻辑已经在代码中实现了。

好的,所有文件都准备好了,让我输出最终版本。

等等,让我再检查一下 go.mod 中的依赖。github.com/tjfoc/gmsm 的版本,我写的是 v1.4.1,但实际最新版本可能不同。不过对于 SDK 来说,指定一个合理的版本即可。

好的,现在输出所有文件。现在让我重新生成完整的 crypto.go 文件,修复 CBC 实现问题:

// File: ymt_v3/crypto.go

package ymt_v3

import (
	"bytes"
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"reflect"
	"sort"
	"strings"
	"time"

	"github.com/tjfoc/gmsm/sm4"
)

// EncryptType 加密类型
type EncryptType string

const (
	// EncryptTypeAES AES-ECB加密
	EncryptTypeAES EncryptType = "aes"
	// EncryptTypeSM4 SM4-CBC加密
	EncryptTypeSM4 EncryptType = "sm4"
)

// ==================== RSA签名 ====================

// SignWithRSA 使用RSA私钥对数据进行签名
func SignWithRSA(signStr string, privateKeyPEM string) (string, error) {
	block, _ := pem.Decode([]byte(privateKeyPEM))
	if block == nil {
		return "", fmt.Errorf("failed to decode PEM private key")
	}

	privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
		if err != nil {
			return "", fmt.Errorf("failed to parse private key: %v", err)
		}
	}

	rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
	if !ok {
		return "", fmt.Errorf("not a RSA private key")
	}

	h := sha256.New()
	h.Write([]byte(signStr))
	hashed := h.Sum(nil)

	signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, crypto.SHA256, hashed)
	if err != nil {
		return "", err
	}

	return base64.StdEncoding.EncodeToString(signature), nil
}

// VerifyWithRSA 使用RSA公钥验证签名
func VerifyWithRSA(signStr, signature string, publicKeyPEM string) error {
	block, _ := pem.Decode([]byte(publicKeyPEM))
	if block == nil {
		return fmt.Errorf("failed to decode PEM public key")
	}

	publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return fmt.Errorf("failed to parse public key: %v", err)
	}

	rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
	if !ok {
		return fmt.Errorf("not a RSA public key")
	}

	h := sha256.New()
	h.Write([]byte(signStr))
	hashed := h.Sum(nil)

	sigBytes, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return fmt.Errorf("failed to decode signature: %v", err)
	}

	return rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hashed, sigBytes)
}

// ==================== AES ECB加密 ====================

// AESECBEncrypt AES-ECB模式加密
func AESECBEncrypt(plaintext []byte, key []byte) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", fmt.Errorf("创建AES cipher失败: %v", err)
	}

	padded := pkcs7Padding(plaintext, aes.BlockSize)

	ciphertext := make([]byte, len(padded))
	for i := 0; i < len(padded); i += aes.BlockSize {
		block.Encrypt(ciphertext[i:i+aes.BlockSize], padded[i:i+aes.BlockSize])
	}

	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// AESECBDecrypt AES-ECB模式解密
func AESECBDecrypt(encryptedData string, key []byte) ([]byte, error) {
	ciphertext, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return nil, fmt.Errorf("解码失败: %v", err)
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("创建AES cipher失败: %v", err)
	}

	if len(ciphertext)%aes.BlockSize != 0 {
		return nil, fmt.Errorf("密文长度不是块大小的整数倍")
	}

	plaintext := make([]byte, len(ciphertext))
	for i := 0; i < len(ciphertext); i += aes.BlockSize {
		block.Decrypt(plaintext[i:i+aes.BlockSize], ciphertext[i:i+aes.BlockSize])
	}

	plaintext, err = pkcs7UnPadding(plaintext)
	if err != nil {
		return nil, fmt.Errorf("去除填充失败: %v", err)
	}

	return plaintext, nil
}

// ==================== SM4 CBC加密 ====================

// SM4CBCEncrypt SM4-CBC模式加密
// IV会前置到密文中解密时自动提取
func SM4CBCEncrypt(plaintext []byte, key []byte) (string, error) {
	if len(key) != 16 {
		return "", fmt.Errorf("SM4密钥长度必须为16字节")
	}

	block, err := sm4.NewCipher(key)
	if err != nil {
		return "", fmt.Errorf("创建SM4 cipher失败: %v", err)
	}

	padded := pkcs7Padding(plaintext, block.BlockSize())

	iv := make([]byte, block.BlockSize())
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return "", fmt.Errorf("生成IV失败: %v", err)
	}

	mode := cipher.NewCBCEncrypter(block, iv)
	ciphertext := make([]byte, len(padded))
	mode.CryptBlocks(ciphertext, padded)

	// IV前置到密文
	result := append(iv, ciphertext...)
	return base64.StdEncoding.EncodeToString(result), nil
}

// SM4CBCDecrypt SM4-CBC模式解密
// 从密文中提取前置的IV进行解密
func SM4CBCDecrypt(encryptedData string, key []byte) ([]byte, error) {
	if len(key) != 16 {
		return nil, fmt.Errorf("SM4密钥长度必须为16字节")
	}

	data, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return nil, fmt.Errorf("解码失败: %v", err)
	}

	block, err := sm4.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("创建SM4 cipher失败: %v", err)
	}

	blockSize := block.BlockSize()
	if len(data) < blockSize*2 {
		return nil, fmt.Errorf("数据长度不足")
	}
	iv := data[:blockSize]
	ciphertext := data[blockSize:]

	mode := cipher.NewCBCDecrypter(block, iv)
	plaintext := make([]byte, len(ciphertext))
	mode.CryptBlocks(plaintext, ciphertext)

	plaintext, err = pkcs7UnPadding(plaintext)
	if err != nil {
		return nil, fmt.Errorf("去除填充失败: %v", err)
	}

	return plaintext, nil
}

// ==================== PKCS7填充 ====================

// pkcs7Padding PKCS7填充
func pkcs7Padding(data []byte, blockSize int) []byte {
	padding := blockSize - len(data)%blockSize
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padText...)
}

// pkcs7UnPadding 去除PKCS7填充
func pkcs7UnPadding(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, fmt.Errorf("数据为空")
	}
	padding := int(data[length-1])
	if padding > length || padding == 0 {
		return nil, fmt.Errorf("无效的填充")
	}
	for i := length - padding; i < length; i++ {
		if data[i] != byte(padding) {
			return nil, fmt.Errorf("无效的填充")
		}
	}
	return data[:length-padding], nil
}

// ==================== 业务参数加密/解密 ====================

// EncryptBizParams 加密业务参数
// 1. 将业务参数去掉"零"值的参数再由小到大按照字母排序转成json字符串得到plaintext
// 2. 使用应用key将plaintext字符串加密得到ciphertext
func EncryptBizParams(params interface{}, key []byte, encType EncryptType) (string, error) {
	plaintext, err := marshalWithoutZero(params)
	if err != nil {
		return "", fmt.Errorf("序列化业务参数失败: %v", err)
	}

	switch encType {
	case EncryptTypeAES:
		return AESECBEncrypt([]byte(plaintext), key)
	case EncryptTypeSM4:
		return SM4CBCEncrypt([]byte(plaintext), key)
	default:
		return "", fmt.Errorf("不支持的加密类型: %s", encType)
	}
}

// DecryptBizParams 解密业务参数
func DecryptBizParams(ciphertext string, key []byte, encType EncryptType) ([]byte, error) {
	switch encType {
	case EncryptTypeAES:
		return AESECBDecrypt(ciphertext, key)
	case EncryptTypeSM4:
		return SM4CBCDecrypt(ciphertext, key)
	default:
		return nil, fmt.Errorf("不支持的加密类型: %s", encType)
	}
}

// EncryptBizParamsRaw 直接加密字节数据(用于回调验签)
func EncryptBizParamsRaw(plaintext []byte, key []byte, encType EncryptType) (string, error) {
	switch encType {
	case EncryptTypeAES:
		return AESECBEncrypt(plaintext, key)
	case EncryptTypeSM4:
		return SM4CBCEncrypt(plaintext, key)
	default:
		return "", fmt.Errorf("不支持的加密类型: %s", encType)
	}
}

// marshalWithoutZero 将结构体序列化为JSON去掉零值字段并按字母排序
func marshalWithoutZero(v interface{}) (string, error) {
	val := reflect.ValueOf(v)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}

	if val.Kind() != reflect.Struct {
		if val.Kind() == reflect.Map {
			return marshalMapSorted(v)
		}
		data, err := json.Marshal(v)
		return string(data), err
	}

	result := make(map[string]interface{})
	typ := val.Type()

	for i := 0; i < val.NumField(); i++ {
		field := val.Field(i)
		fieldType := typ.Field(i)

		jsonTag := fieldType.Tag.Get("json")
		if jsonTag == "" || jsonTag == "-" {
			continue
		}
		name := strings.Split(jsonTag, ",")[0]

		if isZeroValue(field) {
			continue
		}

		result[name] = field.Interface()
	}

	return marshalMapSorted(result)
}

// marshalMapSorted 将map按键排序后序列化为JSON
func marshalMapSorted(m map[string]interface{}) (string, error) {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var buf bytes.Buffer
	buf.WriteByte('{')
	for i, k := range keys {
		if i > 0 {
			buf.WriteByte(',')
		}
		keyBytes, _ := json.Marshal(k)
		buf.Write(keyBytes)
		buf.WriteByte(':')
		valBytes, err := json.Marshal(m[k])
		if err != nil {
			return "", err
		}
		buf.Write(valBytes)
	}
	buf.WriteByte('}')

	return buf.String(), nil
}

// isZeroValue 判断reflect.Value是否为零值
func isZeroValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.String:
		return v.String() == ""
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Slice, reflect.Map, reflect.Ptr:
		return v.IsNil()
	default:
		return false
	}
}

// ==================== 签名相关 ====================

// BuildSignStr 构建签名字符串
// 规则app_id + timestamp + ciphertext
func BuildSignStr(appID, timestamp, ciphertext string) string {
	return appID + timestamp + ciphertext
}

// GenerateTimestamp 生成时间戳,格式 yyyy-MM-dd HH:mm:ss
func GenerateTimestamp() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// ==================== 回调验签 ====================

// VerifyNotifySign 验证回调通知签名
// 1. 获取header头里面的签名信息
// 2. 获取body里面的业务参数data
// 3. 将业务参数data去掉"零"值的参数再由小到大按照字母排序转成json字符串得到plaintext
// 4. 使用应用key将plaintext字符串加密得到ciphertext
// 5. 拼接签名字符串app_id + timestamp + ciphertext
// 6. 使用应用公钥验签
func VerifyNotifySign(header *NotifyHeader, data interface{}, key []byte, publicKeyPEM string, encType Encrypt

## 加密实现

以下是从加密工具获取的完整实现

### 加密实现 1

{
  "success": true,
  "data": "\n### 时间戳和随机数生成规范\n\n**代码模板**\n\n```go\npackage crypto\n\nimport (\n    \"crypto/rand\"\n    \"fmt\"\n    \"math/big\"\n    \"time\"\n)\n\n// GenerateTimestamp 生成秒级时间戳\nfunc GenerateTimestamp() string {\n    return fmt.Sprintf(\"%d\", time.Now().Unix())\n}\n\n// GenerateTimestampMillis 生成毫秒级时间戳\nfunc GenerateTimestampMillis() string {\n    return fmt.Sprintf(\"%d\", time.Now().UnixMilli())\n}\n\n// GenerateNonce 生成指定长度的随机字符串(加密安全)\nfunc GenerateNonce(length int) (string, error) {\n    const charset = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n    b := make([]byte, length)\n    for i := range b {\n        num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))\n        if err != nil {\n            return \"\", err\n        }\n        b[i] = charset[num.Int64()]\n    }\n    return string(b), nil\n}\n```\n\n**注意事项**\n- 确认文档要求的是秒级还是毫秒级时间戳\n- 确认nonce的长度要求通常16-32位\n- 生产环境建议使用加密安全的随机数生成器\n",
  "tool": "nonce_timestamp"
}

### 加密实现 2

{
  "success": true,
  "data": "\n### AES-ECB 加密完整实现指南\n\n**配置参数**\n- 密钥长度: 16 字节\n- 编码方式: base64\n\n**完整代码模板**\n\n```go\npackage crypto\n\nimport (\n    \"crypto/aes\"\n    \"encoding/base64\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"bytes\"\n)\n\n// AESECBEncrypt AES-ECB模式加密\nfunc AESECBEncrypt(plaintext []byte, key []byte, encoding string) (string, error) {\n    block, err := aes.NewCipher(key)\n    if err != nil {\n        return \"\", fmt.Errorf(\"创建AES cipher失败: %v\", err)\n    }\n\n    padded := pkcs7Padding(plaintext, aes.BlockSize)\n\n    ciphertext := make([]byte, len(padded))\n    for i := 0; i \u003c len(padded); i += aes.BlockSize {\n        block.Encrypt(ciphertext[i:i+aes.BlockSize], padded[i:i+aes.BlockSize])\n    }\n\n    if encoding == \"hex\" {\n        return hex.EncodeToString(ciphertext), nil\n    }\n    return base64.StdEncoding.EncodeToString(ciphertext), nil\n}\n\n// AESECBDecrypt AES-ECB模式解密\nfunc AESECBDecrypt(encryptedData string, key []byte, encoding string) ([]byte, error) {\n    var ciphertext []byte\n    var err error\n    \n    if encoding == \"hex\" {\n        ciphertext, err = hex.DecodeString(encryptedData)\n    } else {\n        ciphertext, err = base64.StdEncoding.DecodeString(encryptedData)\n    }\n    if err != nil {\n        return nil, fmt.Errorf(\"解码失败: %v\", err)\n    }\n\n    block, err := aes.NewCipher(key)\n    if err != nil {\n        return nil, fmt.Errorf(\"创建AES cipher失败: %v\", err)\n    }\n\n    if len(ciphertext)%aes.BlockSize != 0 {\n        return nil, fmt.Errorf(\"密文长度不是块大小的整数倍\")\n    }\n\n    plaintext := make([]byte, len(ciphertext))\n    for i := 0; i \u003c len(ciphertext); i += aes.BlockSize {\n        block.Decrypt(plaintext[i:i+aes.BlockSize], ciphertext[i:i+aes.BlockSize])\n    }\n\n    plaintext, err = pkcs7UnPadding(plaintext)\n    if err != nil {\n        return nil, fmt.Errorf(\"去除填充失败: %v\", err)\n    }\n\n    return plaintext, nil\n}\n```\n\n%!(EXTRA string=16)",
  "tool": "aes_ecb_encrypt"
}

### 加密实现 3

{
  "success": true,
  "data": "\n### RSA签名完整实现指南\n\n**适用场景**文档要求使用RSA私钥对请求参数进行签名\n\n**配置参数**\n- 哈希算法: SHA256\n- 编码方式: base64\n\n**完整代码模板**\n```go\npackage crypto\n\nimport (\n    \"crypto\"\n    \"crypto/rand\"\n    \"crypto/rsa\"\n    \"crypto/sha256\"\n    \"crypto/sha512\"\n    \"crypto/x509\"\n    \"encoding/base64\"\n    \"encoding/hex\"\n    \"encoding/pem\"\n    \"fmt\"\n    \"sort\"\n    \"strings\"\n)\n\n// RSAConfig RSA签名配置\ntype RSAConfig struct {\n    PrivateKeyPEM string // PEM格式的私钥\n    KeyID         string // 密钥ID如文档要求携带\n}\n\n// SignWithRSA 使用RSA私钥对请求进行签名\nfunc SignWithRSA(params map[string]string, config *RSAConfig) (string, error) {\n    // 1. 拼接参数(按字典序排序)\n    keys := make([]string, 0, len(params))\n    for k := range params {\n        if k != \"sign\" \u0026\u0026 k != \"signature\" {\n            keys = append(keys, k)\n        }\n    }\n    sort.Strings(keys)\n    \n    var sb strings.Builder\n    for _, k := range keys {\n        if sb.Len() \u003e 0 {\n            sb.WriteString(\"\u0026\")\n        }\n        sb.WriteString(k)\n        sb.WriteString(\"=\")\n        sb.WriteString(params[k])\n    }\n    signStr := sb.String()\n    \n    // 2. 加载私钥\n    block, _ := pem.Decode([]byte(config.PrivateKeyPEM))\n    if block == nil {\n        return \"\", fmt.Errorf(\"failed to decode PEM private key\")\n    }\n    \n    privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n    if err != nil {\n        // 尝试PKCS1格式\n        privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)\n        if err != nil {\n            return \"\", fmt.Errorf(\"failed to parse private key: %v\", err)\n        }\n    }\n    \n    rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)\n    if !ok {\n        return \"\", fmt.Errorf(\"not a RSA private key\")\n    }\n    \n    // 3. 计算哈希\n    var hashed []byte\n    var hashFunc crypto.Hash\n    \n    switch \"SHA256\" {\n    case \"SHA384\":\n        h := sha512.New384()\n        h.Write([]byte(signStr))\n        hashed = h.Sum(nil)\n        hashFunc = crypto.SHA384\n    case \"SHA512\":\n        h := sha512.New()\n        h.Write([]byte(signStr))\n        hashed = h.Sum(nil)\n        hashFunc = crypto.SHA512\n    default: // SHA256\n        h := sha256.New()\n        h.Write([]byte(signStr))\n        hashed = h.Sum(nil)\n        hashFunc = crypto.SHA256\n    }\n    \n    // 4. RSA签名\n    signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, hashFunc, hashed)\n    if err != nil {\n        return \"\", err\n    }\n    \n    // 5. 编码\n    if \"base64\" == \"hex\" {\n        return hex.EncodeToString(signature), nil\n    }\n    return base64.StdEncoding.EncodeToString(signature), nil\n}\n```\n\n**注意事项**\n- 确认文档使用的是 PKCS1 还是 PKCS8 格式\n- 确认是否需要添加额外的固定盐值\n- 确认编码是 Base64 还是 Hex\n- 注意排除签名字段本身sign/signature\n",
  "tool": "rsa_sign"
}

### 加密实现 4

{
  "success": true,
  "data": "\n### SM4-CBC 国密对称加密完整实现指南\n\n**⚠️ 重要:必须使用第三方库,不要自己实现 SM4 算法!**\n**推荐使用github.com/tjfoc/gmsm**\n\n**前置要求**\n```bash\ngo get github.com/tjfoc/gmsm\n```\n\n**配置参数**\n- 编码方式: base64\n\n**完整代码模板(请直接复制使用)**\n\n```go\npackage crypto\n\nimport (\n    \"crypto/cipher\"\n    \"crypto/rand\"\n    \"encoding/base64\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"bytes\"\n\n    \"github.com/tjfoc/gmsm/sm4\"  // ⚠️ 必须使用此库不要自己实现SM4\n)\n\n// SM4CBCEncrypt SM4-CBC模式加密\n// 使用 github.com/tjfoc/gmsm/sm4 实现\nfunc SM4CBCEncrypt(plaintext []byte, key []byte) (string, error) {\n    if len(key) != 16 {\n        return \"\", fmt.Errorf(\"SM4密钥长度必须为16字节\")\n    }\n\n    // 使用 gmsm 库创建 SM4 cipher\n    block, err := sm4.NewCipher(key)\n    if err != nil {\n        return \"\", fmt.Errorf(\"创建SM4 cipher失败: %v\", err)\n    }\n\n    padded := pkcs7Padding(plaintext, block.BlockSize())\n\n    iv := make([]byte, block.BlockSize())\n    if _, err := io.ReadFull(rand.Reader, iv); err != nil {\n        return \"\", fmt.Errorf(\"生成IV失败: %v\", err)\n    }\n\n    mode := cipher.NewCBCEncrypter(block, iv)\n    ciphertext := make([]byte, len(padded))\n    mode.CryptBlocks(ciphertext, padded)\n\n    result := append(iv, ciphertext...)\n    if \"base64\" == \"hex\" {\n        return hex.EncodeToString(result), nil\n    }\n    return base64.StdEncoding.EncodeToString(result), nil\n}\n\n// SM4CBCDecrypt SM4-CBC模式解密\n// 使用 github.com/tjfoc/gmsm/sm4 实现\nfunc SM4CBCDecrypt(encryptedData string, key []byte) ([]byte, error) {\n    if len(key) != 16 {\n        return nil, fmt.Errorf(\"SM4密钥长度必须为16字节\")\n    }\n\n    var data []byte\n    var err error\n    if \"base64\" == \"hex\" {\n        data, err = hex.DecodeString(encryptedData)\n    } else {\n        data, err = base64.StdEncoding.DecodeString(encryptedData)\n    }\n    if err != nil {\n        return nil, fmt.Errorf(\"解码失败: %v\", err)\n    }\n\n    block, err := sm4.NewCipher(key)\n    if err != nil {\n        return nil, fmt.Errorf(\"创建SM4 cipher失败: %v\", err)\n    }\n\n    blockSize := block.BlockSize()\n    if len(data) \u003c blockSize {\n        return nil, fmt.Errorf(\"数据长度不足\")\n    }\n    iv := data[:blockSize]\n    ciphertext := data[blockSize:]\n\n    mode := cipher.NewCBCDecrypter(block, iv)\n    plaintext := make([]byte, len(ciphertext))\n    mode.CryptBlocks(plaintext, ciphertext)\n\n    plaintext, err = pkcs7UnPadding(plaintext)\n    if err != nil {\n        return nil, fmt.Errorf(\"去除填充失败: %v\", err)\n    }\n\n    return plaintext, nil\n}\n\n// pkcs7Padding PKCS7填充\nfunc pkcs7Padding(data []byte, blockSize int) []byte {\n    padding := blockSize - len(data)%blockSize\n    padText := bytes.Repeat([]byte{byte(padding)}, padding)\n    return append(data, padText...)\n}\n\n// pkcs7UnPadding 去除PKCS7填充\nfunc pkcs7UnPadding(data []byte) ([]byte, error) {\n    length := len(data)\n    if length == 0 {\n        return nil, fmt.Errorf(\"数据为空\")\n    }\n    padding := int(data[length-1])\n    if padding \u003e length || padding == 0 {\n        return nil, fmt.Errorf(\"无效的填充\")\n    }\n    for i := length - padding; i \u003c length; i++ {\n        if data[i] != byte(padding) {\n            return nil, fmt.Errorf(\"无效的填充\")\n        }\n    }\n    return data[:length-padding], nil\n}\n```\n\n**⚠️ 重要提醒**\n1. **不要自己实现 SM4 算法**,请直接使用 github.com/tjfoc/gmsm\n2. 这个库是国密官方推荐的 Go 实现,安全可靠\n3. 在 go.mod 中添加依赖:`require github.com/tjfoc/gmsm v1.4.1`\n\n**注意事项**\n- SM4 密钥固定为 16 字节\n- IV 必须随机生成且每次不同\n- CBC 模式需要 IVECB 模式不需要\n",
  "tool": "sm4_cbc_encrypt"
}

### 加密实现 5

{
  "success": true,
  "data": "\n### 参数拼接规范\n\n**常见拼接方式**\n\n1. **字典序排序**按key字典序排序后拼接 `key=value\u0026key2=value2`\n2. **固定顺序**:按文档指定顺序拼接\n3. **JSON字符串**:整个请求体作为签名串\n\n**代码模板**\n\n```go\npackage crypto\n\nimport (\n    \"fmt\"\n    \"sort\"\n    \"strings\"\n)\n\n// BuildSignString 方式1字典序排序拼接\nfunc BuildSignString(params map[string]string) string {\n    keys := make([]string, 0, len(params))\n    for k, v := range params {\n        if v != \"\" \u0026\u0026 k != \"sign\" \u0026\u0026 k != \"signature\" {\n            keys = append(keys, k)\n        }\n    }\n    sort.Strings(keys)\n    \n    var parts []string\n    for _, k := range keys {\n        parts = append(parts, fmt.Sprintf(\"%s=%s\", k, params[k]))\n    }\n    return strings.Join(parts, \"\u0026\")\n}\n\n// BuildSignStringOrdered 方式2固定顺序拼接\nfunc BuildSignStringOrdered(params map[string]string, orderedKeys []string) string {\n    var parts []string\n    for _, k := range orderedKeys {\n        if v, ok := params[k]; ok \u0026\u0026 v != \"\" {\n            parts = append(parts, fmt.Sprintf(\"%s=%s\", k, v))\n        }\n    }\n    return strings.Join(parts, \"\u0026\")\n}\n```\n\n**注意事项**\n- 确认文档指定的排序规则(字典序/固定顺序)\n- 确认空值是否要包含(通常排除空值)\n- 确认是否需要URL编码\n- 注意排除签名字段本身\n",
  "tool": "param_concat"
}