This commit is contained in:
2025-02-06 11:14:33 +08:00
commit 07847a2d9e
535 changed files with 65131 additions and 0 deletions

View File

@ -0,0 +1,93 @@
package coingate
import (
"errors"
"fmt"
"go-admin/common/global"
"go-admin/common/helper"
"go-admin/config"
"go-admin/models/coingatedto"
"io/ioutil"
"net/http"
"strings"
"github.com/bytedance/sonic"
log "github.com/go-admin-team/go-admin-core/logger"
)
/*
获取支付连接
- @endPoint basic url
- @token api 密钥
- @req 请求参数
*/
func GetPaymentUrl(endPoint string, token string, req *coingatedto.OrderRequest) (resp coingatedto.OrderResponse, err error) {
method := "POST"
url := fmt.Sprintf("%s%s", endPoint, "/v2/orders")
payload, err := sonic.Marshal(req)
if err != nil {
return
}
client := &http.Client{}
err = helper.CreateHtppProxy("", config.ExtConfig.ProxyUrl, client)
if err != nil {
return
}
request, err := http.NewRequest(method, url, strings.NewReader(string(payload)))
if err != nil {
fmt.Println(err)
return
}
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
request.Header.Add("Content-Type", "application/json")
res, err := client.Do(request)
if err != nil {
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return
}
if res.StatusCode != http.StatusOK {
log.Error("status:", res.Status, "获取支付失败:", string(body))
err = errors.New("获取支付连接失败,status:" + res.Status)
return
}
err = sonic.Unmarshal(body, &resp)
return
}
/*
支付回调
*/
func CallBack(req *coingatedto.OrderCallBackResponse) error {
switch req.Status {
case global.COINGATE_STATUS_NEW:
case global.COINGATE_STATUS_PENDING:
case global.COINGATE_STATUS_CONFIRMING:
case global.COINGATE_STATUS_PAID:
case global.COINGATE_STATUS_INVALID:
case global.COINGATE_STATUS_EXPIRED:
case global.COINGATE_STATUS_CANCELED:
case global.COINGATE_STATUS_REFUNDED:
case global.COINGATE_STATUS_PARTIALLY_REFUNDED:
default:
errStr := fmt.Sprintf("支付回调未找到状态%s", req.Status)
log.Error(errStr)
return errors.New(errStr)
}
return nil
}

View File

@ -0,0 +1,88 @@
package aeshelper
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"io"
)
// Encrypt text with the passphrase
func Encrypt(text string, passphrase string) string {
salt := make([]byte, 8)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
panic(err.Error())
}
key, iv := DeriveKeyAndIv(passphrase, string(salt))
block, err := aes.NewCipher([]byte(key))
if err != nil {
panic(err)
}
pad := PKCS7Padding([]byte(text), block.BlockSize())
ecb := cipher.NewCBCEncrypter(block, []byte(iv))
encrypted := make([]byte, len(pad))
ecb.CryptBlocks(encrypted, pad)
return base64.StdEncoding.EncodeToString([]byte("Salted__" + string(salt) + string(encrypted)))
}
// Decrypt encrypted text with the passphrase
func Decrypt(encrypted string, passphrase string) string {
ct, _ := base64.StdEncoding.DecodeString(encrypted)
if len(ct) < 16 || string(ct[:8]) != "Salted__" {
return ""
}
salt := ct[8:16]
ct = ct[16:]
key, iv := DeriveKeyAndIv(passphrase, string(salt))
block, err := aes.NewCipher([]byte(key))
if err != nil {
panic(err)
}
cbc := cipher.NewCBCDecrypter(block, []byte(iv))
dst := make([]byte, len(ct))
cbc.CryptBlocks(dst, ct)
return string(PKCS7Trimming(dst))
}
// PKCS7Padding PKCS7Padding
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// PKCS7Trimming PKCS7Trimming
func PKCS7Trimming(encrypt []byte) []byte {
padding := encrypt[len(encrypt)-1]
return encrypt[:len(encrypt)-int(padding)]
}
// DeriveKeyAndIv DeriveKeyAndIv
func DeriveKeyAndIv(passphrase string, salt string) (string, string) {
salted := ""
dI := ""
for len(salted) < 48 {
md := md5.New()
md.Write([]byte(dI + passphrase + salt))
dM := md.Sum(nil)
dI = string(dM[:16])
salted = salted + dI
}
key := salted[0:32]
iv := salted[32:48]
return key, iv
}

View File

@ -0,0 +1,107 @@
package aeshelper
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/hex"
"github.com/forgoer/openssl"
)
const (
sKey = "ptQJqRKxICCTeo6w" // "dde4b1f8a9e6b814"
ivParameter = "O3vZvOJSnQDP9hKT" // "dde4b1f8a9e6b814"
)
// PswEncrypt 加密
func PswEncrypt(src string) (string, error) {
key := []byte(sKey)
iv := []byte(ivParameter)
result, err := Aes128Encrypt([]byte(src), key, iv)
if err != nil {
return "", err
}
return base64.RawStdEncoding.EncodeToString(result), nil
}
// PswDecrypt 解密
func PswDecrypt(src string) (string, error) {
key := []byte(sKey)
iv := []byte(ivParameter)
var result []byte
var err error
result, err = base64.StdEncoding.DecodeString(src)
if err != nil {
return "", err
}
origData, err := Aes128Decrypt(result, key, iv)
if err != nil {
return "", err
}
return string(origData), nil
}
func Aes128Encrypt(origData, key []byte, IV []byte) ([]byte, error) {
if key == nil || len(key) != 16 {
return nil, nil
}
if IV != nil && len(IV) != 16 {
return nil, nil
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
origData = PKCS5Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, IV[:blockSize])
crypted := make([]byte, len(origData))
// 根据CryptBlocks方法的说明如下方式初始化crypted也可以
blockMode.CryptBlocks(crypted, origData)
return crypted, nil
}
func Aes128Decrypt(crypted, key []byte, IV []byte) ([]byte, error) {
if key == nil || len(key) != 16 {
return nil, nil
}
if IV != nil && len(IV) != 16 {
return nil, nil
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, IV[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS5UnPadding(origData)
return origData, nil
}
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 次
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
// 密码加密
func AesEcbEncrypt(origData string) string {
//加密
dst, _ := openssl.AesECBEncrypt([]byte(origData), []byte(sKey), openssl.PKCS7_PADDING)
return hex.EncodeToString(dst)
}
// 密码解密
func AesEcbDecrypt(origData string) string {
value, _ := hex.DecodeString(origData)
dst, _ := openssl.AesECBDecrypt(value, []byte(sKey), openssl.PKCS7_PADDING)
return string(dst)
}

View File

@ -0,0 +1,65 @@
package aeshelper
import (
"encoding/json"
"fmt"
"testing"
"time"
)
// 测试加密解密
func Test_EnDe(t *testing.T) {
a := `asg`
b := Encrypt(a, `code_verify_success`)
c := Decrypt(b, `code_verify_success`)
fmt.Println(`原始为`, a)
fmt.Println(`加密后`, b)
fmt.Println(`解密后`, c)
}
func TestAesEcbEncrypt(t *testing.T) {
//aes := AesEcbEncrypt("123456")
aes := AesEcbEncrypt(fmt.Sprintf("%v_%v", time.Now().Unix(), 1332355333))
dst := AesEcbDecrypt(aes)
fmt.Println(aes)
fmt.Println(dst)
}
// TODO:需要加密的接口
/**
1.合约下单接口 /api/futures/trade/order
2.合约撤单 /api/futures/trade/cancelorder
3.调整保证金 /api/futures/trade/adjustmargin
4.变换逐全仓模式 /api/futures/trade/marginType
5.更改持仓模式(方向) /api/futures/trade/positionSide/dual
6.资产划转 /api/futures/transfer
*/
func TestAesEcbEncryptOrder(t *testing.T) {
data := addFutOrderReq{
OrderType: 1,
BuyType: 3,
TriggerDecide: 3,
IsReduce: 2,
Coin: "asdf",
Price: "333.23",
Num: "23.20",
TriggerPrice: "1.023",
PositionSide: "long",
}
b, _ := json.Marshal(data)
aes := AesEcbEncrypt(string(b))
dst := AesEcbDecrypt(aes)
fmt.Println(aes)
fmt.Println(dst)
}
type addFutOrderReq struct {
OrderType int `json:"order_type"` // 订单类型1限价2限价止盈止损3市价,4市价止盈止损,5强平委托(就是限价委托)
BuyType int `json:"buy_type"` // 买卖类型1买2卖
TriggerDecide int `json:"trigger_decide"` // 触发条件 1按最新成交价格算2按标记价格算
IsReduce int `json:"is_reduce"` // 1是只减仓位(点击仓位列表中的平仓按钮)0正常
Coin string `json:"coin"` // 交易币
Price string `json:"price"` // 下单价格(限价+止盈止损时,该字段必填)
Num string `json:"num"` // 下单数量(市价时该字段必填)
TriggerPrice string `json:"trigger_price"` // 触发价格
PositionSide string `json:"position_side"` // 持仓方向单向持仓模式下可填both;在双向持仓模式下必填,且仅可选择 long 或 short
}

View File

@ -0,0 +1,94 @@
package aesoldhelper
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"io"
"strings"
)
func addBase64Padding(value string) string {
m := len(value) % 4
if m != 0 {
value += strings.Repeat("=", 4-m)
}
return value
}
func removeBase64Padding(value string) string {
return strings.Replace(value, "=", "", -1)
}
// Pad Pad
func Pad(src []byte) []byte {
padding := aes.BlockSize - len(src)%aes.BlockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// Unpad Unpad
func Unpad(src []byte) ([]byte, error) {
length := len(src)
unpadding := int(src[length-1])
if unpadding > length {
return nil, errors.New("unpad error. This could happen when incorrect encryption key is used")
}
return src[:(length - unpadding)], nil
}
// AesEncrypt AesEncrypt
func AesEncrypt(key []byte, text string) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
msg := Pad([]byte(text))
ciphertext := make([]byte, aes.BlockSize+len(msg))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", err
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg))
finalMsg := removeBase64Padding(base64.URLEncoding.EncodeToString(ciphertext))
return finalMsg, nil
}
// AesDecrypt AesDecrypt
func AesDecrypt(key []byte, text string) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
decodedMsg, err := base64.URLEncoding.DecodeString(addBase64Padding(text))
if err != nil {
return "", err
}
if (len(decodedMsg) % aes.BlockSize) != 0 {
return "", errors.New("blocksize must be multipe of decoded message length")
}
iv := decodedMsg[:aes.BlockSize]
msg := decodedMsg[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(msg, msg)
unpadMsg, err := Unpad(msg)
if err != nil {
return "", err
}
return string(unpadMsg), nil
}

View File

@ -0,0 +1,19 @@
package aeshelper
var (
apiaeskey = "9jxFTkydwCJsmIA1TUrv"
)
// EncryptApi 加密apikey
func EncryptApi(apikey, secretkey string) (apikeyen, secrekeyen string) {
apikeyen = Encrypt(apikey, apiaeskey)
secrekeyen = Encrypt(secretkey, apiaeskey)
return apikeyen, secrekeyen
}
// DecryptApi 解密apikey
func DecryptApi(apikeyen, secrekeyen string) (apikey, secrekey string) {
apikey = Decrypt(apikeyen, apiaeskey)
secrekey = Decrypt(secrekeyen, apiaeskey)
return apikey, secrekey
}

View File

@ -0,0 +1,133 @@
package inttostring
import (
"math"
"math/rand"
"strings"
"time"
)
// IntToStr 用于将数字映射为字符串 例如 用户id-->邀请码
type IntToStr struct {
SALT int // 随意取一个数值
Len int // 邀请码长度
PRIME2 byte // 与邀请码长度 8 互质
AlphanumericSet []rune
PRIME1 int // 与字符集长度 36 互质
}
var (
Invite = NewInvite() // 邀请码
)
// NewInvite 邀请码
func NewInvite() *IntToStr {
return &IntToStr{
SALT: 15151239, // 随意取一个数值
Len: 8, // 邀请码长度
PRIME2: 3, // 与邀请码长度 Len 互质
AlphanumericSet: []rune{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
}, // 邀请码长度
PRIME1: 5, // 与字符集长度 36 互质
}
}
func (in IntToStr) DeCode(codeStr string) int {
code := make([]rune, 0, in.Len)
var key rune = 0
// 还原k
for i := 0; i < in.Len; i++ {
for k, v := range in.AlphanumericSet {
if v == rune(codeStr[i]) {
key = rune(k)
break
}
}
code = append(code, key)
}
code2 := make([]rune, 0, in.Len)
// 还原顺序
for i := 0; i < in.Len; i++ {
idx := i * int(in.PRIME2) % in.Len
code2 = append(code2, code[idx])
}
// 还原数字
var x int
for i := 0; i < in.Len; i++ {
code2[i] = (code2[i] - rune(i)*code2[0]) % rune(len(in.AlphanumericSet))
code2[i] = (code2[i] + rune(len(in.AlphanumericSet))) % rune(len(in.AlphanumericSet))
place := math.Pow(float64(len(in.AlphanumericSet)), float64(i)) // 次方运算
x += int(code2[i]) * int(place)
}
// 去盐 + 缩小
x = (x - in.SALT) / in.PRIME1
return x
}
func (in IntToStr) Encode(uid int) string {
// 放大 + 加盐
uid = uid*in.PRIME1 + in.SALT
slIdx := make([]byte, in.Len)
// 扩散
for i := 0; i < in.Len; i++ {
slIdx[i] = byte(uid % len(in.AlphanumericSet)) // 转换进制 获取36进制的每一位值
slIdx[i] = (slIdx[i] + byte(i)*slIdx[0]) % byte(len(in.AlphanumericSet)) // 其他位与个位加和再取余(让个位的变化影响到所有位)
uid = uid / len(in.AlphanumericSet) // 相当于右移一位62进制
}
var code []rune
// 混淆
for i := 0; i < in.Len; i++ {
idx := (byte(i) * in.PRIME2) % byte(in.Len)
code = append(code, in.AlphanumericSet[slIdx[idx]])
}
return string(code)
}
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const smsLetters = "0123456789"
// GenerateRandomString 生成指定长度的随机字符串
func GenerateRandomString(n int) string {
rand.Seed(time.Now().UnixNano()) // 初始化随机数种子
result := make([]byte, n)
for i := range result {
result[i] = letters[rand.Intn(len(letters))]
}
return string(result)
}
// GenerateRandomSmsString 生成指定长度的随机字符串
func GenerateRandomSmsString(n int) string {
rand.Seed(time.Now().UnixNano()) // 初始化随机数种子
result := make([]byte, n)
for i := range result {
result[i] = smsLetters[rand.Intn(len(smsLetters))]
}
return string(result)
}
func EncryptString(s string, prefixLen, suffixLen int) string {
length := len(s)
// 如果字符串长度不足,直接返回原字符串
if length <= prefixLen+suffixLen {
return s
}
// 保留前 prefixLen 位和后 suffixLen 位
prefix := s[:prefixLen]
suffix := s[length-suffixLen:]
// 中间部分用 * 替换
middle := strings.Repeat("*", 6)
// 拼接结果
return prefix + middle + suffix
}

View File

@ -0,0 +1,33 @@
package inttostring
import (
"fmt"
"testing"
)
func Test_a(t *testing.T) {
a:=1156
// b:=byte(a)
fmt.Println(a%10)
}
// 加密解密
func Test_EnDecode(t *testing.T) {
uid := 6515461646
Invi := NewInvite()
encode := Invi.Encode(uid)
DeCode := Invi.DeCode(encode)
fmt.Println(`原始为`, uid)
fmt.Println(`加密后`, encode)
fmt.Println(`解密后`, DeCode)
}
func Test_Invite(t *testing.T) {
for uid := 0; uid < 10000000; uid++ {
encode := Invite.Encode(uid)
DeCode := Invite.DeCode(encode)
if DeCode != uid {
t.Fatal(`加密解密错误`, DeCode, uid)
}
}
}

View File

