1
This commit is contained in:
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