xy_sh-20260724115439/xy_sh/valid.md

772 lines
18 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.23
require (
github.com/gofiber/fiber/v2 v2.52.6
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"
)
func main() {
cfg := config.LoadConfig()
app := router.SetupRouter(cfg)
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"
type Config struct {
ServerPort string
SM3Salt string
SM4Key []byte
}
func LoadConfig() *Config {
port := os.Getenv("SERVER_PORT")
if port == "" {
port = "8080"
}
sm3Salt := os.Getenv("SM3_SALT")
if sm3Salt == "" {
sm3Salt = "default_sm3_salt"
}
sm4KeyStr := os.Getenv("SM4_KEY")
var sm4Key []byte
if sm4KeyStr != "" {
sm4Key = []byte(sm4KeyStr)
} else {
sm4Key = []byte("1234567890123456")
}
return &Config{
ServerPort: port,
SM3Salt: sm3Salt,
SM4Key: sm4Key,
}
}
```
// File: xy_sh/internal/entities/request.go
```go
package entities
// OrderRequest 卡券/直充权益下单接口请求业务参数
type OrderRequest struct {
ActCode string `json:"actCode"` // 活动code
GoodsCode string `json:"goodsCode"` // 供应商商品编号
ActOrderNum string `json:"actOrderNum"` // 活动方订单号
Account string `json:"account,omitempty"` // 充值账号
CallbackUrl string `json:"callbackUrl,omitempty"` // 回调地址
}
// QueryRequest 订单查询接口请求业务参数
type QueryRequest struct {
ActCode string `json:"actCode"` // 活动code
OrderNo string `json:"orderNo"` // 供应商订单号
}
// WechatRechargeRequest 微信立减金订单充值接口请求业务参数
type WechatRechargeRequest struct {
ActCode string `json:"actCode"` // 活动code
OrderNo string `json:"orderNo"` // 供应商订单号
GoodsCode string `json:"goodsCode"` // 供应商商品编号
ActOrderNum string `json:"actOrderNum"` // 活动方订单号
AppId string `json:"appId"` // 公众账号ID
OpenId string `json:"openId"` // 微信用户标识
CallbackUrl string `json:"callbackUrl,omitempty"` // 回调地址
}
// CallbackRequest 充值结果通知接口回调业务参数
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"` // 微信优惠id
}
// CardInfo 卡券信息
type CardInfo struct {
CouponNo string `json:"couponNo"` // 卡号/短链
CouponCode string `json:"couponCode"` // 卡密
ExpireTime string `json:"expireTime"` // 过期时间
}
// EncryptedRequest 加密请求体
type EncryptedRequest struct {
EncryptedData string `json:"encryptedData"` // SM4加密后的业务数据
}
```
// 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"` // JSON格式业务数据进行SM4加密后的字符串
}
// OrderResponse 下单接口响应业务参数
type OrderResponse struct {
OrderNo string `json:"orderNo"` // 权益订单号
CouponNo string `json:"couponNo,omitempty"` // 卡号
CouponCode string `json:"couponCode,omitempty"` // 卡密
Status int `json:"status"` // 状态
ExpireTime string `json:"expireTime,omitempty"` // 有效期
}
// QueryResponse 查询接口响应业务参数
type QueryResponse struct {
OrderNo string `json:"orderNo"` // 权益订单号
Status int `json:"status"` // 订单状态
Account string `json:"account,omitempty"` // 充值账号
CardInfo *CardInfo `json:"cardInfo,omitempty"` // 卡券信息
CouponId string `json:"couponId,omitempty"` // 优惠id
}
// WechatRechargeResponse 微信立减金充值响应业务参数
type WechatRechargeResponse struct {
OrderNo string `json:"orderNo"` // 权益订单号
Status int `json:"status"` // 订单状态
CouponId string `json:"couponId,omitempty"` // 微信优惠id
}
```
// File: xy_sh/internal/middleware/middleware.go
```go
package middleware
import (
"encoding/json"
"strings"
"github.com/gofiber/fiber/v2"
"xy_sh/internal/config"
"xy_sh/pkg/crypto"
)
// VerifySign 验证签名中间件
func VerifySign(cfg *config.Config) fiber.Handler {
return func(c *fiber.Ctx) error {
timestamp := c.Get("timestamp")
sign := c.Get("sign")
if timestamp == "" || sign == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"code": -1,
"msg": "缺少timestamp或sign请求头",
})
}
body := c.Body()
if len(body) == 0 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"code": -1,
"msg": "请求体不能为空",
})
}
var req struct {
EncryptedData string `json:"encryptedData"`
}
if err := json.Unmarshal(body, &req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"code": -1,
"msg": "请求体格式错误",
})
}
// 验证签名SM3(salt + timestamp + encryptedData)
expectedSign := crypto.SM3WithSalt(timestamp+req.EncryptedData, cfg.SM3Salt)
if !strings.EqualFold(expectedSign, sign) {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"code": -1,
"msg": "签名验证失败",
})
}
decrypted, err := crypto.SM4ECBDecrypt(req.EncryptedData, cfg.SM4Key)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"code": -1,
"msg": "解密失败: " + err.Error(),
})
}
c.Locals("decryptedBody", decrypted)
return c.Next()
}
}
```
// File: xy_sh/internal/biz/biz.go
```go
package biz
import (
"encoding/json"
"xy_sh/internal/config"
"xy_sh/internal/entities"
"xy_sh/pkg/crypto"
)
// Biz 业务逻辑层
type Biz struct {
cfg *config.Config
}
// NewBiz 创建业务逻辑实例
func NewBiz(cfg *config.Config) *Biz {
return &Biz{cfg: cfg}
}
// EncryptResponse 加密响应数据
func (b *Biz) EncryptResponse(data interface{}) (string, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return "", err
}
return crypto.SM4ECBEncrypt(jsonBytes, b.cfg.SM4Key)
}
// DecryptRequest 解密请求数据
func (b *Biz) DecryptRequest(encryptedData string) ([]byte, error) {
return crypto.SM4ECBDecrypt(encryptedData, b.cfg.SM4Key)
}
// ProcessOrder 处理下单请求
func (b *Biz) ProcessOrder(req *entities.OrderRequest) (*entities.OrderResponse, error) {
return &entities.OrderResponse{
OrderNo: "HM" + generateOrderNo(),
Status: 0,
ExpireTime: "2025-12-31 23:59:59",
}, nil
}
// ProcessQuery 处理订单查询请求
func (b *Biz) ProcessQuery(req *entities.QueryRequest) (*entities.QueryResponse, error) {
return &entities.QueryResponse{
OrderNo: req.OrderNo,
Status: 0,
}, nil
}
// ProcessWechatRecharge 处理微信立减金充值请求
func (b *Biz) ProcessWechatRecharge(req *entities.WechatRechargeRequest) (*entities.WechatRechargeResponse, error) {
return &entities.WechatRechargeResponse{
OrderNo: req.OrderNo,
Status: 0,
}, nil
}
// ProcessCallback 处理回调通知
func (b *Biz) ProcessCallback(req *entities.CallbackRequest) error {
return nil
}
// generateOrderNo 生成订单号(示例)
func generateOrderNo() string {
return "0000000000000000"
}
```
// File: xy_sh/internal/service/service.go
```go
package service
import (
"encoding/json"
"github.com/gofiber/fiber/v2"
"xy_sh/internal/biz"
"xy_sh/internal/config"
"xy_sh/internal/entities"
)
// Service 服务层
type Service struct {
biz *biz.Biz
cfg *config.Config
}
// NewService 创建服务实例
func NewService(cfg *config.Config) *Service {
return &Service{
biz: biz.NewBiz(cfg),
cfg: cfg,
}
}
// Order 卡券/直充权益下单接口
func (s *Service) Order(c *fiber.Ctx) error {
decrypted := c.Locals("decryptedBody").([]byte)
var req entities.OrderRequest
if err := json.Unmarshal(decrypted, &req); err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "业务参数解析失败: " + err.Error(),
})
}
resp, err := s.biz.ProcessOrder(&req)
if err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: err.Error(),
})
}
encryptedData, err := s.biz.EncryptResponse(resp)
if err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "响应加密失败: " + err.Error(),
})
}
return c.JSON(entities.ApiResponse{
Code: 0,
Msg: "请求成功",
Data: encryptedData,
})
}
// Query 卡券/直充/微信立减金订单查询接口
func (s *Service) Query(c *fiber.Ctx) error {
decrypted := c.Locals("decryptedBody").([]byte)
var req entities.QueryRequest
if err := json.Unmarshal(decrypted, &req); err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "业务参数解析失败: " + err.Error(),
})
}
resp, err := s.biz.ProcessQuery(&req)
if err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: err.Error(),
})
}
encryptedData, err := s.biz.EncryptResponse(resp)
if err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "响应加密失败: " + err.Error(),
})
}
return c.JSON(entities.ApiResponse{
Code: 0,
Msg: "请求成功",
Data: encryptedData,
})
}
// WechatRecharge 微信立减金订单充值接口
func (s *Service) WechatRecharge(c *fiber.Ctx) error {
decrypted := c.Locals("decryptedBody").([]byte)
var req entities.WechatRechargeRequest
if err := json.Unmarshal(decrypted, &req); err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "业务参数解析失败: " + err.Error(),
})
}
resp, err := s.biz.ProcessWechatRecharge(&req)
if err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: err.Error(),
})
}
encryptedData, err := s.biz.EncryptResponse(resp)
if err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "响应加密失败: " + err.Error(),
})
}
return c.JSON(entities.ApiResponse{
Code: 0,
Msg: "请求成功",
Data: encryptedData,
})
}
// Callback 卡券/直充/微信立减金充值结果通知接口
func (s *Service) Callback(c *fiber.Ctx) error {
decrypted := c.Locals("decryptedBody").([]byte)
var req entities.CallbackRequest
if err := json.Unmarshal(decrypted, &req); err != nil {
return c.Status(fiber.StatusBadRequest).SendString("参数解析失败")
}
if err := s.biz.ProcessCallback(&req); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return c.SendString("ok")
}
```
// File: xy_sh/internal/router/router.go
```go
package router
import (
"github.com/gofiber/fiber/v2"
"xy_sh/internal/config"
"xy_sh/internal/middleware"
"xy_sh/internal/service"
)
// SetupRouter 设置路由
func SetupRouter(cfg *config.Config) *fiber.App {
app := fiber.New()
svc := service.NewService(cfg)
api := app.Group("/api", middleware.VerifySign(cfg))
{
api.Post("/order", svc.Order)
api.Post("/query", svc.Query)
api.Post("/wechat/recharge", svc.WechatRecharge)
api.Post("/callback", svc.Callback)
}
return app
}
```
// File: xy_sh/internal/test/example.go
```go
package test
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"xy_sh/pkg/crypto"
)
const (
sm3Salt = "your_sm3_salt"
sm4Key = "1234567890123456"
)
// APIClient API客户端示例
type APIClient struct {
BaseURL string
SM3Salt string
SM4Key []byte
}
// NewAPIClient 创建API客户端
func NewAPIClient(baseURL string) *APIClient {
return &APIClient{
BaseURL: baseURL,
SM3Salt: sm3Salt,
SM4Key: []byte(sm4Key),
}
}
// EncryptAndSign 加密业务数据并生成签名
func (c *APIClient) EncryptAndSign(bizData interface{}) (timestamp string, encryptedData string, sign string, err error) {
jsonBytes, err := json.Marshal(bizData)
if err != nil {
return "", "", "", fmt.Errorf("序列化失败: %v", err)
}
encryptedData, err = crypto.SM4ECBEncrypt(jsonBytes, c.SM4Key)
if err != nil {
return "", "", "", fmt.Errorf("加密失败: %v", err)
}
timestamp = fmt.Sprintf("%d", time.Now().UnixMilli())
sign = crypto.SM3WithSalt(timestamp+encryptedData, c.SM3Salt)
return timestamp, encryptedData, sign, nil
}
// SendRequest 发送请求
func (c *APIClient) SendRequest(path string, bizData interface{}) (map[string]interface{}, error) {
timestamp, encryptedData, sign, err := c.EncryptAndSign(bizData)
if err != nil {
return nil, err
}
reqBody := map[string]string{
"encryptedData": encryptedData,
}
reqBytes, _ := json.Marshal(reqBody)
req, err := http.NewRequest("POST", c.BaseURL+path, bytes.NewReader(reqBytes))
if err != nil {
return nil, fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("timestamp", timestamp)
req.Header.Set("sign", sign)
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %v", err)
}
var result map[string]interface{}
if err := json.Unmarshal(respBytes, &result); err != nil {
return nil, fmt.Errorf("解析响应失败: %v", err)
}
return result, nil
}
// ExampleOrder 下单接口示例
func ExampleOrder() {
client := NewAPIClient("http://localhost:8080")
bizData := map[string]interface{}{
"actCode": "ACT001",
"goodsCode": "123456",
"actOrderNum": "00001",
"account": "19912345678",
"callbackUrl": "https://xxx/notice",
}
result, err := client.SendRequest("/api/order", bizData)
if err != nil {
fmt.Printf("下单失败: %v\n", err)
return
}
fmt.Printf("下单响应: %+v\n", result)
if data, ok := result["data"].(string); ok && data != "" {
decrypted, err := crypto.SM4ECBDecrypt(data, client.SM4Key)
if err == nil {
fmt.Printf("解密后数据: %s\n", string(decrypted))
}
}
}
// ExampleQuery 查询接口示例
func ExampleQuery() {
client := NewAPIClient("http://localhost:8080")
bizData := map[string]interface{}{
"actCode": "FBG6vdYqGE4mGX7EH/woEg==",
"orderNo": "HM1757046717684000102707339840b23222",
}
result, err := client.SendRequest("/api/query", bizData)
if err != nil {
fmt.Printf("查询失败: %v\n", err)
return
}
fmt.Printf("查询响应: %+v\n", result)
}
// ExampleWechatRecharge 微信立减金充值示例
func ExampleWechatRecharge() {
client := NewAPIClient("http://localhost:8080")
bizData := map[string]interface{}{
"actCode": "FBG6vdYqGE4mGX7EH/woEg==",
"goodsCode": "0001",
"actOrderNum": "0001",
"openId": "0001",
"appId": "00001",
}
result, err := client.SendRequest("/api/wechat/recharge", bizData)
if err != nil {
fmt.Printf("充值失败: %v\n", err)
return
}
fmt.Printf("充值响应: %+v\n", result)
}
// ExampleCallback 回调通知示例
func ExampleCallback() {
client := NewAPIClient("http://localhost:8080")
bizData := map[string]interface{}{
"orderNo": "HM202509291010001",
"actOrderNum": "XY2025092910100001",
"status": 3,
"account": "19912345678",
}
result, err := client.SendRequest("/api/callback", bizData)
if err != nil {
fmt.Printf("回调失败: %v\n", err)
return
}
fmt.Printf("回调响应: %+v\n", result)
}
```
// File: xy_sh/pkg/crypto/crypto.go
```go
package crypto
import (
"bytes"
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/tjfoc/gmsm/sm3"
"github.com/tjfoc/gmsm/sm4"
)
// SM4ECBEncrypt SM4-ECB模式加密
func SM4ECBEncrypt(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())
ciphertext := make([]byte, len(padded))
for i := 0; i < len(padded); i += block.BlockSize() {
block.Encrypt(ciphertext[i:i+block.BlockSize()], padded[i:i+block.BlockSize()])
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// SM4ECBDecrypt SM4-ECB模式解密
func SM4ECBDecrypt(encryptedData string, key []byte) ([]byte, error) {
if len(key) != 16 {
return nil, fmt.Errorf("SM4密钥长度必须为16字节")
}
ciphertext, 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(ciphertext)%blockSize != 0 {
return nil, fmt.Errorf("密文长度不是块大小的整数倍")
}
plaintext := make([]byte, len(ciphertext))
for i := 0; i < len(ciphertext); i += blockSize {
block.Decrypt(plaintext[i:i+blockSize], ciphertext[i:i+blockSize])
}
plaintext, err = pkcs7UnPadding(plaintext)
if err != nil {
return nil, fmt.Errorf("去除填充失败: %v", err)
}
return plaintext, nil
}
// SM3WithSalt 带盐的SM3哈希计算
// 对应Java的 SmUtil.sm3WithSalt(saltBytes).digestHex(data)
// 实现方式SM3(salt + data),输出十六进制字符串
func SM3WithSalt(data string, salt string) string {
h := sm3.New()
h.Write([]byte(salt))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
// SM3Hash 计算SM3哈希值
func SM3Hash(data []byte) string {
return hex.EncodeToString(sm3.Sm3Sum(data))
}
// 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
}
```