订单查询/退款

This commit is contained in:
wuchao 2024-06-19 18:32:34 +08:00
parent 39cbe5a8ba
commit ee7a93f73d
49 changed files with 3503 additions and 47 deletions

View File

@ -5,5 +5,18 @@ const (
TOKEN_Admin = "Admin_token_"
ADMIN_V1 = "/admin/api/v1"
FRONT_API_V1 = "/api/v1"
FRONT_API_V1 = "/api/v1"
FRONT_API_V1_Auth = "/api/v1/auth"
STATUSABLED = 1
// 订单状态 1-待支付 2-已支付 3 充值完成 4充值异常 5卷链核销 6 退款中 7 退款完成 8 退款失败
ORDER_STATUS_DEFAULT = 1
ORDER_STATUS_PAY = 2
ORDER_STATUS_FINISH = 3
ORDER_STATUS_FAIL = 4
ORDER_STATUS_OFFSET = 5
ORDER_STATUS_ReFUNDING = 6
ORDER_STATUS_ReFUNDEND = 7
ORDER_STATUS_ReFUNDFAIL = 8
)

View File

@ -21,6 +21,22 @@ const (
//未登录
NotLogin = 1000
//抽奖中
OrderLottery = 1001
//商品不存在
OrderProductNotExist = 1002
//兴业下单失败
XINYEOrderFAIL = 1003
//订单不允许退款
OrderNOTAuthREFUND = 2001
// 订单更新失败
OrderRefundUpdateFail = 2002
//退款失败
OrderRefundFail = 2003
//邮储服务异常
YouChuOrderRefundFail = 2004
)
var MsgEN = map[int]string{
@ -33,11 +49,19 @@ var MsgEN = map[int]string{
}
var MsgZH = map[int]string{
Success: "请求成功",
ParamError: "参数错误",
NotFound: "数据不存在",
NotAuth: "未经授权",
NotLogin: "未登录",
SystemError: "系统错误",
Success: "请求成功",
ParamError: "参数错误",
NotFound: "数据不存在",
NotAuth: "未经授权",
NotLogin: "未登录",
OrderLottery: "兑换中",
OrderProductNotExist: "商品不存在",
XINYEOrderFAIL: "三方下单失败",
OrderNOTAuthREFUND: "该订单暂不支持退款",
OrderRefundUpdateFail: "订单更新失败",
OrderRefundFail: "退款失败",
YouChuOrderRefundFail: "邮储服务异常",
}
var MsgMap map[string]map[int]string = map[string]map[int]string{"en": MsgZH}

View File

@ -175,3 +175,21 @@ func Frequence(key string) bool {
return true
}
}
func GetUserId(c *gin.Context) int {
userIdStr, _ := c.Get("userId")
if userIdStr != nil {
var userId, _ = userIdStr.(int)
return userId
}
return 0
}
func GetUserPhone(c *gin.Context) string {
userIdStr, _ := c.Get("phone")
if userIdStr != nil {
var userId, _ = userIdStr.(string)
return userId
}
return ""
}

View File

@ -0,0 +1,91 @@
package front
import (
"github.com/ahmetb/go-linq/v3"
"github.com/gin-gonic/gin"
"qteam/app/constants/common"
"qteam/app/constants/errorcode"
"qteam/app/http/controllers"
"qteam/app/http/entities/front"
"qteam/app/models/ordersmodel"
"qteam/app/services"
"qteam/app/third/market"
"qteam/app/utils"
"qteam/config"
"strconv"
)
func CreateOrder(c *gin.Context) {
var request = controllers.GetRequest(c).(*front.OrderCreateRequest)
//userId := controllers.GetUserId(c)
userId := 1
code, data := services.CreateOrderService(userId, request.ProductId)
controllers.HandCodeRes(c, data, code)
}
func OrderList(c *gin.Context) {
var request = controllers.GetRequest(c).(*front.OrderListRequest)
//userId := controllers.GetUserId(c)
userId := 1
code, orderList, count := services.OrderQueryService(userId, request)
var rsp []front.OrderQueryResponse
if count > 0 {
linq.From(orderList).SelectT(func(in ordersmodel.Orders) (out front.OrderQueryResponse) {
out.ResponseFromDb(in)
return out
}).ToSlice(&rsp)
}
controllers.HandCodeRes(c, gin.H{"data": rsp, "count": count}, code)
}
func OrderQuery(c *gin.Context) {
var request = controllers.GetRequest(c).(*front.OrderQueryRequest)
orderId, _ := strconv.Atoi(request.OrderId)
order := ordersmodel.Orders{Id: orderId}
var OrderQueryResponse front.OrderQueryResponse
has, err := services.OrderDetailService(&order)
if err != nil {
controllers.Error(c, 500, "订单查询失败")
return
}
if has {
if order.State < common.ORDER_STATUS_PAY && utils.IsNil(order.VoucherLink) {
_, rsp := services.YouChuOrderQuery(order)
utils.Log(nil, "三方订单查询", rsp)
if rsp.OrderSta == "03" {
client := market.NewMarketClient(config.GetConf().OpenApiMarketConfig)
data, err := client.MarketSend(order.OrderNo, strconv.Itoa(order.VoucherId), "", "1")
if err != nil {
controllers.Error(c, 500, "三方订单查询失败")
return
}
if data.ErrCode == "00" {
err := services.OrdersUpdateService(front.OrdersUpdateRequest{Id: order.Id, Status: common.ORDER_STATUS_PAY, VoucherLink: data.Data.ShortUrl})
if err != nil {
controllers.Error(c, 500, "订单更新失败")
utils.Log(nil, "营销系统下单失败", data)
return
}
}
}
}
OrderQueryResponse.ResponseFromDb(order)
controllers.Success(c, OrderQueryResponse, "请求成功")
} else {
controllers.HandCodeRes(c, nil, errorcode.NotFound)
return
}
}
func OrderRefund(c *gin.Context) {
var request = controllers.GetRequest(c).(*front.OrderQueryRequest)
//userId := controllers.GetUserId(c)
userId := 1
OrderId, _ := strconv.Atoi(request.OrderId)
code := services.OrderRefundService(userId, OrderId)
controllers.HandCodeRes(c, nil, code)
}
func OrderNotify(c *gin.Context) {
}

View File

@ -1 +1,14 @@
package front
import (
"github.com/gin-gonic/gin"
"qteam/app/http/controllers"
"qteam/app/http/entities/front"
"qteam/app/services"
)
func UnionLogin(c *gin.Context) {
req := controllers.GetRequest(c).(*front.UnionLoginRequest)
code, login := services.YouChuLogin(req)
controllers.HandCodeRes(c, login, code)
}

View File

@ -1 +0,0 @@
package entities

View File

@ -1 +0,0 @@
package entities

View File

@ -0,0 +1 @@
package front

View File

@ -0,0 +1,43 @@
package front
import (
"qteam/app/models/ordersmodel"
"qteam/app/utils"
)
type OrderCreateRequest struct {
ProductId int `form:"product_id" validate:"required"`
}
type OrderQueryRequest struct {
OrderId string `form:"order_id" validate:"required"`
}
type OrderListRequest struct {
PageRequest
}
type OrderQueryResponse struct {
Id int `json:"id"`
OrderNo string `json:"order_no"`
UserId int `json:"user_id"`
UserName string `json:"user_name"`
Mobile string `json:"mobile"`
ProductId int `json:"product_id"`
ProductName string `json:"product_name"`
State int `json:"state"`
VoucherLink string `json:"voucher_link"`
CreateTime string `json:"create_time"`
}
func (p *OrderQueryResponse) ResponseFromDb(l ordersmodel.Orders) {
utils.EntityCopy(p, &l)
p.CreateTime = l.CreateTime.Format("2006-01-02 15:04:05")
return
}
type OrdersUpdateRequest struct {
Id int `json:"id" validate:"required" form:"id" validate:"required" example:"1"`
Status int `json:"status" form:"status" validate:"oneof=1 2 3 4" example:"1"` // '状态(1/待支付,2/已支付,3/已完成4/取消5作废)'
VoucherLink string `json:"voucher_link"`
}

View File

@ -0,0 +1 @@
package front

View File

@ -0,0 +1,16 @@
package front
type UnionLoginRequest struct {
Code string `form:"code" json:"code" validate:"required"`
}
type LoginResponse struct {
Token string
}
type YouChuDecryptData struct {
Mobile string `json:"phone"`
CerNo string `json:"cerNo"`
Time string `json:"time"`
UserId string `json:"userId"`
}

View File