@ -0,0 +1,295 @@
package jwthelper
import (
"go-admin/common/const/rediskey"
"go-admin/common/helper"
"go-admin/pkg/utility"
"github.com/golang-jwt/jwt/v5"
"go.uber.org/zap"
"fmt"
"strings"
"time"
log "github.com/go-admin-team/go-admin-core/logger"
)
// LoginUserJwt 用户登入信息
type LoginUserJwt struct {
UserID int
NickName string
Phone string
Email string
OsType int
}
// LoginClaims 自定义声明结构体并内嵌jwt.StandardClaims
// jwt包自带的jwt.StandardClaims只包含了官方字段
// 我们这里需要额外记录一个nickname字段所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type LoginClaims struct {
UserID int `json:"userid"`
NickName string `json:"nickname"`
Phone string `json:"phone"`
Email string `json:"email"`
jwt.RegisteredClaims
}
// JwtSigningKey Jwt signing key
var JwtSigningKey = []byte("tokexapp.com")
var LoginTokenValidTime = 48 * time.Hour // 登录验证token有效期
var LoginAdminTokenValidTime = 24 * time.Hour
var TokenNotValid = -1 // Token无效
var TokenExpire = -2 // Token过期
var TokenSuccess = 1 // Token正常
// CreateJwtToken 创建用户jwt token
func CreateJwtToken(userJwt LoginUserJwt, minute int) (string, string) {
// 创建一个我们自己的声明
expire := time.Now().Add(time.Minute * time.Duration(minute)) //.Unix()
claims := LoginClaims{
UserID: userJwt.UserID,
Phone: userJwt.Phone,
NickName: userJwt.NickName,
Email: userJwt.Email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expire),
Issuer: "Tokex", // 签发人
},
}
// 使用指定的签名方法创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString(JwtSigningKey)
if err != nil {
log.Error("获取jwt token失败:", zap.Error(err))
return "", ""
}
return tokenStr, fmt.Sprintf("%v", expire.Unix())
}
// MidValidToken 检验token是否有效,返回token解密后的string
func MidValidToken(tokenStr string, source int) (int, string) {
// 解析token
token, err := jwt.ParseWithClaims(tokenStr, &LoginClaims{}, func(token *jwt.Token) (i interface{}, err error) {
return JwtSigningKey, nil
})
if err != nil {
if strings.Contains(err.Error(), "token is expired") {
return TokenExpire, ""
}
// loghelper.Error("MidValidToken解析token失败", zap.Error(err))
return TokenNotValid, ""
}
if !token.Valid {
log.Error("MidValidToken解析token无效", zap.Error(err))
return TokenNotValid, ""
}
claims, ok := token.Claims.(*LoginClaims)
if !ok {
return TokenNotValid, ""
}
// 校验token
key := fmt.Sprintf(rediskey.AppLoginUserToken, claims.UserID)
if source == 3 {
key = fmt.Sprintf(rediskey.PCLoginUserToken, claims.UserID)
}
appToken, _ := helper.DefaultRedis.GetString(key)
if len(appToken) == 0 {
//说明未登入或者过期
return TokenExpire, ""
}
if appToken != tokenStr {
//说明是被t
return TokenExpire, ""
}
return TokenSuccess, strings.Join([]string{utility.IntToString(claims.UserID),
claims.NickName, claims.Phone, claims.Email}, ",")
}
// GetLoginUserJwt 解析用户登入信息
func GetLoginUserJwt(tokenStr string) LoginUserJwt {
if len(tokenStr) == 0 {
return LoginUserJwt{}
}
tokenArr := strings.Split(tokenStr, ",")
arrLen := len(tokenArr)
item := LoginUserJwt{
UserID: utility.StringAsInteger(tokenArr[0]),
}
if arrLen > 1 {
item.NickName = tokenArr[1]
}
if arrLen > 2 {
item.Phone = tokenArr[2]
}
if arrLen > 3 {
item.Email = tokenArr[3]
}
return item
}
// AgentLoginJwt ==============Agent 代理商 JWT========================//
type AgentLoginJwt struct {
AgentId int `json:"agent_id"` // 代理商ID
UserId int `json:"user_id"` // 代理商关联的用户ID
Name string `json:"name"` // 代理商姓名
Email string `json:"email"` // 代理商邮件
IP string `json:"ip"` // 代理商IP
}
type AgentLoginClaims struct {
AgentId int `json:"agent_id"` // 代理商ID
UserId int `json:"user_id"` // 代理商关联的用户ID
Name string `json:"name"` // 代理商姓名
Email string `json:"email"` // 代理商邮件
IP string `json:"ip"` // 代理商IP
jwt.RegisteredClaims //StandardClaims
}
var JwtAgentSigningKey = []byte("tokexagent.com") //Jwt signing key
// CreateAgentJwtToken 代理商产生的token
func CreateAgentJwtToken(userJwt AgentLoginJwt, minute int) string {
// 创建一个我们自己的声明
exp := time.Now().Add(time.Minute * time.Duration(minute))
c := AgentLoginClaims{
userJwt.AgentId,
userJwt.UserId, // 自定义字段
userJwt.Name,
userJwt.Email,
userJwt.IP,
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(exp), // 过期时间
Issuer: "Tokex-Agent", // 签发人
},
}
// 使用指定的签名方法创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
tokenStr, err := token.SignedString(JwtAgentSigningKey)
if err != nil {
log.Error("获取jwt token失败:", zap.Error(err))
return ""
}
return tokenStr
}
// MidValidAgentToken 检验token是否有效,返回token解密后的string
func MidValidAgentToken(tokenStr string) (int, string) {
// 解析token
token, err := jwt.ParseWithClaims(tokenStr, &AgentLoginClaims{}, func(token *jwt.Token) (i interface{}, err error) {
return JwtAgentSigningKey, nil
})
if err != nil {
return TokenNotValid, ""
}
if !token.Valid {
log.Error("MidValidAgentToken解析token无效", zap.Error(err))
return TokenNotValid, ""
}
claims, ok := token.Claims.(*AgentLoginClaims)
if !ok {
return TokenNotValid, ""
}
// 校验token
key := fmt.Sprintf(rediskey.AgentLoginUserToken, claims.AgentId)
appToken, _ := helper.DefaultRedis.GetString(key)
if len(appToken) == 0 {
return TokenExpire, ""
}
if appToken != tokenStr {
return TokenExpire, ""
}
return TokenSuccess, strings.Join([]string{utility.IntToString(claims.AgentId), utility.IntToString(claims.UserId),
claims.Name, claims.Email, claims.IP}, ",")
}
// GetLoginAgentJwt 解析代理商登入信息
func GetLoginAgentJwt(tokenStr string) AgentLoginJwt {
if len(tokenStr) == 0 {
return AgentLoginJwt{}
}
tokenArr := strings.Split(tokenStr, ",")
arrLen := len(tokenArr)
item := AgentLoginJwt{
AgentId: utility.StringAsInteger(tokenArr[0]),
}
if arrLen > 1 {
item.UserId = utility.StringAsInteger(tokenArr[1])
}
if arrLen > 2 {
item.Name = tokenArr[2]
}
if arrLen > 3 {
item.Email = tokenArr[3]
}
if arrLen > 4 {
item.IP = tokenArr[4]
}
return item
}
// ========== Admin 专用 JWT ========== //
type AdminLogin struct {
UserID int `json:"user_id"`
Account string `json:"account"`
NickName string `json:"nickname"`
Phone string `json:"phone"`
Email string `json:"email"`
IP string `json:"ip"`
}
type AdminLoginClaims struct {
AdminLogin
jwt.RegisteredClaims //StandardClaims
}
var AdminSignedKey = []byte("adm23inTOKexKey:237") // Admin 签名 Key
const AdminTokenIssue = "tokexAdmin" // Admin 签发人
// GenerateAdminLoginToken 生成管理员登录 Token
func GenerateAdminLoginToken(admin AdminLogin, expires time.Duration) string {
exp := time.Now().Add(expires)
claims := AdminLoginClaims{
admin,
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(exp), // 过期时间
Issuer: AdminTokenIssue, // 签发人
},
}
// 使用指定的签名方法创建签名对象
jwtToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(AdminSignedKey)
if err != nil {
log.Error("获取 jwtToken 失败:", zap.Error(err))
}
return jwtToken
}
// ParseAdminLoginToken 解析管理员登录 Token
func ParseAdminLoginToken(jwtToken string) (pass bool, adminLogin AdminLoginClaims) {
// 解析 jwtToken
token, err := jwt.ParseWithClaims(jwtToken, &AdminLoginClaims{}, func(token *jwt.Token) (i interface{}, err error) {
return AdminSignedKey, nil
})
if err != nil {
log.Error("解析 jwtToken 失败:", zap.Error(err))
return false, adminLogin
}
// 判断是否验证通过,并将 Claims => AdminLoginClaims
if claims, pass := token.Claims.(*AdminLoginClaims); pass && token.Valid {
return pass, *claims
}
return false, adminLogin
}

View File

@ -0,0 +1,17 @@
package md5helper
import (
"crypto/md5"
"encoding/hex"
)
// MD5 md5加密
func MD5(input string) string {
cc := md5.Sum([]byte(input))
return hex.EncodeToString(cc[:])
}
// MD52 md52次
func MD52(input string) string {
return MD5(MD5(input))
}

View File

@ -0,0 +1,55 @@
package md5helper
import (
"crypto/md5"
"encoding/hex"
"fmt"
"testing"
)
func TestMD52(t *testing.T) {
got := MD52("Qq123456")
fmt.Println(got)
}
func TestMD5(t *testing.T) {
got := []byte("Qq123456")
s := md5.New()
s.Write(got)
fmt.Println(hex.EncodeToString(s.Sum(nil)))
cc := md5.Sum(got)
fmt.Println(hex.EncodeToString(cc[:]))
fmt.Printf("%x\n", md5.Sum(got))
}
// go test -bench=_QE_ -benchmem -run=^$
func Benchmark_QE_1(b *testing.B) {
for i := 0; i < b.N; i++ {
input := `Qq123456`
s := md5.New()
s.Write([]byte(input))
hex.EncodeToString(s.Sum(nil))
}
}
func Benchmark_QE_2(b *testing.B) {
for i := 0; i < b.N; i++ {
input := `Qq123456`
cc := md5.Sum([]byte(input))
hex.EncodeToString(cc[:])
}
}
func Benchmark_QE_3(b *testing.B) {
for i := 0; i < b.N; i++ {
input := `Qq123456`
fmt.Sprintf("%x\n", md5.Sum([]byte(input)))
}
}
// Benchmark_QE_1-6 6354160 189.8 ns/op 48 B/op 2 allocs/op
// Benchmark_QE_2-6 7352328 162.9 ns/op 32 B/op 1 allocs/op
// Benchmark_QE_3-6 3007480 396.9 ns/op 80 B/op 3 allocs/op

View File

@ -0,0 +1,181 @@
package emailhelper
import (
"errors"
"fmt"
"go-admin/config"
"go-admin/pkg/utility"
"regexp"
"sync"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/mailjet/mailjet-apiv3-go"
"go.uber.org/zap"
"gopkg.in/gomail.v2"
)
var (
cacheEmail = make(map[string]*gomail.Dialer)
mu = sync.RWMutex{}
)
// getGoMailIn 邮件连接池
func getGoMailIn(key string) *gomail.Dialer {
mu.RLock()
defer mu.RUnlock()
item, ok := cacheEmail[key]
if ok {
return item
}
return nil
}
// CheckIsEmail 检测是否是邮箱
func CheckIsEmail(email string) bool {
if email == `` {
return false
}
pattern := `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` // 匹配电子邮箱
reg := regexp.MustCompile(pattern)
return reg.MatchString(email)
}
// SendFrontedEmail 发送邮件
func SendFrontedEmail(toEmail string, code string) error {
// 邮箱配置
from := config.ExtConfig.EmailConfig.MailFrom // 发送者邮箱
password := config.ExtConfig.EmailConfig.MailSmtpPass // Gmail 密码或应用专用密码
to := toEmail // 收件人邮箱
smtpHost := config.ExtConfig.EmailConfig.MailSmtpHost // Gmail SMTP 服务器
smtpPort := config.ExtConfig.EmailConfig.MailSmtpPort // SMTP 端口
link := fmt.Sprintf("%s/verify?email=%s&verify_code=%s&type=register", config.ExtConfig.Domain, toEmail, code)
// 创建邮件消息
subject := "注册验证"
body := fmt.Sprintf("<h1>注册验证</h1><p>您收到此电子邮件,用于进行邮箱验证,请点击下面的链接或打开下面的网址继续。 <p>You have received this email for email verification, please click the link below or open the URL below to continue.</p> %s </p>", link)
m := gomail.NewMessage()
m.SetHeader("From", from) // 发件人
m.SetHeader("To", to) // 收件人
m.SetHeader("Subject", subject) // 邮件主题
m.SetBody("text/html", body) // 邮件内容(纯文本)
// 设置 SMTP 服务器信息
d := gomail.NewDialer(smtpHost, utility.StringToInt(smtpPort), from, password)
// 发送邮件
if err := d.DialAndSend(m); err != nil {
log.Error("发送邮件失败: %v", err)
return err
}
return nil
}
// SendEmail 发送邮件
//func SendEmail(send config.EmailSend) (string, bool) {
// m := gomail.NewMessage()
// m.SetHeader("From", m.FormatAddress(send.From, send.Username)) // 这里等软件名字定下来以后使用多语言配置具体名称
// m.SetHeader("To", send.To) // "bob@example.com", "cora@example.com")
// // m.SetAddressHeader("Cc", "dan@example.com", "Dan")
// m.SetHeader("Subject", send.Subject)
// m.SetBody("text/html", send.Content) // "Hello <b>Bob</b> and <i>Cora</i>!")
// // m.Attach("/home/Alex/lolcat.jpg")
// key := send.Server + send.From
// gmailClient := getGoMailIn(key)
// if gmailClient == nil {
// mu.Lock()
// d := gomail.NewDialer(send.Server, send.Port, send.From, send.Secret)
// d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
// cacheEmail[key] = d
// mu.Unlock()
// gmailClient = d
// }
//
// // Send the email to Bob, Cora and Dan.
// if err := gmailClient.DialAndSend(m); err != nil {
// log.Error("d.DialAndSend", zap.Error(err))
// return "发送失败", false
// }
//
// return "", true
//}
var (
apiKeyPublic = "00d2889da90d5d90767bf04dc1bdc6fa"
apiKeyPrivate = "f68cd84cd88b7e2aabce79c878a77e97"
)
func MailJetSend(receive string, code string) error {
mailjetClient := mailjet.NewMailjetClient(apiKeyPublic, apiKeyPrivate) //"00d2889da90d5d90767bf04dc1bdc6fa", "f68cd84cd88b7e2aabce79c878a77e97")
messagesInfo := []mailjet.InfoMessagesV31{
{
From: &mailjet.RecipientV31{
Email: "email@tokex.shop",
Name: "Tokex",
},
To: &mailjet.RecipientsV31{
mailjet.RecipientV31{
Email: receive,
Name: "",
},
},
Subject: "【Tokex】邮箱验证码",
TextPart: "您的邮箱验证码为:" + code,
// HTMLPart: "<h3>欢迎注册登录我们的服务您的验证码为1234567</h3><br />May the delivery force be with you!",
// CustomID: "AppGettingStartedTest",
// TemplateID: 1234,
},
}
messages := mailjet.MessagesV31{Info: messagesInfo}
res, err := mailjetClient.SendMailV31(&messages)
if err != nil {
log.Error("MailJetSend", zap.Error(err))
return err
}
// fmt.Printf("Data: %+v\n", res)
if res.ResultsV31[0].Status != "success" {
log.Error("发送失败", zap.Any("resilt", res))
return errors.New("发送失败")
}
return nil
}
func MailJetSendMsg(receive string, subject, textPart string) error {
mailjetClient := mailjet.NewMailjetClient(apiKeyPublic, apiKeyPrivate) //"00d2889da90d5d90767bf04dc1bdc6fa", "f68cd84cd88b7e2aabce79c878a77e97")
messagesInfo := []mailjet.InfoMessagesV31{
{
From: &mailjet.RecipientV31{
Email: "email@tokex.shop",
Name: "Tokex",
},
To: &mailjet.RecipientsV31{
mailjet.RecipientV31{
Email: receive,
Name: "",
},
},
Subject: subject,
TextPart: textPart,
},
}
messages := mailjet.MessagesV31{Info: messagesInfo}
res, err := mailjetClient.SendMailV31(&messages)
if err != nil {
log.Error("MailJetSend", zap.Error(err))
return err
}
// fmt.Printf("Data: %+v\n", res)
if res.ResultsV31[0].Status != "success" {
log.Error("发送失败", zap.Any("resilt", res))
return errors.New("发送失败")
}
return nil
}

View File

