xy_sh-20260724181350/xy_sh/valid.md

632 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// File: xy_sh/go.mod
```go
module xy_sh
go 1.26
require (
github.com/gofiber/fiber/v2 v2.52.6
github.com/tjfoc/gmsm v1.4.1
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.28.0 // indirect
)
```
// File: xy_sh/pkg/crypto/crypto.go
```go
package crypto
import (
"bytes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"math/big"
"time"
"github.com/tjfoc/gmsm/sm3"
"github.com/tjfoc/gmsm/sm4"
)
// ============================================================
// 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("base64解码失败: %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
}
// 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
}
// ============================================================
// SM3 哈希 / HMAC-SM3
// ============================================================
// SM3Hash 计算SM3哈希值
func SM3Hash(data []byte, encoding string) (string, error) {
h := sm3.New()
h.Write(data)
hashBytes := h.Sum(nil)
if encoding == "base64" {
return base64.StdEncoding.EncodeToString(hashBytes), nil
}
return hex.EncodeToString(hashBytes), nil
}
// SM3HashString 计算字符串的SM3哈希值
func SM3HashString(data string, encoding string) (string, error) {
return SM3Hash([]byte(data), encoding)
}
// HMACSM3 HMAC-SM3计算
// 对应 Java hutool 的 sm3WithSalt(saltBytes).digestHex(data)
func HMACSM3(data []byte, key []byte) string {
h := hmac.New(sm3.New, key)
h.Write(data)
return hex.EncodeToString(h.Sum(nil))
}
// ============================================================
// 时间戳 / 随机数
// ============================================================
// GenerateTimestampMillis 生成毫秒级时间戳字符串
func GenerateTimestampMillis() string {
return fmt.Sprintf("%d", time.Now().UnixMilli())
}
// GenerateNonce 生成指定长度的随机字符串
func GenerateNonce(length int) (string, error) {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
for i := range b {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
return "", err
}
b[i] = charset[num.Int64()]
}
return string(b), nil
}
// ============================================================
// 签名生成与验证(文档专用)
// ============================================================
// GenerateSign 生成签名
// 规则HMAC-SM3(salt, timestamp + encryptedData) -> hex
func GenerateSign(timestamp string, encryptedData string, sm3Salt []byte) string {
data := timestamp + encryptedData
return HMACSM3([]byte(data), sm3Salt)
}
// VerifySign 验证签名
func VerifySign(timestamp string, encryptedData string, sign string, sm3Salt []byte) bool {
expected := GenerateSign(timestamp, encryptedData, sm3Salt)
return expected == sign
}
```
// File: xy_sh/internal/config/config.go
```go
package config
import "os"
// Config 应用配置
type Config struct {
ServerPort string // 服务监听端口
SM4Key []byte // SM4 加密密钥16字节
SM3Salt []byte // SM3 签名盐值
}
// LoadConfig 加载配置(从环境变量读取,生产环境建议使用配置文件或配置中心)
func LoadConfig() *Config {
return &Config{
ServerPort: getEnv("SERVER_PORT", "8080"),
SM4Key: []byte(getEnv("SM4_KEY", "1234567890123456")), // 默认16字节测试密钥
SM3Salt: []byte(getEnv("SM3_SALT", "default_sm3_salt")),
}
}
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
```
// File: xy_sh/internal/entities/request.go
```go
package entities
// EncryptedRequest 通用加密请求体
type EncryptedRequest struct {
EncryptedData string `json:"encryptedData"`
}
// OrderCreateRequest 卡券/直充权益下单业务参数(解密后)
type OrderCreateRequest struct {
ActCode string `json:"actCode"` // 活动code
GoodsCode string `json:"goodsCode"` // 供应商商品编号
ActOrderNum string `json:"actOrderNum"` // 活动方订单号
Account string `json:"account,omitempty"` // 充值账号
CallbackURL string `json:"callbackUrl,omitempty"` // 回调地址
}
```
// File: xy_sh/internal/entities/response.go
```go
package entities
// ApiResponse 通用API响应
type ApiResponse struct {
Code int `json:"code"` // 0成功 -1失败
Msg string `json:"msg"` // 错误信息
Data interface{} `json:"data"` // 加密后的业务数据字符串
}
// OrderCreateResponse 下单响应业务数据(解密后)
type OrderCreateResponse struct {
OrderNo string `json:"orderNo"` // 供应商订单号
CouponNo string `json:"couponNo,omitempty"` // 卡号
CouponCode string `json:"couponCode,omitempty"` // 卡密
Status int `json:"status"` // -1发放中 0成功 1失败 2已核销 3已过期
ExpireTime string `json:"expireTime,omitempty"` // 有效期
}
```
// File: xy_sh/internal/middleware/middleware.go
```go
package middleware
import (
"log"
"github.com/gofiber/fiber/v2"
"xy_sh/pkg/crypto"
)
// SM3Salt 全局SM3盐值由config初始化时注入
var SM3Salt []byte
// SM4Key 全局SM4密钥由config初始化时注入
var SM4Key []byte
// AuthMiddleware 签名验证中间件
// 验证请求头中的 timestamp 和 sign
func AuthMiddleware() fiber.Handler {
return func(c *fiber.Ctx) error {
timestamp := c.Get("timestamp")
sign := c.Get("sign")
if timestamp == "" || sign == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"code": -1,
"msg": "缺少timestamp或sign请求头",
})
}
// 读取请求体中的 encryptedData
var body struct {
EncryptedData string `json:"encryptedData"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"code": -1,
"msg": "请求体解析失败",
})
}
// 验证签名
if !crypto.VerifySign(timestamp, body.EncryptedData, sign, SM3Salt) {
log.Printf("[中间件] 签名验证失败: timestamp=%s, sign=%s", timestamp, sign)
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"code": -1,
"msg": "签名验证失败",
})
}
log.Printf("[中间件] 签名验证通过: timestamp=%s", timestamp)
return c.Next()
}
}
```
// File: xy_sh/internal/biz/biz.go
```go
package biz
import (
"encoding/json"
"fmt"
"log"
"xy_sh/internal/entities"
"xy_sh/pkg/crypto"
)
// Biz 业务逻辑层
type Biz struct {
SM4Key []byte
}
// NewBiz 创建业务逻辑实例
func NewBiz(sm4Key []byte) *Biz {
return &Biz{SM4Key: sm4Key}
}
// DecryptRequest 解密请求体中的 encryptedData 并解析为指定结构体
func (b *Biz) DecryptRequest(encryptedData string, target interface{}) error {
plaintext, err := crypto.SM4CBCDecrypt(encryptedData, b.SM4Key)
if err != nil {
return fmt.Errorf("SM4解密失败: %v", err)
}
if err := json.Unmarshal(plaintext, target); err != nil {
return fmt.Errorf("JSON解析失败: %v", err)
}
log.Printf("[业务] 解密请求数据: %s", string(plaintext))
return nil
}
// EncryptResponse 加密响应业务数据
func (b *Biz) EncryptResponse(data interface{}) (string, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
encrypted, err := crypto.SM4CBCEncrypt(jsonBytes, b.SM4Key)
if err != nil {
return "", fmt.Errorf("SM4加密失败: %v", err)
}
log.Printf("[业务] 加密响应数据: %s", string(jsonBytes))
return encrypted, nil
}
// CreateOrder 卡券/直充权益下单业务逻辑
// TODO: 根据实际业务需求实现
func (b *Biz) CreateOrder(req *entities.OrderCreateRequest) (*entities.OrderCreateResponse, error) {
log.Printf("[业务] 下单请求: actCode=%s, goodsCode=%s, actOrderNum=%s, account=%s",
req.ActCode, req.GoodsCode, req.ActOrderNum, req.Account)
// TODO: 调用供应商实际接口或数据库操作
// 以下为模拟实现
resp := &entities.OrderCreateResponse{
OrderNo: fmt.Sprintf("HM%d%s", crypto.GenerateTimestampMillis(), "27073398f3772c00"),
Status: 0,
ExpireTime: "2025-09-12 00:00:00",
}
log.Printf("[业务] 下单成功: orderNo=%s", resp.OrderNo)
return resp, nil
}
```
// File: xy_sh/internal/service/service.go
```go
package service
import (
"log"
"github.com/gofiber/fiber/v2"
"xy_sh/internal/biz"
"xy_sh/internal/entities"
)
// Service HTTP服务层
type Service struct {
biz *biz.Biz
}
// NewService 创建服务实例
func NewService(biz *biz.Biz) *Service {
return &Service{biz: biz}
}
// HandleOrderCreate 卡券/直充权益下单接口
func (s *Service) HandleOrderCreate(c *fiber.Ctx) error {
// 解析请求体
var req entities.EncryptedRequest
if err := c.BodyParser(&req); err != nil {
log.Printf("[服务] 请求体解析失败: %v", err)
return c.Status(fiber.StatusBadRequest).JSON(entities.ApiResponse{
Code: -1,
Msg: "请求体解析失败",
})
}
// 解密业务参数
var bizReq entities.OrderCreateRequest
if err := s.biz.DecryptRequest(req.EncryptedData, &bizReq); err != nil {
log.Printf("[服务] 解密业务参数失败: %v", err)
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "解密业务参数失败: " + err.Error(),
})
}
// 调用业务逻辑
bizResp, err := s.biz.CreateOrder(&bizReq)
if err != nil {
log.Printf("[服务] 下单失败: %v", err)
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: err.Error(),
})
}
// 加密响应业务数据
encryptedData, err := s.biz.EncryptResponse(bizResp)
if err != nil {
log.Printf("[服务] 加密响应数据失败: %v", err)
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "加密响应数据失败",
})
}
log.Printf("[服务] 下单成功: orderNo=%s", bizResp.OrderNo)
return c.JSON(entities.ApiResponse{
Code: 0,
Msg: "请求成功",
Data: encryptedData,
})
}
```
// File: xy_sh/internal/router/router.go
```go
package router
import (
"github.com/gofiber/fiber/v2"
"xy_sh/internal/middleware"
"xy_sh/internal/service"
)
// SetupRoutes 注册路由
func SetupRoutes(app *fiber.App, svc *service.Service) {
// 所有接口都需要签名验证
api := app.Group("/api", middleware.AuthMiddleware())
// 接口1卡券/直充权益下单接口
api.Post("/order/create", svc.HandleOrderCreate)
}
```
// File: xy_sh/cmd/server/main.go
```go
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"xy_sh/internal/biz"
"xy_sh/internal/config"
"xy_sh/internal/middleware"
"xy_sh/internal/router"
"xy_sh/internal/service"
)
func main() {
// 加载配置
cfg := config.LoadConfig()
// 初始化全局加密密钥(注入中间件)
middleware.SM4Key = cfg.SM4Key
middleware.SM3Salt = cfg.SM3Salt
// 初始化业务层
bizLayer := biz.NewBiz(cfg.SM4Key)
// 初始化服务层
svc := service.NewService(bizLayer)
// 创建 Fiber 应用
app := fiber.New(fiber.Config{
AppName: "xy_sh - 卡券/直充权益服务",
})
// 注册路由
router.SetupRoutes(app, svc)
// 启动服务
addr := ":" + cfg.ServerPort
log.Printf("[启动] 服务监听地址: %s", addr)
log.Fatal(app.Listen(addr))
}
```
// File: xy_sh/internal/test/example_test.go
```go
package test
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"testing"
"xy_sh/pkg/crypto"
)
// 测试配置(需与服务器配置一致)
var (
testSM4Key = []byte("1234567890123456")
testSM3Salt = []byte("default_sm3_salt")
testBaseURL = "http://localhost:8080"
)
// TestOrderCreate 模拟卡券/直充权益下单接口请求
func TestOrderCreate(t *testing.T) {
// 1. 构造业务参数
bizReq := map[string]interface{}{
"actCode": "ACT001",
"goodsCode": "123456",
"actOrderNum": "00001",
"account": "19912345678",
"callbackUrl": "https://xxx/notice",
}
// 2. 序列化并加密
bizJSON, _ := json.Marshal(bizReq)
encryptedData, err := crypto.SM4CBCEncrypt(bizJSON, testSM4Key)
if err != nil {
t.Fatalf("SM4加密失败: %v", err)
}
// 3. 生成签名
timestamp := crypto.GenerateTimestampMillis()
sign := crypto.GenerateSign(timestamp, encryptedData, testSM3Salt)
// 4. 构造请求体
reqBody := map[string]string{
"encryptedData": encryptedData,
}
reqBodyJSON, _ := json.Marshal(reqBody)
// 5. 发送HTTP请求
req, err := http.NewRequest("POST", testBaseURL+"/api/order/create", bytes.NewReader(reqBodyJSON))
if err != nil {
t.Fatalf("创建请求失败: %v", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("timestamp", timestamp)
req.Header.Set("sign", sign)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
t.Fatalf("发送请求失败: %v", err)
}
defer resp.Body.Close()
// 6. 读取响应
respBody, _ := io.ReadAll(resp.Body)
fmt.Printf("响应状态码: %d\n", resp.StatusCode)
fmt.Printf("响应体: %s\n", string(respBody))
// 7. 解析响应
var apiResp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(respBody, &apiResp); err != nil {
t.Fatalf("解析响应失败: %v", err)
}
if apiResp.Code != 0 {
t.Fatalf("请求失败: code=%d, msg=%s", apiResp.Code, apiResp.Msg)
}
// 8. 解密响应数据
if len(apiResp.Data) > 0 {
// data 是字符串(加密后的数据)
var encryptedDataStr string
if err := json.Unmarshal(apiResp.Data, &encryptedDataStr); err != nil {
t.Fatalf("解析加密数据失败: %v", err)
}
plaintext, err := crypto.SM4CBCDecrypt(encryptedDataStr, testSM4Key)
if err != nil {
t.Fatalf("解密响应数据失败: %v", err)
}
fmt.Printf("解密后业务数据: %s\n", string(plaintext))
}
t.Log("下单接口测试通过")
}
```