@ -1,4 +1,4 @@
package entities
package front
import (
"qteam/app/models/brandmodel"

View File

@ -0,0 +1,110 @@
package front
import "encoding/json"
type YouChuRequest struct {
Request string `json:"request"`
Signature string `json:"signature"`
AccessToken string `json:"accessToken"`
EncryptKey string `json:"encryptKey"`
}
type YouChuRequestBody struct {
Body YouChuOrderRequest `json:"body"`
Head YouChuRequestHeader `json:"head"`
}
type YouChuRequestHeader struct {
PartnerTxSriNo string `json:"partnerTxSriNo"`
Method string `json:"method"`
Version string `json:"version"`
MerchantId string `json:"merchantId"`
AppID string `json:"appID"`
AccessType string `json:"accessType"`
Reserve string `json:"reserve"`
}
type YouChuOrderRequest struct {
BusiMainId string `json:"busiMainId"`
ReqTransTime string `json:"reqTransTime"`
Data OrderQuery `json:"data"`
}
type OrderQuery struct {
TxnCode string `json:"txnCode"` //固定值 1004
SourceId string `json:"sourceId"` //固定值 16
ReqDate string `json:"reqDate"` //YYYYMMDDHHmmss
ReqTraceId string `json:"reqTraceId"` //订单号
MchtNo string `json:"mchtNo"` //收单商户号
OldSeqNo string `json:"oldSeqNo"` //收单商户号
TxnDt string `json:"txnDt"` //YYYYMMDD
}
type InsertOrderResponse struct {
OrderNo string `json:"order_no"`
NotifyUrl string `json:"notify_url"`
}
type YouChuOrderQueryResponse struct {
RespCode string `json:"code"`
RespMsg string `json:"respMsg"`
RespCd string `json:"respCd"`
ReqTraceId string `json:"reqTraceId"` //请求方流水号或者订单号,
OrderNo string `json:"orderNo"` //统一收单订单号
OrderSta string `json:"orderSta"` //03-支付成功 04-支付失败 05-检查失败
TxnFg string `json:"txnFg"` //0-正常 2-已部分退货 3- 已全部退货
}
type YouChuOrderRefundResponse struct {
RespCode string `json:"code"`
RespMsg string `json:"respMsg"`
RespCd string `json:"respCd"`
TxnCode string `json:"txnCode"`
SourceId string `json:"sourceId"`
ReqTraceId string `json:"ReqTraceId"`
RefundOrderNo string `json:"refundOrderNo"`
RefundOrderSta string `json:"refundOrderSta"`
MchtNo string `json:"mchtNo"`
TxnAmt string `json:"txnAmt"`
}
type YouChuOrderRefundRequest struct {
Body RefundRequestBody `json:"body"`
Head YouChuRequestHeader `json:"head"`
}
type RefundRequestBody struct {
BusiMainId string `json:"busiMainId"`
ReqTransTime string `json:"reqTransTime"`
Data RefundRequestData `json:"data"`
}
type RefundRequestData struct {
TxnCode string `json:"txnCode"` //固定值 1004
SourceId string `json:"sourceId"` //固定值 16
ReqDate string `json:"reqDate"` //YYYYMMDDHHmmss
ReqTraceId string `json:"reqTraceId"` //订单号
MchtNo string `json:"mchtNo"` //收单商户号
OrgTxnSeq string `json:"orgTxnSeq"` //收单系统订单号
OrgTxnAmt string `json:"orgTxnAmt"` //原交易金额
TxnAmt string `json:"txnAmt"` //退货金额
RefundDesc string `json:"refundDesc"` //退货原因
}
type YouChuOrderNotifyRequest struct {
}
func (this *YouChuOrderRequest) ToMap() (resultMap map[string]interface{}) {
// Marshal the struct to JSON, ignoring omitempty fields.
jsonBytes, err := json.Marshal(this)
if err != nil {
return
}
// Unmarshal the JSON into a map to get the final result.
err = json.Unmarshal(jsonBytes, &resultMap)
if err != nil {
return
}
return resultMap
}

View File

@ -1,12 +1,12 @@
package entities
package front
type IdRequest struct {
Id int64 `json:"id"`
}
type PageRequest struct {
Page int64 `json:"page"`
PageSize int64 `json:"pageSize"`
Page int `form:"page"`
PageSize int `form:"pageSize"`
}
type PageRsp struct {

View File

@ -1,12 +1,22 @@
package requestmapping
import "qteam/app/constants/common"
import (
"qteam/app/constants/common"
"qteam/app/http/entities/front"
)
var FrontRequestMap = map[string]func() interface{}{
//"/v1/login": func() interface{} {
// return new(front.LoginRequest)
//},
// 奶茶专区代金券列表
common.FRONT_API_V1 + "/VoucherList": func() interface{} { return new(struct{}) },
//联合登录
common.FRONT_API_V1 + "/UnionLogin": func() interface{} { return new(front.UnionLoginRequest) },
// 生成订单
common.FRONT_API_V1_Auth + "/order/create": func() interface{} { return new(front.OrderCreateRequest) },
//订单列表
common.FRONT_API_V1_Auth + "/order/list": func() interface{} { return new(front.OrderListRequest) },
// 查询订单
common.FRONT_API_V1_Auth + "/order/query": func() interface{} { return new(front.OrderQueryRequest) },
//退款订单
common.FRONT_API_V1_Auth + "/order/refund": func() interface{} { return new(front.OrderQueryRequest) },
}

View File

@ -48,7 +48,25 @@ func RegisterRoute(router *gin.Engine) {
//api版本
v1 := router.Group(common.FRONT_API_V1, middlewares.ValidateRequest())
{
//优惠券品牌、商品列表
v1.POST("/VoucherList", front.VoucherList)
// 联合登录
v1.POST("/UnionLogin", front.UnionLogin)
v1.POST("/order/notify", front.OrderNotify)
//auth
auth := v1.Group("auth")
//auth := v1.Group("auth", middlewares.Auth())
{
order := auth.Group("/order")
{
order.POST("/create", front.CreateOrder)
order.POST("/list", front.OrderList)
order.POST("/query", front.OrderQuery)
order.POST("/refund", front.OrderRefund)
}
}
}
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

View File

@ -13,23 +13,25 @@ var (
// 实体
type Orders struct {
Id string `xorm:"'id' UNSIGNED INT pk autoincr"`
OrderNo string `xorm:"'order_no' varchar(50)"`
UserId int `xorm:"'user_id' int(0)"`
YouchuUserId int `xorm:"'YouChu_User_id' int(11)"`
UserName string `xorm:"'user_name' varchar(11)"`
Mobile string `xorm:"'mobile' varchar(13)"`
ProductId int `xorm:"'product_id' int(0)"`
ProductName string `xorm:"'product_name' varchar(50)"`
Price string `xorm:"'price' decimal(10,2)"`
ActivityId string `xorm:"'activity_id' UNSIGNED INT"`
State string `xorm:"'state' UNSIGNED TINYINT"`
VoucherId int `xorm:"'voucher_id' int(0)"`
VoucherLink string `xorm:"'voucher_link' varchar(255)"`
CreateTime time.Time `xorm:"'create_time' datetime"`
UpdateTime time.Time `xorm:"'update_time' datetime"`
ExchangeTime time.Time `xorm:"'exchange_time' datetime"`
Deleted time.Time `xorm:"'Deleted' datetime"`
Id int `xorm:"'id' UNSIGNED INT pk autoincr"`
OrderNo string `xorm:"'order_no' varchar(50)"`
OrgTxnSeq string `xorm:"'orgTxnSeq' varchar(255)"`
UserId int `xorm:"'user_id' int(0)"`
UserName string `xorm:"'user_name' varchar(11)"`
Mobile string `xorm:"'mobile' varchar(13)"`
ProductId int `xorm:"'product_id' int(0)"`
ProductName string `xorm:"'product_name' varchar(50)"`
Price string `xorm:"'price' decimal(10,2)"`
State int `xorm:"'state' UNSIGNED TINYINT"`
VoucherId int `xorm:"'voucher_id' int(0)"`
VoucherLink string `xorm:"'voucher_link' varchar(255)"`
RefundOrderNo string `xorm:"'refundOrderNo' varchar(255)"`
RefundOrderSta string `xorm:"'refundOrderSta' varchar(20)"`
RefundTime time.Time `xorm:"'create_time' datetime"`
CreateTime time.Time `xorm:"'create_time' datetime"`
UpdateTime time.Time `xorm:"'update_time' datetime"`
ExchangeTime time.Time `xorm:"'exchange_time' datetime"`
Deleted time.Time `xorm:"'Deleted' datetime"`
}
// 表名

View File

@ -0,0 +1,155 @@
package services
import (
"qteam/app/constants/common"
"qteam/app/constants/errorcode"
"qteam/app/http/entities/front"
"qteam/app/models/ordersmodel"
"qteam/app/models/productsmodel"
"qteam/app/models/usersmodel"
"qteam/app/utils"
redis_util "qteam/app/utils/redis"
"qteam/config"
"strconv"
"time"
"xorm.io/builder"
)
func CreateOrderService(userId int, productId int) (code int, data front.InsertOrderResponse) {
var err error
{ // redis保证用户当前抽奖结束才能开始下次抽奖
key := utils.GetRealKey("lottery_code:" + strconv.Itoa(userId) + strconv.Itoa(productId))
ok, err := redis_util.AcquireLock(key, time.Second*10)
if ok {
defer redis_util.Del(key)
} else {
if err != nil {
utils.Log(nil, "CreateOrderService", err.Error())
}
code = errorcode.OrderLottery
return code, data
}
}
session := ordersmodel.GetInstance().GetDb().NewSession()
defer func() {
if err != nil {
_ = session.Rollback()
return
}
_ = session.Close()
}()
var product productsmodel.Products
has, err := productsmodel.GetInstance().GetDb().Where("id =?", productId).Get(&product)
if err != nil {
return errorcode.SystemError, data
}
if !has {
return errorcode.OrderProductNotExist, data
}
var user usersmodel.Users
_, err = usersmodel.GetInstance().GetDb().Where("id =?", userId).Get(&user)
if err != nil {
return errorcode.SystemError, data
}
order := ordersmodel.Orders{
OrderNo: utils.GenerateOrderNumber(),
UserId: userId,
ProductId: productId,
ProductName: product.Name,
UserName: user.Name,
Price: product.Price,
Mobile: user.Phone,
VoucherId: product.ThirdProductId,
State: common.STATUSABLED,
CreateTime: time.Now(),
}
if err = session.Begin(); err != nil {
return errorcode.SystemError, data
}
if _, err := session.Insert(order); err != nil {
utils.Log(nil, "CreateOrderService", err.Error())
return errorcode.SystemError, data
} else {
_ = session.Commit()
}
data.OrderNo = order.OrderNo
data.NotifyUrl = config.GetConf().YouChu.NotifyUrl
return errorcode.Success, data
}
func OrderQueryService(userId int, OrderRequest *front.OrderListRequest) (code int, data []ordersmodel.Orders, count int64) {
repo := ordersmodel.GetInstance().GetDb()
count, err := repo.Where("user_id = ?", userId).
Desc("id").Limit(OrderRequest.PageSize, (OrderRequest.Page-1)*OrderRequest.PageSize).FindAndCount(&data)
code = handErr(err)
return
}
func OrderDetailService(order *ordersmodel.Orders) (has bool, err error) {
repo := ordersmodel.GetInstance().GetDb()
conn := builder.NewCond()
if order.Id != 0 {
conn = conn.And(builder.Eq{"id": order.Id})
}
if order.OrderNo != "" {
conn = conn.And(builder.Eq{"order_no": order.OrderNo})
}
return repo.Where(conn).Get(order)
}
func OrdersUpdateService(req front.OrdersUpdateRequest) (err error) {
repo := ordersmodel.GetInstance().GetDb()
var order ordersmodel.Orders
if req.Id != 0 {
order.Id = req.Id
}
if req.Status != 0 {
order.State = req.Status
}
if req.VoucherLink != "" {
order.VoucherLink = req.VoucherLink
}
_, err = repo.Where("Id = ?", req.Id).Update(&order)
return
}
func OrderRefundService(userId int, orderId int) (code int) {
{ // redis保证用户当前抽奖结束才能开始下次抽奖
key := utils.GetRealKey("lottery_code:" + strconv.Itoa(userId) + strconv.Itoa(orderId))
ok, err := redis_util.AcquireLock(key, time.Second*10)
if ok {
defer redis_util.Del(key)
} else {
if err != nil {
utils.Log(nil, "CreateOrderService", err.Error())
}
return errorcode.SystemError
}
}
order := ordersmodel.Orders{Id: orderId, UserId: userId}
has, err := OrderDetailService(&order)
if err != nil || !has {
return errorcode.NotFound
}
if order.State >= common.ORDER_STATUS_PAY {
return errorcode.OrderNOTAuthREFUND
}
code, response := YouChuOrderRefund(order)
if code != errorcode.Success {
return code
} else {
if response.RefundOrderSta != "03" {
return errorcode.OrderRefundFail
} else {
order.State = common.ORDER_STATUS_ReFUNDING
order.RefundOrderNo = response.RefundOrderNo
order.RefundOrderSta = response.RefundOrderSta
order.RefundTime = time.Now()
_, err := ordersmodel.GetInstance().GetDb().Update(order)
if err != nil {
return errorcode.OrderRefundUpdateFail
}
return errorcode.Success
}
}
}

160
app/services/Sm2Service.go Normal file
View File

@ -0,0 +1,160 @@
package services
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/tjfoc/gmsm/sm2"
"math/big"
"qteam/config"
"strings"
)
// 生成公钥、私钥
func GenerateSM2Key() (PublicKey string, PrivateKey string, err error) {
// 生成私钥、公钥
privKey, err := sm2.GenerateKey(rand.Reader)
if err != nil {
fmt.Println("生成密钥对失败:", err)
return "", "", err
}
return PublicKeyToString(&privKey.PublicKey), PrivateKeyToString(privKey), nil
}
// PublicKeyToString 公钥sm2.PublicKey转字符串(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func PublicKeyToString(publicKey *sm2.PublicKey) string {
xBytes := publicKey.X.Bytes()
yBytes := publicKey.Y.Bytes()
// 确保坐标字节切片长度相同
byteLen := len(xBytes)
if len(yBytes) > byteLen {
byteLen = len(yBytes)
}
// 为坐标补齐前导零
xBytes = append(make([]byte, byteLen-len(xBytes)), xBytes...)
yBytes = append(make([]byte, byteLen-len(yBytes)), yBytes...)
// 添加 "04" 前缀
publicKeyBytes := append([]byte{0x04}, append(xBytes, yBytes...)...)
return strings.ToUpper(hex.EncodeToString(publicKeyBytes))
}
// PrivateKeyToString 私钥sm2.PrivateKey 转字符串(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func PrivateKeyToString(privateKey *sm2.PrivateKey) string {
return strings.ToUpper(hex.EncodeToString(privateKey.D.Bytes()))
}
func SM2Decrypt(cipherText string) (string, error) {
if cipherText == "" {
return "", nil
}
decodedBytes, err := base64.StdEncoding.DecodeString(cipherText)
if err != nil {
fmt.Println("解码错误:", err)
return "", nil
}
decrypt, err := decryptLoc(config.GetConf().Sm2.PublicKey, config.GetConf().Sm2.PrivateKey, string(decodedBytes))
if err != nil {
return "", err
}
return decrypt, nil
}
func SM2Encrypt(cipherText string) (string, error) {
if cipherText == "" {
return "", nil
}
decrypt, err := encryptLoc(config.GetConf().Sm2.PublicKey, cipherText)
if err != nil {
return "", err
}
return decrypt, nil
}
func encryptLoc(publicKeyStr, data string) (string, error) {
publicKeyObj, err := StringToPublicKey(publicKeyStr)
if err != nil {
fmt.Println(err)
}
decrypt, err := sm2.Encrypt(publicKeyObj, []byte(data), rand.Reader, sm2.C1C2C3)
if err != nil {
fmt.Println(err)
}
resultStr := hex.EncodeToString(decrypt)
return base64.StdEncoding.EncodeToString([]byte(resultStr)), nil
}
func decryptLoc(publicKeyStr, privateKeyStr, cipherText string) (string, error) {
publicKeyObj, err := StringToPublicKey(publicKeyStr)
if err != nil {
fmt.Println(err)
}
privateKeyObj, err := StringToPrivateKey(privateKeyStr, publicKeyObj)
if err != nil {
fmt.Println(err)
}
decodeString, err := hex.DecodeString(cipherText)
decrypt, err := sm2.Decrypt(privateKeyObj, decodeString, sm2.C1C2C3)
if err != nil {
fmt.Println(err)
}
resultStr := string(decrypt)
fmt.Println("解密后的字符串:", resultStr)
return resultStr, nil
}
// StringToPrivateKey 私钥还原为 sm2.PrivateKey对象(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func StringToPrivateKey(privateKeyStr string, publicKey *sm2.PublicKey) (*sm2.PrivateKey, error) {
privateKeyBytes, err := hex.DecodeString(privateKeyStr)
if err != nil {
return nil, err
}
// 将字节切片转换为大整数
d := new(big.Int).SetBytes(privateKeyBytes)
// 创建 sm2.PrivateKey 对象
privateKey := &sm2.PrivateKey{
PublicKey: *publicKey,
D: d,
}
return privateKey, nil
}
// StringToPublicKey 公钥字符串还原为 sm2.PublicKey 对象(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func StringToPublicKey(publicKeyStr string) (*sm2.PublicKey, error) {
publicKeyBytes, err := hex.DecodeString(publicKeyStr)
if err != nil {
return nil, err
}
// 提取 x 和 y 坐标字节切片
curve := sm2.P256Sm2().Params()
byteLen := (curve.BitSize + 7) / 8
xBytes := publicKeyBytes[1 : byteLen+1]
yBytes := publicKeyBytes[byteLen+1 : 2*byteLen+1]
// 将字节切片转换为大整数
x := new(big.Int).SetBytes(xBytes)
y := new(big.Int).SetBytes(yBytes)
// 创建 sm2.PublicKey 对象
publicKey := &sm2.PublicKey{
Curve: curve,
X: x,
Y: y,
}
return publicKey, nil
}
// 验证签名
func VerSm2Sig(pub *sm2.PublicKey, msg []byte, sign []byte) bool {
isok := pub.Verify(msg, sign)
return isok
}