@ -0,0 +1,236 @@
package httpclient
import (
"bytes"
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"go.uber.org/zap"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/valyala/fasthttp"
)
var (
client = &fasthttp.Client{MaxConnsPerHost: 1000000, TLSConfig: &tls.Config{InsecureSkipVerify: true}, ReadTimeout: time.Second * 60, MaxIdemponentCallAttempts: 0}
clientForm = &fasthttp.Client{MaxConnsPerHost: 1000000, TLSConfig: &tls.Config{InsecureSkipVerify: true}, ReadTimeout: time.Second * 60, MaxIdemponentCallAttempts: 0}
)
// JumioPostBasicAuth
func JumioPostBasicAuth(uri, username, password string, data url.Values) ([]byte, error) {
client := &http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest("POST", uri, strings.NewReader(data.Encode()))
if err != nil {
return nil, err
}
req.SetBasicAuth(username, password)
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
return body, nil
}
// JumioPostOauth
func JumioPostOauth(uri, token string, data []byte) ([]byte, error) {
client := &http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest("POST", uri, bytes.NewReader(data))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
req.Header.Set("User-Agent", "myapp-v1.0.0")
req.Header.Set("Accept", "*/*")
req.Header.Set("Content-Type", "application/json")
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
return body, nil
}
// JumioHttp
func JumioHttp(method, uri, accessToken string, data url.Values) ([]byte, error) {
client := &http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest(method, uri, strings.NewReader(data.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", accessToken))
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
xx := string(body)
fmt.Println(xx)
return body, nil
}
// FastPostByte do application/json POST request via fasthttp
func FastPostByte(url string, body []byte) ([]byte, error) {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer func() {
fasthttp.ReleaseResponse(resp)
fasthttp.ReleaseRequest(req)
}()
req.SetRequestURI(url)
//req.Header.SetContentType("application/x-www-form-urlencoded")
req.Header.SetContentType("application/json; charset=utf-8")
req.Header.Add("Accept", "application/json")
// if w.Authentication && len(w.JwtToken) > 0 {
// req.Header.Set("Authorization", "Bearer "+w.JwtToken)
// }
req.Header.SetMethod("POST")
req.SetBody(body)
// if !loghelper.LevelIsError() {
// loghelper.InfoLog("FastPostByte:", string(body))
// }
err := client.Do(req, resp)
if err != nil {
return nil, err
}
recbody := resp.Body()
arr := make([]byte, len(recbody))
copy(arr, recbody)
return arr, nil
}
// FastGet Fast Get
//
// @params header 格式 []string{
// "key:value",
// "key:value",
// }
//
// @return "中国,深圳"
func FastGet(url string, header ...string) ([]byte, error) {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer func() {
fasthttp.ReleaseResponse(resp)
fasthttp.ReleaseRequest(req)
}()
req.SetRequestURI(url)
// if w.Authentication && len(w.JwtToken) > 0 {
// req.Header.Set("Authorization", "Bearer "+w.JwtToken)
// }
for _, h := range header {
kv := strings.Split(h, ":")
req.Header.Set(kv[0], kv[1])
}
// define webapi client request Method
req.Header.SetMethod("GET")
// DO GET request
err := client.Do(req, resp)
if err != nil {
return nil, err
}
body := resp.Body()
// arr := make([]byte, len(body))
// copy(arr, body)
return body, nil
}
// fastFormPost do POST request via fasthttp
func fastFormPost(url string, parmlist map[string]string) (*fasthttp.Response, error) {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer func() {
fasthttp.ReleaseResponse(resp)
fasthttp.ReleaseRequest(req)
}()
req.SetRequestURI(url)
// if w.Authentication && len(w.JwtToken) > 0 {
// req.Header.Set("Authorization", "Bearer "+w.JwtToken)
// }
req.Header.SetMethod("POST")
args := req.PostArgs()
for k, v := range parmlist {
args.Set(k, v)
}
err := clientForm.Do(req, resp) // fasthttp.DoTimeout(req, resp, timeOut)
if err != nil {
log.Error("post request error", zap.Error(err))
return nil, err
}
out := fasthttp.AcquireResponse()
resp.CopyTo(out)
return out, nil
}
// PostForm do POST request via fasthttp
func PostForm(url string, parmlist map[string]string) ([]byte, error) {
resp, err := fastFormPost(url, parmlist)
if err != nil {
return nil, err
}
arr := make([]byte, len(resp.Body()))
copy(arr, resp.Body())
// defer fasthttp.ReleaseResponse(resp)
// return resp.Body()
return arr, nil
}
// ClientIP 获取真实的IP 1.1.1.1, 2.2.2.2, 3.3.3.3
func ClientIP(ctx *fasthttp.RequestCtx) string {
clientIP := string(ctx.Request.Header.Peek("X-Forwarded-For"))
if index := strings.IndexByte(clientIP, ','); index >= 0 {
clientIP = clientIP[0:index]
// 获取最开始的一个 即 1.1.1.1
}
clientIP = strings.TrimSpace(clientIP)
if len(clientIP) > 0 {
return clientIP
}
clientIP = strings.TrimSpace(string(ctx.Request.Header.Peek("X-Real-Ip")))
if len(clientIP) > 0 {
return clientIP
}
return ctx.RemoteIP().String()
}
// MergeQuery appends additional query values to an existing URL.
func MergeQuery(u url.URL, q url.Values) url.URL {
uv := u.Query()
for k, vs := range q {
for _, v := range vs {
uv.Add(k, v)
}
}
u.RawQuery = uv.Encode()
return u
}

192
pkg/httpclient/httputils.go Normal file
View File

@ -0,0 +1,192 @@
package httpclient
// http request 工具函数
import (
"crypto/tls"
"errors"
"fmt"
"log"
"net/url"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpproxy"
)
var (
fastHttpClient = &fasthttp.Client{
Name: "http",
MaxConnsPerHost: 10000,
MaxIdleConnDuration: 30 * time.Second,
ReadTimeout: 50 * time.Second,
WriteTimeout: 50 * time.Second,
TLSConfig: &tls.Config{InsecureSkipVerify: true},
}
socksDialer fasthttp.DialFunc
proxyUrl = ""
proxyScheme = "socks5"
)
func NewHttpRequestWithFasthttp(reqMethod, reqUrl, postData string, headers map[string]string) ([]byte, error) {
// loghelper.InfoLog("use fasthttp client")
if len(proxyUrl) > 0 {
if socksDialer == nil {
socksDialer = fasthttpproxy.FasthttpSocksDialer(strings.TrimPrefix(proxyUrl, proxyScheme+"://"))
fastHttpClient.Dial = socksDialer
}
}
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer func() {
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
}()
for k, v := range headers {
req.Header.Set(k, v)
}
req.Header.SetMethod(reqMethod)
req.SetRequestURI(reqUrl)
req.SetBodyString(postData)
err := fastHttpClient.Do(req, resp)
if err != nil {
return nil, err
}
if resp.StatusCode() != 200 {
return nil, errors.New(fmt.Sprintf("HttpStatusCode:%d ,Desc:%s", resp.StatusCode(), string(resp.Body())))
}
return resp.Body(), nil
}
func HttpGet(reqUrl string) (map[string]interface{}, error) {
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", nil)
if err != nil {
return nil, err
}
var bodyDataMap map[string]interface{}
err = sonic.Unmarshal(respData, &bodyDataMap)
if err != nil {
log.Println(string(respData))
return nil, err
}
return bodyDataMap, nil
}
func HttpGet2(reqUrl string, headers map[string]string) (map[string]interface{}, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded"
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers)
if err != nil {
return nil, err
}
var bodyDataMap map[string]interface{}
err = sonic.Unmarshal(respData, &bodyDataMap)
if err != nil {
log.Println("respData", string(respData))
return nil, err
}
return bodyDataMap, nil
}
func HttpGet3(reqUrl string, headers map[string]string) ([]interface{}, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded"
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers)
if err != nil {
return nil, err
}
// println(string(respData))
var bodyDataMap []interface{}
err = sonic.Unmarshal(respData, &bodyDataMap)
if err != nil {
log.Println("respData", string(respData))
return nil, err
}
return bodyDataMap, nil
}
func HttpGet4(reqUrl string, headers map[string]string, result interface{}) error {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded"
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers)
if err != nil {
return err
}
err = sonic.Unmarshal(respData, result)
if err != nil {
log.Printf("HttpGet4 - jsonhelper.Unmarshal failed : %v, resp %s", err, string(respData))
return err
}
return nil
}
func HttpGet5(reqUrl string, headers map[string]string) ([]byte, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded"
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers)
if err != nil {
return nil, err
}
return respData, nil
}
func HttpPostForm(reqUrl string, postData url.Values) ([]byte, error) {
headers := map[string]string{
"Content-Type": "application/x-www-form-urlencoded"}
return NewHttpRequestWithFasthttp("POST", reqUrl, postData.Encode(), headers)
}
func HttpPostForm2(reqUrl string, postData url.Values, headers map[string]string) ([]byte, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded"
return NewHttpRequestWithFasthttp("POST", reqUrl, postData.Encode(), headers)
}
func HttpPostForm3(reqUrl string, postData string, headers map[string]string) ([]byte, error) {
return NewHttpRequestWithFasthttp("POST", reqUrl, postData, headers)
}
func HttpPostForm4(reqUrl string, postData map[string]string, headers map[string]string) ([]byte, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/json"
data, _ := sonic.Marshal(postData)
return NewHttpRequestWithFasthttp("POST", reqUrl, string(data), headers)
}
func HttpDeleteForm(reqUrl string, postData url.Values, headers map[string]string) ([]byte, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded"
return NewHttpRequestWithFasthttp("DELETE", reqUrl, postData.Encode(), headers)
}
func HttpPut(reqUrl string, postData url.Values, headers map[string]string) ([]byte, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded"
return NewHttpRequestWithFasthttp("PUT", reqUrl, postData.Encode(), headers)
}

367
pkg/httputils/httputils.go Normal file
View File

@ -0,0 +1,367 @@
package httputils
// http request 工具函数
import (
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/valyala/fasthttp"
"golang.org/x/net/proxy"
)
var (
fastHttpClient = &fasthttp.Client{
Name: "http",
MaxConnsPerHost: 10000,
MaxIdleConnDuration: 30 * time.Second,
ReadTimeout: 50 * time.Second,
WriteTimeout: 50 * time.Second,
TLSConfig: &tls.Config{InsecureSkipVerify: true}, // 跳过证书验证
}
clientHuoBi *http.Client // 火币交易所 HTTP 客户端
clientBinance *http.Client // 币安交易所 HTTP 客户端
)
func init() {
// 克隆默认的 HTTP 传输配置
t := http.DefaultTransport.(*http.Transport).Clone()
t.MaxIdleConns = 100
t.MaxConnsPerHost = 10000
t.MaxIdleConnsPerHost = 100
t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // 跳过证书验证
// 初始化火币交易所 HTTP 客户端
clientHuoBi = &http.Client{
Timeout: 50 * time.Second, // 请求超时时间
Transport: t,
}
// 初始化币安交易所 HTTP 客户端
clientBinance = &http.Client{
Timeout: 50 * time.Second, // 请求超时时间
Transport: t,
}
}
/*
初始化请求代理
- @proxy 代理地址http ip:port
*/
func InitProxy(proxy string) {
if proxy != "" {
fastHttpClient.Dial = createHTTPProxyDialer(proxy) // 设置代理拨号器
}
}
// net http 请求(币安)
func NewHttpRequestBinance(reqMethod, reqUrl, postData string, reqHeaders map[string]string) ([]byte, error) {
req, _ := http.NewRequest(reqMethod, reqUrl, strings.NewReader(postData)) // 创建新的 HTTP 请求
if req.Header.Get("User-Agent") == "" {
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36") // 设置用户代理
}
if reqHeaders != nil {
for k, v := range reqHeaders {
req.Header.Add(k, v) // 添加请求头
}
}
resp, err := clientBinance.Do(req) // 发送请求
if err != nil {
return nil, err
}
defer resp.Body.Close() // 确保关闭响应体
bodyData, err := ioutil.ReadAll(resp.Body) // 读取响应体
if err != nil {
return nil, err
}
arr := make([]byte, len(bodyData))
copy(arr, bodyData)
if resp.StatusCode != 200 { // 检查响应状态码
return nil, errors.New(string(arr))
}
return arr, nil
}
// fast http 执行请求
func NewHttpRequestWithFasthttp(reqMethod, reqUrl, postData string, headers map[string]string) ([]byte, error) {
req := fasthttp.AcquireRequest() // 从 fasthttp 获取请求
resp := fasthttp.AcquireResponse() // 从 fasthttp 获取响应
defer func() {
fasthttp.ReleaseRequest(req) // 释放请求
fasthttp.ReleaseResponse(resp) // 释放响应
}()
for k, v := range headers {
req.Header.Set(k, v) // 设置请求头
}
req.Header.SetMethod(reqMethod) // 设置请求方法
req.SetRequestURI(reqUrl) // 设置请求 URL
req.SetBodyString(postData) // 设置请求体
err := fastHttpClient.Do(req, resp) // 执行请求
if err != nil {
return nil, err
}
recbody := resp.Body() // 获取响应体
arr := make([]byte, len(recbody))
copy(arr, recbody)
if resp.StatusCode() != 200 { // 检查响应状态码
return nil, errors.New(fmt.Sprintf("HttpStatusCode:%d ,Desc:%s", resp.StatusCode(), string(recbody)))
}
return recbody, nil
}
// HTTP GET 请求,返回 map
func HttpGet(reqUrl string) (map[string]interface{}, error) {
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", nil) // 发送 GET 请求
if err != nil {
return nil, err
}
var bodyDataMap map[string]interface{}
err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应
if err != nil {
log.Println(string(respData)) // 打印响应数据
return nil, err
}
return bodyDataMap, nil
}
// HTTP GET 请求,带请求头
func HttpGet2(reqUrl string, headers map[string]string) (map[string]interface{}, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded" // 设置内容类型
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) // 发送 GET 请求
if err != nil {
return nil, err
}
var bodyDataMap map[string]interface{}
err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应
if err != nil {
log.Println("respData", string(respData)) // 打印响应数据
return nil, err
}
return bodyDataMap, nil
}
// HTTP GET 请求,返回接口切片
func HttpGet3(reqUrl string, headers map[string]string) ([]interface{}, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded" // 设置内容类型
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) // 发送 GET 请求
if err != nil {
return nil, err
}
var bodyDataMap []interface{}
err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应
if err != nil {
log.Println("respData", string(respData)) // 打印响应数据
return nil, err
}
return bodyDataMap, nil
}
// HTTP GET 请求,结果存储在 result 中
func HttpGet4(reqUrl string, headers map[string]string, result interface{}) error {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded" // 设置内容类型
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) // 发送 GET 请求
if err != nil {
return err
}
err = sonic.Unmarshal(respData, result) // 解析 JSON 响应
if err != nil {
log.Printf("HttpGet4 - json.Unmarshal failed : %v, resp %s", err, string(respData))
return err
}
return nil
}
// HTTP GET 请求,返回原始字节
func HttpGet5(reqUrl string, headers map[string]string) ([]byte, error) {
if headers == nil {
headers = map[string]string{}
}
headers["Content-Type"] = "application/x-www-form-urlencoded" // 设置内容类型
respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) // 发送 GET 请求
if err != nil {
return nil, err
}
return respData, nil
}
// HTTP POST 请求,表单数据
func HttpPostForm(reqUrl string, postData url.Values) ([]byte, error) {
respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData.Encode(), nil) // 发送 POST 请求
if err != nil {
return nil, err
}
return respData, nil
}
// HTTP POST 请求,返回 map
func HttpPost(reqUrl string, postData string) (map[string]interface{}, error) {
respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求
if err != nil {
return nil, err
}
var bodyDataMap map[string]interface{}
err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应
if err != nil {
log.Println(string(respData)) // 打印响应数据
return nil, err
}
return bodyDataMap, nil
}
// HTTP POST 请求,返回原始字节
func HttpPost2(reqUrl string, postData string) ([]byte, error) {
respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求
if err != nil {
return nil, err
}
return respData, nil
}
// HTTP POST 请求,返回接口切片
func HttpPost3(reqUrl string, postData string) ([]interface{}, error) {
respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求
if err != nil {
return nil, err
}
var bodyDataMap []interface{}
err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应
if err != nil {
log.Println(string(respData)) // 打印响应数据
return nil, err
}
return bodyDataMap, nil
}
// HTTP POST 请求,结果存储在 result 中
func HttpPost4(reqUrl string, postData string, result interface{}) error {
respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求
if err != nil {
return err
}
err = sonic.Unmarshal(respData, result) // 解析 JSON 响应
if err != nil {
log.Printf("HttpPost4 - json.Unmarshal failed : %v, resp %s", err, string(respData))
return err
}
return nil
}
// HTTP POST 请求,返回原始字节
func HttpPost5(reqUrl string, postData string) ([]byte, error) {
respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求
if err != nil {
return nil, err
}
return respData, nil
}
// HTTP POST 请求,表单数据,带请求头
func HttpPostForm2(reqUrl string, postData url.Values, headers map[string]string) ([]byte, error) {
respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData.Encode(), headers) // 发送 POST 请求
if err != nil {
return nil, err
}
return respData, nil
}
// 创建 HTTP 代理拨号器
func createHTTPProxyDialer(proxy string) fasthttp.DialFunc {
// 解析代理 URL
proxyURL, err := url.Parse(proxy)
if err != nil {
log.Fatal(err)
}
// 返回拨号器函数
return func(addr string) (net.Conn, error) {
// 选择代理协议
switch proxyURL.Scheme {
case "http", "https":
proxyConn, err := net.Dial("tcp", proxyURL.Host)
if err != nil {
return nil, err
}
// Send the HTTP CONNECT request to the proxy
_, err = proxyConn.Write([]byte("CONNECT " + addr + " HTTP/1.1\r\nHost: " + addr + "\r\n\r\n"))
if err != nil {
return nil, err
}
// Read the response from the proxy
buf := make([]byte, 4096)
n, err := proxyConn.Read(buf)
if err != nil {
return nil, err
}
// Check for a successful response (HTTP 200)
if !isConnectSuccess(buf[:n]) {
return nil, err
}
return proxyConn, nil
case "socks5":
return socks5Dial(proxyURL.Host)
default:
return nil, fmt.Errorf("不支持的代理协议: %s", proxyURL.Scheme)
}
}
}
func isConnectSuccess(response []byte) bool {
return len(response) > 0 && strings.HasPrefix(string(response), "HTTP/1.1 200")
}
// socks5Dial 使用 SOCKS5 代理拨号
func socks5Dial(proxyAddr string) (net.Conn, error) {
// 创建 SOCKS5 代理拨号器
dialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
if err != nil {
return nil, err
}
// 使用代理拨号
return dialer.Dial("tcp", "destination_address:port") // 替换为目标地址和端口
}

View File

@ -0,0 +1,36 @@
package jsonhelper
import (
"github.com/bytedance/sonic"
jsonIterator "github.com/json-iterator/go"
"github.com/vmihailenco/msgpack/v5"
)
var (
IJson = jsonIterator.ConfigCompatibleWithStandardLibrary
// Marshal is exported by common package.
Marshal = IJson.Marshal
// Unmarshal is exported by common package.
Unmarshal = IJson.Unmarshal
// MarshalIndent is exported by common package.
MarshalIndent = IJson.MarshalIndent
// NewDecoder is exported by common package.
NewDecoder = IJson.NewDecoder
// NewEncoder is exported by common package.
NewEncoder = IJson.NewEncoder
// MarshalMsgPack msgpack方式序列化
MarshalMsgPack = msgpack.Marshal
// msgpack方式反序列化
UnmarshalMsgPack = msgpack.Unmarshal
NewDecoderMsgPack = msgpack.NewDecoder
NewEncoderMsgPack = msgpack.NewEncoder
)
func ToJsonString(v interface{}) string {
if result, err := sonic.Marshal(v); err == nil {
return string(result)
}
return ""
}

View File

@ -0,0 +1,31 @@
package jsonhelper
import (
"encoding/json"
"testing"
)
var jsonStr = `{"W":1561651}`
var obj struct {
W int
}
// 对比下 IJson 和 系统自带的 json 的效率
// go test -bench=_QE_ -benchmem -run=^$
// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据
func Benchmark_QE_1(b *testing.B) {
for i := 0; i < b.N; i++ {
json.Unmarshal([]byte(jsonStr), &obj)
}
}
func Benchmark_QE_2(b *testing.B) {
for i := 0; i < b.N; i++ {
Unmarshal([]byte(jsonStr), &obj)
}
}
// Benchmark_QE_1-6 2252055 531.2 ns/op 240 B/op 6 allocs/op
// Benchmark_QE_2-6 7650109 158.7 ns/op 16 B/op 1 allocs/op
// 确实快了很多 不知道复杂的结构会不会不一样

View File

