xy_sh-20260723184208/xy_sh/valid.md

592 lines
15 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.5
github.com/tjfoc/gmsm v1.4.1
)
```
// File: xy_sh/go.sum
```go
```
// File: xy_sh/Dockerfile
```dockerfile
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /server ./cmd/server
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /server .
EXPOSE 8080
CMD ["./server"]
```
// File: xy_sh/cmd/server/main.go
```go
package main
import (
"log"
"xy_sh/internal/config"
"xy_sh/internal/middleware"
"xy_sh/internal/service"
"github.com/gofiber/fiber/v2"
)
func main() {
cfg := config.Load()
app := fiber.New(fiber.Config{
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
})
// 全局中间件
app.Use(middleware.CORS())
app.Use(middleware.RequestLogger())
// 注册路由
service.RegisterRoutes(app, 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"
"time"
)
// Config 应用配置
type Config struct {
ServerPort string
ReadTimeout time.Duration
WriteTimeout time.Duration
SM3Salt string
SM4Key []byte
CallbackURL string
}
// Load 加载配置,优先从环境变量读取
func Load() *Config {
sm4KeyStr := getEnv("SM4_KEY", "1234567890abcdef")
return &Config{
ServerPort: getEnv("SERVER_PORT", "8080"),
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
SM3Salt: getEnv("SM3_SALT", "default_salt"),
SM4Key: []byte(sm4KeyStr),
CallbackURL: getEnv("CALLBACK_URL", ""),
}
}
func getEnv(key, defaultValue string) string {
if v := os.Getenv(key); v != "" {
return v
}
return defaultValue
}
```
// File: xy_sh/internal/entities/request.go
```go
package entities
// OrderCreateBizReq 卡券/直充权益下单请求业务参数
type OrderCreateBizReq struct {
ActCode string `json:"actCode"`
GoodsCode string `json:"goodsCode"`
ActOrderNum string `json:"actOrderNum"`
Account string `json:"account,omitempty"`
CallbackURL string `json:"callbackUrl,omitempty"`
}
// OrderQueryBizReq 卡券/直充/微信立减金订单查询请求业务参数
type OrderQueryBizReq struct {
ActCode string `json:"actCode"`
OrderNo string `json:"orderNo"`
}
// WechatRechargeBizReq 微信立减金订单充值请求业务参数
type WechatRechargeBizReq 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"`
}
// CallbackNotifyBizReq 回调通知请求业务参数
type CallbackNotifyBizReq 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"`
}
// CardInfo 卡券信息
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
// ApiResponse 通用API响应结构
type ApiResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// OrderCreateBizResp 下单响应业务参数
type OrderCreateBizResp struct {
OrderNo string `json:"orderNo"`
CouponNo string `json:"couponNo,omitempty"`
CouponCode string `json:"couponCode,omitempty"`
Status int `json:"status"`
ExpireTime string `json:"expireTime,omitempty"`
}
// OrderQueryBizResp 订单查询响应业务参数
type OrderQueryBizResp struct {
OrderNo string `json:"orderNo"`
Status int `json:"status"`
Account string `json:"account,omitempty"`
CardInfo *CardInfo `json:"cardInfo,omitempty"`
CouponId string `json:"couponId,omitempty"`
}
// WechatRechargeBizResp 微信立减金充值响应业务参数
type WechatRechargeBizResp struct {
OrderNo string `json:"orderNo"`
Status int `json:"status"`
CouponId string `json:"couponId,omitempty"`
}
```
// File: xy_sh/internal/middleware/middleware.go
```go
package middleware
import (
"log"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
)
// CORS 跨域中间件
func CORS() fiber.Handler {
return cors.New(cors.Config{
AllowOrigins: "*",
AllowMethods: "GET,POST,PUT,DELETE,OPTIONS",
AllowHeaders: "Origin,Content-Type,Accept,timestamp,sign",
})
}
// RequestLogger 请求日志中间件
func RequestLogger() fiber.Handler {
return func(c *fiber.Ctx) error {
start := time.Now()
err := c.Next()
duration := time.Since(start)
log.Printf("[%s] %s %s - %d (%v)",
c.Method(),
c.Path(),
c.IP(),
c.Response().StatusCode(),
duration,
)
return err
}
}
```
// File: xy_sh/internal/biz/biz.go
```go
package biz
import (
"xy_sh/internal/entities"
)
// Biz 业务逻辑接口
type Biz interface {
// CreateOrder 卡券/直充权益下单
CreateOrder(req *entities.OrderCreateBizReq) (*entities.OrderCreateBizResp, error)
// QueryOrder 卡券/直充/微信立减金订单查询
QueryOrder(req *entities.OrderQueryBizReq) (*entities.OrderQueryBizResp, error)
// WechatRecharge 微信立减金订单充值
WechatRecharge(req *entities.WechatRechargeBizReq) (*entities.WechatRechargeBizResp, error)
// ProcessCallback 处理回调通知
ProcessCallback(req *entities.CallbackNotifyBizReq) error
}
type bizImpl struct{}
// NewBiz 创建业务逻辑实例
func NewBiz() Biz {
return &bizImpl{}
}
func (b *bizImpl) CreateOrder(req *entities.OrderCreateBizReq) (*entities.OrderCreateBizResp, error) {
// TODO: 实现下单业务逻辑
return &entities.OrderCreateBizResp{
OrderNo: "HM" + generateOrderNo(),
Status: -1,
}, nil
}
func (b *bizImpl) QueryOrder(req *entities.OrderQueryBizReq) (*entities.OrderQueryBizResp, error) {
// TODO: 实现订单查询业务逻辑
return &entities.OrderQueryBizResp{
OrderNo: req.OrderNo,
Status: 0,
}, nil
}
func (b *bizImpl) WechatRecharge(req *entities.WechatRechargeBizReq) (*entities.WechatRechargeBizResp, error) {
// TODO: 实现微信立减金充值业务逻辑
return &entities.WechatRechargeBizResp{
OrderNo: req.OrderNo,
Status: -1,
}, nil
}
func (b *bizImpl) ProcessCallback(req *entities.CallbackNotifyBizReq) error {
// TODO: 处理回调通知业务逻辑
return nil
}
func generateOrderNo() string {
return "ORDERNO"
}
```
// File: xy_sh/internal/service/service.go
```go
package service
import (
"encoding/json"
"log"
"xy_sh/internal/biz"
"xy_sh/internal/config"
"xy_sh/internal/entities"
"xy_sh/pkg/crypto"
"github.com/gofiber/fiber/v2"
)
// RegisterRoutes 注册所有路由
func RegisterRoutes(app *fiber.App, cfg *config.Config) {
b := biz.NewBiz()
// 接口1卡券/直充权益下单
app.Post("/api/order/create", handleWithDecrypt(cfg, b, func(reqBytes []byte) (interface{}, error) {
var bizReq entities.OrderCreateBizReq
if err := json.Unmarshal(reqBytes, &bizReq); err != nil {
return nil, err
}
return b.CreateOrder(&bizReq)
}))
// 接口2卡券/直充/微信立减金订单查询
app.Post("/api/order/query", handleWithDecrypt(cfg, b, func(reqBytes []byte) (interface{}, error) {
var bizReq entities.OrderQueryBizReq
if err := json.Unmarshal(reqBytes, &bizReq); err != nil {
return nil, err
}
return b.QueryOrder(&bizReq)
}))
// 接口3微信立减金订单充值
app.Post("/api/order/recharge", handleWithDecrypt(cfg, b, func(reqBytes []byte) (interface{}, error) {
var bizReq entities.WechatRechargeBizReq
if err := json.Unmarshal(reqBytes, &bizReq); err != nil {
return nil, err
}
return b.WechatRecharge(&bizReq)
}))
// 接口4卡券/直充/微信立减金充值结果通知接口(接收供应商回调)
app.Post("/api/callback/notify", handleCallback(cfg, b))
}
// handleWithDecrypt 通用处理:验证签名 -> 解密 -> 执行业务 -> 加密响应
func handleWithDecrypt(cfg *config.Config, b biz.Biz, handler func([]byte) (interface{}, error)) fiber.Handler {
return func(c *fiber.Ctx) error {
// 1. 验证签名
timestamp := c.Get("timestamp")
sign := c.Get("sign")
if timestamp == "" || sign == "" {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "缺少timestamp或sign",
})
}
// 2. 获取加密数据
var reqBody struct {
EncryptedData string `json:"encryptedData"`
}
if err := c.BodyParser(&reqBody); err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "请求体解析失败",
})
}
// 3. 验证SM3签名sign = SM3(salt + timestamp + encryptedData)
expectedSign := crypto.SM3WithSalt(cfg.SM3Salt, timestamp+reqBody.EncryptedData)
if expectedSign != sign {
log.Printf("签名验证失败: expected=%s, got=%s", expectedSign, sign)
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "签名验证失败",
})
}
// 4. SM4解密业务数据
bizData, err := crypto.SM4ECBDecrypt(reqBody.EncryptedData, cfg.SM4Key)
if err != nil {
log.Printf("SM4解密失败: %v", err)
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "数据解密失败",
})
}
// 5. 执行业务逻辑
result, err := handler(bizData)
if err != nil {
log.Printf("业务处理失败: %v", err)
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: err.Error(),
})
}
// 6. 加密响应数据
respBytes, err := json.Marshal(result)
if err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "响应序列化失败",
})
}
encryptedResp, err := crypto.SM4ECBEncrypt(respBytes, cfg.SM4Key)
if err != nil {
return c.JSON(entities.ApiResponse{
Code: -1,
Msg: "响应加密失败",
})
}
return c.JSON(entities.ApiResponse{
Code: 0,
Msg: "请求成功",
Data: encryptedResp,
})
}
}
// handleCallback 处理供应商回调通知
func handleCallback(cfg *config.Config, b biz.Biz) fiber.Handler {
return func(c *fiber.Ctx) error {
// 1. 验证签名
timestamp := c.Get("timestamp")
sign := c.Get("sign")
if timestamp == "" || sign == "" {
return c.Status(400).SendString("missing auth headers")
}
// 2. 获取加密数据
var reqBody struct {
EncryptedData string `json:"encryptedData"`
}
if err := c.BodyParser(&reqBody); err != nil {
return c.Status(400).SendString("invalid body")
}
// 3. 验证SM3签名
expectedSign := crypto.SM3WithSalt(cfg.SM3Salt, timestamp+reqBody.EncryptedData)
if expectedSign != sign {
log.Printf("回调签名验证失败: expected=%s, got=%s", expectedSign, sign)
return c.Status(400).SendString("sign verification failed")
}
// 4. SM4解密业务数据
bizData, err := crypto.SM4ECBDecrypt(reqBody.EncryptedData, cfg.SM4Key)
if err != nil {
log.Printf("回调数据SM4解密失败: %v", err)
return c.Status(400).SendString("decrypt failed")
}
// 5. 解析回调业务参数
var callbackReq entities.CallbackNotifyBizReq
if err := json.Unmarshal(bizData, &callbackReq); err != nil {
log.Printf("回调数据解析失败: %v", err)
return c.Status(400).SendString("parse failed")
}
// 6. 处理回调业务
if err := b.ProcessCallback(&callbackReq); err != nil {
log.Printf("回调处理失败: %v", err)
return c.Status(500).SendString("process failed")
}
// 7. 返回成功必须返回200且内容为ok
return c.Status(200).SendString("ok")
}
}
```
// File: xy_sh/pkg/crypto/crypto.go
```go
package crypto
import (
"encoding/base64"
"encoding/hex"
"fmt"
"time"
"github.com/tjfoc/gmsm/sm3"
"github.com/tjfoc/gmsm/sm4"
)
// ==================== SM3 with Salt ====================
// SM3WithSalt 使用SM3加盐哈希
// 对应Java hutool的 SmUtil.sm3WithSalt(salt).digestHex(data)
// hutool实现为SM3(salt + data)
func SM3WithSalt(salt string, data string) string {
h := sm3.New()
h.Write([]byte(salt))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
// ==================== SM4-ECB 加密/解密 ====================
// SM4ECBEncrypt SM4-ECB模式加密返回base64编码字符串
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模式解密输入base64编码字符串返回明文
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
}
// ==================== PKCS7 填充 ====================
func pkcs7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := make([]byte, padding)
for i := range padText {
padText[i] = byte(padding)
}
return append(data, padText...)
}
func pkcs7UnPadding(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, fmt.Errorf("数据为空")
}
padding := int(data[len(data)-1])
if padding > len(data) || padding == 0 {
return nil, fmt.Errorf("无效的填充")
}
for i := len(data) - padding; i < len(data); i++ {
if int(data[i]) != padding {
return nil, fmt.Errorf("无效的填充")
}
}
return data[:len(data)-padding], nil
}
// ==================== 时间戳工具 ====================
// GenerateTimestampMillis 生成毫秒级时间戳
func GenerateTimestampMillis() string {
return fmt.Sprintf("%d", time.Now().UnixMilli())
}
```