View File

@ -0,0 +1,31 @@
package services
import (
"qteam/app/constants/common"
"qteam/app/constants/errorcode"
"qteam/app/http/entities/front"
"qteam/app/models/usersmodel"
"qteam/app/third/youchu"
"qteam/app/utils"
"qteam/config"
)
func YouChuLogin(req *front.UnionLoginRequest) (code int, login front.LoginResponse) {
client := youchu.NewYouChuClient(config.GetConf().YouChu)
YouChuResponse, err := client.Login(req.Code)
if err != nil {
return
}
if YouChuResponse.RespCode == "000000" {
// 获取用户信息
var user usersmodel.Users
err := usersmodel.GetInstance().GetDb().Where("status = ?", common.STATUSABLED).Where("phone = ?", YouChuResponse.Phone).Find(&user)
code = handErr(err)
login.Token = utils.GeneratorJwtToken(utils.User{
Id: user.Id,
Phone: user.Phone,
})
return errorcode.Success, login
}
return errorcode.NotFound, login
}

View File

@ -2,20 +2,20 @@ package services
import (
"github.com/ahmetb/go-linq/v3"
"qteam/app/http/entities"
"qteam/app/http/entities/front"
"qteam/app/models/brandmodel"
"qteam/app/models/productsmodel"
"qteam/config"
)
func VoucherList() (code int, VoucherListResponse entities.VoucherListResponse) {
func VoucherList() (code int, VoucherListResponse front.VoucherListResponse) {
VoucherListResponse.MilkUrl = config.GetConf().YouChu.MilkUrl
var BrandData []brandmodel.Brand
var VoucherData []productsmodel.MilkProductsList
code, BrandData = MilkBrandList()
if BrandData != nil {
var MilkList []entities.MilkList
linq.From(BrandData).SelectT(func(in brandmodel.Brand) (d entities.MilkList) {
var MilkList []front.MilkList
linq.From(BrandData).SelectT(func(in brandmodel.Brand) (d front.MilkList) {
d.ResponseFromDb(in)
return
}).ToSlice(&MilkList)
@ -23,8 +23,8 @@ func VoucherList() (code int, VoucherListResponse entities.VoucherListResponse)
}
code, VoucherData = MilkProductList()
if VoucherData != nil {
var MilkVoucherList []entities.MilkVoucherList
linq.From(VoucherData).SelectT(func(in productsmodel.MilkProductsList) (d entities.MilkVoucherList) {
var MilkVoucherList []front.MilkVoucherList
linq.From(VoucherData).SelectT(func(in productsmodel.MilkProductsList) (d front.MilkVoucherList) {
d.ResponseFromDb(in)
return
}).ToSlice(&MilkVoucherList)

View File

@ -0,0 +1,36 @@
package services
import (
"encoding/json"
"fmt"
"qteam/app/http/entities/front"
"qteam/app/models/ordersmodel"
"qteam/app/third/youchu"
"qteam/config"
)
func DecryptYouChuData(data string) (info front.YouChuDecryptData) {
// 先解Base64,再解Hex16进
decrypt, err := SM2Decrypt(data)
if err != nil {
return
}
err = json.Unmarshal([]byte(decrypt), &info)
if err != nil {
fmt.Println("解析JSON错误:", err)
return
}
return
}
func YouChuOrderQuery(order ordersmodel.Orders) (code int, response front.YouChuOrderQueryResponse) {
client := youchu.NewYouChuClient(config.GetConf().YouChu)
code, response = client.OrderQuery(order)
return code, response
}
func YouChuOrderRefund(order ordersmodel.Orders) (code int, response front.YouChuOrderRefundResponse) {
client := youchu.NewYouChuClient(config.GetConf().YouChu)
code, response = client.OrderRefund(order)
return code, response
}

View File

@ -0,0 +1,86 @@
package youchu
import (
"bytes"
"encoding/json"
"github.com/pkg/errors"
"io/ioutil"
"net/http"
"qteam/config"
"time"
)
type YouChuClient struct {
cfg config.YouChuConfig
}
type YouChuSendRequest struct {
CustNo string `json:"custNo"`
ReqTransTime string `json:"reqTransTime"`
Code string `json:"code"`
}
type YouChuResponse struct {
RespCode string `json:"respCode"` //000000-成功 069801-系统繁忙,请稍后重试 820007-未查询到客户号
RespMsg string `json:"respMsg"` //描 述 (失败时必填)
Phone string `json:"phone"`
IdtLastFour string `json:"idtLastFour"`
CustName string `json:"custName"`
CustFlag string `json:"custFlag"`
}
func (this *YouChuSendRequest) toMap() (resultMap map[string]interface{}) {
// Marshal the struct to JSON, ignoring omitempty fields.
jsonBytes, err := json.Marshal(this)
if err != nil {
return
}
// Unmarshal the JSON into a map to get the final result.
err = json.Unmarshal(jsonBytes, &resultMap)
if err != nil {
return
}
return resultMap
}
func (this *YouChuClient) doPost(url string, partnerTxSriNo string, method string, jsonBytes []byte) (body []byte, err error) {
// 创建POST请求
url = this.cfg.Host + url
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBytes))
if err != nil {
return
}
// 设置Content-Type头
//req.Header.Set("Content-Type", "application/json")
req.Header.Set("partnerTxSriNo", partnerTxSriNo)
req.Header.Set("method", method)
req.Header.Set("version", "1")
req.Header.Set("appID", this.cfg.AppID)
req.Header.Set("merchantId", this.cfg.MerchantId)
req.Header.Set("reqTime", time.Now().Format("20060102150405"))
req.Header.Set("accessType", "API")
// 创建HTTP客户端
client := &http.Client{
Timeout: 3 * time.Second, // 设置请求超时时间为5秒
}
// 发送请求并处理响应
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
// 读取响应体
if resp.StatusCode != http.StatusOK {
err = errors.New("HTTP request failed: " + resp.Status)
return
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return
}

View File

@ -0,0 +1,145 @@
package youchu
import (
"encoding/json"
"fmt"
mrand "math/rand"
"qteam/app/constants/errorcode"
"qteam/app/http/entities/front"
"qteam/app/models/ordersmodel"
"qteam/app/utils"
"qteam/app/utils/postbank"
"qteam/config"
"time"
)
func NewYouChuClient(cfg config.YouChuConfig) *YouChuClient {
return &YouChuClient{
cfg: cfg,
}
}
func (this *YouChuClient) Login(code string) (res YouChuResponse, err error) {
partnerTxSriNo := time.Now().Format("20060102150405") + fmt.Sprintf("%10d", mrand.Intn(10000))
url := this.cfg.MerchantId + ".html?partnerTxSriNo=" + partnerTxSriNo
request := YouChuSendRequest{
CustNo: this.cfg.MerchantId,
ReqTransTime: time.Now().Format("20060102150405"),
Code: code,
}
bytes, err := json.Marshal(request)
if err != nil {
return res, err
}
data, err := this.doPost(url, partnerTxSriNo, "crecard.getCustomerInfo", bytes)
if err != nil {
return res, err
}
err = json.Unmarshal(data, &res)
return
}
func (this *YouChuClient) OrderQuery(order ordersmodel.Orders) (code int, response front.YouChuOrderQueryResponse) {
var BusiMainId = time.Now().Format("20060102150405") + utils.RandomString(10)
request := front.YouChuRequestBody{
Body: front.YouChuOrderRequest{
BusiMainId: BusiMainId,
ReqTransTime: order.CreateTime.Format("2006-01-02 15:04:05"),
Data: front.OrderQuery{
TxnCode: "1004",
SourceId: "16",
ReqTraceId: order.OrderNo,
ReqDate: time.Now().Format("20060102150405"),
TxnDt: time.Now().Format("20060102"),
MchtNo: config.GetConf().YouChu.MchtNo,
OldSeqNo: utils.GenerateRequestNumber(),
},
},
Head: front.YouChuRequestHeader{
PartnerTxSriNo: BusiMainId,
AccessType: "API",
Reserve: "",
Method: "b2c.gatewaypay.orderQuery",
Version: "1",
MerchantId: config.GetConf().YouChu.MerchantId,
AppID: config.GetConf().YouChu.AppID,
},
}
url := config.GetConf().YouChu.MerchantId + ".html?partnerTxSriNo=" + request.Body.BusiMainId
requestData := EncryptRequest(request)
bytes, err := json.Marshal(requestData)
if err != nil {
return errorcode.SystemError, response
}
post, err := this.doPost(url, request.Body.BusiMainId, "b2c.gatewaypay.orderQuery", bytes)
if err != nil {
return errorcode.SystemError, response
}
err = json.Unmarshal(post, &response)
if err != nil {
return errorcode.SystemError, front.YouChuOrderQueryResponse{}
}
return errorcode.Success, response
}
func (this *YouChuClient) OrderRefund(order ordersmodel.Orders) (code int, response front.YouChuOrderRefundResponse) {
var BusiMainId = time.Now().Format("20060102150405") + utils.RandomString(10)
request := front.YouChuOrderRefundRequest{
Head: front.YouChuRequestHeader{
PartnerTxSriNo: BusiMainId,
AccessType: "API",
Reserve: "",
Method: "b2c.gatewaypay.orderRefund",
Version: "1",
MerchantId: config.GetConf().YouChu.MerchantId,
AppID: config.GetConf().YouChu.AppID,
},
Body: front.RefundRequestBody{
BusiMainId: BusiMainId,
ReqTransTime: order.CreateTime.Format("2006-01-02 15:04:05"),
Data: front.RefundRequestData{
TxnCode: "1003",
SourceId: "16",
ReqTraceId: order.OrderNo,
ReqDate: time.Now().Format("20060102150405"),
MchtNo: config.GetConf().YouChu.MchtNo,
OrgTxnSeq: order.OrgTxnSeq,
TxnAmt: order.Price,
OrgTxnAmt: order.Price,
RefundDesc: "用户主动退款",
},
},
}
url := config.GetConf().YouChu.MerchantId + ".html?partnerTxSriNo=" + request.Body.BusiMainId
requestData := EncryptRequest(request)
bytes, err := json.Marshal(requestData)
if err != nil {
return errorcode.SystemError, response
}
post, err := this.doPost(url, request.Body.BusiMainId, "b2c.gatewaypay.orderRefund", bytes)
utils.Log(nil, "b2c.orderRefund", post)
if err != nil {
return errorcode.YouChuOrderRefundFail, response
}
err = json.Unmarshal(post, &response)
if err != nil {
return errorcode.SystemError, front.YouChuOrderRefundResponse{}
}
return errorcode.Success, response
}
func EncryptRequest(request interface{}) (RequestData front.YouChuRequest) {
input, _ := json.Marshal(request)
MerchantId := config.GetConf().YouChu.MerchantId
PrivateKey := config.GetConf().Sm2.PrivateKey
PublicKey := config.GetConf().Sm2.PublicKey
encrypt, err := postbank.Encrypt(MerchantId, PrivateKey, PublicKey, string(input), "", true)
if err != nil {
return front.YouChuRequest{}
}
err = json.Unmarshal([]byte(encrypt), &RequestData)
if err != nil {
return front.YouChuRequest{}
}
return RequestData
}

34
app/utils/Sm4/sm4.go Normal file
View File

@ -0,0 +1,34 @@
package Sm4
import (
"crypto/cipher"
sm4 "github.com/tjfoc/gmsm/sm4"
)
func EncryptSM4(plainText string, key string) ([]byte, error) {
block, err := sm4.NewCipher([]byte(key))
if err != nil {
return nil, err
}
ciphertext := make([]byte, len(plainText))
iv := []byte("UISwD9fW6cFh9SNS") // 使用16字节的初始化向量
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, []byte(plainText))
return ciphertext, nil
}
func DecryptSM4(ciphertext string, key string) ([]byte, error) {
block, err := sm4.NewCipher([]byte(key))
if err != nil {
return nil, err
}
plaintext := make([]byte, len(ciphertext))
iv := []byte("UISwD9fW6cFh9SNS") // 使用与加密相同的初始化向量
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(plaintext, []byte(ciphertext))
return plaintext, nil
}

View File

@ -1,6 +1,10 @@
package encrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"math/rand"
"unsafe"
)
@ -35,3 +39,41 @@ func LotteryEncryptDecode(str string) bool {
}
return true
}
// =================== CBC ======================
func AesEncryptCBC(origData []byte, key []byte) (str string) {
// 分组秘钥
// NewCipher该函数限制了输入k的长度必须为16, 24或者32
block, _ := aes.NewCipher(key)
blockSize := block.BlockSize() // 获取秘钥块的长度
origData = pkcs5Padding(origData, blockSize) // 补全码
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) // 加密模式
encrypted := make([]byte, len(origData)) // 创建数组
blockMode.CryptBlocks(encrypted, origData) // 加密
return base64.StdEncoding.EncodeToString(encrypted)
}
func AesDecryptCBC(data string, key []byte) (decrypted []byte) {
encrypted, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return
}
block, _ := aes.NewCipher(key) // 分组秘钥
blockSize := block.BlockSize() // 获取秘钥块的长度
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // 加密模式
decrypted = make([]byte, len(encrypted)) // 创建数组
blockMode.CryptBlocks(decrypted, encrypted) // 解密
decrypted = pkcs5UnPadding(decrypted) // 去除补全码
return decrypted
}
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func pkcs5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

81
app/utils/postbank/cmd.go Normal file
View File

@ -0,0 +1,81 @@
package postbank
import (
"context"
"fmt"
"github.com/spf13/cobra"
)
// Cmd go run main.go postbank
var Cmd = &cobra.Command{
Use: "postbank",
Short: "邮储国密SM辅助工具",
}
// merchantId, privateKey, sopPublicKey, inputJson, true
const (
merchantIdKey = "merchantId"
privateKeyKey = "priKey"
sopPublicKeyKey = "sopPublicKey"
inputKey = "input"
isRequestKey = "isRequest"
accessTokenKey = "accessToken"
shortMerchantIdKey = "m"
shortPrivateKeyKey = "p"
shortSopPublicKeyKey = "s"
shortInputKey = "i"
shortIsRequestKey = "r"
shortAccessTokenKey = "a"
merchantIdContextKey = "merchantId"
privateKeyContextKey = "priKey"
sopPublicKeyContextKey = "sopPublicKey"
inputContextKey = "input"
isRequestContextKey = "isRequest"
accessTokenContextKey = "accessToken"
)
func init() {
encryptCmd.PersistentFlags().StringP(merchantIdKey, shortMerchantIdKey, "", "商户号")
encryptCmd.PersistentFlags().StringP(privateKeyKey, shortPrivateKeyKey, "", "私钥")
encryptCmd.PersistentFlags().StringP(sopPublicKeyKey, shortSopPublicKeyKey, "", "对方公钥")
encryptCmd.PersistentFlags().StringP(inputKey, shortInputKey, "", "待加密的JSON字符串")
encryptCmd.PersistentFlags().BoolP(isRequestKey, shortIsRequestKey, false, "请求加密")
encryptCmd.PersistentFlags().StringP(accessTokenKey, shortAccessTokenKey, "", "accessToken")
decryptCmd.PersistentFlags().StringP(merchantIdKey, shortMerchantIdKey, "", "商户号")
decryptCmd.PersistentFlags().StringP(privateKeyKey, shortPrivateKeyKey, "", "私钥")
decryptCmd.PersistentFlags().StringP(sopPublicKeyKey, shortSopPublicKeyKey, "", "对方公钥")
decryptCmd.PersistentFlags().StringP(inputKey, shortInputKey, "", "待解密的JSON字符串")
decryptCmd.PersistentFlags().BoolP(isRequestKey, shortIsRequestKey, false, "请求加密")
Cmd.AddCommand(encryptCmd, decryptCmd, generateCmd)
}
func forceArgsToStringContext(cmd *cobra.Command, key, ctxKey string) error {
data, err := cmd.Flags().GetString(key)
if err != nil {
return fmt.Errorf("缺少参数:<%s>%w", key, err)
}
cmd.SetContext(context.WithValue(cmd.Context(), ctxKey, data))
return nil
}
func argsToStringContext(cmd *cobra.Command, key, ctxKey string) error {
data, err := cmd.Flags().GetString(key)
if err != nil {
data = ""
}
cmd.SetContext(context.WithValue(cmd.Context(), ctxKey, data))
return nil
}
func forceArgsToBoolContext(cmd *cobra.Command, key, ctxKey string) error {
data, err := cmd.Flags().GetBool(key)
if err != nil {
return fmt.Errorf("缺少参数:<%s>%w", key, err)
}
cmd.SetContext(context.WithValue(cmd.Context(), ctxKey, data))
return nil
}

View File

@ -0,0 +1,50 @@
package cmd
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"qteam/app/utils/postbank/smtypes"
)
func DeCrypt(merchantId string, priKey string, sopPublicKey string, input *smtypes.RequestData) ([]byte, error) {
// 解密逻辑
requestJson, err := json.Marshal(input)
if err != nil {
return nil, fmt.Errorf("请求体json编码失败%s", err)
}
path, err := smv2()
if err != nil {
return nil, fmt.Errorf("解码获取当前目录失败:%s", err)
}
cmd := fmt.Sprintf("%s postbank decrypt --merchantId=\"%s\" --priKey=\"%s\" --sopPublicKey=\"%s\" --input=\"%s\" %s 2>&1",
path, merchantId, priKey, sopPublicKey, base64.StdEncoding.EncodeToString(requestJson), "--isRequest")
// 返回解密后的数据
return toExec(cmd), nil
}
func smv2() (string, error) {
path, err := os.Getwd()
if err != nil {
return "", err
}
return fmt.Sprintf("%s/%s/smv2", filepath.Dir(filepath.Dir(path)), "untils/sm"), nil
}
func toExec(cmdString string) []byte {
var stdoutBuf, stderrBuf bytes.Buffer
cmd := exec.Command(cmdString)
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
err := cmd.Run()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
fmt.Printf("stdout: %s\nstderr: %s\n", stdoutBuf.String(), stderrBuf.String())
return stdoutBuf.Bytes()
}