@ -0,0 +1,119 @@
package timehelper
import "time"
//GetDateUnixDate 返回带毫秒的时间戳如果需要转化为时间类型time,
//不带毫秒的time.Unix(1663315884651/1000,0)
//带毫秒的time.Unix(1663315884651/1000, 1000000*(1663315884651%1000))
func GetDateUnixDate(date time.Time) int64 {
return date.UnixMilli() //Unix() //* 1000
}
//GetUnixDate 时间撮转化为time类型
func GetUnixDate(date int64) time.Time {
return time.Unix(date/1000, 1000000*(date%1000))
}
func ConvertTimeLocalSec(date int64) time.Time {
d1 := time.Unix(date/1000, 1000000*(date%1000))
d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), d1.Nanosecond(), time.Local)
return d2
}
//TimeSubDays 时间间隔天数
func TimeSubDays(t1, t2 time.Time) int {
if t1.Location().String() != t2.Location().String() {
return -1
}
hours := t1.Sub(t2).Hours()
if hours <= 0 {
return -1
}
// sub hours less than 24
if hours < 24 {
// may same day
t1y, t1m, t1d := t1.Date()
t2y, t2m, t2d := t2.Date()
isSameDay := t1y == t2y && t1m == t2m && t1d == t2d
if isSameDay {
return 0
}
return 1
}
// equal or more than 24
if (hours/24)-float64(int(hours/24)) == 0 { // just 24's times
return int(hours / 24)
}
// more than 24 hours
return int(hours/24) + 1
}
//ConvertTimeLocal 时间在数据库返回给结构体的时候,结构体的时间时区是非本地的,需要转化为本地的再转变为时间戳
func ConvertTimeLocal(d1 time.Time) time.Time {
d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), 0, time.Local)
return d2
}
//ConvertTimeDayLocal s
func ConvertTimeDayLocal(d1 time.Time) time.Time {
d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), 0, 0, 0, 0, time.Local)
return d2
}
//转为时分秒
func ToTimeHour(ltime int64) string {
now := IntToTime(ltime)
return now.Format("15:04:05")
}
// func ParseInLocation(layout, value string, loc *Location) (Time, error) (layout已带时区时可直接用Parse)
//time.ParseInLocation("2006-01-02 15:04:05", "2017-05-11 14:06:06", time.Local)
//转为时间类型
func IntToTime(intime int64) time.Time {
return time.Unix(intime/1000, 0)
}
//GetPastDay 当前时间算起过去num天内的开始日期、结束日期
func GetPastDay(num int) (start, end time.Time) {
tn := time.Now()
//当前时间,天数为单位
nowday := time.Date(tn.Year(), tn.Month(), tn.Day(), 0, 0, 0, 0, time.Local)
//过去30天
oldTime := nowday.AddDate(0, 0, -num)
return oldTime, nowday
}
//ConvertTimeToString 格式时间返回前台yyyy-mm-dd hh:mm:ss
func ConvertTimeToString(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
}
//ConvertStringToTime 格式时间返回前台yyyy-mm-dd hh:mm:ss
func ConvertStringToTime(t string) time.Time {
t2, _ := time.Parse("2006-01-02 15:04:05", t)
return t2
}
//ConvertTimeToStringMin 格式时间返回前台yyyy-mm-dd hh:mm:ss
func ConvertTimeToStringMin(t time.Time) string {
return t.Format("2006-01-02 15:04")
}
//ConvertTimeToStringDay 格式时间返回前台yyyy-mm-dd
func ConvertTimeToStringDay(t time.Time) string {
return t.Format("2006-01-02")
}
//ConvertTimeToString2 格式时间返回前台yyyy.mm.dd hh:mm 2020.06.02 17:50
func ConvertTimeToString2(t time.Time) string {
return t.Format("2006.01.02 15:04")
}
//ConvertTimeToStringYear 格式时间返回前台mm-dd yyyy
func ConvertTimeToStringYear(s time.Time) string {
return s.Format("01-02 2006")
}

295
pkg/udunhelper/udun.go Normal file
View File

@ -0,0 +1,295 @@
package udunhelper
import (
"crypto/md5"
"crypto/tls"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
log "github.com/go-admin-team/go-admin-core/logger"
ext "go-admin/config"
"go-admin/pkg/jsonhelper"
"go-admin/pkg/utility/seqs"
"io"
"net/http"
"strings"
"time"
"go.uber.org/zap"
)
const (
createAddressUrl = "/mch/address/create"
withdrawUrl = "/mch/withdraw"
checkAddressUrl = "/mch/check/address"
supportCoinsUrl = "/mch/support-coins"
existAddressUrl = "/mch/exist/address"
Notify = "/api/v1/line/notify"
)
var (
clientUDun *http.Client
)
func init() {
t := http.DefaultTransport.(*http.Transport).Clone()
t.MaxIdleConns = 100
t.MaxConnsPerHost = 10000
t.MaxIdleConnsPerHost = 100
t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
//u盾client
clientUDun = &http.Client{
Timeout: 50 * time.Second, //time.Duration(timeout) * time.Second,
Transport: t,
}
//UdKey = config.AppGlobalConfig.UDunKey // "d333ae13beb3a96c0225847098267cf3"
//UdMerchantId = config.AppGlobalConfig.UDunMerchantID // "311129"
//CallUrl = config.AppGlobalConfig.CurrServerIp + "/api/recharge/notify" // "https://8.218.110.85/api/recharge/notify" // 回调地址
//baseUrl = config.AppGlobalConfig.UDunUrl //"https://sig10.udun.io"
}
/* 回调例子
"timestamp": 1535005047,
"nonce": 100000,
"sign": "e1bee3a417b9c606ba6cedda26db761a",
"body": "{\"address\":\"DJY781Z8qbuJeuA7C3McYivbX8kmAUXPsW\",\"amount\":\"12345678\",\"blockHigh\":\"102419\",\"coinType\":\"206\",\"decimals\":\"8\",\"fee\":\"452000\",
\"mainCoinType\":\"206\",\"status\":3,\"tradeId\":\"20181024175416907\",\"tradeType\":1,\"txId\":\"31689c332536b56a2246347e206fbed2d04d461a3d668c4c1de32a75a8d436f0\"}"
*/
// GetSupportCoinsByMerchant 获取商家支持币种
func GetSupportCoinsByMerchant(baseReq BaseRequest, showBalance bool) BaseCoinsMerchant {
mp := make(map[string]interface{}, 0)
//mp["merchantId"] = config.AppGlobalConfig.UDunMerchantID
mp["merchantId"] = ext.ExtConfig.UDunConfig.UDunMerchantID
mp["showBalance"] = showBalance
reqBody, _ := json.Marshal(mp)
sign := Signature(baseReq.Nonce, baseReq.Timestamp, string(reqBody))
url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, supportCoinsUrl)
re, err := sendRequestUDun(url, baseReq.Nonce, baseReq.Timestamp, string(reqBody), sign)
if err != nil {
log.Error("GetSupportCoinsByMerchant", zap.Error(err))
return BaseCoinsMerchant{}
}
var res BaseCoinsMerchant
err = jsonhelper.Unmarshal(re, &res)
if err != nil {
return BaseCoinsMerchant{}
}
return res
}
// CreateAddress 创建地址
func CreateAddress(baseReq BaseRequest, callback []*CallbackRequest) BaseCoinAddress {
for _, c := range callback {
c.MerchantId = ext.ExtConfig.UDunConfig.UDunMerchantID
c.CallUrl = ext.ExtConfig.UDunConfig.CurrServerIp + Notify // "/api/recharge/notify"
}
reqBody, _ := json.Marshal(callback)
sign := Signature(baseReq.Nonce, baseReq.Timestamp, string(reqBody))
url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, createAddressUrl)
re, err := sendRequestUDun(url, baseReq.Nonce, baseReq.Timestamp, string(reqBody), sign)
if err != nil {
log.Error("CreateAddress", zap.Error(err), zap.String("url", url))
return BaseCoinAddress{}
}
var res BaseCoinAddress
err = jsonhelper.Unmarshal(re, &res)
if err != nil {
log.Error("CreateAddress Unmarshal", zap.Error(err))
return BaseCoinAddress{}
}
return res
}
// Withdraw 提现
func Withdraw(withdraw []*WithdrawRequest) (code int, msg string) {
timeStamp := time.Now().Unix()
nonce := seqs.Rand().RandInt(600000)
// 序列化
reqBody, _ := json.Marshal(withdraw)
// 签名
sign := Signature(nonce, timeStamp, string(reqBody))
url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, withdrawUrl)
// 提交
re, err := sendRequestUDun(url, nonce, timeStamp, string(reqBody), sign)
if err != nil {
log.Error("Withdraw", zap.Error(err))
return 0, "调用失败"
}
// 反序列化
var res BaseRes
if err := jsonhelper.Unmarshal(re, &res); err != nil {
return 0, "Withdraw jsonhelper.Unmarshal失败"
}
return res.Code, getWithDrawMsg(res.Code) //res.Message
}
// CheckAddress 检查地址是否正确
func CheckAddress(baseReq BaseRequest, verify []*VerifyRequest) (code int, msg string) {
reqBody, _ := json.Marshal(verify)
sign := Signature(baseReq.Nonce, baseReq.Timestamp, string(reqBody))
url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, checkAddressUrl)
re, err := sendRequestUDun(url, baseReq.Nonce, baseReq.Timestamp, string(reqBody), sign)
if err != nil {
log.Error("CheckAddress", zap.Error(err))
return 0, "调用失败"
}
var res BaseRes
err = jsonhelper.Unmarshal(re, &res)
if err != nil {
return 0, "jsonhelper.Unmarshal失败"
}
return res.Code, res.Message
}
// ExistAddress 检查地址是否在udun存在
func ExistAddress(baseReq BaseRequest, verify []*VerifyRequest) (code int, msg string) {
reqBody, _ := json.Marshal(verify)
sign := Signature(baseReq.Nonce, baseReq.Timestamp, string(reqBody))
url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, existAddressUrl)
re, err := sendRequestUDun(url, baseReq.Nonce, baseReq.Timestamp, string(reqBody), sign)
if err != nil {
log.Error("ExistAddress", zap.Error(err))
return 0, "调用失败"
}
var res BaseRes
err = jsonhelper.Unmarshal(re, &res)
if err != nil {
return 0, "jsonhelper.Unmarshal失败"
}
return res.Code, res.Message
}
func Signature(nonce int, timestamp int64, body string) string {
msg := fmt.Sprintf("%v%v%v%v", body, ext.ExtConfig.UDunConfig.UDunKey, nonce, timestamp)
h := md5.New()
h.Write([]byte(msg))
return hex.EncodeToString(h.Sum(nil))
}
func CheckCallBackSign(timestamp string, nonce string, body string) string {
raw := body + ext.ExtConfig.UDunConfig.UDunKey + nonce + timestamp
h := md5.New()
h.Write([]byte(raw))
return hex.EncodeToString(h.Sum(nil))
}
func sendRequestUDun(url string, nonce int, timestamp int64, reqBody string, sign string) ([]byte, error) {
q := HttpRequest{Nonce: nonce, Timestamp: timestamp, Sign: sign, Body: reqBody}
body, _ := json.Marshal(q)
headers := map[string]string{}
headers["Content-Type"] = "application/json"
resp, err := newHttpRequest(url, string(body), headers)
if err != nil {
return nil, err
}
return resp, nil
}
func newHttpRequest(reqUrl, postData string, reqHeaders map[string]string) ([]byte, error) {
req, _ := http.NewRequest("POST", reqUrl, strings.NewReader(postData))
if req.Header.Get("User-Agent") == "" {
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36")
}
if reqHeaders != nil {
for k, v := range reqHeaders {
req.Header.Add(k, v)
}
}
resp, err := clientUDun.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
bodyData, err := io.ReadAll(resp.Body) // ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
arr := make([]byte, len(bodyData))
copy(arr, bodyData)
if resp.StatusCode != 200 {
return nil, errors.New(string(arr))
}
return arr, nil
}
/*
createAddressUrl 创建地址 接口返回code说明
-1 生成地址失敗
200 生成地址成功
4001 商户不存在
4005 非法參數
4045 幣種信息錯誤
4162 簽名異常
4163 簽名錯誤
4166 商戶沒有配置套餐
4168 商戶地址達到上限
4169 商戶已禁用
4175 錢包編號錯誤
4017 商戶沒有創建錢包
4176 錢包未添加支持該幣種
4188 暫不支持
4226 商戶普通賬戶被禁用
4261 商戶管理員賬戶被禁用
4262 賬戶不存在
withdrawUrl 提币 接口返回code说明
200 提幣成功
523 參數為空
581 無效的提幣金額
4005 非法參數
4014 幣種為空
4034 未找到該幣種信息
4162 簽名異常
4163 簽名錯誤
4169 商戶已被禁用
4183 到賬地址異常
4193 EOS金額小數點後超過4位長度
4214 暫無可用的幣種
4226 商戶普通賬戶被禁用
4261 商戶管理員賬戶被禁用
4284 商户不存在
4288 業務編號(BusinessId)重復,請勿重復申請
4598 傳入body中的list對象中的所有merchantId必須保持一致
4001 商户不存在
*/
//分充值回調和提幣回調, 其中提幣最多會進行兩次回調( 審核回調 + 交易結果回調)
//func SendRequest(url string, nonce int, timestamp int64, reqBody string, sign string) (code int64, msg string, extra string) {
// q := HttpRequest{Nonce: nonce, Timestamp: timestamp, Sign: sign, Body: reqBody}
// body, _ := json.Marshal(q)
// resp, err := clientUDun.Post(url, "application/json", bytes.NewBuffer(body))
// if err != nil {
// return http.StatusBadRequest, err.Error(), ""
// }
// defer resp.Body.Close()
// r, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// return http.StatusBadGateway, err.Error(), ""
// }
//
// js := gjson.ParseBytes(r)
// code = js.Get("code").Int()
// msg = js.Get("message").String()
// if js.Get("data").Exists() {
// extra = js.Get("data").String()
// }
// return
//}
// CallBackFun 網關收到交易處理結果,調用商戶提供的回調接口,通知商戶具體變化信息。該接口網關發送給您指定的回調地址的內容,處理您的業務信息。
// 分充值回調和提幣回調, 其中提幣最多會進行兩次回調( 審核回調 + 交易結果回調)
func CallBackFun(res CallBackBase) {
}

153
pkg/udunhelper/udunmodel.go Normal file
View File

@ -0,0 +1,153 @@
package udunhelper
type VerifyRequest struct {
MerchantId string `json:"merchantId"`
MainCoinType string `json:"mainCoinType"`
Address string `json:"address"`
}
type CallbackRequest struct {
MerchantId string `json:"merchantId"` //商户号
MainCoinType int `json:"mainCoinType"` //主幣種編號,使用獲取商戶幣種信息接口
CallUrl string `json:"callUrl"` //回調地址,通過該接口創建的地址,以後關於該地址的充幣信息會通過您指定的回調地址通知您
WalletId string `json:"walletId"` //錢包編號,默認根據主錢包生成地址,可不填写
Alias string `json:"alias"` //地址別名,可不填写
}
type WithdrawRequest struct {
/**
{
"address":"raadSxrUhG5EQVCY75CSGaVLWCeXd6yH6s",
"amount":"0.11",
"merchantId":"100109",
"mainCoinType":"144",
"coinType":"144",
"callUrl":"http://localhost:8080/mch/callBack",
"businessId":"15",
"memo":"10112"
}
*/
Address string `json:"address"` //提币地址
Amount string `json:"amount"` //提币数量
MerchantId string `json:"merchantId"` //商户号
MainCoinType string `json:"mainCoinType"` //主幣種編號,使用獲取商戶幣種信息接口
CoinType string `json:"coinType"` //子幣種編號,使用獲取商戶幣種信息接口
CallUrl string `json:"callUrl"` //回調地址通過該callUrl告知您該筆提幣交易的狀態
BusinessId string `json:"businessId"` //業務編號,必須保證該字段在系統內唯一,如果重復,則該筆提幣錢包將不會進行接收
WalletId string `json:"-"`
Memo string `json:"memo"` //備註,XRP和EOS這兩種幣的提幣申請該字段可選其他類型幣種不填
}
type BaseRequest struct {
Timestamp int64 `json:"timestamp"`
Nonce int `json:"nonce"`
}
type HttpRequest struct {
Timestamp int64 `json:"timestamp"` //时间戳
Nonce int `json:"nonce"` //随机数
Sign string `json:"sign"` //签名
Body string `json:"body"` //请求body
}
type BaseRes struct {
Code int `json:"code"` //200才是返回成功调用
Message string `json:"message"` //
}
type BaseCoinsMerchant struct {
Code int `json:"code"` //200才是返回成功调用
Message string `json:"message"` //
Data []CoinsMerchant `json:"data"` //
}
//CoinsMerchant 商家持仓的币种信息
type CoinsMerchant struct {
MainCoinType string `json:"mainCoinType"` //主幣種類型
CoinType string `json:"coinType"` // 幣種類型
Symbol string `json:"symbol"` //BTC
Name string `json:"name"` //幣種別名,BTC
Logo string `json:"logo"` //幣種log地址
CoinName string `json:"coinName"` //幣種全稱,Bitcoin
MainSymbol string `json:"mainSymbol"` //主幣種單位
Decimals string `json:"decimals"` //幣種精度,8
TokenStatus int `json:"tokenStatus"` //0 主幣 1代幣
Balance int64 `json:"balance"` //幣種余額
}
type BaseCoinAddress struct {
Code int `json:"code"` //200才是返回成功调用
Message string `json:"message"` //
Data CoinAddress `json:"data"` //
}
//CoinAddress 地址返回
type CoinAddress struct {
CoinType int `json:"coinType"` // 幣種類型
Address string `json:"address"` //地址
}
type CallBackBase struct {
TimeStamp int64 `json:"timestamp"`
Nonce int `json:"nonce"`
Sign string `json:"sign"`
Body CallBackRes `json:"body"` //
}
//CallBackRes 回调结构体
type CallBackRes struct {
Address string `json:"address"` //地址
Amount string `json:"amount"` //交易數量,根據幣種精度獲取實際金額,實際金額=amount/pow(10,decimals)即實際金額等於amount除以10的decimals次方
Fee string `json:"fee"` //礦工費,根據幣種精度獲取實際金額,實際金額獲取同上
Decimals string `json:"decimals"` //幣種精度
CoinType string `json:"coinType"` //子幣種編號,使用獲取商戶幣種信息接口
MainCoinType string `json:"mainCoinType"` //主幣種類型
BusinessId string `json:"businessId"` //業務編號,提幣回調時為提幣請求時傳入的,充幣回調無值
BlockHigh string `json:"blockHigh"` //區塊高度
TradeId string `json:"tradeId"` //業務流水號
TxId string `json:"txid"` //區塊鏈交易哈希
Memo string `json:"memo"` //備註XRP和EOS使用獲取商戶幣種信息接口這2種類型幣的充提幣可能有值
Status int `json:"status"` //狀態,0待審核,1審核成功,2審核駁回,3交易成功,4交易失敗
TradeType int `json:"tradeType"` //交易類型,1充幣回調,2提幣回調
}
func getWithDrawMsg(code int) string {
msg := ""
switch code {
case 200:
msg = "提币成功"
case 523:
msg = "参数为空"
case 581:
msg = "无效的提币金额"
case 4005:
msg = "非法参数"
case 4034:
msg = "未找到该币种信息"
case 4162:
msg = "签名异常"
case 4163:
msg = "签名错误"
case 4169:
msg = "商户已被禁用"
case 4183:
msg = "到账地址异常"
case 4193:
msg = "EOS金额小数点后超过4位长度"
case 4214:
msg = "暂无可用的币种"
case 4226:
msg = "商户普通账户被禁用"
case 4261:
msg = "商户管理员账户被禁用"
case 4284:
msg = "商户不存在"
case 4288:
msg = "业务编号重复,请勿重复申请"
case 4598:
msg = "传入body中的list对象中的所有merchantId必须保持一致"
case 4001:
msg = "商户不存在"
}
return msg
}

