1
This commit is contained in:
93
pkg/coingate/coingatehelper.go
Normal file
93
pkg/coingate/coingatehelper.go
Normal 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
|
||||
}
|
||||
88
pkg/cryptohelper/aeshelper/aes256.go
Normal file
88
pkg/cryptohelper/aeshelper/aes256.go
Normal 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
|
||||
}
|
||||
107
pkg/cryptohelper/aeshelper/aeshelper.go
Normal file
107
pkg/cryptohelper/aeshelper/aeshelper.go
Normal 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)
|
||||
}
|
||||
65
pkg/cryptohelper/aeshelper/aeshelper_test.go
Normal file
65
pkg/cryptohelper/aeshelper/aeshelper_test.go
Normal 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
|
||||
}
|
||||
94
pkg/cryptohelper/aeshelper/aesoldhelper/aesoldhelper.go
Normal file
94
pkg/cryptohelper/aeshelper/aesoldhelper/aesoldhelper.go
Normal 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
|
||||
}
|
||||
19
pkg/cryptohelper/aeshelper/apikey.go
Normal file
19
pkg/cryptohelper/aeshelper/apikey.go
Normal 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
|
||||
}
|
||||
133
pkg/cryptohelper/inttostring/Inttoostr.go
Normal file
133
pkg/cryptohelper/inttostring/Inttoostr.go
Normal 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
|
||||
}
|
||||
33
pkg/cryptohelper/inttostring/Inttoostr_test.go
Normal file
33
pkg/cryptohelper/inttostring/Inttoostr_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
295
pkg/cryptohelper/jwthelper/jwthelper.go
Normal file
295
pkg/cryptohelper/jwthelper/jwthelper.go
Normal 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
|
||||
}
|
||||
17
pkg/cryptohelper/md5helper/md5helper.go
Normal file
17
pkg/cryptohelper/md5helper/md5helper.go
Normal 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))
|
||||
}
|
||||
55
pkg/cryptohelper/md5helper/md5helper_test.go
Normal file
55
pkg/cryptohelper/md5helper/md5helper_test.go
Normal 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
|
||||
181
pkg/emailhelper/emailhelper.go
Normal file
181
pkg/emailhelper/emailhelper.go
Normal 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
|
||||
}
|
||||
236
pkg/httpclient/httpclient.go
Normal file
236
pkg/httpclient/httpclient.go
Normal 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
192
pkg/httpclient/httputils.go
Normal 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
367
pkg/httputils/httputils.go
Normal 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") // 替换为目标地址和端口
|
||||
}
|
||||
36
pkg/jsonhelper/jsonhelper.go
Normal file
36
pkg/jsonhelper/jsonhelper.go
Normal 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 ""
|
||||
}
|
||||
31
pkg/jsonhelper/jsonhelper_test.go
Normal file
31
pkg/jsonhelper/jsonhelper_test.go
Normal 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
|
||||
// 确实快了很多 不知道复杂的结构会不会不一样
|
||||
119
pkg/timehelper/timehelper.go
Normal file
119
pkg/timehelper/timehelper.go
Normal 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
295
pkg/udunhelper/udun.go
Normal 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
153
pkg/udunhelper/udunmodel.go
Normal 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
|
||||
}
|
||||
35
pkg/utility/biginthelper/bighelper.go
Normal file
35
pkg/utility/biginthelper/bighelper.go
Normal 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)
|
||||
}
|
||||
55
pkg/utility/biginthelper/bighelper_test.go
Normal file
55
pkg/utility/biginthelper/bighelper_test.go
Normal 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进制的转换
|
||||
119
pkg/utility/biginthelper/biginthelper.go
Normal file
119
pkg/utility/biginthelper/biginthelper.go
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
197
pkg/utility/cmap/concurrentmap.go
Normal file
197
pkg/utility/cmap/concurrentmap.go
Normal 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
|
||||
}
|
||||
60
pkg/utility/cmap/concurrentmap_test.go
Normal file
60
pkg/utility/cmap/concurrentmap_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
160
pkg/utility/cmap/iterator.go
Normal file
160
pkg/utility/cmap/iterator.go
Normal 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)
|
||||
}
|
||||
89
pkg/utility/decimalhelper.go
Normal file
89
pkg/utility/decimalhelper.go
Normal 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
301
pkg/utility/floathelper.go
Normal 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
|
||||
}
|
||||
}
|
||||
14
pkg/utility/floathelper_test.go
Normal file
14
pkg/utility/floathelper_test.go
Normal 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
76
pkg/utility/idhelper.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
pkg/utility/idhelper_test.go
Normal file
26
pkg/utility/idhelper_test.go
Normal 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
234
pkg/utility/ipnet.go
Normal 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?")
|
||||
}
|
||||
40
pkg/utility/lockkey/lockkey.go
Normal file
40
pkg/utility/lockkey/lockkey.go
Normal 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()
|
||||
}
|
||||
34
pkg/utility/lockkey/lockkey_test.go
Normal file
34
pkg/utility/lockkey/lockkey_test.go
Normal 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
54
pkg/utility/maps.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
47
pkg/utility/ratecheck/ratecheck.go
Normal file
47
pkg/utility/ratecheck/ratecheck.go
Normal 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
112
pkg/utility/regexhelper.go
Normal 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
44
pkg/utility/safego.go
Normal 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
29
pkg/utility/search.go
Normal 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
|
||||
}
|
||||
14
pkg/utility/search_test.go
Normal file
14
pkg/utility/search_test.go
Normal 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
68
pkg/utility/seqs/rand.go
Normal 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
141
pkg/utility/slices.go
Normal 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
|
||||
//}
|
||||
30
pkg/utility/snowflakehelper/snowflakehelper.go
Normal file
30
pkg/utility/snowflakehelper/snowflakehelper.go
Normal 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
485
pkg/utility/stringhelper.go
Normal 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
|
||||
}
|
||||
49
pkg/utility/stringhelper_test.go
Normal file
49
pkg/utility/stringhelper_test.go
Normal 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 进行替换
|
||||
306
pkg/utility/timehelper/timehelper.go
Normal file
306
pkg/utility/timehelper/timehelper.go
Normal 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()
|
||||
}
|
||||
18
pkg/utility/timehelper/timehelper_test.go
Normal file
18
pkg/utility/timehelper/timehelper_test.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user