BIN
app/utils/postbank/cmd/sm Normal file

Binary file not shown.

View File

@ -0,0 +1,62 @@
package cmd
import (
"fmt"
"qteam/app/utils/postbank"
"testing"
)
const (
merchantId = "lansexiongdi666"
// 模块名称 服开门户网站-文档中心-API文档-接口方法业务前缀
// 此处默认为普通接口取数币服务模块名称 ecny 文件接口这里传ufile
moduleName = "ecny"
// appID 服开门户网站-用户中心-我的应用-APP_ID
appID = "961925472724332544001"
// url 协议 + 定版ip端口号 + 接口方法业务前缀 + 合作方编号 + 接口地址后缀 + 合作方交易流水号(工具类生成)
url = "http://220.248.252.123:8443/sop-h5/biz/${moduleName}/${merchantId}.htm?partnerTxSriNo=${partnerTxSriNo}"
// h5token h5申请token的url 协议 + 定版ip端口号 + 接口地址业务前缀 + 合作方编号 + 合作方交易流水号(工具类生成)
h5token = "http://220.248.252.123:8443/sop-h5/h5/authToken/apply/${merchantId}?partnerTxSriNo=${partnerTxSriNo}"
// 合作方私钥
privateKey = "7FE05FEB81673ABF0AC4F9EEB99FB03BFE7BE432CA474CB9AAD8E302CAF00766"
// 合作方公钥
publicKey = "0432F7E91033AC40472885B11EB673B1DE942FD555E95CD12E799CC50DD9E93B04A6AC254D725C22DCBD65436D97A46727B09FFFA1B1098EEA8D43F79AAB84F5B1"
// 服开与合作方配对公钥 服开门户网站-用户中心-公钥配置-平台生产公钥
sopPublicKey = "041F63D23554FC1F5A36D43ABD0DE368A202849BCC03BEB34E7EF4F8C478E12F5529DB649B7C2C2DB5F55B4DC89A6EC83A8226218B7D931638DBFC4B344C628C33"
// sopPrivateKey 服开与合作方配对私钥 服开门户网站-用户中心-私钥配置-平台生产私钥
sopPrivateKey = "174AB6AB44FF23A6B65934B54E9D94B00A81D0D50D57DB0C38257E2AFB2D6C35"
// 文件上传接口
// fileUrl = http://220.248.252.123:8443/sop-h5/biz/ufile/upload/接口名/接口版本/合作方编号/appID/流水号
fileUrl = "http://220.248.252.123:8443/sop-h5/biz/ufile/upload/${method}/${version}/${merchantId}/${appID}/${partnerTxSriNo}"
// 解码格式 如果文件下载时解析出来的报文乱码,请改变解码格式
charsetName = "gbk"
m_id = "lansexiongdi666"
pri = "7FE05FEB81673ABF0AC4F9EEB99FB03BFE7BE432CA474CB9AAD8E302CAF00766"
msoapub = "04A3C235C15070E127679628FE024E6C77FFCB1840B4EFBE09D19CBE0EE69BEA231912943984D34C03989771E503DFBB4ECB186F4BBC27764109E63C2875005C81"
soapb = "0432F7E91033AC40472885B11EB673B1DE942FD555E95CD12E799CC50DD9E93B04A6AC254D725C22DCBD65436D97A46727B09FFFA1B1098EEA8D43F79AAB84F5B1"
)
// TestSm4Decrypt test sm4 decrypt
func TestSm4Decrypt(t *testing.T) {
//respJson := "{\n\t\"accessToken\": \"C0A3853384EA0443C2942FF1A1B0757D\",\n\t\"encryptKey\": \"04F15C783C14DBD91DE13696BFCDAA4523F2D6CABB610A8BA809D7224234E53422EA4143ED6C3BD92CB9745A0330142E94C7F6EB36B5601A133F9E7E8C42E18C693BC975AD245EF14FDA9734523F407B7561CC390883D8D45C35DD8E3575970BFD75554EB9119F4A18B65EC5F3AB37D866\",\n\t\"request\": \"JBKuG9pmsYwLgFPJL5nFci9jEgX9M60ebFK8lNteJqJZgZZEF7Sg7+qabTq7AOhHN/u9a+O4Zv8e\\r\\nIncXNC9sFPKOrqRJaMmAZTrDCwD3VhDt/maMn9lQbdkXaqMwo4S1iYiqoni5riS/8ivbFqz4sQvv\\r\\nPoiyIaQCMkqAAEkFxL6aSlPGY7f7kdLHK6cpixT3h2H1coMPuCQrG1J7jnbe+IlPJiVOKn5Hchus\\r\\n4TnHAuH/y9/8W9sW2tILApqm0MUpNxkor3qkAEqkbhciuJJDp/lDqebcJeRt2UET6UtTFTECYEWR\\r\\nNPfQCHKQxMWWserd/wCcWMx8ePPK/UKqUW8Rpg6hncKcC8b0U5i0z3MFuKJscz/DAS/X6Vex9kUp\\r\\njvy5c+ahm/rx0y5pTkNKP9KzVTFE6SffyA3E5NR+6fIQ9VvOU1A/YPiBhCvLF/dCodUk\",\n\t\"signature\": \"60bd9f1bb4a234acf4c8789f048151cbd688538b8f02d2cf87cdd74309c346e6#6aaaad738d9c10b3aaf85c86b5134d95b4d815dc017b43bc9e5e012243eb26f6\"\n}" // 解析接到的请求
respJson := EN()
reqMsg, err := postbank.Decrypt(merchantId, privateKey, sopPublicKey, respJson, true)
fmt.Println(reqMsg, err)
}
func TestSm4Encrypt(t *testing.T) {
input := "{\"body\":{\"voucher_id\":\"1708321361154\",\"voucher_code\":\"AAABt6wYMDBjNf25\",\"short_url\":\"http://test.22233.cn/AAABt6wYMDBjNf25\",\"voucher_sdate\":\"20240219\",\"voucher_edate\":\"20240331\",\"code_type\":\"00\"},\"header\":{\"partnerTxSriNo\":\"\",\"method\":\"\",\"version\":\"1\",\"merchantId\":\"YANWEI001\",\"appID\":\"YANWEI00001\",\"respTime\":\"20240301141359\"}}"
res, err := postbank.Encrypt(merchantId, privateKey, sopPublicKey, input, "", true)
fmt.Println(res, err)
}
func EN() string {
input := "{\"head\":\"{\"appID\":\"PSBC\",\"merchantId\":\"lansexiongdi666\",\"method\":\"points.elecCoupRece\",\"partnerTxSriNo\":\"202406131805225935940667\",\"reqTime\":\"20240613180522\",\"version\":\"1\"}\",\"body\":\"{\"acName\":\"2024年二季度新客开卡礼营销活动\",\"orderNo\":\"20240613001534531\",\"merNo\":\"1000000004\",\"acCode\":\"202403HK110052930016\",\"num\":\"1\",\"svcNo\":\"YCTZBZS0004\",\"transSeq\":\"99360000007202406131805227108867\"}\"}"
res, _ := postbank.Encrypt(merchantId, sopPrivateKey, publicKey, input, "", true)
return res
}
func TestGenerateKey(t *testing.T) {
hexPri, publicKeyHex := postbank.GenerateKey()
fmt.Println(hexPri, publicKeyHex)
}