View File

@ -0,0 +1,35 @@
package biginthelper
import (
"math/big"
"regexp"
"strconv"
)
// ParseBigInt 将字符串转换为 big.Int
func ParseBigInt(value string) *big.Int {
bi := new(big.Int)
i, ok := bi.SetString(value, 10)
if !ok {
return bi
}
return i
}
// IntToHex convert int to hexadecimal representation
// int64转换为16进制字符串
func IntToHex(i int64) string {
return strconv.FormatInt(i, 16)
}
// BigToHex 将bigint转化为16进制带 0x 的字符串
func BigToHex(bigInt big.Int) string {
return "0x" + IntToHex(bigInt.Int64())
}
// CheckIsAddress Check is a eth Address
// 检查是否是以太坊地址 正则匹配
func CheckIsAddress(addr string) bool {
re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$")
return re.MatchString(addr)
}

View File

@ -0,0 +1,55 @@
package biginthelper
import (
"fmt"
"math/rand"
"strconv"
"strings"
"testing"
)
func Test_ParseBigInt(t *testing.T) {
fmt.Println(ParseBigInt(`231513`))
fmt.Println(ParseBigInt(`654wdf16`))
fmt.Println(ParseBigInt(`5455_1655`))
fmt.Println(ParseBigInt(``))
fmt.Println(ParseBigInt(`af`))
}
func Test_IntToHex(t *testing.T) {
fmt.Println(strings.TrimLeft(`01615`, "0"))
fmt.Println(strings.TrimLeft(`1615`, "0"))
fmt.Println(strings.TrimLeft(`0x1615`, "0"))
}
// i := int64(32)
// s := strconv.FormatInt(i, 16)
// println(s)
// 对比strconv.FormatInt(i, 16)和fmt.Sprintf("0x%x", i)的性能消耗
// go test -bench=_QE_ -benchmem
// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据
func Benchmark_QE_strconv(b *testing.B) {
for i := 0; i < b.N; i++ {
strconv.FormatInt(getInt64(), 16)
}
}
func Benchmark_QE_fmt(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Sprintf("0x%x", getInt64())
}
}
func getInt64() int64 {
return int64(rand.Intn(10000))
}
// 结果
// Benchmark_QE_strconv-6 47142570 24.29 ns/op 5 B/op 1 allocs/op
// Benchmark_QE_fmt-6 14787649 82.41 ns/op 8 B/op 1 allocs/op
// 改为随机数后
// Benchmark_QE_strconv-6 27890760 42.48 ns/op 3 B/op 0 allocs/op
// Benchmark_QE_fmt-6 10595380 108.6 ns/op 15 B/op 1 allocs/op
// 结论 尽量使用 strconv.FormatInt(i, 16) 进行16进制的转换

View File

@ -0,0 +1,119 @@
package biginthelper
import (
"bytes"
"fmt"
"math/big"
"strings"
)
// BigIntString BigIntString
func BigIntString(balance *big.Int, decimals int64) string {
amount := BigIntFloat(balance, decimals)
deci := fmt.Sprintf("%%0.%vf", decimals)
return clean(fmt.Sprintf(deci, amount))
}
// BigIntFloat BigIntFloat
func BigIntFloat(balance *big.Int, decimals int64) *big.Float {
if balance.Sign() == 0 {
return big.NewFloat(0)
}
bal := new(big.Float)
bal.SetInt(balance)
pow := bigPow(10, decimals)
p := big.NewFloat(0)
p.SetInt(pow)
bal.Quo(bal, p)
return bal
}
func bigPow(a, b int64) *big.Int {
r := big.NewInt(a)
return r.Exp(r, big.NewInt(b), nil)
}
func clean(newNum string) string {
stringBytes := bytes.TrimRight([]byte(newNum), "0")
newNum = string(stringBytes)
if stringBytes[len(stringBytes)-1] == 46 {
newNum += "0"
}
if stringBytes[0] == 46 {
newNum = "0" + newNum
}
return newNum
}
// GetActualHex 获取真实的十六进制..
func GetActualHex(h string) string {
h = strings.TrimLeft(h, "0")
var hex string
if strings.Index(h, "0x") == 0 {
hex = h[2:]
} else {
hex = h
}
if len(h)%2 != 0 {
hex = "0" + hex
}
return "0x" + hex
}
// HexToBig HexToBig
func HexToBig(h string) *big.Int {
i := big.NewInt(0)
h = strings.Replace(h, "0x", "", -1)
if h == "" {
return i
}
if _, ok := i.SetString(h, 16); !ok {
return nil
}
return i
}
// ConvertNumToFloat ConvertNumToFloat
func ConvertNumToFloat(num int64) float64 {
switch num {
case 1:
return 10.0
case 2:
return 100.0
case 3:
return 1000.0
case 4:
return 10000.0
case 5:
return 100000.0
case 6:
return 1000000.0
case 7:
return 10000000.0
case 8:
return 100000000.0
case 9:
return 1000000000.0
case 10:
return 10000000000.0
case 11:
return 100000000000.0
case 12:
return 1000000000000.0
case 13:
return 10000000000000.0
case 14:
return 100000000000000.0
case 15:
return 1000000000000000.0
case 16:
return 10000000000000000.0
case 17:
return 100000000000000000.0
case 18:
return 1000000000000000000.0
default:
return 0.0
}
}

View File

@ -0,0 +1,197 @@
package cmap
import (
"sync"
)
var SHARD_COUNT = 32
// 一个分片的map存储器 可并发
const ShardCount = 31 // 分区数量
// ConcurrentMap A "thread" safe map of type string:Anything.
// To avoid lock bottlenecks this map is dived to several (ShardCount) map shards.
type ConcurrentMap []*ConcurrentMapShared // 分片存储map 可并发
// ConcurrentMapShared A "thread" safe string to anything map.
type ConcurrentMapShared struct {
items map[string]interface{}
sync.RWMutex // Read Write mutex, guards access to internal map.
}
// New Creates a new concurrent map.
func New() ConcurrentMap {
m := make(ConcurrentMap, SHARD_COUNT)
for i := 0; i < SHARD_COUNT; i++ {
m[i] = &ConcurrentMapShared{items: make(map[string]interface{})}
}
return m
}
// GetNoLock retrieves an element from map under given key.
func (m ConcurrentMap) GetNoLock(shard *ConcurrentMapShared, key string) (interface{}, bool) {
// Get item from shard.
val, ok := shard.items[key]
return val, ok
}
// SetNoLock retrieves an element from map under given key.
func (m ConcurrentMap) SetNoLock(shard *ConcurrentMapShared, key string, value interface{}) {
shard.items[key] = value
}
// NewConcurrentMap 创建
func NewConcurrentMap() ConcurrentMap {
m := make(ConcurrentMap, ShardCount)
for i := 0; i < ShardCount; i++ {
m[i] = &ConcurrentMapShared{items: make(map[string]interface{})}
}
return m
}
// GetShard 返回给定键下的分片
func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared {
return m[fnv32(key)&ShardCount]
}
// MSet 存储一组map
func (m ConcurrentMap) MSet(data map[string]interface{}) {
for key, value := range data {
shard := m.GetShard(key)
shard.Lock()
shard.items[key] = value
shard.Unlock()
}
}
// Set the given value under the specified key.
// 在指定的键下设置给定的值。
func (m ConcurrentMap) Set(key string, value interface{}) {
// Get map shard.
shard := m.GetShard(key)
shard.Lock()
shard.items[key] = value
shard.Unlock()
}
// UpsertCb Callback to return new element to be inserted into the map
// It is called while lock is held, therefore it MUST NOT
// try to access other keys in same map, as it can lead to deadlock since
// Go sync.RWLock is not reentrant
// 回调函数返回新元素插入到映射中。它在锁被持有时被调用,因此它一定不要试图访问同一映射中的其他键,因为它可能导致死锁。 RWLock是不可重入的
type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{}
// Upsert Insert or Update - updates existing element or inserts a new one using UpsertCb
// 插入或更新——使用UpsertCb更新现有元素或插入新元素
func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res interface{}) {
shard := m.GetShard(key)
shard.Lock()
v, ok := shard.items[key]
res = cb(ok, v, value)
shard.items[key] = res
shard.Unlock()
return res
}
// SetIfAbsent Sets the given value under the specified key if no value was associated with it.
// 如果没有值与指定键关联,则在指定键下设置给定值。
func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool {
// Get map shard.
shard := m.GetShard(key)
shard.Lock()
_, ok := shard.items[key]
if !ok {
shard.items[key] = value
}
shard.Unlock()
return !ok
}
// Get retrieves an element from map under given key.
func (m ConcurrentMap) Get(key string) (interface{}, bool) {
shard := m.GetShard(key)
shard.RLock()
val, ok := shard.items[key]
shard.RUnlock()
return val, ok
}
// Count returns the number of elements within the map.
func (m ConcurrentMap) Count() int {
count := 0
for i := 0; i < ShardCount; i++ {
shard := m[i]
shard.RLock()
count += len(shard.items)
shard.RUnlock()
}
return count
}
// Has Looks up an item under specified key 存在性
func (m ConcurrentMap) Has(key string) bool {
shard := m.GetShard(key)
shard.RLock()
_, ok := shard.items[key]
shard.RUnlock()
return ok
}
// Remove removes an element from the map. 移除
func (m ConcurrentMap) Remove(key string) {
// Try to get shard.
shard := m.GetShard(key)
shard.Lock()
delete(shard.items, key)
shard.Unlock()
}
// RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held
// If returns true, the element will be removed from the map
// 是一个在map.RemoveCb()调用中执行的回调函数当Lock被持有时如果返回true该元素将从map中移除
type RemoveCb func(key string, v interface{}, exists bool) bool
// RemoveCb locks the shard containing the key, retrieves its current value and calls the callback with those params
// If callback returns true and element exists, it will remove it from the map
// Returns the value returned by the callback (even if element was not present in the map)
// 如果callback返回true且element存在则将其从map中移除。返回callback返回的值(即使element不存在于map中)
func (m ConcurrentMap) RemoveCb(key string, cb RemoveCb) bool {
shard := m.GetShard(key)
shard.Lock()
v, ok := shard.items[key]
remove := cb(key, v, ok)
if remove && ok {
delete(shard.items, key)
}
shard.Unlock()
return remove
}
// Pop removes an element from the map and returns it
// 从映射中移除一个元素并返回它
func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) {
// Try to get shard.
shard := m.GetShard(key)
shard.Lock()
v, exists = shard.items[key]
delete(shard.items, key)
shard.Unlock()
return v, exists
}
// IsEmpty checks if map is empty. 是否是空的
func (m ConcurrentMap) IsEmpty() bool {
return m.Count() == 0
}
// 将键值映射为数字uint32
func fnv32(key string) uint32 {
const prime32 = uint32(16777619)
hash := uint32(2166136261)
for i := 0; i < len(key); i++ {
hash *= prime32
hash ^= uint32(key[i])
}
return hash
}

View File

@ -0,0 +1,60 @@
package cmap
import (
"fmt"
"go-admin/pkg/utility"
"hash/crc32"
"testing"
)
// 离散性测试
func Test_fnv32(t *testing.T) {
st := make(map[uint32]int)
for i := 0; i < 1000000; i++ {
fnv := crc32.ChecksumIEEE([]byte(utility.GenerateRandString(8)))
k := fnv & 15
count, ok := st[k]
if !ok {
st[k] = 1
}
st[k] = count + 1
}
for k, v := range st {
fmt.Println(k, "\t", float64(v)/1000000)
}
}
// go test -bench=_QE_ -benchmem -run=^$
// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据
// Benchmark_QE_1-6 146641 8192 ns/op 32 B/op 3 allocs/op
// Benchmark_QE_2-6 143118 8246 ns/op 40 B/op 4 allocs/op
//
// Benchmark_QE_1-6 146289 8212 ns/op 32 B/op 3 allocs/op
// Benchmark_QE_2-6 144918 8239 ns/op 40 B/op 4 allocs/op
func Benchmark_QE_1(b *testing.B) {
for i := 0; i < b.N; i++ {
fnv32(utility.GenerateRandString(8))
}
}
func Benchmark_QE_2(b *testing.B) {
for i := 0; i < b.N; i++ {
crc32.ChecksumIEEE([]byte(utility.GenerateRandString(8)))
}
}
// go test -bench=_QE2_ -benchmem -benchtime=5s -run=^$
// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据
// Benchmark_QE2_1-6 1000000000 0.2623 ns/op 0 B/op 0 allocs/op
// Benchmark_QE2_2-6 1000000000 0.2631 ns/op 0 B/op 0 allocs/op
func Benchmark_QE2_1(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = i & 31
}
}
func Benchmark_QE2_2(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = i % 31
}
}

View File

@ -0,0 +1,160 @@
package cmap
import (
"sync"
"github.com/bytedance/sonic"
)
// 迭代器部分
// Tuple Used by the Iter & IterBuffered functions to wrap two variables together over a channel
// 由Iter & IterBuffered函数使用在一个通道上封装两个变量
type Tuple struct {
Key string
Val interface{}
}
// Iter returns an iterator which could be used in a for range loop.
// 返回一个可用于for范围循环的迭代器。
// Deprecated: using IterBuffered() will get a better performence
// 使用IterBuffered()将获得更好的性能
func (m ConcurrentMap) Iter() <-chan Tuple {
chans := snapshot(m)
ch := make(chan Tuple) // 不带缓冲
go fanIn(chans, ch)
return ch
}
// IterBuffered returns a buffered iterator which could be used in a for range loop.
// 返回一个可用于for范围循环的缓冲迭代器。
func (m ConcurrentMap) IterBuffered() <-chan Tuple {
chans := snapshot(m)
total := 0
for _, c := range chans {
total += cap(c)
}
ch := make(chan Tuple, total) // 一次性写完到缓冲中
go fanIn(chans, ch)
return ch
}
// Returns a array of channels that contains elements in each shard,
// which likely takes a snapshot of `m`.
// It returns once the size of each buffered channel is determined,
// before all the channels are populated using goroutines.
// 返回一个通道数组其中包含每个shard中的元素它可能会获取' m '的快照。
// 一旦确定了每个缓冲通道的大小在使用goroutines填充所有通道之前它将返回。
func snapshot(m ConcurrentMap) (chans []chan Tuple) {
chans = make([]chan Tuple, ShardCount)
wg := sync.WaitGroup{}
wg.Add(ShardCount)
// Foreach shard.
for index, shard := range m {
go func(index int, shard *ConcurrentMapShared) {
shard.RLock()
chans[index] = make(chan Tuple, len(shard.items))
wg.Done() // 只要创建了通道就不用再阻塞了
for key, val := range shard.items {
chans[index] <- Tuple{key, val}
}
shard.RUnlock()
close(chans[index])
}(index, shard)
}
wg.Wait()
return chans
}
// fanIn reads elements from channels `chans` into channel `out`
// 从通道' chans '读取元素到通道' out '
func fanIn(chans []chan Tuple, out chan Tuple) {
wg := sync.WaitGroup{}
wg.Add(len(chans))
for _, ch := range chans {
go func(ch chan Tuple) {
for t := range ch {
out <- t
}
wg.Done()
}(ch)
}
wg.Wait()
close(out)
}
// Items returns all items as map[string]interface{}
// 返回所有条目作为map[string]interface{}
func (m ConcurrentMap) Items() map[string]interface{} {
tmp := make(map[string]interface{})
// Insert items to temporary map. 向临时映射中插入项目。
for item := range m.IterBuffered() {
tmp[item.Key] = item.Val
}
return tmp
}
// IterCb Iterator callback,called for every key,value found in
// maps. RLock is held for all calls for a given shard
// therefore callback sess consistent view of a shard,
// but not across the shards
// 迭代器回调函数在map中找到的每个键和值都会被调用。
// RLock对给定分片的所有调用都保持因此回调获得一个分片的一致视图但不跨分片
type IterCb func(key string, v interface{})
// IterCb Callback based iterator, cheapest way to read all elements in a map.
// 基于回调的迭代器,读取映射中所有元素的最便宜方法。
func (m ConcurrentMap) IterCb(fn IterCb) {
for idx := range m {
shard := (m)[idx]
shard.RLock()
for key, value := range shard.items {
fn(key, value)
}
shard.RUnlock()
}
}
// Keys returns all keys as []string
// 返回所有键为[]字符串
func (m ConcurrentMap) Keys() []string {
count := m.Count()
ch := make(chan string, count)
go func() {
// Foreach shard.
wg := sync.WaitGroup{}
wg.Add(ShardCount)
for _, shard := range m {
go func(shard *ConcurrentMapShared) {
// Foreach key, value pair.
shard.RLock()
for key := range shard.items {
ch <- key
}
shard.RUnlock()
wg.Done()
}(shard)
}
wg.Wait()
close(ch)
}()
// Generate keys
keys := make([]string, 0, count)
for k := range ch {
keys = append(keys, k)
}
return keys
}
// MarshalJSON Reviles ConcurrentMap "private" variables to json marshal.
// 将存储的所有数据json序列化输出
func (m ConcurrentMap) MarshalJSON() ([]byte, error) {
tmp := make(map[string]interface{})
for item := range m.IterBuffered() {
tmp[item.Key] = item.Val
}
return sonic.Marshal(tmp)
}

