xy_sh-20260727101126/xy_sh/valid.md

1082 lines
24 KiB
Markdown
Raw 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.0
github.com/tjfoc/gmsm v1.4.1
)
```
// File: xy_sh/cmd/server/main.go
```go
package main
import (
"log"
"xy_sh/internal/config"
"xy_sh/internal/router"
"xy_sh/pkg/crypto"
"github.com/gofiber/fiber/v2"
)
func main() {
cfg := config.LoadConfig()
crypto.InitCrypto(cfg.SM3Salt, cfg.SM4Key)
app := fiber.New(fiber.Config{
AppName: "XY SH API Server",
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
})
router.SetupRoutes(app)
log.Printf("服务启动,监听端口: %s", cfg.ServerPort)
if err := app.Listen(":" + cfg.ServerPort); err != nil {
log.Fatalf("服务启动失败: %v", err)
}
}
```
// File: xy_sh/internal/config/config.go
```go
package config
import (
"os"
"time"
)
type Config struct {
ServerPort string
SM3Salt string
SM4Key string
ReadTimeout time.Duration
WriteTimeout time.Duration
}
func LoadConfig() *Config {
port := getEnv("SERVER_PORT", "8080")
sm3Salt := getEnv("SM3_SALT", "default_salt_change_me")
sm4Key := getEnv("SM4_KEY", "default_key_16by")
return &Config{
ServerPort: port,
SM3Salt: sm3Salt,
SM4Key: sm4Key,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
}
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
```
// File: xy_sh/pkg/crypto/crypto.go
```go
package crypto
import (
"bytes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"github.com/tjfoc/gmsm/sm3"
"github.com/tjfoc/gmsm/sm4"
)
var (
sm3Salt []byte
sm4Key []byte
)
func InitCrypto(salt, key string) {
sm3Salt = []byte(salt)
keyBytes := []byte(key)
if len(keyBytes) < 16 {
padded := make([]byte, 16)
copy(padded, keyBytes)
sm4Key = padded
} else if len(keyBytes) > 16 {
sm4Key = keyBytes[:16]
} else {
sm4Key = keyBytes
}
}
func SM3Hash(data []byte) string {
h := sm3.New()
h.Write(data)
hashBytes := h.Sum(nil)
return hex.EncodeToString(hashBytes)
}
func SM3HashWithSalt(data string) string {
h := hmac.New(sm3.New, sm3Salt)
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func GenerateSign(timestamp, encryptedData string) string {
signStr := timestamp + encryptedData
return SM3HashWithSalt(signStr)
}
func VerifySign(timestamp, encryptedData, sign string) bool {
expectedSign := GenerateSign(timestamp, encryptedData)
return hmac.Equal([]byte(expectedSign), []byte(sign))
}
func SM4CBCEncrypt(plaintext []byte) (string, error) {
if len(sm4Key) != 16 {
return "", fmt.Errorf("SM4密钥长度必须为16字节")
}
block, err := sm4.NewCipher(sm4Key)
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
}
func SM4CBCDecrypt(encryptedData string) ([]byte, error) {
if len(sm4Key) != 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(sm4Key)
if err != nil {
return nil, fmt.Errorf("创建SM4 cipher失败: %v", err)
}
blockSize := block.BlockSize()
if len(data) < blockSize {
return nil, errors.New("数据长度不足")
}
iv := data[:blockSize]
ciphertext := data[blockSize:]
if len(ciphertext)%blockSize != 0 {
return nil, errors.New("密文长度不是块大小的整数倍")
}
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
}
func pkcs7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
func pkcs7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("数据为空")
}
padding := int(data[length-1])
if padding > length || padding == 0 {
return nil, errors.New("无效的填充")
}
for i := length - padding; i < length; i++ {
if data[i] != byte(padding) {
return nil, errors.New("无效的填充")
}
}
return data[:length-padding], nil
}
```
// File: xy_sh/internal/entities/request.go
```go
package entities
type EncryptedRequest struct {
EncryptedData string `json:"encryptedData"`
}
type OrderRequest struct {
ActCode string `json:"actCode"`
GoodsCode string `json:"goodsCode"`
ActOrderNum string `json:"actOrderNum"`
Account string `json:"account,omitempty"`
CallbackUrl string `json:"callbackUrl,omitempty"`
}
type QueryOrderRequest struct {
ActCode string `json:"actCode"`
OrderNo string `json:"orderNo"`
}
type WxRechargeRequest struct {
ActCode string `json:"actCode"`
OrderNo string `json:"orderNo"`
GoodsCode string `json:"goodsCode"`
ActOrderNum string `json:"actOrderNum"`
AppId string `json:"appId"`
OpenId string `json:"openId"`
CallbackUrl string `json:"callbackUrl,omitempty"`
}
type CallbackRequest struct {
OrderNo string `json:"orderNo"`
ActOrderNum string `json:"actOrderNum"`
Status int `json:"status"`
Account string `json:"account,omitempty"`
CardInfo *CardInfo `json:"cardInfo,omitempty"`
CouponId string `json:"couponId,omitempty"`
}
type CardInfo struct {
CouponNo string `json:"couponNo"`
CouponCode string `json:"couponCode"`
ExpireTime string `json:"expireTime"`
}
```
// File: xy_sh/internal/entities/response.go
```go
package entities
type CommonResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
type OrderResponseData struct {
OrderNo string `json:"orderNo"`
CouponNo string `json:"couponNo,omitempty"`
CouponCode string `json:"couponCode,omitempty"`
Status int `json:"status"`
ExpireTime string `json:"expireTime,omitempty"`
}
type QueryOrderResponseData struct {
OrderNo string `json:"orderNo"`
Status int `json:"status"`
Account string `json:"account,omitempty"`
CardInfo *CardInfo `json:"cardInfo,omitempty"`
CouponId string `json:"couponId,omitempty"`
}
type WxRechargeResponseData struct {
OrderNo string `json:"orderNo"`
Status int `json:"status"`
CouponId string `json:"couponId,omitempty"`
}
const (
StatusSending = -1
StatusSuccess = 0
StatusFailed = 1
StatusRedeemed = 2
StatusExpired = 3
)
const (
CodeSuccess = 0
CodeFailed = -1
)
```
// File: xy_sh/internal/middleware/middleware.go
```go
package middleware
import (
"encoding/json"
"xy_sh/internal/entities"
"xy_sh/pkg/crypto"
"github.com/gofiber/fiber/v2"
)
func SignVerifyMiddleware() 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(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "缺少timestamp或sign请求头",
Data: nil,
})
}
body := c.Body()
var req entities.EncryptedRequest
if err := json.Unmarshal(body, &req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "请求体格式错误",
Data: nil,
})
}
if req.EncryptedData == "" {
return c.Status(fiber.StatusBadRequest).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "缺少encryptedData参数",
Data: nil,
})
}
if !crypto.VerifySign(timestamp, req.EncryptedData, sign) {
return c.Status(fiber.StatusUnauthorized).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "签名验证失败",
Data: nil,
})
}
decryptedData, err := crypto.SM4CBCDecrypt(req.EncryptedData)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "数据解密失败: " + err.Error(),
Data: nil,
})
}
c.Locals("decryptedData", decryptedData)
c.Locals("timestamp", timestamp)
return c.Next()
}
}
func EncryptResponseMiddleware() fiber.Handler {
return func(c *fiber.Ctx) error {
err := c.Next()
if err != nil {
return err
}
body := c.Response().Body()
if len(body) == 0 {
return nil
}
var resp entities.CommonResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil
}
if resp.Data != nil {
dataBytes, err := json.Marshal(resp.Data)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "响应数据序列化失败",
Data: nil,
})
}
encryptedData, err := crypto.SM4CBCEncrypt(dataBytes)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "响应数据加密失败",
Data: nil,
})
}
resp.Data = encryptedData
newBody, err := json.Marshal(resp)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "响应序列化失败",
Data: nil,
})
}
c.Response().SetBody(newBody)
}
return nil
}
}
```
// File: xy_sh/internal/biz/biz.go
```go
package biz
import (
"crypto/rand"
"encoding/hex"
"errors"
"io"
"strconv"
"sync"
"time"
"xy_sh/internal/entities"
)
type OrderStore struct {
mu sync.RWMutex
orders map[string]*OrderInfo
}
type OrderInfo struct {
OrderNo string
ActOrderNum string
GoodsCode string
ActCode string
Account string
Status int
CouponNo string
CouponCode string
ExpireTime string
CouponId string
AppId string
OpenId string
CreateTime time.Time
UpdateTime time.Time
}
var store *OrderStore
func init() {
store = &OrderStore{
orders: make(map[string]*OrderInfo),
}
}
func CreateOrder(req *entities.OrderRequest) (*entities.OrderResponseData, error) {
store.mu.Lock()
defer store.mu.Unlock()
orderNo := generateOrderNo()
orderInfo := &OrderInfo{
OrderNo: orderNo,
ActOrderNum: req.ActOrderNum,
GoodsCode: req.GoodsCode,
ActCode: req.ActCode,
Account: req.Account,
Status: entities.StatusSuccess,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
orderInfo.CouponNo = generateCouponNo()
orderInfo.CouponCode = orderInfo.CouponNo
orderInfo.ExpireTime = time.Now().AddDate(1, 0, 0).Format("2006-01-02 15:04:05")
store.orders[orderNo] = orderInfo
return &entities.OrderResponseData{
OrderNo: orderNo,
CouponNo: orderInfo.CouponNo,
CouponCode: orderInfo.CouponCode,
Status: orderInfo.Status,
ExpireTime: orderInfo.ExpireTime,
}, nil
}
func QueryOrder(req *entities.QueryOrderRequest) (*entities.QueryOrderResponseData, error) {
store.mu.RLock()
defer store.mu.RUnlock()
orderInfo, exists := store.orders[req.OrderNo]
if !exists {
return nil, errors.New("订单不存在")
}
resp := &entities.QueryOrderResponseData{
OrderNo: orderInfo.OrderNo,
Status: orderInfo.Status,
Account: orderInfo.Account,
CouponId: orderInfo.CouponId,
}
if orderInfo.CouponNo != "" {
resp.CardInfo = &entities.CardInfo{
CouponNo: orderInfo.CouponNo,
CouponCode: orderInfo.CouponCode,
ExpireTime: orderInfo.ExpireTime,
}
}
return resp, nil
}
func WxRecharge(req *entities.WxRechargeRequest) (*entities.WxRechargeResponseData, error) {
store.mu.Lock()
defer store.mu.Unlock()
if orderInfo, exists := store.orders[req.OrderNo]; exists {
return &entities.WxRechargeResponseData{
OrderNo: orderInfo.OrderNo,
Status: orderInfo.Status,
CouponId: orderInfo.CouponId,
}, nil
}
orderNo := req.OrderNo
orderInfo := &OrderInfo{
OrderNo: orderNo,
ActOrderNum: req.ActOrderNum,
GoodsCode: req.GoodsCode,
ActCode: req.ActCode,
AppId: req.AppId,
OpenId: req.OpenId,
Status: entities.StatusSuccess,
CouponId: generateCouponId(),
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
store.orders[orderNo] = orderInfo
return &entities.WxRechargeResponseData{
OrderNo: orderNo,
Status: orderInfo.Status,
CouponId: orderInfo.CouponId,
}, nil
}
func HandleCallback(req *entities.CallbackRequest) error {
store.mu.Lock()
defer store.mu.Unlock()
orderInfo, exists := store.orders[req.OrderNo]
if !exists {
orderInfo = &OrderInfo{
OrderNo: req.OrderNo,
ActOrderNum: req.ActOrderNum,
CreateTime: time.Now(),
}
store.orders[req.OrderNo] = orderInfo
}
orderInfo.Status = req.Status
orderInfo.Account = req.Account
orderInfo.CouponId = req.CouponId
orderInfo.UpdateTime = time.Now()
if req.CardInfo != nil {
orderInfo.CouponNo = req.CardInfo.CouponNo
orderInfo.CouponCode = req.CardInfo.CouponCode
orderInfo.ExpireTime = req.CardInfo.ExpireTime
}
return nil
}
func generateOrderNo() string {
return "HM" + strconv.FormatInt(time.Now().UnixMilli(), 10) + randomString(16)
}
func generateCouponNo() string {
return randomString(16)
}
func generateCouponId() string {
return randomString(8)
}
func randomString(n int) string {
b := make([]byte, (n+1)/2)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
panic(err)
}
return hex.EncodeToString(b)[:n]
}
```
// File: xy_sh/internal/service/service.go
```go
package service
import (
"encoding/json"
"xy_sh/internal/biz"
"xy_sh/internal/entities"
"github.com/gofiber/fiber/v2"
)
func CreateOrderHandler(c *fiber.Ctx) error {
decryptedData := c.Locals("decryptedData").([]byte)
var req entities.OrderRequest
if err := json.Unmarshal(decryptedData, &req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "业务参数解析失败: " + err.Error(),
Data: nil,
})
}
if req.ActCode == "" || req.GoodsCode == "" || req.ActOrderNum == "" {
return c.Status(fiber.StatusBadRequest).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "缺少必填参数: actCode, goodsCode, actOrderNum",
Data: nil,
})
}
result, err := biz.CreateOrder(&req)
if err != nil {
return c.Status(fiber.StatusOK).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: err.Error(),
Data: nil,
})
}
return c.Status(fiber.StatusOK).JSON(entities.CommonResponse{
Code: entities.CodeSuccess,
Msg: "请求成功",
Data: result,
})
}
func QueryOrderHandler(c *fiber.Ctx) error {
decryptedData := c.Locals("decryptedData").([]byte)
var req entities.QueryOrderRequest
if err := json.Unmarshal(decryptedData, &req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "业务参数解析失败: " + err.Error(),
Data: nil,
})
}
if req.ActCode == "" || req.OrderNo == "" {
return c.Status(fiber.StatusBadRequest).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "缺少必填参数: actCode, orderNo",
Data: nil,
})
}
result, err := biz.QueryOrder(&req)
if err != nil {
return c.Status(fiber.StatusOK).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: err.Error(),
Data: nil,
})
}
return c.Status(fiber.StatusOK).JSON(entities.CommonResponse{
Code: entities.CodeSuccess,
Msg: "请求成功",
Data: result,
})
}
func WxRechargeHandler(c *fiber.Ctx) error {
decryptedData := c.Locals("decryptedData").([]byte)
var req entities.WxRechargeRequest
if err := json.Unmarshal(decryptedData, &req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "业务参数解析失败: " + err.Error(),
Data: nil,
})
}
if req.ActCode == "" || req.OrderNo == "" || req.GoodsCode == "" || req.ActOrderNum == "" ||
req.AppId == "" || req.OpenId == "" {
return c.Status(fiber.StatusBadRequest).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: "缺少必填参数: actCode, orderNo, goodsCode, actOrderNum, appId, openId",
Data: nil,
})
}
result, err := biz.WxRecharge(&req)
if err != nil {
return c.Status(fiber.StatusOK).JSON(entities.CommonResponse{
Code: entities.CodeFailed,
Msg: err.Error(),
Data: nil,
})
}
return c.Status(fiber.StatusOK).JSON(entities.CommonResponse{
Code: entities.CodeSuccess,
Msg: "请求成功",
Data: result,
})
}
func CallbackHandler(c *fiber.Ctx) error {
decryptedData := c.Locals("decryptedData").([]byte)
var req entities.CallbackRequest
if err := json.Unmarshal(decryptedData, &req); err != nil {
return c.Status(fiber.StatusBadRequest).SendString("参数解析失败")
}
if req.OrderNo == "" || req.ActOrderNum == "" {
return c.Status(fiber.StatusBadRequest).SendString("缺少必填参数")
}
if err := biz.HandleCallback(&req); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("处理失败")
}
return c.Status(fiber.StatusOK).SendString("ok")
}
func HealthHandler(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"status": "ok",
"message": "服务运行正常",
})
}
```
// File: xy_sh/internal/router/router.go
```go
package router
import (
"xy_sh/internal/middleware"
"xy_sh/internal/service"
"github.com/gofiber/fiber/v2"
)
func SetupRoutes(app *fiber.App) {
app.Get("/health", service.HealthHandler)
app.Get("/", service.HealthHandler)
v1 := app.Group("/api/v1")
v1.Post("/order/create",
middleware.SignVerifyMiddleware(),
middleware.EncryptResponseMiddleware(),
service.CreateOrderHandler,
)
v1.Post("/order/query",
middleware.SignVerifyMiddleware(),
middleware.EncryptResponseMiddleware(),
service.QueryOrderHandler,
)
v1.Post("/wx/recharge",
middleware.SignVerifyMiddleware(),
middleware.EncryptResponseMiddleware(),
service.WxRechargeHandler,
)
v1.Post("/callback/notify",
middleware.SignVerifyMiddleware(),
service.CallbackHandler,
)
}
```
// File: xy_sh/internal/test/example_test.go
```go
package test
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
"xy_sh/internal/entities"
"xy_sh/internal/router"
"xy_sh/pkg/crypto"
"github.com/gofiber/fiber/v2"
)
func init() {
crypto.InitCrypto("test_salt_12345", "test_key_16bytes")
}
func setupTestApp() *fiber.App {
app := fiber.New()
router.SetupRoutes(app)
return app
}
func makeEncryptedRequest(t *testing.T, app *fiber.App, path string, bizData interface{}) (*http.Response, []byte) {
t.Helper()
bizJSON, err := json.Marshal(bizData)
if err != nil {
t.Fatalf("业务数据序列化失败: %v", err)
}
encryptedData, err := crypto.SM4CBCEncrypt(bizJSON)
if err != nil {
t.Fatalf("加密失败: %v", err)
}
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
sign := crypto.GenerateSign(timestamp, encryptedData)
reqBody := entities.EncryptedRequest{
EncryptedData: encryptedData,
}
reqJSON, _ := json.Marshal(reqBody)
req := httptest.NewRequest(http.MethodPost, path, bytes.NewReader(reqJSON))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("timestamp", timestamp)
req.Header.Set("sign", sign)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("请求失败: %v", err)
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return resp, body
}
func TestCreateOrder(t *testing.T) {
app := setupTestApp()
bizData := entities.OrderRequest{
ActCode: "ACT001",
GoodsCode: "123456",
ActOrderNum: "TEST_ORDER_001",
Account: "19912345678",
CallbackUrl: "https://example.com/callback",
}
resp, body := makeEncryptedRequest(t, app, "/api/v1/order/create", bizData)
if resp.StatusCode != http.StatusOK {
t.Errorf("期望状态码200实际: %d, 响应: %s", resp.StatusCode, string(body))
}
var commonResp entities.CommonResponse
if err := json.Unmarshal(body, &commonResp); err != nil {
t.Fatalf("响应解析失败: %v, 响应: %s", err, string(body))
}
if commonResp.Code != entities.CodeSuccess {
t.Errorf("期望code=0实际: %d, msg: %s", commonResp.Code, commonResp.Msg)
}
t.Logf("下单接口测试成功,响应: %s", string(body))
}
func TestQueryOrder(t *testing.T) {
app := setupTestApp()
createBizData := entities.OrderRequest{
ActCode: "ACT001",
GoodsCode: "123456",
ActOrderNum: "TEST_ORDER_002",
Account: "19912345678",
}
_, createBody := makeEncryptedRequest(t, app, "/api/v1/order/create", createBizData)
var createResp entities.CommonResponse
if err := json.Unmarshal(createBody, &createResp); err != nil {
t.Fatalf("创建订单响应解析失败: %v", err)
}
if createResp.Code != entities.CodeSuccess {
t.Fatalf("创建订单失败: %s", createResp.Msg)
}
encryptedData := createResp.Data.(string)
decryptedData, err := crypto.SM4CBCDecrypt(encryptedData)
if err != nil {
t.Fatalf("解密创建订单响应失败: %v", err)
}
var orderResp entities.OrderResponseData
if err := json.Unmarshal(decryptedData, &orderResp); err != nil {
t.Fatalf("解析创建订单业务数据失败: %v", err)
}
queryBizData := entities.QueryOrderRequest{
ActCode: "ACT001",
OrderNo: orderResp.OrderNo,
}
resp, body := makeEncryptedRequest(t, app, "/api/v1/order/query", queryBizData)
if resp.StatusCode != http.StatusOK {
t.Errorf("期望状态码200实际: %d, 响应: %s", resp.StatusCode, string(body))
}
var queryResp entities.CommonResponse
if err := json.Unmarshal(body, &queryResp); err != nil {
t.Fatalf("查询响应解析失败: %v", err)
}
if queryResp.Code != entities.CodeSuccess {
t.Errorf("查询失败code: %d, msg: %s", queryResp.Code, queryResp.Msg)
}
t.Logf("查询接口测试成功,响应: %s", string(body))
}
func TestWxRecharge(t *testing.T) {
app := setupTestApp()
bizData := entities.WxRechargeRequest{
ActCode: "FBG6vdYqGE4mGX7EH/woEg==",
OrderNo: "WX_TEST_ORDER_001",
GoodsCode: "0001",
ActOrderNum: "WX_ORDER_001",
AppId: "wx1234567890",
OpenId: "oABC1234567890",
}
resp, body := makeEncryptedRequest(t, app, "/api/v1/wx/recharge", bizData)
if resp.StatusCode != http.StatusOK {
t.Errorf("期望状态码200实际: %d, 响应: %s", resp.StatusCode, string(body))
}
var commonResp entities.CommonResponse
if err := json.Unmarshal(body, &commonResp); err != nil {
t.Fatalf("响应解析失败: %v", err)
}
if commonResp.Code != entities.CodeSuccess {
t.Errorf("期望code=0实际: %d, msg: %s", commonResp.Code, commonResp.Msg)
}
t.Logf("微信立减金充值接口测试成功,响应: %s", string(body))
}
func TestCallback(t *testing.T) {
app := setupTestApp()
bizData := entities.CallbackRequest{
OrderNo: "HM202509291010001",
ActOrderNum: "XY2025092910100001",
Status: entities.StatusExpired,
Account: "19912345678",
}
resp, body := makeEncryptedRequest(t, app, "/api/v1/callback/notify", bizData)
if resp.StatusCode != http.StatusOK {
t.Errorf("期望状态码200实际: %d, 响应: %s", resp.StatusCode, string(body))
}
if string(body) != "ok" {
t.Errorf("期望响应'ok',实际: %s", string(body))
}
t.Logf("回调通知接口测试成功")
}
func TestInvalidSign(t *testing.T) {
app := setupTestApp()
bizData := entities.OrderRequest{
ActCode: "ACT001",
GoodsCode: "123456",
ActOrderNum: "TEST_ORDER_003",
}
bizJSON, _ := json.Marshal(bizData)
encryptedData, _ := crypto.SM4CBCEncrypt(bizJSON)
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
wrongSign := "wrong_signature_12345"
reqBody := entities.EncryptedRequest{EncryptedData: encryptedData}
reqJSON, _ := json.Marshal(reqBody)
req := httptest.NewRequest(http.MethodPost, "/api/v1/order/create", bytes.NewReader(reqJSON))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("timestamp", timestamp)
req.Header.Set("sign", wrongSign)
resp, _ := app.Test(req)
if resp.StatusCode != http.StatusUnauthorized {
t.Errorf("期望状态码401签名验证失败实际: %d", resp.StatusCode)
}
t.Logf("签名验证失败测试成功")
}
func TestHealthCheck(t *testing.T) {
app := setupTestApp()
req := httptest.NewRequest(http.MethodGet, "/health", nil)
resp, body := app.Test(req)
if resp.StatusCode != http.StatusOK {
t.Errorf("期望状态码200实际: %d", resp.StatusCode)
}
t.Logf("健康检查接口测试成功,响应: %s", string(body))
}
```