BIN
app/utils/postbank/cmd/smv2 Normal file

Binary file not shown.

View File

@ -0,0 +1,54 @@
package postbank
import (
"encoding/base64"
"fmt"
"github.com/spf13/cobra"
)
// go run main.go postbank decrypt --key="sm4 key"
var decryptCmd = &cobra.Command{
Use: "decrypt",
Short: "邮储国密解密",
Args: func(cmd *cobra.Command, args []string) error {
var err error
if err = forceArgsToStringContext(cmd, merchantIdKey, merchantIdContextKey); err != nil {
return err
}
if err = forceArgsToStringContext(cmd, privateKeyKey, privateKeyContextKey); err != nil {
return err
}
if err = forceArgsToStringContext(cmd, sopPublicKeyKey, sopPublicKeyContextKey); err != nil {
return err
}
if err = forceArgsToStringContext(cmd, inputKey, inputContextKey); err != nil {
return err
}
if err = forceArgsToBoolContext(cmd, isRequestKey, isRequestContextKey); err != nil {
return err
}
return nil
},
Run: decryptRun,
}
func decryptRun(cmd *cobra.Command, args []string) {
merchantId := cmd.Context().Value(merchantIdContextKey).(string)
privateKey := cmd.Context().Value(privateKeyContextKey).(string)
sopPublicKey := cmd.Context().Value(sopPublicKeyContextKey).(string)
inputJson := cmd.Context().Value(inputContextKey).(string)
isRequest := cmd.Context().Value(isRequestContextKey).(bool)
res, errStr := decrypt(merchantId, privateKey, sopPublicKey, inputJson, isRequest)
cmd.Println(res)
cmd.Print(errStr)
}
func decrypt(merchantId, privateKey, sopPublicKey, inputJson string, isRequest bool) (string, string) {
data, _ := base64.StdEncoding.DecodeString(inputJson)
resp, err := Decrypt(merchantId, privateKey, sopPublicKey, string(data), isRequest)
if err != nil {
return "fail", fmt.Sprintf("解密失败: %v", err)
}
return resp, "success"
}

View File

@ -0,0 +1,62 @@
package postbank
import (
"encoding/base64"
"fmt"
"github.com/spf13/cobra"
)
// go run main.go postbank encrypt --key="sm4 key"
var encryptCmd = &cobra.Command{
Use: "encrypt",
Short: "邮储国密加密",
Args: func(cmd *cobra.Command, args []string) error {
var err error
if err = forceArgsToStringContext(cmd, merchantIdKey, merchantIdContextKey); err != nil {
return err
}
if err = forceArgsToStringContext(cmd, privateKeyKey, privateKeyContextKey); err != nil {
return err
}
if err = forceArgsToStringContext(cmd, sopPublicKeyKey, sopPublicKeyContextKey); err != nil {
return err
}
if err = forceArgsToStringContext(cmd, inputKey, inputContextKey); err != nil {
return err
}
if err = forceArgsToBoolContext(cmd, isRequestKey, isRequestContextKey); err != nil {
return err
}
// 不强制
if err = argsToStringContext(cmd, accessTokenKey, accessTokenContextKey); err != nil {
return err
}
return nil
},
Run: encryptRun,
}
func encryptRun(cmd *cobra.Command, args []string) {
merchantId := cmd.Context().Value(merchantIdContextKey).(string)
privateKey := cmd.Context().Value(privateKeyContextKey).(string)
sopPublicKey := cmd.Context().Value(sopPublicKeyContextKey).(string)
inputJson := cmd.Context().Value(inputContextKey).(string)
isRequest := cmd.Context().Value(isRequestContextKey).(bool)
accessToken := cmd.Context().Value(accessTokenContextKey).(string)
res, errStr := encrypt(merchantId, privateKey, sopPublicKey, inputJson, accessToken, isRequest)
cmd.Println(res)
cmd.Print(errStr)
}
func encrypt(merchantId, privateKey, sopPublicKey, inputJson, accessToken string, isRequest bool) (string, string) {
data, _ := base64.StdEncoding.DecodeString(inputJson)
resp, err := Encrypt(merchantId, privateKey, sopPublicKey, string(data), accessToken, isRequest)
if err != nil {
return "fail", fmt.Sprintf("加密失败: %v", err)
}
return resp, "success"
}

View File