View File

@ -0,0 +1,89 @@
package utility
import (
"math/rand"
"time"
"github.com/shopspring/decimal"
)
func StrToDecimal(data string) decimal.Decimal {
result, _ := decimal.NewFromString(data)
return result
}
// DecimalCutStr 按保留的小数点位数,去掉多余的小数 非四舍五入
func DecimalCutStr(num decimal.Decimal, size int32) string {
if num.Cmp(decimal.Zero) == 0 {
return `0`
}
str := num.Truncate(size)
result := str.String()
return result
}
// Random 生成一个在 [start, end] 范围内的随机数,保留一位小数
// start 开始数字
// end 结束数字
// floatNum 保留小数位数
func DecimalRandom(start, end decimal.Decimal, floatNum int) decimal.Decimal {
// 创建一个本地随机数生成器
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// 将 start 和 end 转换为浮点数
startFloat, _ := start.Float64()
endFloat, _ := end.Float64()
// 生成随机数
randomFloat := startFloat + r.Float64()*(endFloat-startFloat)
// 保留一位小数
randomDecimal := decimal.NewFromFloat(randomFloat).Round(int32(floatNum))
return randomDecimal
}
// DiscardDecimal 舍弃 decimal 类型的最后指定位数小数
// value: 输入的 decimal 值
// discardDigits: 需要舍弃的小数位数
func DiscardDecimal(value decimal.Decimal, discardDigits int32) decimal.Decimal {
// 如果 discardDigits 小于 0直接返回原值
if discardDigits < 0 {
return value
}
// 获取当前值的小数位数
currentPrecision := value.Exponent() * -1
// 获取整数部分
integerPart := value.Truncate(0)
// 如果小数位数超过一位且小于两位
if currentPrecision > 1 && currentPrecision < 2 {
// 如果有整数部分,截断一位小数
if !integerPart.IsZero() {
return value.Truncate(currentPrecision - 1)
}
// 如果没有整数部分,按正常流程处理
}
// 如果小数位数只有一位
if currentPrecision == 1 {
// 如果有整数部分,返回整数部分
if !integerPart.IsZero() {
return integerPart
}
// 如果没有整数部分,保持原数据不变
return value
}
// 如果小数位数超过两位,按正常流程处理
if currentPrecision > discardDigits {
precision := currentPrecision - discardDigits
return value.Truncate(precision)
}
return value
}

301
pkg/utility/floathelper.go Normal file
View File

@ -0,0 +1,301 @@
package utility
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/shopspring/decimal"
)
// Float64CutString 保留size位小数 不足则填充0
func Float64CutString(num float64, size int32) string {
de := decimal.NewFromFloat(num).Truncate(size)
return de.StringFixed(size)
}
func FloatAddCutFixStr(num1, num2 float64, size int32) string {
result := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2)).Truncate(size)
return result.StringFixed(size)
}
// FloatCut 按保留的小数点位数,去掉多余的小数
func FloatCut(num float64, size int32) float64 {
de := decimal.NewFromFloat(num)
str := de.Truncate(size)
result, _ := str.Float64()
return result
}
// FloatCutStr 按保留的小数点位数,去掉多余的小数 非四舍五入
func FloatCutStr(num float64, size int32) string {
if num == 0 {
return `0`
}
de := decimal.NewFromFloat(num)
str := de.Truncate(size)
result := str.String()
return result
}
// StringFloat64Cut 保留8为小数后边为0不截取
func StringFloat64Cut(num string, size int32) string {
// 清理输入字符串,去除空字符和其他非数字字符
if strings.Contains(num, "x00") {
fmt.Sprintf("打印信息", num)
}
cleanedNum := strings.TrimRight(num, "\x00") // 去除空字符
cleanedNum = strings.TrimSpace(cleanedNum) // 去除空格
cleanedNum = strings.ReplaceAll(strings.ReplaceAll(cleanedNum, ",", ""), "\x00", "") // 去除逗号
de, err := decimal.NewFromString(cleanedNum)
if err != nil {
return ""
}
return de.Truncate(size).String()
}
// StrToFloatCut 按保留的小数点位数,去掉多余的小数 非四舍五入
func StrToFloatCut(num string, size int32) float64 {
if num == "" {
return 0
}
de, _ := decimal.NewFromString(num)
str := de.Truncate(size)
result, _ := str.Float64()
return result
}
// FloatThousand 对float进行千分位处理返回字符串,比如2568965463.256545 => 2,568,965,463.256545
func FloatThousand(num float64) string {
if num <= 1000 {
return decimal.NewFromFloat(num).String()
}
n := decimal.NewFromFloat(num).String()
dec := ""
if strings.Index(n, ".") != -1 {
dec = n[strings.Index(n, ".")+1:]
n = n[0:strings.Index(n, ".")]
}
for i := 0; i <= len(n); i = i + 4 {
a := n[0 : len(n)-i]
b := n[len(n)-i:]
n = a + "," + b
}
if n[0:1] == "," {
n = n[1:]
}
if n[len(n)-1:] == "," {
n = n[0 : len(n)-1]
}
if dec != "" {
n = n + "." + dec
}
return n
}
// Float8ToString 按保留的小数点8位数,去掉多余的小数, return string
func Float8ToString(num float64) string {
return FloatToString(num, 8)
}
// FloatAdd float + float
func FloatAdd(num1, num2 float64) float64 {
result := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2))
f, _ := result.Float64()
return f
}
func FloatAddCutStr(num1, num2 float64, size int32) string {
result := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2))
return result.Truncate(size).String()
}
func FloatAddCut(num1, num2 float64, size int32) float64 {
result := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2))
f, _ := result.Truncate(size).Float64()
return f
}
// FloatSub float - float
func FloatSub(num1, num2 float64) float64 {
if num2 == 0 {
return num1
}
result := decimal.NewFromFloat(num1).Sub(decimal.NewFromFloat(num2))
f, _ := result.Float64()
return f
}
// FloatSubCut float - float
func FloatSubCut(num1, num2 float64, size int32) float64 {
if num2 == 0 {
return num1
}
result := decimal.NewFromFloat(num1).Sub(decimal.NewFromFloat(num2))
f, _ := result.Truncate(size).Float64()
return f
}
// FloatSubCutStr float - float
func FloatSubCutStr(num1, num2 float64, size int32) string {
if num2 == 0 {
return decimal.NewFromFloat(num1).Truncate(size).String()
}
result := decimal.NewFromFloat(num1).Sub(decimal.NewFromFloat(num2))
f := result.Truncate(size).String()
return f
}
// FloatDiv float / float 两数相除
func FloatDiv(num1, num2 float64) float64 {
result := decimal.NewFromFloat(num1).Div(decimal.NewFromFloat(num2))
f, _ := result.Float64()
return f
}
func FloatDivCutStr(num1, num2 float64, size int32) string {
result := decimal.NewFromFloat(num1).Div(decimal.NewFromFloat(num2))
result = result.Truncate(size)
s := result.String()
return s
}
func FloatDivCutFixStr(num1, num2 float64, size int32) string {
result := decimal.NewFromFloat(num1).Div(decimal.NewFromFloat(num2))
return result.Truncate(size).StringFixed(size)
}
func FloatDivCut(num1, num2 float64, size int32) float64 {
result := decimal.NewFromFloat(num1).Div(decimal.NewFromFloat(num2))
result = result.Truncate(size)
f, _ := result.Float64()
return f
}
// FloatMul float * float
func FloatMul(num1, num2 float64) float64 {
result := decimal.NewFromFloat(num1).Mul(decimal.NewFromFloat(num2))
f, _ := result.Float64()
return f
}
// FloatMulCut 两数相乘并返回小数点后size位的float64
func FloatMulCut(num1, num2 float64, size int32) float64 {
result := decimal.NewFromFloat(num1).Mul(decimal.NewFromFloat(num2))
result = result.Truncate(size)
f, _ := result.Float64()
return f
}
// FloatMulCutStr float * float 两数相乘并返回指定小数位数的float64 返回字符串
func FloatMulCutStr(num1, num2 float64, size int32) string {
result := decimal.NewFromFloat(num1).Mul(decimal.NewFromFloat(num2))
result = result.Truncate(size)
return result.String()
}
// FloatMulCutFixStr float * float 两数相乘并返回指定小数位数的float64 返回字符串
func FloatMulCutFixStr(num1, num2 float64, size int32) string {
result := decimal.NewFromFloat(num1).Mul(decimal.NewFromFloat(num2))
result = result.Truncate(size)
return result.StringFixed(size)
}
// GetTotalAmt 计算需要冻结的币 数量*??/价格
func GetTotalAmt(num int, price, contractVal float64, size int32) float64 {
de := decimal.NewFromInt(int64(num)).
Mul(decimal.NewFromFloat(contractVal)).
Div(decimal.NewFromFloat(price)).
Truncate(size)
result2, _ := de.Float64()
return result2
}
func GetNonce() string {
s := strconv.FormatInt(time.Now().UnixNano(), 10)[0:11]
return s
}
// IsEqual 比对2个float64 是否相等
func IsEqual(num1, num2 float64, size int32) bool {
n1 := decimal.NewFromFloat(num1).Truncate(size)
n2 := decimal.NewFromFloat(num2).Truncate(size)
return n1.Equal(n2)
}
// GetDealAmt 根据下单张数,下单总的冻结金额,计算本次成交金额
func GetDealAmt(num, totalNum int, totalAmt float64, size int32) float64 {
if num == totalNum {
return totalAmt
}
de := decimal.NewFromFloat(totalAmt).
Div(decimal.NewFromInt(int64(num))).
Mul(decimal.NewFromInt(int64(num))).
Truncate(size)
result2, _ := de.Float64()
return result2
}
func ToFloat64(v interface{}) float64 {
if v == nil {
return 0.0
}
switch v.(type) {
case float64:
return v.(float64)
case string:
vStr := v.(string)
vF, _ := strconv.ParseFloat(vStr, 64)
return vF
default:
panic("to float64 error.")
}
}
func ToInt(v interface{}) int {
if v == nil {
return 0
}
switch v.(type) {
case string:
vStr := v.(string)
vInt, _ := strconv.Atoi(vStr)
return vInt
case int:
return v.(int)
case float64:
vF := v.(float64)
return int(vF)
default:
panic("to int error.")
}
}
func ToInt64(v interface{}) int64 {
if v == nil {
return 0
}
switch v.(type) {
case float64:
return int64(v.(float64))
default:
vv := fmt.Sprint(v)
if vv == "" {
return 0
}
vvv, err := strconv.ParseInt(vv, 0, 64)
if err != nil {
return 0
}
return vvv
}
}

View File

@ -0,0 +1,14 @@
package utility
import (
"fmt"
"testing"
)
func Test_FloatCutStr(t *testing.T) {
fmt.Println(FloatCutStr(10, -1))
}
func TestFloat64CutString(t *testing.T) {
xx := Float64CutString(1002277.51198900, 3)
fmt.Println(xx)
}

76
pkg/utility/idhelper.go Normal file
View File

@ -0,0 +1,76 @@
package utility
import (
"github.com/rs/xid"
"go.uber.org/zap"
"fmt"
"math"
"math/rand"
"time"
log "github.com/go-admin-team/go-admin-core/logger"
)
// GetXid Package xid is a globally unique id generator library
// 包xid是一个全局唯一的id生成器库
func GetXid() string {
return xid.New().String()
}
// GetGuid 获取guid 基于时间戳和MAC地址的uuid 可以视为随机字符串
//func GetGuid() string {
// return uuid.NewV1().String()
//}
//var orderLock sync.Mutex
// GetOrderNo 获取订单id 看样子已经废弃 改用采用雪花算法获取了
//func GetOrderNo() string {
// orderLock.Lock()
// defer orderLock.Unlock()
// date := time.Now().Format("200601021504")
// code := fmt.Sprintf("%s%07d", date, time.Now().Nanosecond()/100)
// time.Sleep(30 * time.Millisecond)
// return code
//}
// GetRandIntStr 生成len位的随机数字
func GetRandIntStr(len int, prefix string) string {
rand.Seed(time.Now().UnixNano())
num := rand.Int31n(int32(math.Pow(10, float64(len))))
x := fmt.Sprintf("%s%0*d", prefix, len, num)
return x
}
// GenerateRandString 生成指定位数的字符串
// 虽然繁琐 但理解之后就觉得很精妙
func GenerateRandString(length int) string {
var chars = []byte(`ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`) // 长度:(1,256)
rand.Seed(time.Now().UnixNano())
clen := len(chars)
maxRb := 255 - (256 % clen) // [-1,255] 255 - (256%36) = 251 避免模偏倚 为了每个字符被取到的几率相等
b := make([]byte, length)
r := make([]byte, length+(length/4)) // storage for random bytes. 存储随机字节
for i := 0; ; {
// 将随机的byte值填充到byte数组中 以供使用
if _, err := rand.Read(r); err != nil {
log.Error(`GenerateRandString`, zap.Error(err))
return ``
}
for _, rb := range r {
c := int(rb)
if c > maxRb {
// Skip this number to avoid modulo bias.跳过这个数字以避免模偏倚
continue
}
b[i] = chars[c%clen]
i++
if i == length { // 直到取到合适的长度
return string(b)
}
}
}
}

View File

@ -0,0 +1,26 @@
package utility
import (
"fmt"
"testing"
"time"
)
func Test_GenerateRandString(t *testing.T) {
for i := 0; i < 10; i++ {
t.Log(GenerateRandString(6))
time.Sleep(time.Microsecond)
}
}
func TestName(t *testing.T) {
for i := 0; i < 10; i++ {
t.Log(GetRandIntStr(6, ""))
time.Sleep(time.Microsecond)
}
// t.Log(math.Pow(10, 5))
}
func TestGetXid(t *testing.T) {
data := GetXid()
fmt.Println(data)
}

234
pkg/utility/ipnet.go Normal file
View File

@ -0,0 +1,234 @@
package utility
import (
"errors"
"net"
"net/url"
"sort"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
func GetIp(ctx *gin.Context) string {
ip := ctx.ClientIP()
if len(ip) > 0 {
return ip
}
return ""
}
// ExternalIP get external ip. 获取外部的ip
func ExternalIP() (res []string) {
inters, err := net.Interfaces()
if err != nil {
return
}
for _, inter := range inters {
if !strings.HasPrefix(inter.Name, "lo") {
addrs, err := inter.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
if ipNet, ok := addr.(*net.IPNet); ok {
if ipNet.IP.IsLoopback() || ipNet.IP.IsLinkLocalMulticast() || ipNet.IP.IsLinkLocalUnicast() {
continue
}
if ip4 := ipNet.IP.To4(); ip4 != nil {
switch true {
case ip4[0] == 10:
continue
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
continue
case ip4[0] == 192 && ip4[1] == 168:
continue
default:
res = append(res, ipNet.IP.String())
}
}
}
}
}
}
return
}
// InternalIP get internal ip. 获取内部的ip
func InternalIP() string {
inters, err := net.Interfaces()
if err != nil {
return ""
}
for _, inter := range inters {
if inter.Flags&net.FlagUp == net.FlagUp {
continue
}
if !strings.HasPrefix(inter.Name, "lo") {
addrs, err := inter.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
}
}
return ""
}
// GetFreePort gets a free port. 获得一个自由端口
func GetFreePort() (port int, err error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return 0, err
}
defer listener.Close()
addr := listener.Addr().String()
_, portString, err := net.SplitHostPort(addr)
if err != nil {
return 0, err
}
return strconv.Atoi(portString)
}
// ParseRpcAddress parses rpc address such as tcp@127.0.0.1:8972 quic@192.168.1.1:9981
func ParseRpcAddress(addr string) (network string, ip string, port int, err error) {
ati := strings.Index(addr, "@")
if ati <= 0 {
return "", "", 0, errors.New("invalid rpc address: " + addr)
}
network = addr[:ati]
addr = addr[ati+1:]
var portStr string
ip, portStr, err = net.SplitHostPort(addr)
if err != nil {
return "", "", 0, err
}
port, err = strconv.Atoi(portStr)
return network, ip, port, err
}
func ConvertMeta2Map(meta string) map[string]string {
var rt = make(map[string]string)
if meta == "" {
return rt
}
v, err := url.ParseQuery(meta)
if err != nil {
return rt
}
for key := range v {
rt[key] = v.Get(key)
}
return rt
}
func ConvertMap2String(meta map[string]string) string {
var buf strings.Builder
keys := make([]string, 0, len(meta))
for k := range meta {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
vs := meta[k]
keyEscaped := url.QueryEscape(k)
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(keyEscaped)
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(vs))
}
return buf.String()
}
// ExternalIPV4 gets external IPv4 address of this server.
func ExternalIPV4() (string, error) {
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
addrs, err := iface.Addrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an ipv4 address
}
return ip.String(), nil
}
}
return "", errors.New("are you connected to the network?")
}
// ExternalIPV6 gets external IPv6 address of this server.
func ExternalIPV6() (string, error) {
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
addrs, err := iface.Addrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To16()
if ip == nil {
continue // not an ipv4 address
}
return ip.String(), nil
}
}
return "", errors.New("are you connected to the network?")
}

View File

@ -0,0 +1,40 @@
package lockkey
import (
"fmt"
"sync"
)
type LockByKey struct {
m map[string]*sync.RWMutex
l sync.Mutex
}
func NewLockByKey() *LockByKey {
return &LockByKey{m: make(map[string]*sync.RWMutex)}
}
func (lk *LockByKey) Lock(key string) {
lk.l.Lock()
defer lk.l.Unlock()
mu, ok := lk.m[key]
if !ok {
mu = &sync.RWMutex{}
lk.m[key] = mu
}
mu.Lock()
}
func (lk *LockByKey) Unlock(key string) {
lk.l.Lock()
defer lk.l.Unlock()
mu, ok := lk.m[key]
if !ok {
panic(fmt.Sprintf("unlock non-existent key: %v", key))
}
mu.Unlock()
}