@ -0,0 +1,19 @@
package postbank
import (
"github.com/spf13/cobra"
)
// GenerateKey go run main.go postbank generateKey
var generateCmd = &cobra.Command{
Use: "generateKey",
Short: "邮储国密sm2公私钥生成",
Args: nil,
Run: generateRun,
}
func generateRun(cmd *cobra.Command, args []string) {
pri, pub := GenerateKey()
cmd.Println(pri)
cmd.Print(pub)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
* 来源于 github.com/tjfoc/gmsm
* 修改适配 邮储对接

View File

@ -0,0 +1,219 @@
package sm2
// reference to ecdsa
import (
"crypto"
"crypto/elliptic"
"crypto/rand"
"encoding/asn1"
"errors"
"github.com/tjfoc/gmsm/sm3"
"io"
"math/big"
)
var (
default_uid = []byte{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}
)
type PublicKey struct {
elliptic.Curve
X, Y *big.Int
}
type PrivateKey struct {
PublicKey
D *big.Int
}
type sm2Signature struct {
R, S *big.Int
}
func (priv *PrivateKey) Public() crypto.PublicKey {
return &priv.PublicKey
}
var errZeroParam = errors.New("zero parameter")
var one = new(big.Int).SetInt64(1)
var two = new(big.Int).SetInt64(2)
func (priv *PrivateKey) Sign(random io.Reader, msg []byte, signer crypto.SignerOpts) ([]byte, error) {
r, s, err := Sm2Sign(priv, msg, nil, random)
if err != nil {
return nil, err
}
return asn1.Marshal(sm2Signature{r, s})
}
func Sm2Sign(priv *PrivateKey, msg, uid []byte, random io.Reader) (r, s *big.Int, err error) {
digest, err := priv.PublicKey.Sm3Digest(msg, uid)
if err != nil {
return nil, nil, err
}
e := new(big.Int).SetBytes(digest)
c := priv.PublicKey.Curve
N := c.Params().N
if N.Sign() == 0 {
return nil, nil, errZeroParam
}
var k *big.Int
for { // 调整算法细节以实现SM2
for {
k, err = randFieldElement(c, random)
if err != nil {
r = nil
return
}
r, _ = priv.Curve.ScalarBaseMult(k.Bytes())
r.Add(r, e)
r.Mod(r, N)
if r.Sign() != 0 {
if t := new(big.Int).Add(r, k); t.Cmp(N) != 0 {
break
}
}
}
rD := new(big.Int).Mul(priv.D, r)
s = new(big.Int).Sub(k, rD)
d1 := new(big.Int).Add(priv.D, one)
d1Inv := new(big.Int).ModInverse(d1, N)
s.Mul(s, d1Inv)
s.Mod(s, N)
if s.Sign() != 0 {
break
}
}
return
}
func (pub *PublicKey) Sm3Digest(msg, uid []byte) ([]byte, error) {
if len(uid) == 0 {
uid = default_uid
}
za, err := getZ(pub, uid)
if err != nil {
return nil, err
}
e, err := msgHash(za, msg)
if err != nil {
return nil, err
}
return e.Bytes(), nil
}
func Sm2Verify(pub *PublicKey, msg, uid []byte, r, s *big.Int) bool {
c := pub.Curve
N := c.Params().N
one := new(big.Int).SetInt64(1)
if r.Cmp(one) < 0 || s.Cmp(one) < 0 {
return false
}
if r.Cmp(N) >= 0 || s.Cmp(N) >= 0 {
return false
}
if len(uid) == 0 {
uid = default_uid
}
za, err := getZ(pub, uid)
if err != nil {
return false
}
e, err := msgHash(za, msg)
if err != nil {
return false
}
t := new(big.Int).Add(r, s)
t.Mod(t, N)
if t.Sign() == 0 {
return false
}
var x *big.Int
x1, y1 := c.ScalarBaseMult(s.Bytes())
x2, y2 := c.ScalarMult(pub.X, pub.Y, t.Bytes())
x, _ = c.Add(x1, y1, x2, y2)
x.Add(x, e)
x.Mod(x, N)
return x.Cmp(r) == 0
}
func msgHash(za, msg []byte) (*big.Int, error) {
e := sm3.New()
e.Write(za)
e.Write(msg)
return new(big.Int).SetBytes(e.Sum(nil)[:32]), nil
}
func bigIntToByte(n *big.Int) []byte {
byteArray := n.Bytes()
// If the most significant byte's most significant bit is set,
// prepend a 0 byte to the slice to avoid being interpreted as a negative number.
if (byteArray[0] & 0x80) != 0 {
byteArray = append([]byte{0}, byteArray...)
}
return byteArray
}
func getZ(pub *PublicKey, uid []byte) ([]byte, error) {
z := sm3.New()
uidLen := len(uid) * 8
entla := []byte{byte(uidLen >> 8), byte(uidLen & 255)}
z.Write(entla)
z.Write(uid)
// a 先写死,原来的没有暴露
z.Write(bigIntToByte(sm2P256ToBig(&sm2P256.a)))
z.Write(bigIntToByte(sm2P256.B))
z.Write(bigIntToByte(sm2P256.Gx))
z.Write(bigIntToByte(sm2P256.Gy))
z.Write(bigIntToByte(pub.X))
z.Write(bigIntToByte(pub.Y))
return z.Sum(nil), nil
}
func randFieldElement(c elliptic.Curve, random io.Reader) (k *big.Int, err error) {
if random == nil {
random = rand.Reader //If there is no external trusted random source,please use rand.Reader to instead of it.
}
params := c.Params()
b := make([]byte, params.BitSize/8+8)
_, err = io.ReadFull(random, b)
if err != nil {
return
}
k = new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, one)
k.Mod(k, n)
k.Add(k, one)
return
}
func GenerateKey(random io.Reader) (*PrivateKey, error) {
c := P256Sm2()
if random == nil {
random = rand.Reader //If there is no external trusted random source,please use rand.Reader to instead of it.
}
params := c.Params()
b := make([]byte, params.BitSize/8+8)
_, err := io.ReadFull(random, b)
if err != nil {
return nil, err
}
k := new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, two)
k.Mod(k, n)
k.Add(k, one)
priv := new(PrivateKey)
priv.PublicKey.Curve = c
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
return priv, nil
}

View File

@ -0,0 +1,45 @@
package sm2
import (
"encoding/hex"
"errors"
"math/big"
)
func ReadPrivateKeyFromHex(Dhex string) (*PrivateKey, error) {
c := P256Sm2()
d, err := hex.DecodeString(Dhex)
if err != nil {
return nil, err
}
k := new(big.Int).SetBytes(d)
params := c.Params()
one := new(big.Int).SetInt64(1)
n := new(big.Int).Sub(params.N, one)
if k.Cmp(n) >= 0 {
return nil, errors.New("privateKey's D is overflow.")
}
priv := new(PrivateKey)
priv.PublicKey.Curve = c
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
return priv, nil
}
func ReadPublicKeyFromHex(Qhex string) (*PublicKey, error) {
q, err := hex.DecodeString(Qhex)
if err != nil {
return nil, err
}
if len(q) == 65 && q[0] == byte(0x04) {
q = q[1:]
}
if len(q) != 64 {
return nil, errors.New("publicKey is not uncompressed.")
}
pub := new(PublicKey)
pub.Curve = P256Sm2()
pub.X = new(big.Int).SetBytes(q[:32])
pub.Y = new(big.Int).SetBytes(q[32:])
return pub, nil
}

View File

@ -0,0 +1,163 @@
package util
import (
"bytes"
"crypto/elliptic"
"crypto/rand"
"errors"
zzsm2 "github.com/ZZMarquis/gm/sm2"
"github.com/tjfoc/gmsm/sm3"
"github.com/tjfoc/gmsm/x509"
"math/big"
"qteam/app/utils/postbank/internal/sm2"
)
func Sm2Decrypt(privateKey *sm2.PrivateKey, encryptData []byte) ([]byte, error) {
C1Byte := make([]byte, 65)
copy(C1Byte, encryptData[:65])
x, y := elliptic.Unmarshal(privateKey.Curve, C1Byte)
dBC1X, dBC1Y := privateKey.Curve.ScalarMult(x, y, bigIntToByte(privateKey.D))
dBC1Bytes := elliptic.Marshal(privateKey.Curve, dBC1X, dBC1Y)
kLen := len(encryptData) - 65 - 32
t, err := kdf(dBC1Bytes, kLen)
if err != nil {
return nil, err
}
M := make([]byte, kLen)
for i := 0; i < kLen; i++ {
M[i] = encryptData[65+i] ^ t[i]
}
C3 := make([]byte, 32)
copy(C3, encryptData[len(encryptData)-32:])
u := calculateHash(dBC1X, M, dBC1Y)
if bytes.Compare(u, C3) == 0 {
return M, nil
} else {
return nil, errors.New("解密失败")
}
}
func Sm2Encrypt(publicKey *sm2.PublicKey, m []byte) ([]byte, error) {
kLen := len(m)
var C1, t []byte
var err error
var kx, ky *big.Int
for {
k, _ := rand.Int(rand.Reader, publicKey.Params().N)
C1x, C1y := zzsm2.GetSm2P256V1().ScalarBaseMult(bigIntToByte(k))
// C1x, C1y := sm2.P256Sm2().ScalarBaseMult(bigIntToByte(k))
C1 = elliptic.Marshal(publicKey.Curve, C1x, C1y)
kx, ky = publicKey.ScalarMult(publicKey.X, publicKey.Y, bigIntToByte(k))
kpbBytes := elliptic.Marshal(publicKey, kx, ky)
t, err = kdf(kpbBytes, kLen)
if err != nil {
return nil, err
}
if !isAllZero(t) {
break
}
}
C2 := make([]byte, kLen)
for i := 0; i < kLen; i++ {
C2[i] = m[i] ^ t[i]
}
C3 := calculateHash(kx, m, ky)
r := make([]byte, 0, len(C1)+len(C2)+len(C3))
r = append(r, C1...)
r = append(r, C2...)
r = append(r, C3...)
return r, nil
}
func isAllZero(m []byte) bool {
for i := 0; i < len(m); i++ {
if m[i] != 0 {
return false
}
}
return true
}
func calculateHash(x *big.Int, M []byte, y *big.Int) []byte {
digest := sm3.New()
digest.Write(bigIntToByte(x))
digest.Write(M)
digest.Write(bigIntToByte(y))
result := digest.Sum(nil)[:32]
return result
}
func bigIntToByte(n *big.Int) []byte {
byteArray := n.Bytes()
// If the most significant byte's most significant bit is set,
// prepend a 0 byte to the slice to avoid being interpreted as a negative number.
if (byteArray[0] & 0x80) != 0 {
byteArray = append([]byte{0}, byteArray...)
}
return byteArray
}
func kdf(Z []byte, klen int) ([]byte, error) {
ct := 1
end := (klen + 31) / 32
result := make([]byte, 0)
for i := 1; i <= end; i++ {
b, err := sm3hash(Z, toByteArray(ct))
if err != nil {
return nil, err
}
result = append(result, b...)
ct++
}
last, err := sm3hash(Z, toByteArray(ct))
if err != nil {
return nil, err
}
if klen%32 == 0 {
result = append(result, last...)
} else {
result = append(result, last[:klen%32]...)
}
return result, nil
}
func sm3hash(sources ...[]byte) ([]byte, error) {
b, err := joinBytes(sources...)
if err != nil {
return nil, err
}
md := make([]byte, 32)
h := x509.SM3.New()
h.Write(b)
h.Sum(md[:0])
return md, nil
}
func joinBytes(params ...[]byte) ([]byte, error) {
var buffer bytes.Buffer
for i := 0; i < len(params); i++ {
_, err := buffer.Write(params[i])
if err != nil {
return nil, err
}
}
return buffer.Bytes(), nil
}
func toByteArray(i int) []byte {
byteArray := []byte{
byte(i >> 24),
byte((i & 16777215) >> 16),
byte((i & 65535) >> 8),
byte(i & 255),
}
return byteArray
}

View File

@ -0,0 +1,62 @@
package util
import (
"crypto/md5"
"crypto/rand"
"encoding/hex"
"math/big"
"strings"
"time"
)
func GenerateSM4Key() []byte {
str := "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"
buffer := make([]byte, 16)
for i := 0; i < 16; i++ {
nextInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(str))))
buffer[i] = str[nextInt.Int64()]
}
return buffer
}
func GenAccessToken(token string) string {
if token != "" {
return token
}
now := time.Now()
return strings.ToUpper(Md5Hash(now.Format("2006A01B02CD15E04F05"), ""))
}
func Md5Hash(password, salt string) string {
m := md5.New()
m.Write([]byte(salt + password))
return hex.EncodeToString(m.Sum(nil))
}
// GetSM4IV 获取SM4的IV
func GetSM4IV() []byte {
return []byte("UISwD9fW6cFh9SNS")
}
func Padding(input []byte, mode int) []byte {
if input == nil {
return nil
} else {
var ret []byte
if mode == 1 {
p := 16 - len(input)%16
ret = make([]byte, len(input)+p)
copy(ret, input)
for i := 0; i < p; i++ {
ret[len(input)+i] = byte(p)
}
} else {
p := input[len(input)-1]
ret = make([]byte, len(input)-int(p))
copy(ret, input[:len(input)-int(p)])
}
return ret
}
}

204
app/utils/postbank/sm.go Normal file
View File

@ -0,0 +1,204 @@
package postbank
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/ZZMarquis/gm/sm4"
"math/big"
"qteam/app/utils/postbank/internal/sm2"
"qteam/app/utils/postbank/internal/util"
"strings"
)
func checkInData(reqData map[string]string, key string) (string, error) {
data, ok := reqData[key]
if !ok {
return "", errors.New("请求数据中不存在" + key)
}
return data, nil
}
func Decrypt(merchantId, privateKey, sopPublicKey, respJson string, isRequest bool) (string, error) {
var reqData map[string]string
err := json.Unmarshal([]byte(respJson), &reqData)
if err != nil {
return "", err
}
keys := [4]string{}
if isRequest {
keys = [4]string{"request", "signature", "encryptKey", "accessToken"}
} else {
keys = [4]string{"response", "signature", "encryptKey", "accessToken"}
}
var inEncryptKey, inAccessToken, inData, inSignature string
for i := 0; i < 4; i++ {
data, err := checkInData(reqData, keys[i])
if err != nil {
return "", err
}
switch keys[i] {
case "request", "response":
inData = data
case "signature":
inSignature = data
case "encryptKey":
inEncryptKey = data
case "accessToken":
inAccessToken = data
}
}
checked := verify(fmt.Sprintf("%s%s%s", inData, inEncryptKey, inAccessToken), inSignature, sopPublicKey, merchantId)
if !checked {
return "", errors.New("签名验证失败")
}
priKey, err := sm2.ReadPrivateKeyFromHex(privateKey)
if err != nil {
return "", errors.New("读取私钥失败")
}
hexEncryptKey, err := hex.DecodeString(inEncryptKey)
if err != nil {
return "", errors.New("解密sm4key失败")
}
sm4Key, err := util.Sm2Decrypt(priKey, hexEncryptKey)
request, _ := base64.StdEncoding.DecodeString(inData)
encryptedSm4Key, err := sm4.CBCDecrypt(sm4Key, util.GetSM4IV(), request)
return string(util.Padding(encryptedSm4Key, 0)), nil
}
func Encrypt(merchantId, privateKey, sopPublicKey, inputJson, token string, isRequest bool) (string, error) {
sm4Key := util.GenerateSM4Key()
iv := util.GetSM4IV()
tmp, err := sm4.CBCEncrypt(sm4Key, iv, util.Padding([]byte(inputJson), 1))
if err != nil {
return "", err
}
responseMsg := base64.StdEncoding.EncodeToString(tmp)
responseMsg = addNewline(responseMsg)
pubKey, err := sm2.ReadPublicKeyFromHex(sopPublicKey)
if err != nil {
return "", errors.New("读取私钥失败")
}
encryptKeyBytes, err := util.Sm2Encrypt(pubKey, sm4Key)
encryptKey := strings.ToUpper(hex.EncodeToString(encryptKeyBytes))
//accessToken := util.GenAccessToken(token)
accessToken := token
signContent := fmt.Sprintf("%s%s%s", responseMsg, encryptKey, accessToken)
signature, err := sign(merchantId, privateKey, signContent)
var reqData map[string]string
if isRequest {
reqData = map[string]string{
"request": responseMsg,
"signature": signature,
"encryptKey": encryptKey,
"accessToken": accessToken,
}
} else {
reqData = map[string]string{
"response": responseMsg,
"signature": signature,
"encryptKey": encryptKey,
"accessToken": accessToken,
}
}
jsonStr, err := json.Marshal(reqData)
if err != nil {
return "", err
}
return string(jsonStr), err
}
// GenerateKey 生成密钥对
func GenerateKey() (string, string) {
pri, _ := sm2.GenerateKey(rand.Reader)
hexPri := pri.D.Text(16)
// 获取公钥
publicKeyHex := publicKeyToString(&pri.PublicKey)
return strings.ToUpper(hexPri), publicKeyHex
}
// publicKeyToString 公钥sm2.PublicKey转字符串(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func publicKeyToString(publicKey *sm2.PublicKey) string {
xBytes := publicKey.X.Bytes()
yBytes := publicKey.Y.Bytes()
// 确保坐标字节切片长度相同
byteLen := len(xBytes)
if len(yBytes) > byteLen {
byteLen = len(yBytes)
}
// 为坐标补齐前导零
xBytes = append(make([]byte, byteLen-len(xBytes)), xBytes...)
yBytes = append(make([]byte, byteLen-len(yBytes)), yBytes...)
// 添加 "04" 前缀
publicKeyBytes := append([]byte{0x04}, append(xBytes, yBytes...)...)
return strings.ToUpper(hex.EncodeToString(publicKeyBytes))
}
func addNewline(str string) string {
lineLength := 76
var result strings.Builder
for i := 0; i < len(str); i++ {
if i > 0 && i%lineLength == 0 {
result.WriteString("\r\n")
}
result.WriteByte(str[i])
}
return result.String()
}
func sign(merchantId string, privateKeyHex string, signContent string) (string, error) {
privateKey, err := sm2.ReadPrivateKeyFromHex(privateKeyHex)
if err != nil {
return "", err
}
r, s, err := sm2.Sm2Sign(privateKey, []byte(signContent), []byte(merchantId), rand.Reader)
if err != nil {
return "", err
}
return rSToSign(r, s), nil
}
func verify(content string, signature string, publicKeyStr string, merchantId string) bool {
pubKey, err := sm2.ReadPublicKeyFromHex(publicKeyStr)
if err != nil {
panic(fmt.Sprintf("pubKeyBytes sm2 ReadPublicKeyFromHex err: %v", err))
}
r, s := signToRS(signature)
return sm2.Sm2Verify(pubKey, []byte(content), []byte(merchantId), r, s)
}
func signToRS(signStr string) (*big.Int, *big.Int) {
signSub := strings.Split(signStr, "#")
if len(signSub) != 2 {
panic(fmt.Sprintf("err rs: %x", signSub))
}
r, _ := new(big.Int).SetString(signSub[0], 16)
s, _ := new(big.Int).SetString(signSub[1], 16)
return r, s
}
func rSToSign(r *big.Int, s *big.Int) string {
rStr := r.Text(16)
sStr := s.Text(16)
return fmt.Sprintf("%s#%s", rStr, sStr)
}

View File