View File

@ -0,0 +1,34 @@
package lockkey
import (
"fmt"
"testing"
"time"
)
func TestLock(t *testing.T) {
lk := NewLockByKey()
// Lock and unlock the same key multiple times.
for i := 0; i < 5; i++ {
go func() {
lk.Lock("key")
fmt.Println("locked")
lk.Unlock("key")
fmt.Println("unlocked")
}()
}
// Lock and unlock different keys.
lk.Lock("key1")
fmt.Println("locked key1")
lk.Unlock("key1")
fmt.Println("unlocked key1")
lk.Lock("key2")
fmt.Println("locked key2")
lk.Unlock("key2")
fmt.Println("unlocked key2")
time.Sleep(3 * time.Second)
}

54
pkg/utility/maps.go Normal file
View File

@ -0,0 +1,54 @@
package utility
import (
"github.com/mitchellh/mapstructure"
"reflect"
"time"
)
// 映射;
type maps struct {
}
// 构建;
func Maps() *maps {
return &maps{}
}
// 转为结构体;
func (this *maps) Struct(src, dst interface{}) error {
config := &mapstructure.DecoderConfig{
WeaklyTypedInput: true,
DecodeHook: mapstructure.ComposeDecodeHookFunc(ToTimeHookFunc()),
Result: &dst,
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
if err = decoder.Decode(src); err != nil {
return err
}
return nil
}
func ToTimeHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if t != reflect.TypeOf(time.Time{}) {
return data, nil
}
switch f.Kind() {
case reflect.String:
return time.Parse(time.RFC3339, data.(string))
case reflect.Float64:
return time.Unix(0, int64(data.(float64))*int64(time.Millisecond)), nil
case reflect.Int64:
return time.Unix(0, data.(int64)*int64(time.Millisecond)), nil
default:
return data, nil
}
}
}

View File

@ -0,0 +1,47 @@
package ratecheck
import (
"github.com/juju/ratelimit"
gocache "github.com/patrickmn/go-cache"
"go-admin/pkg/utility"
"time"
)
var (
// Map of limiters with TTL
tokenBuckets = gocache.New(120*time.Minute, 1*time.Minute)
userDur = 1 * time.Second
userSize int64 = 1
orderDur = 20 * time.Second
)
// CheckRateLimit 根据key检测在规定时间内是否超过访问次数,限流
func CheckRateLimit(key string, duration time.Duration, size int64) bool {
if _, found := tokenBuckets.Get(key); !found {
tokenBuckets.Set(
key,
ratelimit.NewBucket(duration, size),
duration)
}
expiringMap, found := tokenBuckets.Get(key)
if !found {
return false
}
return expiringMap.(*ratelimit.Bucket).TakeAvailable(1) > 0
}
// CheckUserRateLimit 根据key检测在规定时间内单个用户是否超过访问次数,限流,默认5秒1次请求
func CheckUserRateLimit(userid int, methodName string) bool {
key := methodName + "-" + utility.IntTostring(userid)
return CheckRateLimit(key, userDur, userSize)
}
// 检测订单成交是否重复推送
func CheckOrderIdIsExist(tradeId string) bool {
_, found := tokenBuckets.Get(tradeId)
if !found {
tokenBuckets.Set(tradeId, true, orderDur)
}
return found
}

112
pkg/utility/regexhelper.go Normal file
View File

@ -0,0 +1,112 @@
package utility
import (
"regexp"
)
var (
emailRegexp, _ = regexp.Compile("(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)")
)
// ValidateEmail 验证邮箱
func ValidateEmail(email string) bool {
// userName := "[a-zA-Z0-9][a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]{0,62}[a-zA-Z0-9]"
// domainName := "[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?"
// topLevelDomainName := "(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*"
// pattern := "^" + userName + "@" + domainName + topLevelDomainName + "$"
// reg := regexp.MustCompile(pattern)
// match := reg.MatchString(email)
match := emailRegexp.MatchString(email)
return match
}
// ValidatePhoneNumber 手机号码验证,+91-9819882936
//
// 1. Mobile Number
// - Starts with 6,7,8,8
// - 10 digit
// - Prexix can be +91, 0
//
// 2. LandLine Number
// - {area-code}-{local number}
// - 10 digit number
// - area codes range from 2-digits to 4-digits
// - ex. 02321-238200
func ValidatePhoneNumber(num string) bool {
prefixMobileNum := `(?:(?:\+|0{0,2})91([\s-])?|[0]?)?`
mobileNum := `[6-9]\d{9}`
landLineNum := `((0)?(([1-9]\d{1}-\d{8})|([1-9]\d{2}-\d{7})|([1-9]\d{3}-\d{6})))`
pattern := "^(" + "(" + prefixMobileNum + mobileNum + ")" + "|" + landLineNum + ")$"
reg := regexp.MustCompile(pattern)
match := reg.MatchString(num)
return match
}
// ValidatePhoneForeign 外国手机
func ValidatePhoneForeign(phone string) bool {
reg, err := regexp.MatchString("^[0-9]{7}$", phone)
if err != nil {
return false
}
return reg
}
// IsMobileChina 中国手机验证
func IsMobileChina(phone string) bool {
// reg, err := regexp.MatchString("^[0-9]{11}$", phone)
reg, err := regexp.MatchString("^(13[0-9]|14[0-9]|15[0-9]|17[0-9]|18[0-9]|19[0-9])\\d{8}$", phone)
if err != nil {
return false
}
return reg
}
// ReturnEmail 替换邮箱中间几位为*号
func ReturnEmail(email string) string {
if len(email) == 0 {
return ""
}
re, _ := regexp.Compile("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)")
return re.ReplaceAllString(email, "$1****$3$4")
}
// ReturnPhoneNO 替换手机号中间四位为*
func ReturnPhoneNO(phone string) string {
if len(phone) == 0 {
return ""
}
re, _ := regexp.Compile("(\\d{3})(\\d{4})(\\d{4})")
return re.ReplaceAllString(phone, "$1****$3")
}
// CheckPasswordOk
// 密码长度minLength-maxLength位可使用字母、数字、符号组成区分大小写至少包含两种
// minLength: 指定密码的最小长度
// maxLength指定密码的最大长度
// pwd明文密码
func CheckPasswordOk(minLength, maxLength int, pwd string) bool {
if len(pwd) < minLength {
return false // fmt.Errorf("BAD PASSWORD: The password is shorter than %d characters", minLength)
}
if len(pwd) > maxLength {
return false // fmt.Errorf("BAD PASSWORD: The password is logner than %d characters", maxLength)
}
// patternList := []string{`[0-9]+`, `[a-z]+`, `[A-Z]+`, `[~!@#$%^&*?_-]+`}
isNum, _ := regexp.MatchString(`[0-9]+`, pwd)
isLower, _ := regexp.MatchString(`[a-z]+`, pwd)
isUpper, _ := regexp.MatchString(`[A-Z]+`, pwd)
//isSpe, _ := regexp.MatchString(`[~!@#$%^&*?_-]+`, pwd)
return isNum && isLower && isUpper
}
// IsAccountTwo 用户账号验证
func IsAccountTwo(phone string) bool {
// reg, err := regexp.MatchString("^[0-9]{11}$", phone)
reg, err := regexp.MatchString("^[A-Za-z\\d]{6,12}$", phone)
if err != nil {
return false
}
return reg
}

44
pkg/utility/safego.go Normal file
View File

@ -0,0 +1,44 @@
package utility
import (
"fmt"
"runtime"
"runtime/debug"
"strings"
"github.com/go-admin-team/go-admin-core/logger"
)
// SafeGo 安全地启动一个 goroutine捕获 panic
func SafeGo(fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
// 记录 Goroutine ID、panic 信息和堆栈
logger.Error(fmt.Sprintf("Recovered from panic in Goroutine %s: %v\nStack Trace:\n%s", getGoroutineID(), r, string(debug.Stack())))
}
}()
fn()
}()
}
// 获取 Goroutine ID
func getGoroutineID() string {
buf := make([]byte, 64)
n := runtime.Stack(buf, false)
stack := string(buf[:n])
// 提取 Goroutine ID
id := strings.Split(stack, " ")[1]
return id
}
func SafeGoParam[T any](fn func(T), param T) {
go func() {
defer func() {
if r := recover(); r != nil {
logger.Error(fmt.Sprintf(" SafeGoParam Recovered from panic in Goroutine %s: %v\nStack Trace:\n%s", getGoroutineID(), r, string(debug.Stack())))
}
}()
fn(param) // 执行传入的函数
}()
}

29
pkg/utility/search.go Normal file
View File

@ -0,0 +1,29 @@
package utility
import "sort"
// 二分查找(会将切片 a 排为升序)
//
// 找到返回 true未找到返回 false
func BinarySearch(a []int, x int) bool {
if !sort.IntsAreSorted(a) {
sort.Ints(a)
}
l, r := 0, len(a)-1
for l <= r {
m := (l + r) / 2
if a[m] == x {
return true
}
// x 在左边
if x < a[m] {
r = m - 1
} else {
l = m + 1
}
}
return false
}

View File

@ -0,0 +1,14 @@
package utility
import (
"fmt"
"testing"
)
func TestBinarySearch(t *testing.T) {
// a := []int{5, 4, 3, 2}
a := []int{7, 5, 3, 9, 2, 6}
dst := BinarySearch(a, 106)
fmt.Println(dst)
}

68
pkg/utility/seqs/rand.go Normal file
View File

@ -0,0 +1,68 @@
package seqs
import (
cr "crypto/rand"
"math/big"
mr "math/rand"
"time"
)
// 随机生成器;
type rand struct {
}
// 全局随机数;
var rnd *mr.Rand
func init() {
rnd = mr.New(mr.NewSource(time.Now().UnixNano()))
}
// 构建;
func Rand() *rand {
return &rand{}
}
// 随机数字;
func (rd *rand) DigitId(_len int) string {
return rd.newId([]byte("0123456789"), _len)
}
// 随机字母;
func (rd *rand) ChrtId(_len int) string {
return rd.newId([]byte("abcdefghijklmnopqrstuvwxyz"), _len)
}
// 随机混合(数字+字母);
func (rd *rand) BothId(_len int) string {
return rd.newId([]byte("0123456789abcdefghijklmnopqrstuvwxyz"), _len)
}
// 随机范围(长整型);
func (rd *rand) RandI64(min, max int64) int64 {
bi, _ := cr.Int(cr.Reader, big.NewInt(max-min))
return min + bi.Int64()
}
// 随机范围(0 ~ max);
func (rd *rand) RandInt(max int) int {
return rnd.Intn(max)
}
// 随机中文;
func (rd *rand) ChrtCn(_len int) string {
a := make([]rune, _len)
for i := range a {
a[i] = rune(rd.RandI64(19968, 40869))
}
return string(a)
}
// newId;
func (rd *rand) newId(tmpl []byte, _len int) string {
var r []byte
for i := 0; i < _len; i++ {
r = append(r, tmpl[rnd.Intn(len(tmpl))])
}
return string(r)
}

141
pkg/utility/slices.go Normal file
View File

@ -0,0 +1,141 @@
package utility
import "strings"
// ContainsStr []string 包含元素?
func ContainsStr(arr []string, v string) bool {
for _, a := range arr {
if a == v {
return true
}
}
return false
}
func RemoveByValue(slice []string, value string) []string {
for i, v := range slice {
if v == value {
// 找到值,删除对应索引
return append(slice[:i], slice[i+1:]...)
}
}
return slice // 未找到返回原切片
}
// ContainsInt []int 包含元素?
func ContainsInt(arr []int, v int) bool {
for _, a := range arr {
if a == v {
return true
}
}
return false
}
func HasSuffix(data string, suffixs []string) bool {
for _, suffix := range suffixs {
if strings.HasSuffix(data, suffix) {
return true
}
}
return false
}
// SplitSlice 将 []string 切片根据最大数量分割成二维数组
func SplitSlice(slice []string, maxSize int) [][]string {
var result [][]string
// 遍历切片,每次取 maxSize 个元素
for i := 0; i < len(slice); i += maxSize {
end := i + maxSize
// 如果 end 超出切片长度,则取到切片末尾
if end > len(slice) {
end = len(slice)
}
// 将当前段添加到结果中
result = append(result, slice[i:end])
}
return result
}
//// 切片;
//type slices struct {
//}
//
//// 构建;
//func Slices() *slices {
// return &slices{}
//}
//
//// 包含元素?
//func (this *slices) Contains(s interface{}, v interface{}) bool {
// // 相关定义;
// ss := reflect.Indirect(reflect.ValueOf(s))
// slen := ss.Len()
// // 遍历;
// for i := 0; i < slen; i++ {
// // 定位元素;
// sv := reflect.Indirect(ss.Index(i))
// if fmt.Sprint(sv.Interface()) == fmt.Sprint(v) {
// return true
// }
// }
// return false
//}
//
//// 转为切片;
//func (this *slices) Slice(s interface{}) []interface{} {
// // 相关定义;
// ss := reflect.Indirect(reflect.ValueOf(s))
// slen := ss.Len()
// // 遍历;
// out := make([]interface{}, slen)
// for i := 0; i < slen; i++ {
// // 追加;
// out[i] = ss.Index(i).Interface()
// }
// return out
//}
//
//// 校验为nil?
//func (this *slices) IsNil(v interface{}) bool {
// if v == nil {
// return true
// }
// vi := reflect.ValueOf(v)
// if vi.Kind() == reflect.Ptr {
// return vi.Elem().IsNil()
// }
// return vi.IsNil()
//}
//
//// StringSliceReflectEqual 判断 string和slice 是否相等
//// 因为使用了反射所以效率较低可以看benchmark结果
//func (this *slices) StringSliceReflectEqual(a, b []string) bool {
// return reflect.DeepEqual(a, b)
//}
//
//// StringSliceEqual 判断 string和slice 是否相等
//// 使用了传统的遍历方式
//func (this *slices) StringSliceEqual(a, b []string) bool {
// if len(a) != len(b) {
// return false
// }
//
// // reflect.DeepEqual的结果保持一致
// if (a == nil) != (b == nil) {
// return false
// }
//
// // bounds check 边界检查
// // 避免越界
// b = b[:len(a)]
// for i, v := range a {
// if v != b[i] {
// return false
// }
// }
//
// return true
//}

View File

@ -0,0 +1,30 @@
package snowflakehelper
// 雪花算法用于生成订单号
import (
"fmt"
"go-admin/config"
"github.com/bwmarrin/snowflake"
)
var (
snowNode *snowflake.Node
)
func init() {
snowflake.Epoch = 1649212361224 // time.Now().UnixMilli()
// nodeId := utility.StringAsInt64()
node, err := snowflake.NewNode(config.ExtConfig.ServiceId)
if err != nil {
fmt.Println("snowflake.NewNode err:", err)
return
}
snowNode = node
}
// GetOrderId 生成int64订单id
func GetOrderId() int64 {
return snowNode.Generate().Int64()
}

485
pkg/utility/stringhelper.go Normal file
View File

@ -0,0 +1,485 @@
package utility
import (
"fmt"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"unicode/utf8"
"unsafe"
"go.uber.org/zap"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/shopspring/decimal"
)
// 获取不超过指定长度的字段
func StringCut(s string, maxLength int) string {
if len(s) > maxLength {
return s[:maxLength] // 获取前 maxLength 个字符
}
return s // 如果长度不足,返回原字符串
}
// FloatToStringZero 去掉多余的小数0,比如0.0245000返回0.0245,return string
func FloatToStringZero(num string) string {
de, _ := decimal.NewFromString(num)
str := de.String()
return str
}
// IntTostring int to string
func IntTostring(num int) string {
if num == 0 {
return "0"
}
return strconv.Itoa(num)
}
// StringLen 获取字符串真实长度
func StringLen(s string) int {
return len([]rune(s))
}
// StringIsNil 检测字符串长度是否为0
func StringIsNil(str string) bool {
if len(str) == 0 {
return true
}
return len(strings.Trim(str, " ")) == 0
}
// StringTrim 去前后空格
func StringTrim(str string) string {
return strings.Trim(str, " ")
}
// StringAsFloat tries to convert a string to float, and if it can't, just returns zero
// 尝试将字符串转换为浮点数如果不能则返回0
func StringAsFloat(s string) float64 {
if len(s) == 0 {
return 0.0
}
if f, err := strconv.ParseFloat(s, 64); err == nil {
return f
}
return 0.0
}
func StringAsDecimal(s string) decimal.Decimal {
if len(s) == 0 {
return decimal.Zero
}
f, err := decimal.NewFromString(s)
if err != nil {
log.Error("字符串转化decimal失败: ", zap.Error(err))
return decimal.Zero
}
return f
}
func StringAsFloatCut(s string, size int32) float64 {
if len(s) == 0 {
return 0
}
f, err := decimal.NewFromString(s)
if err != nil {
log.Error("字符串转化decimal失败: ", zap.Error(err))
return 0
}
d, _ := f.Truncate(size).Float64()
return d
}
// FloatToString 按保留的小数点位数,去掉多余的小数,return string
func FloatToString(num float64, size int32) string {
if num == 0 {
return getZero(size)
}
de := decimal.NewFromFloat(num)
str := de.Truncate(size).String()
return str
}
func getZero(size int32) string {
switch size {
case 0:
return "0"
case 1:
return "0.0"
case 2:
return "0.00"
case 3:
return "0.000"
case 4:
return "0.0000"
case 5:
return "0.00000"
case 6:
return "0.000000"
}
return "0"
}
func GetGearNumStr(size int32) string {
switch size {
case 0:
return "1"
case 1:
return "0.1"
case 2:
return "0.01"
case 3:
return "0.001"
case 4:
return "0.0001"
case 5:
return "0.00001"
case 6:
return "0.000001"
case 7:
return "0.0000001"
case 8:
return "0.00000001"
}
return "0.1"
}
func FloatToStr(num float64) string {
de := decimal.NewFromFloat(num)
str := de.String()
return str
}
// StringAsInteger returns the integer value extracted from string, or zero
func StringAsInteger(s string) int {
if s == "" {
return 0
}
if i, err := strconv.ParseInt(s, 10, 32); err == nil {
return int(i)
}
return 0
}
// StringAsInt64 returns the int64 value extracted from string, or zero
func StringAsInt64(s string) int64 {
if s == "" {
return 0
}
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
return i
}
return 0
}
func StringAsInt32(s string) int32 {
if s == "" {
return 0
}
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
return int32(i)
}
return 0
}
// StringToDecimal String To Decimal
func StringToDecimal(val string) decimal.Decimal {
cleanedNum := strings.TrimRight(val, "\x00") // 去除空字符
cleanedNum = strings.TrimSpace(cleanedNum) // 去除空格
cleanedNum = strings.ReplaceAll(cleanedNum, ",", "") // 去除逗号
d, err := decimal.NewFromString(cleanedNum)
if err != nil {
return decimal.Zero
}
return d
}
// StringToInt String => int
func StringToInt(val string) int {
i, err := strconv.Atoi(val)
if err != nil {
return 0
}
return i
}
// StringToFloat64 String => Float64
func StringToFloat64(val string) float64 {
d, err := strconv.ParseFloat(val, 64)
if err != nil {
return 0
}
return d
}
// ToLower 返回小写字符串
func ToLower(str string) string {
return strings.ToLower(str)
}
// ToUpper 返回大写字符串
func ToUpper(str string) string {
return strings.ToUpper(str)
}
// IntToString int to string
func IntToString(num int) string {
if num == 0 {
return "0"
}
return strconv.Itoa(num)
}
// CheckPhone returns true if a given sequence has between 9 and 14 digits
func CheckPhone(phone string, acceptEmpty bool) bool {
phone = OnlyDigits(phone)
return (acceptEmpty && (phone == "")) || ((len([]rune(phone)) >= 9) && (len([]rune(phone)) <= 14))
}
// OnlyDigits returns only the numbers from the given string, after strip all the rest ( letters, spaces, etc. )
func OnlyDigits(sequence string) string {
if utf8.RuneCountInString(sequence) > 0 {
re, _ := regexp.Compile(`[\D]`)
sequence = re.ReplaceAllString(sequence, "")
}
return sequence
}
// CheckEmail returns true if the given sequence is a valid email address
// See https://tools.ietf.org/html/rfc2822#section-3.4.1 for details about email address anatomy
func CheckEmail(email string) bool {
if email == "" {
return false
}
re := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
return re.MatchString(email)
}
// IsNumericType checks if an interface's concrete type corresponds to some of golang native numeric types
func IsNumericType(x interface{}) bool {
switch x.(type) {
case uint:
return true
case uint8: // Or byte
return true
case uint16:
return true
case uint32:
return true
case uint64:
return true
case int:
return true
case int8:
return true
case int16:
return true
case int32:
return true
case float32:
return true
case float64:
return true
case complex64:
return true
case complex128:
return true
default:
return false
}
}
// B2S converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func B2S(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// S2B converts string to a byte slice without memory allocation.
// Note it may break if string and/or slice header will change
// in the future go versions.
func S2B(s string) (b []byte) {
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
bh.Data = sh.Data
bh.Len = sh.Len
bh.Cap = sh.Len
return b
}
// Int64ToString int64 转到string
func Int64ToString(num int64) string {
if num == 0 {
return ""
}
return strconv.FormatInt(num, 10)
}
// Float64ToString Float64 转 string
// prec: 小数位数 -1 为全保留 与 FloatCutStr 不同的是 这里会四舍五入
func Float64ToString(num float64, prec int32) string {
return strconv.FormatFloat(num, 'f', int(prec), 64)
}
// SliceRemoveDuplicates 除去[]string数组重复的数据
func SliceRemoveDuplicates(slice []string) []string {
sort.Strings(slice)
i := 0
var j int
for {
if i >= len(slice)-1 {
break
}
for j = i + 1; j < len(slice) && slice[i] == slice[j]; j++ {
}
slice = append(slice[:i+1], slice[j:]...)
i++
}
return slice
}
func GetSymbolIdStr(coinId, currId string) string {
return fmt.Sprintf("%v/%v", coinId, currId)
}
func GetSymbolCoinIdAndCurrId(symbolIdStr string) (string, string) {
l := strings.Split(symbolIdStr, "/")
return l[0], l[1]
}
// CheckIdCard 检验身份证
func CheckIdCard(card string) bool {
// 18位身份证 ^(\d{17})([0-9]|X)$
// 匹配规则
// (^\d{15}$) 15位身份证
// (^\d{18}$) 18位身份证
// (^\d{17}(\d|X|x)$) 18位身份证 最后一位为X的用户
regRuler := "(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)"
// 正则调用规则
reg := regexp.MustCompile(regRuler)
// 返回 MatchString 是否匹配
return reg.MatchString(card)
}
func FilteredSQLInject(to_match_str string) bool {
//过滤
//ORACLE 注解 -- /**/
//关键字过滤 update ,delete
// 正则的字符串, 不能用 " " 因为" "里面的内容会转义
str := `(?:')|(?:--)|(/\*(?:.|[\n\r])*?\*/)|((select|update|and|or|delete|insert|trancate|char|chr|into|substr|ascii|declare|exec|count|master|into|drop|execute))`
re, err := regexp.Compile(str)
if err != nil {
return false
}
return re.MatchString(to_match_str)
}
func TrimHtml(src string) string {
//将HTML标签全转换成小写
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
src = re.ReplaceAllStringFunc(src, strings.ToLower)
//去除STYLE
re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
src = re.ReplaceAllString(src, "")
//去除SCRIPT
re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
src = re.ReplaceAllString(src, "")
//去除所有尖括号内的HTML代码并换成换行符
re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
src = re.ReplaceAllString(src, "\n")
//去除连续的换行符
re, _ = regexp.Compile("\\s{2,}")
src = re.ReplaceAllString(src, "\n")
return strings.TrimSpace(src)
}
// ReplaceImages 过滤掉图片
func ReplaceImages(htmls string) string {
result := htmls
regx := `<img[^>]*src[="'s]+[^.]*/([^.]+).[^"']+["']?[^>]*>`
var imgRE = regexp.MustCompile(regx)
imgs := imgRE.FindAllStringSubmatch(htmls, -1)
for i := range imgs {
v := imgs[i]
result = strings.ReplaceAll(result, v[0], "")
}
return result
}
// KeySubString 文章搜索专用
func KeySubString(key, value string, leng int) string {
b := ReplaceImages(value)
pos := strings.Index(b, key)
if pos < 0 {
return ""
}
start := b[0:pos]
end := b[pos:]
out := Substr(end, leng)
return start + out
}
// Substr 截取字符串
func Substr(s string, l int) string {
if len(s) <= l {
return s
}
ss, sl, rl, rs := "", 0, 0, []rune(s)
for _, r := range rs {
rint := int(r)
if rint < 128 {
rl = 1
} else {
rl = 2
}
if sl+rl > l {
break
}
sl += rl
ss += string(r)
}
return ss
}
/*
根据字符串获取小数位数
*/
func GetPrecision(value string) int {
// 去掉末尾的多余零
value = strings.TrimRight(value, "0")
// 找到小数点的位置
decimalPos := strings.Index(value, ".")
if decimalPos == -1 {
// 如果没有小数点说明是整数精度为0
return 0
}
// 计算小数点后的位数
return len(value) - decimalPos - 1
}
// 替换字符串后缀
func ReplaceSuffix(text, oldSuffix, newSuffix string) string {
if strings.HasSuffix(text, oldSuffix) {
return text[:len(text)-len(oldSuffix)] + newSuffix
}
return text
}

View File

@ -0,0 +1,49 @@
package utility
import (
"fmt"
"math/rand"
"strconv"
"testing"
)
func Test_(t *testing.T) {
// t.Log(decimal.NewFromFloat(0))
t.Log(strconv.FormatFloat(1.04, 'f', 1, 64))
t.Log(strconv.FormatFloat(1.05, 'f', 1, 64))
t.Log(strconv.FormatFloat(1.06, 'f', 1, 64))
t.Log(strconv.FormatFloat(1.006, 'f', 2, 64))
// t.Log(strconv.FormatFloat(`1.006`, 64))
}
// i := int64(32)
// s := strconv.FormatInt(i, 16)
// println(s)
// 对比下 Float64ToString 和 FloatCutStr 的效率
// go test -bench=_QE_ -benchmem
// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据
func Benchmark_QE_1(b *testing.B) {
for i := 0; i < b.N; i++ {
Float64ToString(getRandData(), 2)
}
}
func Benchmark_QE_2(b *testing.B) {
for i := 0; i < b.N; i++ {
FloatCutStr(getRandData(), 2) // 已废弃
}
}
func getRandData() float64 {
return float64(rand.Intn(1000000)) / 1000
}
func TestTrimHtml(t *testing.T) {
a := `sff` // `<script>alert('ab')</script>`
x := TrimHtml(a)
fmt.Print(x)
}
// Benchmark_QE_1-6 4902826 243.3 ns/op 31 B/op 2 allocs/op
// Benchmark_QE_2-6 1275004 940.6 ns/op 137 B/op 11 allocs/op
// 故此将优先使用 Float64ToString 并将已使用的 FloatCutStr 进行替换

View File

@ -0,0 +1,306 @@
package timehelper
import (
"fmt"
"go-admin/pkg/utility"
"strings"
"time"
)
const (
DATEFORMAT = "2006-01-02"
TIMEFORMAT = "2006-01-02 15:04:05"
MONTHFORMAT = "2006-01"
DATETIMEFORMAT = "2006-01-02 15:04"
TimeNil = "1900-01-01 00:00:00"
DefaultUnix = -62135596800 // 时间戳默认初始值
)
// GetDateUnixDate 返回带毫秒的时间戳如果需要转化为时间类型time,
// 不带毫秒的time.Unix(1663315884651/1000,0)
// 带毫秒的time.Unix(1663315884651/1000, 1000000*(1663315884651%1000))
func GetDateUnixDate(date time.Time) int64 {
return date.UnixMilli()
}
func ConvertTimeLocalSec(date int64) time.Time {
d1 := time.Unix(date/1000, 1000000*(date%1000))
d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), d1.Nanosecond(), time.Local)
return d2
}
// TimeSubDays 时间间隔天数
func TimeSubDays(t1, t2 time.Time) int {
if t1.Location().String() != t2.Location().String() {
return -1
}
hours := t1.Sub(t2).Hours()
if hours <= 0 {
return -1
}
// sub hours less than 24
if hours < 24 {
// may same day
t1y, t1m, t1d := t1.Date()
t2y, t2m, t2d := t2.Date()
isSameDay := (t1y == t2y && t1m == t2m && t1d == t2d)
if isSameDay {
return 0
}
return 1
}
// equal or more than 24
if (hours/24)-float64(int(hours/24)) == 0 { // just 24's times
return int(hours / 24)
}
// more than 24 hours
return int(hours/24) + 1
}
// ConvertTimeLocal s
func ConvertTimeLocal(d1 time.Time) time.Time {
d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), 0, time.Local)
return d2
}
// ConvertTimeLocalLong 转本地时间戳输出
func ConvertTimeLocalLong(d1 time.Time) int64 {
d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), 0, time.Local)
return d2.Unix()
}
// ConvertTimeDayLocal s
func ConvertTimeDayLocal(d1 time.Time) time.Time {
d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), 0, 0, 0, 0, time.Local)
return d2
}
// // ToTimeHour 转为时分秒
// func ToTimeHour(ltime int64) string {
// now := IntToTime(ltime)
// return now.Format("15:04:05")
// }
// ParseTimeStrInLocal 从时间字符串解析时间,默认 当前时间
func ParseTimeStrInLocal(timeStr string, defaultT ...time.Time) time.Time {
t, err := time.ParseInLocation(TIMEFORMAT, timeStr, time.Local)
if err != nil {
if len(defaultT) > 0 {
return defaultT[0]
}
return time.Now()
}
return t
}
// IntToTime 时间戳转为时间类型
func IntToTime(intime int64) time.Time {
return time.Unix(intime/1000, 0)
}
func Int64ToTime(in int64) time.Time {
return time.Unix(in, 0)
}
// GetPastDay 当前时间算起过去num天内的开始日期、结束日期
func GetPastDay(num int) (start, end time.Time) {
tn := time.Now()
// 当前时间,天数为单位
nowday := time.Date(tn.Year(), tn.Month(), tn.Day(), 0, 0, 0, 0, time.Local)
// 过去30天
oldTime := nowday.AddDate(0, 0, -num)
return oldTime, nowday
}
// ConvertTimeToString 格式时间返回前台yyyy-mm-dd hh:mm:ss
func ConvertTimeToString(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
}
// ConvertTimeToStringMin 格式时间返回前台yyyy-mm-dd hh:mm:ss
func ConvertTimeToStringMin(t time.Time) string {
return t.Format("2006-01-02 15:04")
}
// ConvertTimeToStringDay 格式时间返回前台yyyy-mm-dd
func ConvertTimeToStringDay(t time.Time) string {
return t.Format("2006-01-02")
}
// ConvertTimeToString2 格式时间返回前台yyyy.mm.dd hh:mm 2020.06.02 17:50
func ConvertTimeToString2(t time.Time) string {
return t.Format("2006.01.02 15:04")
}
// ConvertTimeTostringYear 格式时间返回前台mm-dd yyyy
func ConvertTimeTostringYear(s time.Time) string {
return s.Format("01-02 2006")
}
func Time2TimeStampMilli(s time.Time) string {
return utility.Int64ToString(s.UnixMilli())
}
// GetWeeHours 返回若干天后的凌晨时间戳 若day=0 则返回当天凌晨的时间
// var cstSh, _ = time.LoadLocation("Asia/Shanghai") //上海时区
func GetWeeHours(day int) time.Time {
t := time.Now() // 当前时间
OnHour := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
return OnHour.AddDate(0, 0, day)
// 另一种写法
// return uint(time.Date(t.Year(), t.Month(), t.Day()+day, 0, 0, 0, 0, t.Location()).Unix())
}
// GetMonths 返回若干月后的凌晨时间戳 若months=0 则返回当月凌晨的时间
func GetMonths(months int) time.Time {
t := time.Now() // 当前时间
OnHour := time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location())
return OnHour.AddDate(0, months, 0)
}
func GetTimeFromStrDate(date string) (year, month, day int) {
d, err := time.Parse(DATEFORMAT, date)
if err != nil {
fmt.Println("出生日期解析错误!")
return 0, 0, 0
}
year = d.Year()
month = int(d.Month())
day = d.Day()
return
}
func GetAge(year int) (age int) {
if year <= 0 {
age = -1
}
nowyear := time.Now().Year()
age = nowyear - year
return
}
// GetWeekDayByNum 根据输入的数字日期返回周XX,字符串隔开的 (0,1,2,3)
func GetWeekDayByNum(weeks string, lang string) []string {
var result []string
if len(weeks) == 0 {
return result
}
weeks = strings.TrimRight(weeks, ",")
arr := strings.Split(weeks, ",")
weekMap := map[string]string{}
switch lang {
case "zh-CN":
weekMap = map[string]string{
"0": "周日",
"1": "周一",
"2": "周二",
"3": "周三",
"4": "周四",
"5": "周五",
"6": "周六",
}
case "zh-HK":
weekMap = map[string]string{
"0": "周日",
"1": "週一",
"2": "週二",
"3": "週三",
"4": "週四",
"5": "週五",
"6": "週六",
}
case "jp":
weekMap = map[string]string{
"0": "日曜日",
"1": "月曜日",
"2": "火曜日",
"3": "水曜日",
"4": "木曜日",
"5": "金曜日",
"6": "土曜日",
}
case "kr":
weekMap = map[string]string{
"0": "일요일",
"1": "월요일",
"2": "화요일",
"3": "수요일",
"4": "목요일",
"5": "금요일",
"6": "토요일",
}
default:
weekMap = map[string]string{
"0": "Sunday",
"1": "Monday",
"2": "Tuesday",
"3": "Wednesday",
"4": "Thursday",
"5": "Friday",
"6": "Saturday",
}
}
for _, a := range arr {
if value, ok := weekMap[a]; ok {
result = append(result, value)
}
}
return result
}
// 时间差计算
func GetDateSub(begin time.Time, end time.Time) time.Duration {
begin1 := GetDateFormat(begin)
end1 := GetDateFormat(end)
return end1.Sub(begin1)
}
// GetDateFormat 格式化日期用来计算时间差
func GetDateFormat(date time.Time) time.Time {
return time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 0, time.UTC)
}
// 本周一
func ThisWeek1() time.Time {
// w1 = today - (Weekday+6)%7
today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local)
factor := time.Duration((today.Weekday()+6)%7) * 24 * time.Hour
return today.Add(-factor)
}
// 本月 1号
func ThisMonthD1() time.Time {
// d1 = today - day - 1
today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local)
factor := time.Duration(today.Day()-1) * 24 * time.Hour
return today.Add(-factor)
}
// 近 xx 天
func Latest7Day(day int) time.Time {
today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local)
return today.Add(-(time.Duration(day)) * 24 * time.Hour)
}
func LatestMonth(num int) time.Time {
today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local)
return today.AddDate(0, -num, 0)
}
// 近 30 天
func Latest30Day() time.Time {
today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local)
return today.Add(-30 * 24 * time.Hour)
}
func YMDUnix(t time.Time) int64 {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()).Unix()
}

View File

@ -0,0 +1,18 @@
package timehelper
import (
"fmt"
"testing"
"time"
)
func Test_GetWeeHours(t *testing.T) {
fmt.Println(time.Unix(1672129500, 0))
fmt.Println(GetWeeHours(-1))
}
func TestGetTimeFromStrDate(t *testing.T) {
year, _, _ := GetTimeFromStrDate("2022-06-12")
age := GetAge(year)
fmt.Println(age)
}