@ -0,0 +1,61 @@
package postbank
import (
"fmt"
"testing"
)
const (
merchantId = "YANWEI001"
// 模块名称 服开门户网站-文档中心-API文档-接口方法业务前缀
// 此处默认为普通接口取数币服务模块名称 ecny 文件接口这里传ufile
moduleName = "ecny"
// appID 服开门户网站-用户中心-我的应用-APP_ID
appID = "961925472724332544001"
// url 协议 + 定版ip端口号 + 接口方法业务前缀 + 合作方编号 + 接口地址后缀 + 合作方交易流水号(工具类生成)
url = "http://220.248.252.123:8443/sop-h5/biz/${moduleName}/${merchantId}.htm?partnerTxSriNo=${partnerTxSriNo}"
// h5token h5申请token的url 协议 + 定版ip端口号 + 接口地址业务前缀 + 合作方编号 + 合作方交易流水号(工具类生成)
h5token = "http://220.248.252.123:8443/sop-h5/h5/authToken/apply/${merchantId}?partnerTxSriNo=${partnerTxSriNo}"
// 合作方私钥
privateKey = "A56E64D94612E26095091C7321846979B3FA4AF2DCD3B13375FDE3888F2E126E"
// 合作方公钥
publicKey = "04A7DA7EA6F2974F3582DC2BB82190BEAD40C78DC54461020938306FD470C5B0D689DDA8B53D47C16593D67BDC3BE1B6594C4ECB6C536F440CCD02A0405B537873"
// 服开与合作方配对公钥 服开门户网站-用户中心-公钥配置-平台生产公钥
sopPublicKey = "041F63D23554FC1F5A36D43ABD0DE368A202849BCC03BEB34E7EF4F8C478E12F5529DB649B7C2C2DB5F55B4DC89A6EC83A8226218B7D931638DBFC4B344C628C33"
// sopPrivateKey 服开与合作方配对私钥 服开门户网站-用户中心-私钥配置-平台生产私钥
sopPrivateKey = "174AB6AB44FF23A6B65934B54E9D94B00A81D0D50D57DB0C38257E2AFB2D6C35"
// 文件上传接口
// fileUrl = http://220.248.252.123:8443/sop-h5/biz/ufile/upload/接口名/接口版本/合作方编号/appID/流水号
fileUrl = "http://220.248.252.123:8443/sop-h5/biz/ufile/upload/${method}/${version}/${merchantId}/${appID}/${partnerTxSriNo}"
// 解码格式 如果文件下载时解析出来的报文乱码,请改变解码格式
charsetName = "gbk"
m_id = "lansexiongdi666"
pri = "7FE05FEB81673ABF0AC4F9EEB99FB03BFE7BE432CA474CB9AAD8E302CAF00766"
msoapub = "04A3C235C15070E127679628FE024E6C77FFCB1840B4EFBE09D19CBE0EE69BEA231912943984D34C03989771E503DFBB4ECB186F4BBC27764109E63C2875005C81"
soapb = "0432F7E91033AC40472885B11EB673B1DE942FD555E95CD12E799CC50DD9E93B04A6AC254D725C22DCBD65436D97A46727B09FFFA1B1098EEA8D43F79AAB84F5B1"
)
func TestMarketSm4Decrypt(t *testing.T) {
respJson := "{\"request\":\"DUgb\\/aU\\/0ahjHU3e93s4fGNj\\/b2XrhpCSqMhC\\/Q2aXt6BK8W2fKfOBBHTBCtR+5cMiESM27ogbR+\\r\\nzvPqJnyUnNvlVbvO6pLxo3AtieKSAlQkyGSa\\/26NpopHbPrvWKl1Ta8rfEDHLkYeZzLisS85GOQN\\r\\nTLdqamxjhP1tJwvlvdfkLd1zVJ\\/QlpmYSLkzCsiWcOmnr8YlqYb5PMKuOl5yM5KNHVMeHCbfIulV\\r\\nkjNFDSdlFlG+HhRS2KLD1TIYGafLt0DOB+PCg\\/WNb1+09KgLJ2kSBlim\\/MGjlw1hiJY0nLcp1yOL\\r\\nZYK4DqCY5ZPeE9sbzxfe\\/PvyMT0MYvIVJD1cLKrLKxFGWpq0+69L9s4IW10aY5uD4yc4iSoPEzq7\\r\\nLzfGJEybZErNSaCKaFa4sB6foMPgHwUXppw5745PDHrcBwuK746kBQx3yVTAI4pjDx4YuxhjrPa+\\r\\nb3I4NY2PG9uFYBoAewxC34Z5Y9RxRzON3dFY0HPPgNymH+CHq\\/B6OUl2ag2vg4bQA7bTJQAtVfhx\\r\\n+QWcSB2SofsD7JTQGBwNScgfRWhQ7ptQbC5BZV+boP+z1JRb0h3GpfOfpbRPDx51WfQbMOztOkpM\\r\\njYhc2DJ3VjM=\",\"signature\":\"c5bbcba4c567af196959ad2eefe39e84e6708837fff38b8fddcf5fcbc36192d5#fa805cc6c3f257f5bc584cf1d9bb9159530053e5944bfacdbf361e3f09c799b4\",\"encryptKey\":\"0449D815DE5CCDC30CFBCB590F7EEE7C8FC306565608B939B8CDFDDDD8A11A188EE787F78342A564B8F19C3FD7D3573CD48883BC7C9A0B195BCA75DD3FAE1CE487F3C955E560A4CEC0FFC039AE341214476E357BA5DBE06F994EF59AC29A97CDC878088D7D7ADB0508BD293EE7E9CC882B\",\"accessToken\":null}" // 解析接到的请求
reqMsg, err := Decrypt(m_id, pri, msoapub, respJson, true)
fmt.Println(reqMsg, err)
}
// TestSm4Decrypt test sm4 decrypt
func TestSm4Decrypt(t *testing.T) {
respJson := "{\n\t\"accessToken\": \"C0A3853384EA0443C2942FF1A1B0757D\",\n\t\"encryptKey\": \"04F15C783C14DBD91DE13696BFCDAA4523F2D6CABB610A8BA809D7224234E53422EA4143ED6C3BD92CB9745A0330142E94C7F6EB36B5601A133F9E7E8C42E18C693BC975AD245EF14FDA9734523F407B7561CC390883D8D45C35DD8E3575970BFD75554EB9119F4A18B65EC5F3AB37D866\",\n\t\"request\": \"JBKuG9pmsYwLgFPJL5nFci9jEgX9M60ebFK8lNteJqJZgZZEF7Sg7+qabTq7AOhHN/u9a+O4Zv8e\\r\\nIncXNC9sFPKOrqRJaMmAZTrDCwD3VhDt/maMn9lQbdkXaqMwo4S1iYiqoni5riS/8ivbFqz4sQvv\\r\\nPoiyIaQCMkqAAEkFxL6aSlPGY7f7kdLHK6cpixT3h2H1coMPuCQrG1J7jnbe+IlPJiVOKn5Hchus\\r\\n4TnHAuH/y9/8W9sW2tILApqm0MUpNxkor3qkAEqkbhciuJJDp/lDqebcJeRt2UET6UtTFTECYEWR\\r\\nNPfQCHKQxMWWserd/wCcWMx8ePPK/UKqUW8Rpg6hncKcC8b0U5i0z3MFuKJscz/DAS/X6Vex9kUp\\r\\njvy5c+ahm/rx0y5pTkNKP9KzVTFE6SffyA3E5NR+6fIQ9VvOU1A/YPiBhCvLF/dCodUk\",\n\t\"signature\": \"60bd9f1bb4a234acf4c8789f048151cbd688538b8f02d2cf87cdd74309c346e6#6aaaad738d9c10b3aaf85c86b5134d95b4d815dc017b43bc9e5e012243eb26f6\"\n}" // 解析接到的请求
reqMsg, err := Decrypt(merchantId, privateKey, sopPublicKey, respJson, true)
fmt.Println(reqMsg, err)
}
func TestSm4Encrypt(t *testing.T) {
input := "12314"
res, err := Encrypt(merchantId, privateKey, sopPublicKey, input, "", true)
fmt.Println(res, err)
}
func TestGenerateKey(t *testing.T) {
hexPri, publicKeyHex := GenerateKey()
fmt.Println(hexPri, publicKeyHex)
}

View File

@ -0,0 +1,15 @@
package smtypes
type RequestData struct {
AccessToken string `json:"accessToken"`
EncryptKey string `json:"encryptKey"`
Request string `json:"request"`
Signature string `json:"signature"`
}
type ResponseData struct {
AccessToken string `json:"accessToken"`
EncryptKey string `json:"encryptKey"`
Response string `json:"request"`
Signature string `json:"signature"`
}

53
app/utils/redis/redis.go Normal file
View File

@ -0,0 +1,53 @@
package redis
import (
"context"
"fmt"
"github.com/qit-team/snow-core/redis"
"time"
)
// AcquireLock 尝试获取分布式锁
func AcquireLock(key string, expired time.Duration) (bool, error) {
ctx := context.Background()
client := redis.GetRedis()
// 设置锁的值这里可以是一个随机的UUID用于检测是否是同一个客户端持有的锁锁
lockValue := fmt.Sprintf("%d", time.Now().UnixNano())
result := client.SetNX(ctx, key, lockValue, expired)
return result.Val(), result.Err()
}
func Del(key string) error {
client := redis.GetRedis()
result := client.Del(context.Background(), key)
return result.Err()
}
func Set(key string, value interface{}, expired time.Duration) error {
client := redis.GetRedis()
return client.Set(context.Background(), key, value, expired).Err()
}
func Get(key string) (string, error) {
client := redis.GetRedis()
result := client.Get(context.Background(), key)
return result.Result()
}
func Decr(key string) (int64, error) {
client := redis.GetRedis()
result := client.Decr(context.Background(), key)
return result.Result()
}
func Incr(key string) (int64, error) {
client := redis.GetRedis()
result := client.Incr(context.Background(), key)
return result.Result()
}
func SetNX(key string, value interface{}, expired time.Duration) (bool, error) {
client := redis.GetRedis()
result := client.SetNX(context.Background(), key, value, expired)
return result.Result()
}

View File

@ -85,7 +85,7 @@ func encryptLoc(publicKeyStr, data string) (string, error) {
fmt.Println(err)
}
resultStr := hex.EncodeToString(decrypt)
return base64.StdEncoding.EncodeToString([]byte(resultStr)), nil
return resultStr, nil
}
func decryptLoc(publicKeyStr, privateKeyStr, cipherText string) (string, error) {
@ -158,3 +158,11 @@ func VerSm2Sig(pub *sm2.PublicKey, msg []byte, sign []byte) bool {
isok := pub.Verify(msg, sign)
return isok
}
func SignSm2(privateKey *sm2.PrivateKey, msg []byte) ([]byte, error) {
sign, err := privateKey.Sign(rand.Reader, msg, nil)
if err != nil {
return nil, err
}
return sign, nil
}

View File

@ -25,6 +25,7 @@ import (
"regexp"
"runtime"
"strings"
"sync/atomic"
"time"
"unicode"
)
@ -371,7 +372,7 @@ func IsNil(x interface{}) bool {
}
type User struct {
Id int
Id string
Phone string
}
@ -410,3 +411,49 @@ func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
})
return token, Claims, err
}
// generateRequestNumber 生成一个全局唯一的请求流水号
func GenerateRequestNumber() string {
// 时间戳部分,精确到毫秒
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
// 机器标识部分可以是IP地址、主机名或者自定义的机器唯一标识
// 这里简单使用随机数模拟机器标识部分
machineID := mrand.Int63n(1000)
// 序列号部分,使用原子操作保证线程安全的递增
var sequence uint32
atomic.AddUint32(&sequence, 1)
// 生成随机字符串部分,增加复杂度和唯一性
randStr := RandomString(4)
// 拼接各部分生成最终的请求流水号
return fmt.Sprintf("%d-%d-%d-%s", timestamp, machineID, sequence, randStr)
}
// randomString 生成指定长度的随机字符串
func RandomString(n int) string {
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
var letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
var src = mrand.NewSource(time.Now().UnixNano())
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}

View File

@ -36,11 +36,18 @@ type Config struct {
OpenApi OpenApi `toml:"OpenApi"`
Jwt Jwt `toml:"Jwt"`
AliOss AliOss `toml:"AliOss"`
YouChu YouChu `toml:"YouChu"`
YouChu YouChuConfig `toml:"YouChu"`
}
type YouChu struct {
MilkUrl string
type YouChuConfig struct {
Host string
NotifyUrl string
MilkUrl string
MerchantId string //合作方编号
MchtNo string //上单商户号
AppID string //appid
SopPublicKey string //服开公钥
SopPrivateKey string //服开私钥
}
type AliOss struct {

8
go.mod
View File

@ -6,6 +6,8 @@ require (
gitee.com/chengdu_blue_brothers/openapi-go-sdk v0.0.2
github.com/BurntSushi/toml v0.4.1
github.com/Shopify/sarama v1.19.0
github.com/ZZMarquis/gm v1.3.2
github.com/ahmetb/go-linq/v3 v3.2.0
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/forgoer/openssl v1.6.0
github.com/gin-gonic/gin v1.7.7
@ -21,6 +23,7 @@ require (
github.com/qit-team/snow-core v0.1.28
github.com/qit-team/work v0.3.11
github.com/robfig/cron v1.2.0
github.com/spf13/cobra v1.8.0
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271
github.com/swaggo/gin-swagger v1.3.3
github.com/swaggo/swag v1.7.9
@ -29,13 +32,13 @@ require (
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.30.0
gopkg.in/go-playground/validator.v9 v9.31.0
xorm.io/builder v0.3.9
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/ahmetb/go-linq/v3 v3.2.0 // indirect
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
github.com/alibabacloud-go/tea v1.1.17 // indirect
github.com/alibabacloud-go/tea-utils v1.4.4 // indirect
@ -67,6 +70,7 @@ require (
github.com/google/uuid v1.3.0 // indirect
github.com/hetiansu5/accesslog v1.0.0 // indirect
github.com/hetiansu5/cores v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
@ -89,6 +93,7 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tidwall/gjson v1.12.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
@ -111,7 +116,6 @@ require (
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
stathat.com/c/consistent v1.0.0 // indirect
xorm.io/builder v0.3.9 // indirect
xorm.io/core v0.7.3 // indirect
xorm.io/xorm v1.2.5 // indirect
)

9
go.sum
View File

@ -58,6 +58,8 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/ZZMarquis/gm v1.3.2 h1:lFtpzg5zeeVMZ/gKi0gtYcKLBEo9XTqsZDHDz6s3Gow=
github.com/ZZMarquis/gm v1.3.2/go.mod h1:wWbjZYgruQVd7Bb8UkSN8ujU931kx2XUW6nZLCiDE0Q=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
@ -137,6 +139,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -368,6 +371,8 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
@ -640,6 +645,7 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -671,8 +677,11 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=