1
0
mirror of https://github.com/charlienet/go-mixed.git synced 2025-07-18 08:32:40 +08:00

33 Commits

Author SHA1 Message Date
b0a97978d8 update 2023-08-25 15:31:00 +08:00
04aecd4abc new 2022-11-21 16:18:54 +08:00
1f8789e7eb RemoveAt 2022-11-18 17:35:01 +08:00
299e09f8e3 ignore 2022-11-18 16:58:00 +08:00
ea846a321a cache 2022-11-18 16:57:23 +08:00
278c8b4cb7 maps 2022-11-18 16:57:15 +08:00
cf30b4eb4c collection 2022-11-18 16:56:56 +08:00
f3ca69f159 list 2022-11-18 16:56:44 +08:00
bd85140a78 redis 2022-11-18 16:56:12 +08:00
b76be4ce6b update 2022-11-18 16:53:59 +08:00
6e24cf5bdc boom filter 2022-11-18 16:52:43 +08:00
abe445f5e6 list 2022-11-16 17:31:28 +08:00
ac346274c1 update 2022-11-10 10:37:47 +08:00
823cd62148 update 2022-11-09 17:28:42 +08:00
d49c02924c expr 2022-11-09 17:21:30 +08:00
9203c3719f 日历相关计算 2022-11-09 17:16:24 +08:00
132bb0d0e2 add lock 2022-10-10 11:29:07 +08:00
716a199c9b 添加存储 2022-10-10 11:07:59 +08:00
f043d2e5a7 布隆过滤器 2022-09-23 10:22:23 +08:00
5b4f8097d6 update 2022-09-01 12:37:57 +08:00
1071ad3694 RegisterFuzzyDecoders 2022-08-03 17:30:15 +08:00
35751f7fdb temporary 2022-07-29 09:46:29 +08:00
135b3a983b 添加掩码格式 2022-07-28 14:27:23 +08:00
52fabedd66 up 2022-07-26 14:20:09 +08:00
792458a185 update 2022-07-26 14:19:40 +08:00
23865214c8 mod 2022-07-26 14:18:20 +08:00
ebd76d2df6 abs 2022-07-26 14:15:32 +08:00
93352f03c1 cleanup guard 2022-07-26 14:15:13 +08:00
9c86470fa1 使用netip 2022-07-26 14:14:06 +08:00
37e9cabde8 snow flake 2022-07-25 14:51:16 +08:00
886723997e 优化range引用 2022-07-04 12:01:44 +08:00
44304f5b16 ip范围判断 2022-07-01 14:34:37 +08:00
f061b2efeb io.Writer 2022-06-27 16:05:37 +08:00
135 changed files with 4313 additions and 689 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.o
fs/logs/**
.idea/**

View File

@ -1,51 +1,113 @@
package bloom
import "github.com/bits-and-blooms/bitset"
import (
"math"
"github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/expr"
"github.com/charlienet/go-mixed/hash"
"github.com/go-redis/redis/v8"
)
const DEFAULT_SIZE = 2 << 24
var seeds = []uint{7, 11, 13, 31, 37, 61}
type simplehash struct {
cap uint
seed uint
type bitStore interface {
Clear()
Set(pos ...uint) error
Test(pos ...uint) (bool, error)
}
type BloomFilter struct {
set *bitset.BitSet
funcs [6]simplehash
bits uint // 布隆过滤器大小
funcs uint // 哈希函数数量
store bitStore // 位图存储
}
func NewBloomFilter() *BloomFilter {
bf := new(BloomFilter)
for i := 0; i < len(bf.funcs); i++ {
bf.funcs[i] = simplehash{DEFAULT_SIZE, seeds[i]}
type bloomOptions struct {
redisClient *redis.Client
redisKey string
}
bf.set = bitset.New(DEFAULT_SIZE)
type option func(*bloomOptions)
func WithRedis(redis *redis.Client, key string) option {
return func(bo *bloomOptions) {
bo.redisClient = redis
bo.redisKey = key
}
}
// New 初始化布隆过滤器
// https://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
func New(expectedInsertions uint, fpp float64, opts ...option) *BloomFilter {
opt := &bloomOptions{}
for _, f := range opts {
f(opt)
}
bits := optimalNumOfBits(expectedInsertions, fpp)
k := optimalNumOfHashFunctions(bits, expectedInsertions)
bf := &BloomFilter{
bits: bits,
funcs: k,
store: expr.Ternary[bitStore](
opt.redisClient == nil,
newMemStore(bits),
newRedisStore(opt.redisClient, opt.redisKey, bits)),
}
return bf
}
func (bf *BloomFilter) Add(value string) {
for _, f := range bf.funcs {
bf.set.Set(f.hash(value))
}
func (bf *BloomFilter) Add(data string) {
offsets := bf.geOffsets([]byte(data))
bf.store.Set(offsets...)
}
func (bf *BloomFilter) Contains(value string) bool {
if value == "" {
return false
}
ret := true
for _, f := range bf.funcs {
ret = ret && bf.set.Test(f.hash(value))
}
return ret
func (bf *BloomFilter) ExistString(data string) (bool, error) {
return bf.Exists(bytesconv.StringToBytes(data))
}
func (s simplehash) hash(value string) uint {
var result uint = 0
for i := 0; i < len(value); i++ {
result = result*s.seed + uint(value[i])
func (bf *BloomFilter) Exists(data []byte) (bool, error) {
if data == nil || len(data) == 0 {
return false, nil
}
return (s.cap - 1) & result
offsets := bf.geOffsets(data)
isSet, err := bf.store.Test(offsets...)
if err != nil {
return false, err
}
return isSet, nil
}
func (bf *BloomFilter) geOffsets(data []byte) []uint {
offsets := make([]uint, bf.funcs)
for i := uint(0); i < bf.funcs; i++ {
offsets[i] = uint(hash.Murmur3(append(data, byte(i))) % uint64(bf.bits))
}
return offsets
}
// 清空布隆过滤器
func (bf *BloomFilter) Clear() {
bf.store.Clear()
}
// 计算优化的位图长度,
// n 期望放置元素数量,
// p 预期的误判概率
func optimalNumOfBits(n uint, p float64) uint {
return (uint)(-float64(n) * math.Log(p) / (math.Log(2) * math.Log(2)))
}
// 计算哈希函数数量
func optimalNumOfHashFunctions(m, n uint) uint {
return uint(math.Round(float64(m) / float64(n) * math.Log(2)))
}

View File

@ -2,19 +2,121 @@ package bloom_test
import (
"fmt"
"math"
"strconv"
"testing"
"github.com/charlienet/go-mixed/bloom"
"github.com/charlienet/go-mixed/rand"
"github.com/charlienet/go-mixed/sys"
"github.com/go-redis/redis/v8"
"github.com/stretchr/testify/assert"
)
const ()
func TestBloom(t *testing.T) {
b := bloom.NewBloomFilter()
b := bloom.New(1000, 0.03)
for i := 0; i < 1000000; i++ {
b.Add(strconv.Itoa(i))
}
fmt.Println(b.Contains(strconv.Itoa(9999)))
fmt.Println(b.Contains("ss"))
v := "6943553521463296-1635402930"
t.Log(b.ExistString(v))
b.Add(v)
t.Log(b.ExistString(v))
isSet, err := b.ExistString(strconv.Itoa(9999))
fmt.Println("过滤器中包含值:", isSet, err)
isSet, err = b.ExistString("ss")
fmt.Println("过滤器中未包含:", isSet, err)
t.Log(sys.ShowMemUsage())
}
func TestOptimize(t *testing.T) {
expectedInsertions := 1000000 // 期望存储数据量
falseProbability := 0.00002 // 预期误差
bits := uint(float64(-expectedInsertions) * math.Log(falseProbability) / (math.Log(2) * math.Log(2)))
hashSize := uint(math.Round(float64(bits) / float64(expectedInsertions) * math.Log(2)))
t.Log(bits)
t.Log(hashSize)
}
func TestRedis(t *testing.T) {
client := redis.NewClient(&redis.Options{
Addr: "192.168.2.222:6379",
Password: "123456",
})
bf := bloom.New(10000, 0.03, bloom.WithRedis(client, "bloom:test"))
for i := 0; i < 100; i++ {
bf.Add(strconv.Itoa(i))
}
for i := 0; i < 100; i++ {
isSet, err := bf.ExistString(strconv.Itoa(i))
if err != nil {
t.Fatal(err)
}
if !isSet {
t.Log(i, isSet)
}
}
for i := 101; i < 200; i++ {
isSet, err := bf.ExistString(strconv.Itoa(i))
t.Log(isSet, err)
}
}
func TestClear(t *testing.T) {
bf := bloom.New(1000, 0.03)
v := "abc"
bf.Add(v)
isSet, _ := bf.ExistString(v)
assert.True(t, isSet)
bf.Clear()
isSet, _ = bf.ExistString(v)
assert.False(t, isSet)
}
func TestParallel(t *testing.T) {
f := bloom.New(1000, 0.03)
for i := 0; i < 10000; i++ {
v := rand.Hex.Generate(10)
f.Add(v)
isSet, _ := f.ExistString(v)
assert.True(t, isSet)
}
}
func BenchmarkFilter(b *testing.B) {
f := bloom.New(1000, 0.03)
b.RunParallel(func(p *testing.PB) {
for p.Next() {
v := rand.Hex.Generate(10)
f.Add(v)
f.ExistString(v)
// assert.True(b, f.Contains(v))
// assert.True(b, f.Contains(v))
}
})
}

51
bloom/mem_store.go Normal file
View File

@ -0,0 +1,51 @@
package bloom
import (
"github.com/bits-and-blooms/bitset"
"github.com/charlienet/go-mixed/locker"
)
type memStore struct {
size uint
set *bitset.BitSet // 内存位图
lock locker.RWLocker // 同步锁
}
func newMemStore(size uint) *memStore {
return &memStore{
size: size,
set: bitset.New(size),
lock: locker.NewRWLocker(),
}
}
func (s *memStore) Clear() {
s.lock.Lock()
defer s.lock.Unlock()
s.set.ClearAll()
}
func (s *memStore) Set(offsets ...uint) error {
s.lock.Lock()
defer s.lock.Unlock()
for _, p := range offsets {
s.set.Set(p)
}
return nil
}
func (s *memStore) Test(offsets ...uint) (bool, error) {
s.lock.RLock()
defer s.lock.RUnlock()
for _, p := range offsets {
if !s.set.Test(p) {
return false, nil
}
}
return true, nil
}

116
bloom/redis_store.go Normal file
View File

@ -0,0 +1,116 @@
package bloom
import (
"context"
"errors"
"strconv"
"time"
"github.com/go-redis/redis/v8"
)
const (
// ARGV:偏移量offset数组
// KYES[1]: setbit操作的key
// 全部设置为1
setScript = `
for _, offset in ipairs(ARGV) do
redis.call("setbit", KEYS[1], offset, 1)
end
`
//ARGV:偏移量offset数组
//KYES[1]: setbit操作的key
//检查是否全部为1
testScript = `
for _, offset in ipairs(ARGV) do
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
return false
end
end
return true
`
)
var ErrTooLargeOffset = errors.New("超出最大偏移量")
var _ bitStore = &redisBitSet{}
// 使用Redis存储位图
type redisBitSet struct {
store *redis.Client
key string
bits uint
}
func newRedisStore(store *redis.Client, key string, bits uint) *redisBitSet {
return &redisBitSet{
store: store,
key: key,
bits: bits,
}
}
func (s *redisBitSet) Set(offsets ...uint) error {
args, err := s.buildOffsetArgs(offsets)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancel()
_, err = s.store.Eval(ctx, setScript, []string{s.key}, args).Result()
//底层使用的是go-redis,redis.Nil表示操作的key不存在
//需要针对key不存在的情况特殊判断
if err == redis.Nil {
return nil
} else if err != nil {
return err
}
return nil
}
func (s *redisBitSet) Test(offsets ...uint) (bool, error) {
args, err := s.buildOffsetArgs(offsets)
if err != nil {
return false, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancel()
resp, err := s.store.Eval(ctx, testScript, []string{s.key}, args).Result()
// key 不存在,表示还未存放任何数据
if err == redis.Nil {
return false, nil
} else if err != nil {
return false, err
}
exists, ok := resp.(int64)
if !ok {
return false, nil
}
return exists == 1, nil
}
func (s *redisBitSet) Clear() {
}
func (r *redisBitSet) buildOffsetArgs(offsets []uint) ([]string, error) {
args := make([]string, 0, len(offsets))
for _, offset := range offsets {
if offset >= r.bits {
return nil, ErrTooLargeOffset
}
args = append(args, strconv.FormatUint(uint64(offset), 10))
}
return args, nil
}

24
bloom/redis_store_test.go Normal file
View File

@ -0,0 +1,24 @@
package bloom
import (
"testing"
"github.com/go-redis/redis/v8"
)
func TestRedisStore(t *testing.T) {
client := redis.NewClient(&redis.Options{
Addr: "192.168.2.222:6379",
Password: "123456",
})
store := newRedisStore(client, "abcdef", 10000)
err := store.Set(1, 2, 3, 9, 1223)
if err != nil {
t.Fatal(err)
}
t.Log(store.Test(1))
t.Log(store.Test(1, 2, 3))
t.Log(store.Test(4, 5, 8))
}

View File

@ -5,10 +5,21 @@ import (
"encoding/hex"
)
const hextable = "0123456789ABCDEF"
const hexTable = "0123456789ABCDEF"
type BytesResult []byte
// FromHexString 从十六进制获取
func FromHexString(s string) (BytesResult, error) {
b, err := hex.DecodeString(s)
return BytesResult(b), err
}
func FromBase64String(s string) (BytesResult, error) {
b, err := base64.StdEncoding.DecodeString(s)
return BytesResult(b), err
}
func (r BytesResult) Hex() string {
return hex.EncodeToString(r)
}
@ -16,9 +27,11 @@ func (r BytesResult) Hex() string {
func (r BytesResult) UppercaseHex() string {
dst := make([]byte, hex.EncodedLen(len(r)))
j := 0
for _, v := range r {
dst[j] = hextable[v>>4]
dst[j+1] = hextable[v&0x0f]
re := r[:]
for _, v := range re {
dst[j] = hexTable[v>>4]
dst[j+1] = hexTable[v&0x0f]
j += 2
}

View File

@ -20,3 +20,8 @@ func Decode(b []byte, out any) error {
dec := gob.NewDecoder(buf)
return dec.Decode(out)
}
func MsgPackage() {
// msgpack.NewEncoder()
}

View File

@ -1,4 +1,4 @@
package cache
package bigcache
import (
"errors"
@ -8,8 +8,6 @@ import (
"github.com/charlienet/go-mixed/logx"
)
var _ MemCache = &bigCacheClient{}
type BigCacheConfig struct {
Shards int
LifeWindow time.Duration
@ -49,8 +47,13 @@ func NewBigCache(c BigCacheConfig) (*bigCacheClient, error) {
}, nil
}
func (c *bigCacheClient) Get(key string) ([]byte, error) {
return c.cache.Get(key)
func (c *bigCacheClient) Get(key string) ([]byte, bool) {
b, err := c.cache.Get(key)
if err == nil {
return b, false
}
return b, true
}
func (c *bigCacheClient) Set(key string, entry []byte, expire time.Duration) error {
@ -58,7 +61,8 @@ func (c *bigCacheClient) Set(key string, entry []byte, expire time.Duration) err
}
func (c *bigCacheClient) Delete(keys ...string) error {
for _, k := range keys {
ks := keys[:]
for _, k := range ks {
if err := c.cache.Delete(k); err != nil {
return err
}
@ -67,9 +71,23 @@ func (c *bigCacheClient) Delete(keys ...string) error {
return nil
}
func (c *bigCacheClient) Exist(key string) {
func (c *bigCacheClient) Exist(key string) bool {
_, err := c.cache.Get(key)
if err == nil {
return true
}
return !errors.Is(err, bigcache.ErrEntryNotFound)
}
func (c *bigCacheClient) Clear() {
}
func (c *bigCacheClient) IsNotFound(err error) bool {
return errors.Is(err, bigcache.ErrEntryNotFound)
if err == nil {
return true
}
return !errors.Is(err, bigcache.ErrEntryNotFound)
}

27
cache/bigcache/big_cache_test.go vendored Normal file
View File

@ -0,0 +1,27 @@
package bigcache
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestBigCache(t *testing.T) {
r := require.New(t)
c, err := NewBigCache(BigCacheConfig{})
r.Nil(err)
cacheKey := "a"
cacheValue := "bbb"
c.Set(cacheKey, []byte(cacheValue), time.Second*5)
r.True(c.Exist(cacheKey))
r.False(c.Exist("abb"))
b, ok := c.Get(cacheKey)
r.True(ok)
r.Equal(cacheValue, string(b))
}

141
cache/cache.go vendored
View File

@ -3,60 +3,76 @@ package cache
import (
"context"
"errors"
"fmt"
"time"
"github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/locker"
"github.com/charlienet/go-mixed/logx"
"golang.org/x/sync/singleflight"
)
var ErrNotFound = errors.New("key not found")
// 数据加载函数定义
type LoadFunc func(context.Context) (any, error)
type ICache interface {
}
type Cache struct {
prefix string // 键前缀
retry int // 资源获取时的重试次数
mem MemCache // 内存缓存
distributdCache DistributdCache // 分布式缓存
rds DistributedCache // 远程缓存
publishSubscribe PublishSubscribe // 发布订阅
qps *qps //
group singleflight.Group // singleflight.Group
lock locker.ChanLocker // 资源锁
stats *Stats // 缓存命中计数
qps *qps // 访问计数
logger logx.Logger // 日志记录
}
func NewCache(opts ...option) *Cache {
func New(opts ...option) (*Cache, error) {
c := acquireDefaultCache()
for _, f := range opts {
if err := f(c); err != nil {
return c
return c, nil
}
}
go c.subscribe()
return c
// 未设置内存缓存时,添加默认缓存
if c.mem == nil {
c.mem = NewTinyLFU(1<<12, time.Second*30)
}
func (c *Cache) Set(key string, value any, expiration time.Duration) error {
if c.mem != nil {
bytes, err := bytesconv.Encode(value)
return c, nil
}
func (c *Cache) Set(ctx context.Context, key string, value any, expiration time.Duration) error {
buf, err := Marshal(value)
if err != nil {
return err
}
c.mem.Set(key, bytes, expiration)
if c.mem != nil {
c.mem.Set(key, buf, expiration)
}
if c.rds != nil {
c.rds.Set(ctx, key, buf, expiration)
}
return nil
}
func (c *Cache) Get(key string, out any) error {
func (c *Cache) Get(ctx context.Context, key string, out any) error {
if c.mem != nil {
c.getFromMem(key, out)
c.getFromMem(key)
}
if c.distributdCache != nil {
if err := c.distributdCache.Get(key, out); err != nil {
if c.rds != nil {
if err := c.rds.Get(ctx, key, out); err != nil {
}
}
@ -65,7 +81,7 @@ func (c *Cache) Get(key string, out any) error {
}
func (c *Cache) GetFn(ctx context.Context, key string, out any, fn LoadFunc, expiration time.Duration) (bool, error) {
c.Get(key, out)
c.Get(ctx, key, out)
// 多级缓存中未找到时,放置缓存对象
ret, err := fn(ctx)
@ -73,7 +89,7 @@ func (c *Cache) GetFn(ctx context.Context, key string, out any, fn LoadFunc, exp
return false, err
}
c.Set(key, ret, expiration)
c.Set(ctx, key, ret, expiration)
return false, nil
}
@ -82,44 +98,101 @@ func (c *Cache) Exist(key string) (bool, error) {
return false, nil
}
func (c *Cache) Delete(key ...string) error {
func (c *Cache) Delete(ctx context.Context, key ...string) error {
if c.mem != nil {
c.mem.Delete(key...)
}
if c.distributdCache != nil {
c.distributdCache.Delete(key...)
if c.rds != nil {
c.rds.Delete(ctx, key...)
}
for _, k := range key {
c.group.Forget(k)
}
return nil
}
func (c *Cache) subscribe() {
// 清除本地缓存
func (c *Cache) ClearMem() {
if c.mem != nil {
c.mem.Clear()
}
}
func (c *Cache) getFromMem(key string, out any) error {
bytes, err := c.mem.Get(key)
if err != nil {
return err
func (c *Cache) Clear() {
}
if err := bytesconv.Decode(bytes, out); err != nil {
return err
func (c *Cache) Disable() {
}
return nil
func (c *Cache) Enable() {
}
func (c *Cache) getOnce(ctx context.Context, key string) (b []byte, cached bool, err error) {
if c.mem != nil {
b, ok := c.mem.Get(key)
if ok {
return b, true, nil
}
}
c.group.Do(key, func() (any, error) {
if c.mem != nil {
b, ok := c.mem.Get(key)
if ok {
return b, nil
}
}
if c.rds != nil {
c.rds.Get(ctx, key, nil)
}
return nil, nil
})
return
}
func (c *Cache) getFromMem(key string) ([]byte, bool) {
bytes, cached := c.mem.Get(key)
return bytes, cached
}
// 从缓存加载数据
func (c *Cache) getFromCache() {
// 从缓存加载数据
// 1. 检查内存是否存在
// 2. 检查分布缓存是否存在
}
// 从数据源加载数据
func (c *Cache) getFromSource(ctx context.Context, key string, fn LoadFunc) {
func (c *Cache) getFromSource(ctx context.Context, key string, fn LoadFunc) error {
// 1. 尝试获取资源锁,如成功获取到锁加载数据
// 2. 未获取到锁,等待从缓存中获取
fn(ctx)
ch, ok := c.lock.Get(key)
if ok {
defer c.lock.Release(key)
v, err := fn(ctx)
if err != nil {
return fmt.Errorf("load from source err:%v", err)
}
// 取出值存入多级缓存
_ = v
return nil
}
// 等待数据加载完成
select {
case <-ch:
// 未取到结果时,再次获取
return c.getFromSource(ctx, key, fn)
}
}

122
cache/cache_builder.go vendored
View File

@ -1,99 +1,81 @@
package cache
import "github.com/charlienet/go-mixed/logx"
import (
"context"
"time"
"github.com/charlienet/go-mixed/cache/bigcache"
"github.com/charlienet/go-mixed/cache/freecache"
"github.com/charlienet/go-mixed/logx"
)
const defaultPrefix = "cache"
type option func(*Cache) error
type options struct {
Prefix string
}
func acquireDefaultCache() *Cache {
return &Cache{
prefix: defaultPrefix,
qps: NewQps(),
func WithRedis(opts RedisConfig) option {
return func(c *Cache) error {
if len(opts.Prefix) == 0 {
opts.Prefix = defaultPrefix
}
rds := NewRedis(opts)
c.rds = rds
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
return rds.Ping(ctx)
}
}
type cacheBuilder struct {
prefix string
redisOptions RedisConfig
bigCacheConfig BigCacheConfig
freeSize int
publishSubscribe PublishSubscribe
log logx.Logger
func WithBigCache(opts bigcache.BigCacheConfig) option {
return func(c *Cache) error {
mem, err := bigcache.NewBigCache(opts)
c.mem = mem
return err
}
}
func NewCacheBuilder() *cacheBuilder {
return &cacheBuilder{}
}
func WithFreeCache(size int) option {
return func(c *Cache) error {
mem := freecache.NewFreeCache(size)
c.mem = mem
func (b *cacheBuilder) WithLogger(log logx.Logger) *cacheBuilder {
b.log = log
return b
return nil
}
func (b *cacheBuilder) WithPrefix(prefix string) *cacheBuilder {
b.prefix = prefix
return b
}
func (b *cacheBuilder) WithRedis(opts RedisConfig) *cacheBuilder {
b.redisOptions = opts
return b
}
func (b *cacheBuilder) WithBigCache(opts BigCacheConfig) *cacheBuilder {
b.bigCacheConfig = opts
return b
}
func (b *cacheBuilder) WithFreeCache(size int) *cacheBuilder {
b.freeSize = size
return b
}
// 使用自定义分布式缓存
func WithDistributedCache(c DistributdCache) {
func WithDistributedCache(rds DistributedCache) option {
return func(c *Cache) error {
c.rds = rds
return nil
}
}
func (b *cacheBuilder) WithPublishSubscribe(p PublishSubscribe) *cacheBuilder {
b.publishSubscribe = p
return b
func WithPublishSubscribe(p PublishSubscribe) option {
return func(c *Cache) error {
return nil
}
}
func (b cacheBuilder) Build() (*Cache, error) {
var err error
cache := acquireDefaultCache()
if len(b.prefix) > 0 {
cache.prefix = b.prefix
func WithLogger(log logx.Logger) option {
return func(c *Cache) error {
c.logger = log
return nil
}
}
b.redisOptions.Prefix = cache.prefix
redis := NewRedis(b.redisOptions)
if err := redis.Ping(); err != nil {
return cache, err
func acquireDefaultCache() *Cache {
return &Cache{
qps: NewQps(),
}
var mem MemCache
if b.freeSize > 0 {
mem = NewFreeCache(b.freeSize)
} else {
if b.log != nil {
b.bigCacheConfig.log = b.log
}
mem, err = NewBigCache(b.bigCacheConfig)
}
cache.distributdCache = redis
cache.mem = mem
cache.publishSubscribe = b.publishSubscribe
cache.logger = b.log
return cache, err
}

View File

@ -3,27 +3,38 @@ package cache
import (
"testing"
"time"
"github.com/charlienet/go-mixed/logx"
)
func TestBuilder(t *testing.T) {
cache, err := NewCacheBuilder().
WithLogger(logx.NewLogrus(logx.WithFormatter(logx.NewNestedFormatter(logx.NestedFormatterOption{
Color: true,
})))).
WithRedis(RedisConfig{
Addrs: []string{"192.168.2.222:6379"},
Password: "123456",
}).
WithBigCache(BigCacheConfig{}).
// WithFreeCache(10 * 1024 * 1024).
Build()
now := time.Now()
t.Log(now)
t1, _ := time.ParseDuration("9h27m")
t1 += time.Hour * 24
t2, _ := time.ParseDuration("16h28m")
t.Log(t1)
t.Log(t2)
if err != nil {
t.Fatal(err)
}
f := time.Date(2022, time.December, 12, 8, 0, 0, 0, time.Local)
t.Log(f.Sub(time.Now()))
u := SimpleUser{FirstName: "Radomir", LastName: "Sohlich"}
t.Log(cache.Set(defaultKey, u, time.Minute*10))
// cache, err := New(
// WithLogger(logx.NewLogrus(logx.WithNestedFormatter(logx.NestedFormatterOption{
// Color: true,
// }))).
// UseRedis(RedisConfig{
// Addrs: []string{"192.168.2.222:6379"},
// Password: "123456",
// }).
// UseBigCache(bigcache.BigCacheConfig{}).
// Build()
// if err != nil {
// t.Fatal(err)
// }
// ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
// defer cancel()
// u := SimpleUser{FirstName: "Radomir", LastName: "Sohlich"}
// t.Log(cache.Set(ctx, defaultKey, u, time.Minute*10))
}

12
cache/cache_preload.go vendored Normal file
View File

@ -0,0 +1,12 @@
package cache
import "context"
// PreLoadItem 预加载数据项
type PreLoadItem struct {
Key string
Value any
}
// PreloadFunc 数据预加载函数定义
type PreloadFunc func(context.Context) ([]PreLoadItem, error)

85
cache/cache_test.go vendored
View File

@ -6,9 +6,6 @@ import (
"sync/atomic"
"testing"
"time"
"github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/logx"
)
var (
@ -16,25 +13,26 @@ var (
)
func TestNewCache(t *testing.T) {
c, err := NewCacheBuilder().
WithRedis(RedisConfig{
Addrs: []string{"192.168.2.222:6379"},
Password: "123456",
}).
WithPrefix("cache_test").
WithLogger(logx.NewLogrus()).
Build()
// c, err := NewCacheBuilder().
// UseRedis(RedisConfig{
// Addrs: []string{"192.168.2.222:6379"},
// Password: "123456",
// }).
// WithPrefix("cache_test").
// WithLogger(logx.NewLogrus()).
// Build()
if err != nil {
t.Fatal(err)
}
// if err != nil {
// t.Fatal(err)
// }
c.Set("abc", "value", time.Minute*10)
// ctx := context.Background()
// c.Set(ctx, "abc", "value", time.Minute*10)
var s string
c.Get("abc", &s)
// var s string
// c.Get(ctx, "abc", &s)
t.Log(s)
// t.Log(s)
}
type SimpleUser struct {
@ -43,45 +41,43 @@ type SimpleUser struct {
}
func TestMemCache(t *testing.T) {
b, _ := NewBigCache(BigCacheConfig{})
var mems = []MemCache{
NewFreeCache(10 * 1024 * 1024),
b,
}
// b, _ := bigcache.NewBigCache(bigcache.BigCacheConfig{})
// var mems = []MemCache{
// NewFreeCache(10 * 1024 * 1024),
// b,
// }
u := SimpleUser{FirstName: "Radomir", LastName: "Sohlich"}
encoded, _ := bytesconv.Encode(u)
for _, m := range mems {
m.Set(defaultKey, encoded, time.Second)
ret, err := m.Get(defaultKey)
if err != nil {
t.Fatal(err)
}
// u := SimpleUser{FirstName: "Radomir", LastName: "Sohlich"}
// encoded, _ := bytesconv.Encode(u)
// for _, m := range mems {
// m.Set(defaultKey, encoded, time.Second)
// ret, _ := m.Get(defaultKey)
var u2 SimpleUser
bytesconv.Decode(ret, &u2)
t.Log(u2)
}
// var u2 SimpleUser
// bytesconv.Decode(ret, &u2)
// t.Log(u2)
// }
}
func TestDistributedCache(t *testing.T) {
c := NewRedis(RedisConfig{Addrs: []string{"192.168.2.222:6379"}, DB: 6, Password: "123456", Prefix: "abcdef"})
if err := c.Ping(); err != nil {
ctx := context.Background()
if err := c.Ping(ctx); err != nil {
t.Fatal(err)
}
t.Log(c.Exist(defaultKey))
t.Log(c.Exist(ctx, defaultKey))
u := SimpleUser{FirstName: "redis client"}
var u2 SimpleUser
c.Get(defaultKey, &u2)
c.Get(ctx, defaultKey, &u2)
c.Set(defaultKey, u, time.Minute*10)
t.Log(c.Exist(defaultKey))
c.Set(ctx, defaultKey, u, time.Minute*10)
t.Log(c.Exist(ctx, defaultKey))
if err := c.Get(defaultKey, &u2); err != nil {
if err := c.Get(ctx, defaultKey, &u2); err != nil {
t.Fatal("err:", err)
}
t.Logf("%+v", u2)
@ -136,10 +132,9 @@ func load() (any, error) {
}
func buildCache() *Cache {
c, err := NewCacheBuilder().
WithFreeCache(10 * 1024 * 1024).
WithRedis(RedisConfig{Addrs: []string{"192.168.2.222:6379"}, DB: 6, Password: "123456"}).
Build()
c, err := New(
WithFreeCache(10*1024*1024),
WithRedis(RedisConfig{Addrs: []string{"192.168.2.222:6379"}, DB: 6, Password: "123456"}))
if err != nil {
panic(err)

View File

@ -1,10 +0,0 @@
package cache
import "time"
type DistributdCache interface {
Get(key string, out any) error
Set(key string, value any, expiration time.Duration) error
Delete(key ...string) error
Ping() error
}

14
cache/distributed_cache.go vendored Normal file
View File

@ -0,0 +1,14 @@
package cache
import (
"context"
"time"
)
// 分布式缓存接口
type DistributedCache interface {
Get(ctx context.Context, key string, out any) error
Set(ctx context.Context, key string, value any, expiration time.Duration) error
Delete(ctx context.Context, key ...string) error
Ping(ctx context.Context) error
}

10
cache/empty_cache_adaper.go vendored Normal file
View File

@ -0,0 +1,10 @@
package cache
import "context"
// var emptyCache DistributedCache = &emptyCacheAdapter{}
type emptyCacheAdapter struct {
}
func (*emptyCacheAdapter) Delete(ctx context.Context, keys ...string) {}

View File

@ -1,4 +1,4 @@
package cache
package freecache
import (
"errors"
@ -10,8 +10,6 @@ import (
const defaultSize = 10 * 1024 * 1024 // 10M
var _ MemCache = &freeCache{}
type freeCache struct {
cache *freecache.Cache
}
@ -29,8 +27,12 @@ func NewFreeCache(size int) *freeCache {
}
}
func (c *freeCache) Get(key string) ([]byte, error) {
return c.cache.Get([]byte(key))
func (c *freeCache) Get(key string) ([]byte, bool) {
b, err := c.cache.Get([]byte(key))
if err != nil {
return b, false
}
return b, true
}
func (c *freeCache) Set(key string, value []byte, d time.Duration) error {
@ -54,6 +56,10 @@ func (c *freeCache) Exist(key string) error {
return nil
}
func (c *freeCache) Clear() {
}
func (c *freeCache) IsNotFound(err error) bool {
return errors.Is(err, freecache.ErrNotFound)
}

1
cache/freecache/free_cache_test.go vendored Normal file
View File

@ -0,0 +1 @@
package freecache

2
cache/lru.go vendored Normal file
View File

@ -0,0 +1,2 @@
package cache

5
cache/mem.go vendored
View File

@ -3,7 +3,8 @@ package cache
import "time"
type MemCache interface {
Get(key string) ([]byte, error)
Set(key string, entry []byte, expire time.Duration) error
Get(key string) ([]byte, bool)
Set(key string, b []byte, expire time.Duration) error
Delete(key ...string) error
Clear()
}

43
cache/msg_pack.go vendored Normal file
View File

@ -0,0 +1,43 @@
package cache
import (
"github.com/vmihailenco/msgpack/v5"
)
func Marshal(v any) ([]byte, error) {
switch v := v.(type) {
case nil:
return nil, nil
case []byte:
return v, nil
case string:
return []byte(v), nil
}
b, err := msgpack.Marshal(v)
if err != nil {
return nil, err
}
return b, err
}
func Unmarshal(b []byte, v any) error {
if len(b) == 0 {
return nil
}
switch v := v.(type) {
case nil:
return nil
case *[]byte:
clone := make([]byte, len(b))
copy(clone, b)
*v = clone
case *string:
*v = string(b)
return nil
}
return msgpack.Unmarshal(b, v)
}

6
cache/qps.go vendored
View File

@ -31,7 +31,9 @@ func (q *qps) statisticsTotal() {
}
}()
ticker := time.NewTicker(time.Second)
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for range ticker.C {
q.all.viewTotal = atomic.SwapInt64(&q.all.total, 0)
q.memoryTotal.viewTotal = atomic.SwapInt64(&q.memoryTotal.total, 0)
@ -39,5 +41,7 @@ func (q *qps) statisticsTotal() {
q.redisTotal.viewTotal = atomic.SwapInt64(&q.redisTotal.total, 0)
q.redisHit.viewTotal = atomic.SwapInt64(&q.redisHit.total, 0)
q.sourceTotal.viewTotal = atomic.SwapInt64(&q.sourceTotal.total, 0)
// percnt := 0
}
}

14
cache/readme.md vendored
View File

@ -11,3 +11,17 @@
3. 缓存穿透;从数据源中未找到数据时,在缓存中缓存空值。
4. 缓存雪崩;为防止缓存雪崩将资源放入缓存时,对过期时间添加一个随机过期时间,防止缓存同时过期。
5. 自动续期;当访问二级缓存时对使用的资源进行延期。
## 使用方式
创建
```go
cache.New().UseRedis().UseBigCache().Build()
```
```go
Cache.Get(key, dist, func() (bool,error){}, options func(){})
Cache.GetFn(context, key, dist, func() (bool, error))
```

12
cache/redis.go vendored
View File

@ -54,7 +54,7 @@ func NewRedis(c RedisConfig) *redisClient {
}
}
func (c *redisClient) Get(key string, out any) error {
func (c *redisClient) Get(cxt context.Context, key string, out any) error {
val, err := c.client.Get(context.Background(), c.getKey(key)).Result()
if errors.Is(err, redis.Nil) {
return ErrNotFound
@ -72,17 +72,17 @@ func (c *redisClient) Get(key string, out any) error {
return json.Unmarshal(bytesconv.StringToBytes(val), out)
}
func (c *redisClient) Set(key string, value any, expiration time.Duration) error {
func (c *redisClient) Set(ctx context.Context, key string, value any, expiration time.Duration) error {
j, _ := json.Marshal(value)
return c.client.Set(context.Background(), c.getKey(key), j, expiration).Err()
}
func (c *redisClient) Exist(key string) (bool, error) {
func (c *redisClient) Exist(ctx context.Context, key string) (bool, error) {
val, err := c.client.Exists(context.Background(), c.getKey(key)).Result()
return val > 0, err
}
func (c *redisClient) Delete(key ...string) error {
func (c *redisClient) Delete(ctx context.Context, key ...string) error {
keys := make([]string, 0, len(key))
for _, k := range key {
keys = append(keys, c.getKey(k))
@ -96,8 +96,8 @@ func (c *redisClient) Delete(key ...string) error {
return nil
}
func (c *redisClient) Ping() error {
_, err := c.client.Ping(context.Background()).Result()
func (c *redisClient) Ping(ctx context.Context) error {
_, err := c.client.Ping(ctx).Result()
return err
}

20
cache/stats.go vendored Normal file
View File

@ -0,0 +1,20 @@
package cache
import "sync/atomic"
type Stats struct {
Hits uint64
Misses uint64
}
func (s *Stats) IncrementHits() {
atomic.AddUint64(&s.Hits, 1)
}
func (s *Stats) IncrementMisses() {
atomic.AddUint64(&s.Misses, 1)
}
func (c *Cache) Stats() *Stats {
return c.stats
}

64
cache/tiny_lfu.go vendored Normal file
View File

@ -0,0 +1,64 @@
package cache
import (
"time"
"github.com/charlienet/go-mixed/locker"
"github.com/vmihailenco/go-tinylfu"
)
var _ MemCache = &TinyLFU{}
type TinyLFU struct {
mu locker.Locker
lfu *tinylfu.T
ttl time.Duration
}
func NewTinyLFU(size int, ttl time.Duration) *TinyLFU {
return &TinyLFU{
mu: locker.NewLocker(),
lfu: tinylfu.New(size, 100000),
ttl: ttl,
}
}
func (c *TinyLFU) Set(key string, b []byte, expire time.Duration) error {
c.mu.Lock()
defer c.mu.Unlock()
c.lfu.Set(&tinylfu.Item{
Key: key,
Value: b,
ExpireAt: time.Now().Add(c.ttl),
})
return nil
}
func (c *TinyLFU) Get(key string) ([]byte, bool) {
c.mu.Lock()
defer c.mu.Unlock()
val, ok := c.lfu.Get(key)
if !ok {
return nil, false
}
return val.([]byte), true
}
func (c *TinyLFU) Delete(keys ...string) error {
c.mu.Lock()
defer c.mu.Unlock()
for _, k := range keys {
c.lfu.Del(k)
}
return nil
}
func (c *TinyLFU) Clear() {
}

57
cache/tiny_lfu_test.go vendored Normal file
View File

@ -0,0 +1,57 @@
package cache_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/charlienet/go-mixed/cache"
"github.com/charlienet/go-mixed/rand"
)
func TestTinyGet(t *testing.T) {
strFor := func(i int) string {
return fmt.Sprintf("a string %d", i)
}
keyName := func(i int) string {
return fmt.Sprintf("key-%00000d", i)
}
mycache := cache.NewTinyLFU(1000, 1*time.Second)
size := 50000
// Put a bunch of stuff in the cache with a TTL of 1 second
for i := 0; i < size; i++ {
key := keyName(i)
mycache.Set(key, []byte(strFor(i)), time.Second*2)
}
// Read stuff for a bit longer than the TTL - that's when the corruption occurs
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
done := ctx.Done()
loop:
for {
select {
case <-done:
// this is expected
break loop
default:
i := rand.Intn(size)
key := keyName(i)
b, ok := mycache.Get(key)
if !ok {
continue loop
}
got := string(b)
expected := strFor(i)
if got != expected {
t.Fatalf("expected=%q got=%q key=%q", expected, got, key)
}
}
}
}

210
calendar/calendar.go Normal file
View File

@ -0,0 +1,210 @@
package calendar
import (
"time"
)
var WeekStartDay time.Weekday = time.Sunday
type Calendar struct {
time.Time
weekStartsAt time.Weekday
}
func BeginningOfMinute() Calendar {
return Create(time.Now()).BeginningOfMinute()
}
func BeginningOfHour() Calendar {
return Create(time.Now()).BeginningOfHour()
}
func BeginningOfDay() Calendar {
return Create(time.Now()).BeginningOfDay()
}
func BeginningOfWeek() Calendar {
return Create(time.Now()).BeginningOfWeek()
}
func BeginningOfMonth() Calendar {
return Create(time.Now()).BeginningOfMonth()
}
func BeginningOfQuarter() Calendar {
return Create(time.Now()).BeginningOfQuarter()
}
func BeginningOfYear() Calendar {
return Create(time.Now()).BeginningOfYear()
}
func EndOfMinute() Calendar {
return Create(time.Now()).EndOfMinute()
}
func EndOfHour() Calendar {
return Create(time.Now()).EndOfHour()
}
func EndOfDay() Calendar {
return Create(time.Now()).EndOfDay()
}
func EndOfWeek() Calendar {
return Create(time.Now()).EndOfWeek()
}
func EndOfMonth() Calendar {
return Create(time.Now()).EndOfMonth()
}
func EndOfQuarter() Calendar {
return Create(time.Now()).EndOfQuarter()
}
func EndOfYear() Calendar {
return Create(time.Now()).EndOfYear()
}
func (c Calendar) WeekStartsAt(day time.Weekday) Calendar {
return Calendar{
Time: c.Time,
weekStartsAt: day,
}
}
func (c Calendar) BeginningOfMinute() Calendar {
return Calendar{Time: c.Truncate(time.Minute)}
}
func (c Calendar) BeginningOfHour() Calendar {
y, m, d := c.Date()
return Calendar{
Time: time.Date(y, m, d, c.Hour(), 0, 0, 0, c.Location()),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfDay() Calendar {
y, m, d := c.Date()
return Calendar{
Time: time.Date(y, m, d, 0, 0, 0, 0, c.Location()),
}
}
func (c Calendar) BeginningOfWeek() Calendar {
t := c.BeginningOfDay()
weekday := int(t.Weekday())
if c.weekStartsAt != time.Sunday {
weekStartDayInt := int(c.weekStartsAt)
if weekday < weekStartDayInt {
weekday = weekday + 7 - weekStartDayInt
} else {
weekday = weekday - weekStartDayInt
}
}
return Calendar{
Time: t.AddDate(0, 0, -weekday),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfMonth() Calendar {
y, m, _ := c.Date()
return Calendar{
Time: time.Date(y, m, 1, 0, 0, 0, 0, c.Location()),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfQuarter() Calendar {
month := c.BeginningOfMonth()
offset := (int(month.Month()) - 1) % 3
return Calendar{
Time: month.AddDate(0, -offset, 0),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfYear() Calendar {
y, _, _ := c.Date()
return Calendar{
Time: time.Date(y, time.January, 1, 0, 0, 0, 0, c.Location()),
weekStartsAt: c.weekStartsAt}
}
func (c Calendar) EndOfMinute() Calendar {
n := c.BeginningOfMinute()
return Calendar{
Time: n.Add(time.Minute - time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfHour() Calendar {
n := c.BeginningOfHour()
return Calendar{
Time: n.Add(time.Hour - time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfDay() Calendar {
y, m, d := c.Date()
return Calendar{
Time: time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), c.Location()),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfWeek() Calendar {
n := c.BeginningOfWeek()
return Calendar{
Time: n.AddDate(0, 0, 7).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfMonth() Calendar {
n := c.BeginningOfMonth()
return Calendar{
Time: n.AddDate(0, 1, 0).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfQuarter() Calendar {
n := c.BeginningOfQuarter()
return Calendar{
Time: n.AddDate(0, 3, 0).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfYear() Calendar {
n := c.BeginningOfYear()
return Calendar{
Time: n.AddDate(1, 0, 0).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) ToTime() time.Time {
return c.Time
}

38
calendar/calendar_test.go Normal file
View File

@ -0,0 +1,38 @@
package calendar_test
import (
"testing"
"time"
"github.com/charlienet/go-mixed/calendar"
"github.com/stretchr/testify/assert"
)
var format = "2006-01-02 15:04:05.999999999"
func TestToday(t *testing.T) {
t.Log(calendar.Today())
}
func TestBeginningOf(t *testing.T) {
a := assert.New(t)
n := time.Date(2022, 11, 9, 14, 28, 34, 123456789, time.UTC)
a.Equal("2022-11-06 00:00:00", calendar.Create(n).BeginningOfWeek().String())
a.Equal("2022-11-07 00:00:00", calendar.Create(n).WeekStartsAt(time.Monday).BeginningOfWeek().Format(format))
a.Equal("2022-11-09 14:00:00", calendar.Create(n).BeginningOfHour().Format(format))
a.Equal("2022-11-01 00:00:00", calendar.Create(n).BeginningOfMonth().Format(format))
a.Equal("2022-10-01 00:00:00", calendar.Create(n).BeginningOfQuarter().Format(format))
}
func TestEndOf(t *testing.T) {
a := assert.New(t)
n := time.Date(2022, 11, 9, 14, 28, 34, 123456789, time.UTC)
a.Equal("2022-11-09 14:28:59.999999999", calendar.Create(n).EndOfMinute().Format(format))
a.Equal("2022-11-09 14:59:59.999999999", calendar.Create(n).EndOfHour().Format(format))
a.Equal("2022-11-09 23:59:59.999999999", calendar.Create(n).EndOfDay().Format(format))
a.Equal("2022-11-30 23:59:59.999999999", calendar.Create(n).EndOfMonth().Format(format))
a.Equal("2022-12-31 23:59:59.999999999", calendar.Create(n).EndOfQuarter().Format(format))
a.Equal("2022-12-31 23:59:59.999999999", calendar.Create(n).EndOfYear().Format(format))
}

40
calendar/creator.go Normal file
View File

@ -0,0 +1,40 @@
package calendar
import "time"
func Now() Calendar {
return Create(time.Now())
}
func Today() Calendar {
return Now().BeginningOfDay()
}
func Create(t time.Time) Calendar {
return Calendar{
Time: t,
weekStartsAt: WeekStartDay,
}
}
func CreateFromTimestamp(timestamp int64) Calendar {
return Create(time.Unix(timestamp, 0))
}
func CreateFromTimestampMilli(timestamp int64) Calendar {
return Create(time.Unix(timestamp/1e3, (timestamp%1e3)*1e6))
}
func CreateFromTimestampMicro(timestamp int64) Calendar {
return Create(time.Unix(timestamp/1e6, (timestamp%1e6)*1e3))
}
func CreateFromTimestampNano(timestamp int64) Calendar {
return Create(time.Unix(timestamp/1e9, timestamp%1e9))
}
func create(year, month, day, hour, minute, second, nanosecond int) Calendar {
return Calendar{
Time: time.Date(year, time.Month(month), day, hour, minute, second, nanosecond, time.Local),
}
}

24
calendar/duration.go Normal file
View File

@ -0,0 +1,24 @@
package calendar
import "time"
func ParseDuration(s string) (time.Duration, error) {
if len(s) == 0 {
return time.Duration(0), nil
}
return time.ParseDuration(s)
}
func ParseDurationDefault(s string, d time.Duration) time.Duration {
if len(s) == 0 {
return d
}
ret, err := time.ParseDuration(s)
if err != nil {
return d
}
return ret
}

74
calendar/output.go Normal file
View File

@ -0,0 +1,74 @@
package calendar
import "time"
// 布局模板常量
const (
ANSICLayout = time.ANSIC
UnixDateLayout = time.UnixDate
RubyDateLayout = time.RubyDate
RFC822Layout = time.RFC822
RFC822ZLayout = time.RFC822Z
RFC850Layout = time.RFC850
RFC1123Layout = time.RFC1123
RFC1123ZLayout = time.RFC1123Z
RssLayout = time.RFC1123Z
KitchenLayout = time.Kitchen
RFC2822Layout = time.RFC1123Z
CookieLayout = "Monday, 02-Jan-2006 15:04:05 MST"
RFC3339Layout = "2006-01-02T15:04:05Z07:00"
RFC3339MilliLayout = "2006-01-02T15:04:05.999Z07:00"
RFC3339MicroLayout = "2006-01-02T15:04:05.999999Z07:00"
RFC3339NanoLayout = "2006-01-02T15:04:05.999999999Z07:00"
ISO8601Layout = "2006-01-02T15:04:05-07:00"
ISO8601MilliLayout = "2006-01-02T15:04:05.999-07:00"
ISO8601MicroLayout = "2006-01-02T15:04:05.999999-07:00"
ISO8601NanoLayout = "2006-01-02T15:04:05.999999999-07:00"
RFC1036Layout = "Mon, 02 Jan 06 15:04:05 -0700"
RFC7231Layout = "Mon, 02 Jan 2006 15:04:05 GMT"
DayDateTimeLayout = "Mon, Jan 2, 2006 3:04 PM"
DateTimeLayout = "2006-01-02 15:04:05"
DateTimeMilliLayout = "2006-01-02 15:04:05.999"
DateTimeMicroLayout = "2006-01-02 15:04:05.999999"
DateTimeNanoLayout = "2006-01-02 15:04:05.999999999"
ShortDateTimeLayout = "20060102150405"
ShortDateTimeMilliLayout = "20060102150405.999"
ShortDateTimeMicroLayout = "20060102150405.999999"
ShortDateTimeNanoLayout = "20060102150405.999999999"
DateLayout = "2006-01-02"
DateMilliLayout = "2006-01-02.999"
DateMicroLayout = "2006-01-02.999999"
DateNanoLayout = "2006-01-02.999999999"
ShortDateLayout = "20060102"
ShortDateMilliLayout = "20060102.999"
ShortDateMicroLayout = "20060102.999999"
ShortDateNanoLayout = "20060102.999999999"
TimeLayout = "15:04:05"
TimeMilliLayout = "15:04:05.999"
TimeMicroLayout = "15:04:05.999999"
TimeNanoLayout = "15:04:05.999999999"
ShortTimeLayout = "150405"
ShortTimeMilliLayout = "150405.999"
ShortTimeMicroLayout = "150405.999999"
ShortTimeNanoLayout = "150405.999999999"
)
func (c Calendar) String() string {
return c.ToDateTimeString()
}
func (c Calendar) ToDateTimeString() string {
return c.Format(DateTimeLayout)
}
func (c Calendar) ToDateTimeInt() int {
return c.ToShortDateInt()*1000000 + c.Hour()*10000 + c.Minute()*100 + c.Second()
}
func (c Calendar) ToShortDateInt() int {
return c.Year()*10000 + int(c.Month())*100 + c.Day()
}
func (c Calendar) ToMonthInt() int {
return c.Year()*100 + int(c.Month())
}

19
calendar/output_test.go Normal file
View File

@ -0,0 +1,19 @@
package calendar_test
import (
"testing"
"time"
"github.com/charlienet/go-mixed/calendar"
"github.com/stretchr/testify/assert"
)
func TestDayInt(t *testing.T) {
assert := assert.New(t)
n := time.Date(2022, 11, 9, 14, 28, 34, 123456789, time.UTC)
assert.Equal(20221109, calendar.Create(n).ToShortDateInt())
assert.Equal(202211, calendar.Create(n).ToMonthInt())
assert.Equal(20221109142834, calendar.Create(n).ToDateTimeInt())
}

View File

@ -0,0 +1,14 @@
package calendar
import "time"
type ScheduledExecutor struct {
}
func NewScheduledExecutor() *ScheduledExecutor {
return &ScheduledExecutor{}
}
func (e *ScheduledExecutor) Schedule(i any, duration time.Duration) {
}

View File

@ -0,0 +1,14 @@
package calendar_test
import (
"testing"
"time"
"github.com/charlienet/go-mixed/calendar"
)
func TestExecutor(t *testing.T) {
executor := calendar.NewScheduledExecutor()
executor.Schedule(nil, time.Minute)
}

View File

@ -0,0 +1,24 @@
package cleanupguard
import "sync"
type CleanupGuard struct {
enable bool
fn func()
mutex sync.Mutex
}
// 新建清理
func NewCleanupGuard(fn func()) CleanupGuard {
return CleanupGuard{fn: fn, enable: true}
}
func (g *CleanupGuard) Enable() {
g.mutex.Lock()
defer g.mutex.Unlock()
g.enable = true
}
func (g *CleanupGuard) Run() {
g.fn()
}

View File

@ -0,0 +1,15 @@
package deque
import "github.com/charlienet/go-mixed/locker"
type Deque[T any] struct {
locker locker.RWLocker
}
func New[T any]() *Deque[T] {
return &Deque[T]{
locker: locker.EmptyLocker,
}
}

View File

@ -0,0 +1 @@
package deque_test

View File

@ -0,0 +1,195 @@
package list
const minCapacity = 16
type ArrayList[T any] struct {
buf []T
head int
tail int
minCap int
list[T]
}
func NewArrayList[T any](elems ...T) *ArrayList[T] {
minCap := minCapacity
size := len(elems)
for minCap < size {
minCap <<= 1
}
var tail int = size
var buf []T
if len(elems) > 0 {
buf = make([]T, minCap)
copy(buf, elems)
}
l := &ArrayList[T]{
list: list[T]{size: size},
buf: buf,
tail: tail,
minCap: minCap,
}
// for _, v := range elems {
// l.PushBack(v)
// }
return l
}
func (l *ArrayList[T]) PushFront(v T) {
l.mu.Lock()
defer l.mu.Unlock()
l.grow()
l.head = l.prev(l.head)
l.buf[l.head] = v
l.size++
}
func (l *ArrayList[T]) PushBack(v T) {
l.mu.Lock()
defer l.mu.Unlock()
l.grow()
l.buf[l.tail] = v
l.tail = l.next(l.tail)
l.size++
}
func (l *ArrayList[T]) PopFront() T {
l.mu.Lock()
defer l.mu.Unlock()
if l.size <= 0 {
panic("list: PopFront() called on empty list")
}
ret := l.buf[l.head]
var zero T
l.buf[l.head] = zero
l.head = l.next(l.head)
l.size--
l.shrink()
return ret
}
func (l *ArrayList[T]) PopBack() T {
l.mu.Lock()
defer l.mu.Unlock()
l.tail = l.prev(l.tail)
ret := l.buf[l.tail]
var zero T
l.buf[l.tail] = zero
l.size--
l.shrink()
return ret
}
func (l *ArrayList[T]) RemoveAt(at int) T {
if at < 0 || at >= l.Size() {
panic(ErrorOutOffRange)
}
l.mu.Lock()
defer l.mu.Unlock()
rm := (l.head + at) & (len(l.buf) - 1)
if at*2 < l.size {
for i := 0; i < at; i++ {
prev := l.prev(rm)
l.buf[prev], l.buf[rm] = l.buf[rm], l.buf[prev]
rm = prev
}
return l.PopFront()
}
swaps := l.size - at - 1
for i := 0; i < swaps; i++ {
next := l.next(rm)
l.buf[rm], l.buf[next] = l.buf[next], l.buf[rm]
rm = next
}
return l.PopBack()
}
func (l *ArrayList[T]) Front() T {
l.mu.RLock()
defer l.mu.RUnlock()
return l.buf[l.head]
}
func (l *ArrayList[T]) Back() T {
l.mu.RLock()
defer l.mu.RUnlock()
return l.buf[l.tail]
}
func (l *ArrayList[T]) ForEach(fn func(T)) {
l.mu.RLock()
defer l.mu.RUnlock()
n := l.head
for i := 0; i < l.size; i++ {
fn(l.buf[n])
n = l.next(n)
}
}
func (q *ArrayList[T]) prev(i int) int {
return (i - 1) & (len(q.buf) - 1)
}
func (l *ArrayList[T]) next(i int) int {
return (i + 1) & (len(l.buf) - 1)
}
func (l *ArrayList[T]) grow() {
if l.size != len(l.buf) {
return
}
if len(l.buf) == 0 {
if l.minCap == 0 {
l.minCap = minCapacity
}
l.buf = make([]T, l.minCap)
return
}
l.resize()
}
func (l *ArrayList[T]) shrink() {
if len(l.buf) > l.minCap && (l.size<<2) == len(l.buf) {
l.resize()
}
}
// resize resizes the list to fit exactly twice its current contents. This is
// used to grow the list when it is full, and also to shrink it when it is
// only a quarter full.
func (l *ArrayList[T]) resize() {
newBuf := make([]T, l.size<<1)
if l.tail > l.head {
copy(newBuf, l.buf[l.head:l.tail])
} else {
n := copy(newBuf, l.buf[l.head:])
copy(newBuf[n:], l.buf[:l.tail])
}
l.head = 0
l.tail = l.size
l.buf = newBuf
}

View File

@ -0,0 +1,43 @@
package list_test
import (
"testing"
"github.com/charlienet/go-mixed/collections/list"
)
func TestNewArrayList(t *testing.T) {
l := list.NewArrayList(1, 2, 3)
l.ForEach(func(i int) {
t.Log(i)
})
}
func TestArrayPushBack(t *testing.T) {
l := list.NewArrayList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.ForEach(func(i int) {
t.Log(i)
})
}
func TestArrayPushFront(t *testing.T) {
l := list.NewArrayList[int]()
l.PushFront(1)
l.PushFront(2)
l.PushFront(3)
l.PushBack(99)
l.PushBack(88)
l.ForEach(func(i int) {
t.Log(i)
})
}

View File

@ -0,0 +1,155 @@
package list
type LinkedList[T any] struct {
list[T]
front, tail *LinkedNode[T]
}
type LinkedNode[T any] struct {
Value T
Prev, Next *LinkedNode[T]
}
// NewLinkedList 初始化链表
func NewLinkedList[T any](elems ...T) *LinkedList[T] {
l := &LinkedList[T]{}
for _, e := range elems {
l.pushBackNode(&LinkedNode[T]{Value: e})
}
return l
}
func (l *LinkedList[T]) PushBack(v T) *LinkedList[T] {
l.mu.Lock()
defer l.mu.Unlock()
l.pushBackNode(&LinkedNode[T]{Value: v})
return l
}
func (l *LinkedList[T]) PushFront(v T) *LinkedList[T] {
l.mu.Lock()
defer l.mu.Unlock()
l.pushFrontNode(&LinkedNode[T]{Value: v})
return l
}
func (l *LinkedList[T]) FrontNode() *LinkedNode[T] {
return l.front
}
func (l *LinkedList[T]) Front() T {
return l.FrontNode().Value
}
func (l *LinkedList[T]) BackNode() *LinkedNode[T] {
return l.tail
}
func (l *LinkedList[T]) Back() T {
if l.size == 0 {
panic(ErrorOutOffRange)
}
return l.tail.Value
}
func (l *LinkedList[T]) ForEach(fn func(T) bool) {
l.mu.RLock()
defer l.mu.RUnlock()
for current := l.front; current != nil; current = current.Next {
if fn(current.Value) {
break
}
}
}
func (l *LinkedList[T]) GetAt(i int) T {
if i <= l.Size() {
for n, current := 0, l.front; current != nil; current, n = current.Next, n+1 {
if n == i {
return current.Value
}
}
}
return *new(T)
}
func (l *LinkedList[T]) Remove(n *LinkedNode[T]) {
l.mu.Lock()
defer l.mu.Unlock()
if n.Next != nil {
n.Next.Prev = n.Prev
} else {
l.tail = n.Prev
}
if n.Prev != nil {
n.Prev.Next = n.Next
} else {
l.front = n.Next
}
n.Next = nil
n.Prev = nil
l.size--
}
func (l *LinkedList[T]) RemoveAt(index int) {
l.mu.Lock()
defer l.mu.Unlock()
var i int
for current := l.front; current != nil; current = current.Next {
if i == index {
// 重连接
current.Prev.Next = current.Next
current.Next.Prev = current.Prev
current.Prev = nil
current.Next = nil
l.size--
break
}
i++
}
}
func (l *LinkedList[T]) pushBackNode(n *LinkedNode[T]) {
n.Next = nil
n.Prev = l.tail
if l.tail != nil {
l.tail.Next = n
} else {
l.front = n
}
l.tail = n
l.size++
}
func (l *LinkedList[T]) pushFrontNode(n *LinkedNode[T]) {
n.Next = l.front
n.Prev = nil
if l.front != nil {
l.front.Prev = n
} else {
l.tail = n
}
l.front = n
l.size++
}

View File

@ -0,0 +1,103 @@
package list_test
import (
"testing"
"github.com/charlienet/go-mixed/collections/list"
"github.com/stretchr/testify/assert"
)
func TestPushBack(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
}
func TestPushFront(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushFront(1)
l.PushFront(2)
l.PushFront(3)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
}
func TestRemoveAt(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.RemoveAt(1)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
t.Log()
l.RemoveAt(0)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
}
func TestSize(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
assert.Equal(t, 3, l.Size())
l.RemoveAt(0)
assert.Equal(t, 2, l.Size())
}
func TestLinkedListToSlice(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
s := l.ToSlice()
t.Log(s)
}
func BenchmarkLinkedList(b *testing.B) {
l := list.NewLinkedList[int]()
l.Synchronize()
for i := 0; i < b.N; i++ {
l.PushBack(i)
}
}
func TestRemoveNode(t *testing.T) {
l := list.NewLinkedList(1, 2, 3, 4, 5)
// l.ForEach(func(i int) bool {
// t.Log(i)
// return false
// })
l.RemoveAt(1)
for currnet := l.FrontNode(); currnet != nil; currnet = currnet.Next {
t.Logf("%p %+v", currnet, currnet)
}
}

35
collections/list/list.go Normal file
View File

@ -0,0 +1,35 @@
package list
import (
"errors"
"github.com/charlienet/go-mixed/locker"
)
var ErrorOutOffRange = errors.New("out of range")
type List[T any] interface {
}
type list[T any] struct {
size int
mu locker.WithRWLocker
}
func (l *list[T]) Synchronize() {
l.mu.Synchronize()
}
func (l *list[T]) ForEach(fn func(T) bool) { panic("Not Implemented") }
func (l *LinkedList[T]) ToSlice() []T {
s := make([]T, 0, l.Size())
l.ForEach(func(t T) bool {
s = append(s, t)
return false
})
return s
}
func (l *list[T]) Size() int { return l.size }

View File

@ -1 +0,0 @@
package collections

View File

@ -1,11 +0,0 @@
package collections
import "github.com/charlienet/go-mixed/locker"
type options struct {
mu locker.RWLocker
}
func emptyLocker() locker.RWLocker {
return locker.EmptyLocker
}

View File

@ -1,6 +1,8 @@
package collections
import "sync"
import (
"sync"
)
var _ Queue[string] = &ArrayQueue[string]{}

View File

@ -0,0 +1,35 @@
package queue
import (
"github.com/charlienet/go-mixed/collections/list"
)
type Queue[T any] struct {
list list.LinkedList[T]
}
func NewQueue[T any]() *Queue[T] {
return &Queue[T]{}
}
func (q *Queue[T]) Synchronize() *Queue[T] {
q.list.Synchronize()
return q
}
func (q *Queue[T]) Push(v T) {
}
func (q *Queue[T]) Pop(v T) {
}
func (q *Queue[T]) Size() int {
return q.list.Size()
}
func (q *Queue[T]) IsEmpty() bool {
return false
}

View File

@ -0,0 +1 @@
package queue_test

View File

@ -0,0 +1,10 @@
package rbtree
type color bool
const (
black, red color = true, false
)
type TreeNode[K any, V any] struct {
}

View File

@ -0,0 +1 @@
package rbtree

View File

@ -1,42 +0,0 @@
package collections
import "sync"
type rw_queue[T any] struct {
q Queue[T]
mu sync.Mutex
}
func (q *rw_queue[T]) Push(v T) {
q.mu.Lock()
q.q.Put(v)
q.mu.Unlock()
}
func (q *rw_queue[T]) Pop() T {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.Poll()
}
func (q *rw_queue[T]) Peek() T {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.Peek()
}
func (q *rw_queue[T]) Size() int {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.Size()
}
func (q *rw_queue[T]) IsEmpty() bool {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.IsEmpty()
}

View File

@ -1,4 +1,4 @@
package collections
package stack
import "sync"

View File

@ -1,13 +1,13 @@
package collections_test
package stack_test
import (
"testing"
"github.com/charlienet/go-mixed/collections"
"github.com/charlienet/go-mixed/collections/stack"
)
func TestStack(t *testing.T) {
arrayStack := collections.NewArrayStack[string]()
arrayStack := stack.NewArrayStack[string]()
arrayStack.Push("cat")
arrayStack.Push("dog")
arrayStack.Push("hen")

View File

@ -0,0 +1,17 @@
package compiledbuffer
import (
"testing"
"github.com/dlclark/regexp2"
)
func TestCom(t *testing.T) {
regex, err := regexp2.Compile(`^\d{11}[;](?!(37|38))\d{2}\d{6}$`, regexp2.None)
if err != nil {
t.Fatal(err)
}
t.Log(regex.MatchString("14610522152;37764800"))
t.Log(regex.MatchString("14610522152;33764800"))
}

View File

@ -42,7 +42,8 @@ func (z *zipPackage) Write(out *os.File) error {
zipWriter := zip.NewWriter(out)
defer zipWriter.Close()
for _, f := range z.files {
files := z.files
for _, f := range files {
fileWriter, err := zipWriter.Create(f.name)
if err != nil {
return err

View File

@ -1,4 +1,4 @@
package crypto
package sm2
import (
"crypto/rand"
@ -14,7 +14,7 @@ var (
C1C2C3 = 1
)
var _ IAsymmetric = &sm2Instance{}
type option func(*sm2Instance) error
type sm2Instance struct {
mode int
@ -22,9 +22,42 @@ type sm2Instance struct {
puk *s.PublicKey
}
type option func(*sm2Instance) error
func WithSm2PrivateKey(p []byte, pwd []byte) option {
return func(so *sm2Instance) error {
priv, err := x.ReadPrivateKeyFromPem(p, pwd)
if err != nil {
return err
}
func NewSm2(opts ...option) (*sm2Instance, error) {
so.prk = priv
return nil
}
}
func WithSm2PublicKey(p []byte) option {
return func(so *sm2Instance) error {
if len(p) == 0 {
return nil
}
pub, err := x.ReadPublicKeyFromPem(p)
if err != nil {
return err
}
so.puk = pub
return nil
}
}
func WithMode(mode int) option {
return func(so *sm2Instance) error {
so.mode = mode
return nil
}
}
func New(opts ...option) (*sm2Instance, error) {
o := &sm2Instance{
mode: defaultMode,
}
@ -51,37 +84,6 @@ func NewSm2(opts ...option) (*sm2Instance, error) {
return o, nil
}
func ParseSm2PrivateKey(p []byte, pwd []byte) option {
return func(so *sm2Instance) error {
priv, err := x.ReadPrivateKeyFromPem(p, pwd)
if err != nil {
return err
}
so.prk = priv
return nil
}
}
func ParseSm2PublicKey(p []byte) option {
return func(so *sm2Instance) error {
pub, err := x.ReadPublicKeyFromPem(p)
if err != nil {
return err
}
so.puk = pub
return nil
}
}
func WithMode(mode int) option {
return func(so *sm2Instance) error {
so.mode = mode
return nil
}
}
func (o *sm2Instance) Encrypt(msg []byte) ([]byte, error) {
return s.Encrypt(o.puk, msg, rand.Reader, o.mode)
}

View File

@ -1,20 +1,34 @@
package crypto_test
package sm2
import (
"crypto/rand"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"testing"
"github.com/charlienet/go-mixed/crypto"
"github.com/tjfoc/gmsm/sm2"
x "github.com/tjfoc/gmsm/x509"
)
func TestPem(t *testing.T) {
key, _ := sm2.GenerateKey(rand.Reader)
prv, _ := x.WritePrivateKeyToPem(key, []byte{})
pub, _ := x.WritePublicKeyToPem(key.Public().(*sm2.PublicKey))
t.Log(x.WritePublicKeyToHex(&key.PublicKey))
t.Log(string(prv))
t.Log(string(pub))
}
func TestNewSm2(t *testing.T) {
o, err := crypto.NewSm2()
o, err := New()
t.Logf("%+v, %v", o, err)
t.Log(crypto.NewSm2(crypto.ParseSm2PrivateKey([]byte{}, []byte{})))
t.Log(New(WithSm2PrivateKey([]byte{}, []byte{})))
msg := []byte("123456")
sign, err := o.Sign(msg)
@ -49,9 +63,9 @@ hslcifiQY8173nHtaB3R6T0PwRQTwKbpdec0dwVCpvVcdzHtivndlG0mqQ==
)
func TestPrivatePem(t *testing.T) {
signer, err := crypto.NewSm2(
crypto.ParseSm2PrivateKey([]byte(privPem), []byte{}),
crypto.ParseSm2PublicKey([]byte(pubPem)))
signer, err := New(
WithSm2PrivateKey([]byte(privPem), []byte{}),
WithSm2PublicKey([]byte(pubPem)))
t.Log(signer, err)
if err != nil {
@ -67,9 +81,9 @@ func TestPrivatePem(t *testing.T) {
}
func TestBadPublicPem(t *testing.T) {
signer, err := crypto.NewSm2(
crypto.ParseSm2PrivateKey([]byte(privPem), []byte{}),
crypto.ParseSm2PublicKey([]byte(badPubPem)))
signer, err := New(
WithSm2PrivateKey([]byte(privPem), []byte{}),
WithSm2PublicKey([]byte(badPubPem)))
t.Log(signer, err)

View File

@ -1,12 +0,0 @@
package dateconv_test
import (
"testing"
"github.com/charlienet/go-mixed/dateconv"
)
func TestToday(t *testing.T) {
today := dateconv.Today()
t.Log(dateconv.TimeToString(&today))
}

View File

@ -1,43 +0,0 @@
package dateconv
import (
"time"
)
const (
layoutDate = "2006-01-02"
layoutTime = "2006-01-02 15:04:05"
layoutChineseDate = "2006年01月02日"
layoutChineseTime = "2006年01月02日 15:04:05"
)
func Today() time.Time {
t := time.Now()
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
// 日期转换为整数(如:20211222
func DateToInt(date time.Time) int {
return date.Year()*10000 + int(date.Month())*100 + date.Day()
}
// 日期转换为字符串
func DateToString(date *time.Time) string { return formatTime(date, layoutDate) }
// 时间转换为字符串
func TimeToString(date *time.Time) string { return formatTime(date, layoutTime) }
// 日期转换为中文
func DateToChinese(t *time.Time) string { return formatTime(t, layoutChineseDate) }
// 时间转换为中文
func TimeToChinese(t *time.Time) string { return formatTime(t, layoutChineseTime) }
func formatTime(t *time.Time, f string) string {
if t == nil || t.IsZero() {
return ""
}
return t.Format(f)
}

View File

@ -1,11 +0,0 @@
package dateconv
import "time"
func ParseDuration(s string) (time.Duration, error) {
if len(s) == 0 {
return time.Duration(0), nil
}
return time.ParseDuration(s)
}

View File

@ -1,10 +0,0 @@
package dateconv
import (
"testing"
)
func TestParseDuration(t *testing.T) {
t.Log(ParseDuration(""))
t.Log(ParseDuration("abc"))
}

3
db/readme.md Normal file
View File

@ -0,0 +1,3 @@
# 数据访问层,创建
使用gorm作为数据访问层

19
encode/encode.go Normal file
View File

@ -0,0 +1,19 @@
package encode
import "encoding/hex"
type BytesResult []byte
func FromString(s string) BytesResult {
return BytesResult([]byte(s))
}
func FromHexString(s string) BytesResult {
b, _ := hex.DecodeString(s)
return BytesResult(b)
}
func FromBytes(b []byte) BytesResult {
return BytesResult(b)
}

View File

@ -12,6 +12,17 @@ const (
defaultErrorCode = "999999"
)
type Error interface {
Wraped() []error
Code() string
error
private()
}
var _ Error = &CodeError{}
type CodeError struct {
cause error // 原始错误信息
code string // 错误码
@ -70,6 +81,10 @@ func (e *CodeError) WithCause(err error) *CodeError {
return new(err, e.code, e.message)
}
func (e *CodeError) Wraped() []error {
return []error{}
}
func (e *CodeError) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
@ -86,6 +101,8 @@ func (e *CodeError) Format(s fmt.State, verb rune) {
}
}
func (*CodeError) private() {}
func new(err error, code string, args ...any) *CodeError {
return &CodeError{
code: code,
@ -102,7 +119,7 @@ func newf(err error, code string, format string, args ...any) *CodeError {
}
}
func Error(code string, args ...any) *CodeError {
func ErrorWithCode(code string, args ...any) *CodeError {
return new(nil, code, args...)
}

View File

@ -16,8 +16,8 @@ const (
)
var (
globalError = errors.Error(defaultErrorCode, "全局错误对象")
errorbyCode = errors.Error(testCode, "全局错误对象")
globalError = errors.ErrorWithCode(defaultErrorCode, "全局错误对象")
errorbyCode = errors.ErrorWithCode(testCode, "全局错误对象")
)
func TestPersetError(t *testing.T) {
@ -40,7 +40,7 @@ func TestWithMessage(t *testing.T) {
}
func TestNewError(t *testing.T) {
var e error = errors.Error("123456", "测试")
var e error = errors.ErrorWithCode("123456", "测试")
err := e.(*errors.CodeError)
t.Log(e, err.Code())
@ -58,17 +58,17 @@ func TestWithStack(t *testing.T) {
}
func TestLogMessage(t *testing.T) {
t.Logf("%+v", errors.Error("88888", "错误消息"))
t.Log(errors.Error("77777"))
t.Log(errors.Error("77777", "测试"))
t.Logf("%+v", errors.ErrorWithCode("88888", "错误消息"))
t.Log(errors.ErrorWithCode("77777"))
t.Log(errors.ErrorWithCode("77777", "测试"))
}
func TestIs(t *testing.T) {
code1 := "000090"
code2 := "000091"
e1 := errors.Error(code1)
e2 := errors.Error(code1)
e3 := errors.Error(code2)
e1 := errors.ErrorWithCode(code1)
e2 := errors.ErrorWithCode(code1)
e3 := errors.ErrorWithCode(code2)
t.Log(errors.Is(e1, e2))
t.Log(errors.Is(e1, e3))

View File

@ -1,9 +1,128 @@
package expr
// 如为真返回参数一,否则返回参数二
func If[T any](e bool, v1, v2 T) T {
func Ternary[T any](e bool, v1, v2 T) T {
if e {
return v1
}
return v2
}
func TernaryF[T any](condition bool, ifFunc func() T, elseFunc func() T) T {
if condition {
return ifFunc()
}
return elseFunc()
}
type ifElse[T any] struct {
result T
done bool
}
func If[T any](condition bool, result T) *ifElse[T] {
if condition {
return &ifElse[T]{result, true}
}
var t T
return &ifElse[T]{t, false}
}
func IfF[T any](condition bool, resultF func() T) *ifElse[T] {
if condition {
return &ifElse[T]{resultF(), true}
}
var t T
return &ifElse[T]{t, false}
}
func (i *ifElse[T]) ElseIf(condition bool, result T) *ifElse[T] {
if !i.done && condition {
i.result = result
i.done = true
}
return i
}
func (i *ifElse[T]) ElseIfF(condition bool, resultF func() T) *ifElse[T] {
if !i.done && condition {
i.result = resultF()
i.done = true
}
return i
}
func (i *ifElse[T]) Else(result T) T {
if i.done {
return i.result
}
return result
}
func (i *ifElse[T]) ElseF(resultF func() T) T {
if i.done {
return i.result
}
return resultF()
}
type switchCase[T comparable, R any] struct {
predicate T
result R
done bool
}
func Switch[T comparable, R any](predicate T) *switchCase[T, R] {
var result R
return &switchCase[T, R]{
predicate,
result,
false,
}
}
func SwitchF[T comparable, R any](predicate func() T) *switchCase[T, R] {
return Switch[T, R](predicate())
}
func (s *switchCase[T, R]) Case(val T, result R) *switchCase[T, R] {
if !s.done && s.predicate == val {
s.result = result
s.done = true
}
return s
}
func (s *switchCase[T, R]) CaseF(val T, cb func() R) *switchCase[T, R] {
if !s.done && s.predicate == val {
s.result = cb()
s.done = true
}
return s
}
func (s *switchCase[T, R]) Default(result R) R {
if !s.done {
s.result = result
}
return s.result
}
func (s *switchCase[T, R]) DefaultF(cb func() R) R {
if !s.done {
s.result = cb()
}
return s.result
}

View File

@ -1,9 +1,31 @@
package expr
import "testing"
import (
"testing"
func TestIf(t *testing.T) {
"github.com/stretchr/testify/assert"
)
func TestTernary(t *testing.T) {
v1 := 10
v2 := 4
t.Log(If(v1 > v2, v1, v2))
t.Log(Ternary(v1 > v2, v1, v2))
}
func TestIf(t *testing.T) {
is := assert.New(t)
is.Equal(1, If(true, 1).ElseIf(false, 2).Else(3))
is.Equal(1, If(true, 1).ElseIf(true, 2).Else(3))
is.Equal(2, If(false, 1).ElseIf(true, 2).Else(3))
is.Equal(3, If(false, 1).ElseIf(false, 2).Else(3))
}
func TestSwitch(t *testing.T) {
is := assert.New(t)
is.Equal(1, Switch[int, int](42).Case(42, 1).Case(1, 2).Default(3))
is.Equal(1, Switch[int, int](42).Case(42, 1).Case(42, 2).Default(3))
is.Equal(1, Switch[int, int](42).Case(1, 1).Case(42, 2).Default(3))
is.Equal(1, Switch[int, int](42).Case(1, 1).Case(1, 2).Default(3))
}

21
file_store/file_store.go Normal file
View File

@ -0,0 +1,21 @@
package filestore
import (
"errors"
"io"
"os"
)
func New() {
f, err := os.Open("")
_ = err
_ = f
}
func Store(name string, reader io.Reader) error {
return errors.New("abc")
}

6
file_store/oss/oss.go Normal file
View File

@ -0,0 +1,6 @@
package oss
// add two int
func Add(a, b int) {
}

38
file_store/readme.md Normal file
View File

@ -0,0 +1,38 @@
# 文件存储组件
1. 支持磁盘OSSFTP等
2. 支持自定义存储
3. 支持同时存储到不同的存储目标
创建文件存储
```GO
New().WithStore(LocalDisk("D:\abc"))
```
删除文件
```GO
filestore.Delete("filename")
```
文件移动
```GO
filestore.Move("old", "new")
```
文件重命名
```GO
filestore.Rename("old", "new")
```

47
go.mod
View File

@ -1,36 +1,61 @@
module github.com/charlienet/go-mixed
go 1.18
go 1.21
require (
github.com/bits-and-blooms/bitset v1.2.2
github.com/cespare/xxhash/v2 v2.1.2
github.com/bits-and-blooms/bitset v1.8.0
github.com/cespare/xxhash/v2 v2.2.0
github.com/go-playground/universal-translator v0.18.1
github.com/json-iterator/go v1.1.12
github.com/shopspring/decimal v1.3.1
github.com/spaolacci/murmur3 v1.1.0
github.com/tjfoc/gmsm v1.4.1
)
require github.com/alphadose/haxmap v1.3.0
require (
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.1 // indirect
github.com/tebeka/strftime v0.1.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yuin/gopher-lua v1.1.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/text v0.12.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/allegro/bigcache/v3 v3.0.2
github.com/allegro/bigcache/v3 v3.1.0
github.com/coocood/freecache v1.2.3
github.com/vmihailenco/go-tinylfu v0.2.2
)
require (
github.com/alicebob/miniredis/v2 v2.30.5
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/coocood/freecache v1.2.1
github.com/dlclark/regexp2 v1.10.0
github.com/go-redis/redis/v8 v8.11.5
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.2
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/exp v0.0.0-20220608143224-64259d1afd70
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
github.com/vmihailenco/msgpack/v5 v5.3.5
golang.org/x/crypto v0.12.0
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
golang.org/x/sync v0.3.0
golang.org/x/sys v0.11.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)

152
go.sum
View File

@ -1,28 +1,67 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI=
github.com/allegro/bigcache/v3 v3.0.2/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.23.1 h1:jR6wZggBxwWygeXcdNyguCOCIjPsZyNUNlAkTx2fu0U=
github.com/alicebob/miniredis/v2 v2.23.1/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
github.com/alicebob/miniredis/v2 v2.30.5 h1:3r6kTHdKnuP4fkS8k2IrvSfxpxUTcW1SOL0wN7b7Dt0=
github.com/alicebob/miniredis/v2 v2.30.5/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk=
github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
github.com/alphadose/haxmap v1.2.0 h1:noGrAmCE+gNheZ4KpW+sYj9W5uMcO1UAjbAq9XBOAfM=
github.com/alphadose/haxmap v1.2.0/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
github.com/alphadose/haxmap v1.3.0 h1:C/2LboOnPCZP27GmmSXOcwx360st0P8N0fTJ3voefKc=
github.com/alphadose/haxmap v1.3.0/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk=
github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bitset v1.4.0 h1:+YZ8ePm+He2pU3dZlIZiOeAKfrBkXi1lSrXJ/Xzgbu8=
github.com/bits-and-blooms/bitset v1.4.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8=
github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U=
github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
github.com/coocood/freecache v1.2.3 h1:lcBwpZrwBZRZyLk/8EMyQVXRiFl663cCuMOrjCALeto=
github.com/coocood/freecache v1.2.3/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -41,10 +80,20 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo=
github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE=
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4=
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8=
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc=
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -60,25 +109,59 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tebeka/strftime v0.1.5 h1:1NQKN1NiQgkqd/2moD6ySP/5CoZQsKa1d3ZhJ44Jpmg=
github.com/tebeka/strftime v0.1.5/go.mod h1:29/OidkoWHdEKZqzyDLUyC+LmgDgdHo4WAFCDT7D/Ig=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI=
github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220608143224-64259d1afd70 h1:8uGxpY2cLF9H/NSHUiEWUIBZqIcsMzMWIMPCCUkyYgc=
golang.org/x/exp v0.0.0-20220608143224-64259d1afd70/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@ -88,21 +171,41 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 h1:z8Hj/bl9cOV2grsOpEaQFUaly0JWN3i97mo3jXKJNp0=
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -127,8 +230,11 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

118
idGenerator/id_generator.go Normal file
View File

@ -0,0 +1,118 @@
package idgenerator
import (
"fmt"
"math"
"time"
_ "unsafe"
)
// 时间段开始时间 2022-01-01
const startTimeStamp = 1640966400
const (
MachineIdBits = uint(8) //机器id所占的位数
SequenceBits = uint(12) //序列所占的位数
MachineIdMax = int64(-1 ^ (-1 << MachineIdBits)) //支持的最大机器id数量
SequenceMask = uint64(-1 ^ (-1 << SequenceBits)) //
MachineIdShift = uint64(SequenceBits) //机器id左移位数
TimestampShift = uint64(SequenceBits + MachineIdBits) //时间戳左移位数
)
type TimePrecision int
const (
Second TimePrecision = iota // 秒
Minute // 分
Day // 日
)
type Config struct {
Machine int
TimeScope TimePrecision
}
type Generator struct {
machine uint64
timeScope TimePrecision // 时间段精度
timestamp uint64 // 上次生成时间
sequence uint64 // 上次使用序列
}
type Id struct {
machine uint64 // 机器标识
Scope int // 时间精度
Timestamp uint64 // 生成时间
Sequence uint64 // 标识序列
}
func New(cfg Config) *Generator {
return &Generator{
machine: uint64(cfg.Machine),
timeScope: cfg.TimeScope,
}
}
func (g *Generator) Next() Id {
now := currentTimestamp()
if g.timestamp == now && g.sequence == 0 {
fmt.Println(time.Now().Format("2006-01-02 15:04:05.000"), "下一个时间点")
for now <= g.timestamp {
// runtime.Gosched()
now = currentTimestamp()
}
}
g.timestamp = now // 标识生成时间
g.sequence = (g.sequence + 1) & SequenceMask // 生成下一序列,超过最大值时返回零
return Id{
machine: g.machine,
Timestamp: g.timestamp,
Sequence: g.sequence,
}
}
func (i Id) Id() uint64 {
return i.Timestamp<<TimestampShift | i.machine<<MachineIdShift | i.Sequence
}
func (i Id) genCheck() uint64 {
return math.MaxUint64
}
func (i Id) String() string {
return fmt.Sprintf("Time:%d scope: %d Machine: %d Ser:%06d id:%d", i.Timestamp, i.Scope, i.machine, i.Sequence, i.Id())
}
func (g *Generator) NextId() uint64 {
return g.Next().Id()
}
func (g *Generator) Batch(num int) []uint64 {
ret := make([]uint64, num)
for i := 0; i < num; i++ {
ret[i] = g.NextId()
}
return ret
}
func Decode(id uint64) Id {
return Id{}
}
func Verify(id uint64) bool {
return false
}
func currentTimestamp() uint64 {
return uint64(time.Now().Unix() - startTimeStamp)
}
//go:linkname runtimeNano runtime.nanotime
func runtimeNano() int64

View File

@ -0,0 +1,32 @@
package idgenerator_test
import (
"testing"
idgenerator "github.com/charlienet/go-mixed/idGenerator"
)
func TestNextId(t *testing.T) {
generator := idgenerator.New(idgenerator.Config{})
for i := 0; i < 10; i++ {
t.Log(generator.Next().Id())
}
}
func TestBatch(t *testing.T) {
generator := idgenerator.New(idgenerator.Config{})
b := generator.Batch(20)
for _, i := range b {
t.Log(i)
}
}
func TestNext(t *testing.T) {
generator := idgenerator.New(idgenerator.Config{})
for i := 0; i < 10; i++ {
t.Log(generator.Next())
}
}

124
ip_range/ip_range.go Normal file
View File

@ -0,0 +1,124 @@
package iprange
import (
"fmt"
"net/netip"
"regexp"
"strconv"
"strings"
)
var maskPattern = regexp.MustCompile(`\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b`)
type IpRange struct {
segments []ipSegment
}
type ipSegment interface {
Contains(netip.Addr) bool
}
type singleIp struct {
ip netip.Addr
}
func (i *singleIp) Contains(ip netip.Addr) bool {
return i.ip.Compare(ip) == 0
}
type prefixSegments struct {
prefix netip.Prefix
}
func (i *prefixSegments) Contains(ip netip.Addr) bool {
return i.prefix.Contains(ip)
}
type rangeSegment struct {
start netip.Addr
end netip.Addr
}
func (r *rangeSegment) Contains(ip netip.Addr) bool {
return ip.Compare(r.start) >= 0 && ip.Compare(r.end) <= 0
}
// NewRange IP范围判断支持以下规则:
// 单IP地址如 192.168.100.2
// IP范围, 如 192.168.100.120-192.168.100.150
// 掩码模式,如 192.168.2.0/24
func NewRange(ip ...string) (*IpRange, error) {
seg := make([]ipSegment, 0, len(ip))
for _, i := range ip {
if s, err := createSegment(i); err != nil {
return nil, err
} else {
seg = append(seg, s)
}
}
return &IpRange{segments: seg}, nil
}
func (r *IpRange) Contains(ip string) bool {
addr, err := netip.ParseAddr(ip)
if err != nil {
return false
}
for _, v := range r.segments {
if v.Contains(addr) {
return true
}
}
return false
}
func createSegment(ip string) (ipSegment, error) {
switch {
case strings.Contains(ip, "-"):
ips := strings.Split(ip, "-")
if len(ips) != 2 {
return nil, fmt.Errorf("IP范围定义错误:%s", ip)
}
start, err := netip.ParseAddr(ips[0])
if err != nil {
return nil, err
}
end, err := netip.ParseAddr(ips[1])
if err != nil {
return nil, err
}
return &rangeSegment{
start: start,
end: end,
}, nil
case strings.Contains(ip, "/"):
sec := strings.Split(ip, "/")
ip := sec[0]
mask := sec[1]
if maskPattern.MatchString(mask) {
mask = strconv.Itoa(MaskToBits(mask))
}
if prefix, err := netip.ParsePrefix(ip + "/" + mask); err != nil {
return nil, err
} else {
return &prefixSegments{prefix: prefix}, nil
}
default:
i, err := netip.ParseAddr(ip)
if err != nil {
return nil, fmt.Errorf("格式错误, 不是有效的IP地址:%s", ip)
}
return &singleIp{ip: i}, nil
}
}

130
ip_range/ip_range_test.go Normal file
View File

@ -0,0 +1,130 @@
package iprange
import (
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSingleErrorIP(t *testing.T) {
values := []string{
"192.168.01",
"::",
}
for _, v := range values {
r, err := NewRange(v)
t.Log(r, err)
}
}
func TestSingleIp(t *testing.T) {
r, err := NewRange("192.168.0.1")
if err != nil {
t.Fatal(err)
}
assert.True(t, r.Contains("192.168.0.1"))
assert.False(t, r.Contains("192.168.0.123"))
}
func TestSinglePrefix(t *testing.T) {
r, err := NewRange("192.168.2.100/32")
if err != nil {
t.Fatal(err)
}
assert.False(t, r.Contains("192.168.2.56"))
assert.True(t, r.Contains("192.168.2.100"))
assert.False(t, r.Contains("192.168.2.130"))
}
func TestAllIp(t *testing.T) {
r, err := NewRange("0.0.0.0/0")
if err != nil {
t.Fatal(err)
}
assert.True(t, r.Contains("192.168.2.100"))
assert.True(t, r.Contains("192.3.2.100"))
assert.True(t, r.Contains("192.65.2.100"))
assert.True(t, r.Contains("172.168.2.100"))
assert.True(t, r.Contains("8.8.8.8"))
assert.True(t, r.Contains("114.114.114.114"))
}
func TestPrefix(t *testing.T) {
r, err := NewRange("192.168.2.0/24")
if err != nil {
t.Fatal(err)
}
assert.True(t, r.Contains("192.168.2.12"))
assert.True(t, r.Contains("192.168.2.162"))
assert.False(t, r.Contains("192.168.3.162"))
}
func TestPrefix2(t *testing.T) {
r, err := NewRange("192.168.15.0/21")
if err != nil {
t.Fatal(err)
}
assert.True(t, r.Contains("192.168.8.10"))
assert.True(t, r.Contains("192.168.14.162"))
assert.False(t, r.Contains("192.168.3.162"))
assert.False(t, r.Contains("192.168.2.162"))
}
func TestDotMask(t *testing.T) {
r, err := NewRange("192.168.15.0/255.255.248.0")
if err != nil {
t.Fatal(err)
}
assert.True(t, r.Contains("192.168.8.10"))
assert.True(t, r.Contains("192.168.14.162"))
assert.False(t, r.Contains("192.168.3.162"))
assert.False(t, r.Contains("192.168.2.162"))
}
func TestRange(t *testing.T) {
r, err := NewRange("192.168.2.20-192.168.2.30")
if err != nil {
t.Fatal(err)
}
assert.True(t, r.Contains("192.168.2.20"))
assert.True(t, r.Contains("192.168.2.21"))
assert.True(t, r.Contains("192.168.2.30"))
assert.False(t, r.Contains("192.168.2.10"))
assert.False(t, r.Contains("192.168.2.31"))
}
func TestLocalhost(t *testing.T) {
r, err := NewRange("::1")
if err != nil {
t.Fatal(err)
}
assert.True(t, r.Contains("::1"))
}
func TestNetIP(t *testing.T) {
addr, err := netip.ParseAddr("192.168.2.10")
if err != nil {
t.Fatal(err)
}
t.Log(netip.MustParseAddr("192.168.2.4").Compare(addr))
t.Log(netip.MustParseAddr("192.168.2.10").Compare(addr))
t.Log(netip.MustParseAddr("192.168.2.11").Compare(addr))
prefix := netip.MustParsePrefix("192.168.2.0/24")
t.Log(prefix.Contains(netip.MustParseAddr("192.168.2.53")))
t.Log(prefix.Contains(netip.MustParseAddr("192.168.3.53")))
}

34
ip_range/mask_bits.go Normal file
View File

@ -0,0 +1,34 @@
package iprange
import "strings"
var maskBits = map[string]int{
"255": 8,
"254": 7,
"252": 6,
"248": 5,
"240": 4,
"224": 3,
"192": 2,
"128": 1,
"0": 0,
}
func MaskToBits(mask string) int {
bits := 0
secs := strings.Split(mask, ".")
if len(secs) != 4 {
panic("the mask is incorrect")
}
for _, s := range secs {
if v, ok := maskBits[s]; ok {
bits += v
} else {
panic("the mask is incorrect")
}
}
return bits
}

View File

@ -0,0 +1,26 @@
package iprange
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMaskToBits(t *testing.T) {
masks := []struct {
mask string
expect int
}{
{"255.255.255.0", 24},
{"255.255.248.0", 21},
{"255.255.192.0", 18},
{"255.255.255.192", 26},
}
for _, m := range masks {
bits := MaskToBits(m.mask)
assert.Equal(t, m.expect, bits, fmt.Sprintf("IP:%s 掩码位数错误。", m.mask))
}
}

View File

@ -5,6 +5,9 @@ package json
import "encoding/json"
func RegisterFuzzyDecoders() {
}
var (
Marshal = json.Marshal
Unmarshal = json.Unmarshal

View File

@ -2,12 +2,13 @@ package json
import "github.com/charlienet/go-mixed/bytesconv"
// StructToJsonIndent 结构转换为带格式字符串
func StructToJsonIndent(obj any) string {
b, _ := MarshalIndent(obj, "", " ")
return bytesconv.BytesToString(b)
}
// 结构转换为json字符串
// StructToJson 结构转换为json字符串
func StructToJson(obj any) string {
b, _ := Marshal(obj)
return bytesconv.BytesToString(b)

38
ketama/ketama.go Normal file
View File

@ -0,0 +1,38 @@
package ketama
import (
"github.com/charlienet/go-mixed/locker"
"github.com/charlienet/go-mixed/maps"
)
type Ketama struct {
mu locker.WithRWLocker
replicas int
m maps.Map[uint64, string]
}
func New() *Ketama {
return &Ketama{
m: maps.NewHashMap[uint64, string](),
}
}
func (k *Ketama) Synchronize() {
k.mu.Synchronize()
}
func (k *Ketama) Add(nodes ...string) {
k.mu.Lock()
defer k.mu.Unlock()
for _, node := range nodes {
_ = node
}
}
func (k *Ketama) IsEmpty() bool {
k.mu.RLock()
defer k.mu.RUnlock()
return k.m.Count() == 0
}

32
ketama/ketama_test.go Normal file
View File

@ -0,0 +1,32 @@
package ketama_test
import (
"testing"
"github.com/charlienet/go-mixed/ketama"
)
func TestNew(t *testing.T) {
k := ketama.New()
t.Logf("%+v", k)
k.Synchronize()
// k.Lock()
t.Logf("%+v", k)
t.Log(k.IsEmpty())
t.Logf("%+v", k)
t.Log(k.IsEmpty())
t.Logf("%+v", k)
k.Synchronize()
t.Log(k.IsEmpty())
t.Logf("%+v", k)
t.Log(k.IsEmpty())
t.Logf("%+v", k)
t.Log(k.IsEmpty())
t.Logf("%+v", k)
}

View File

@ -0,0 +1,41 @@
package locker
type ChanLocker interface {
Get(key string) (ch <-chan int, ok bool)
Release(key string)
}
type chanSourceLock struct {
m RWLocker
content map[string]chan int
}
func (s *chanSourceLock) Get(key string) (ch <-chan int, ok bool) {
s.m.RLock()
ch, ok = s.content[key]
s.m.RUnlock()
if ok {
return
}
s.m.Lock()
ch, ok = s.content[key]
if ok {
s.m.Unlock()
return
}
s.content[key] = make(chan int)
ch = s.content[key]
ok = true
s.m.Unlock()
return
}
func (s *chanSourceLock) Release(key string) {
s.m.Lock()
ch, ok := s.content[key]
if ok {
close(ch)
delete(s.content, key)
}
s.m.Unlock()
}

View File

@ -1,5 +1,8 @@
package locker
var _ RWLocker = &emptyLocker{}
var _ Locker = &emptyLocker{}
var EmptyLocker = &emptyLocker{}
type emptyLocker struct{}

View File

@ -2,53 +2,93 @@ package locker
import (
"fmt"
"sync/atomic"
)
// 资源
// 带计数器
type countLocker struct {
Locker
Count int32
}
// SourceLocker 资源锁
type SourceLocker struct {
m RWLocker
locks map[string]Locker
locks map[string]*countLocker
}
func NewSourceLocker() *SourceLocker {
return &SourceLocker{
m: NewRWLocker(),
locks: make(map[string]Locker),
locks: make(map[string]*countLocker),
}
}
func (s *SourceLocker) Lock(key string) {
s.m.RLock()
l, ok := s.locks[key]
s.m.RUnlock()
if ok {
s.m.RUnlock()
atomic.AddInt32(&l.Count, 1)
l.Lock()
fmt.Println("加锁")
} else {
s.m.RUnlock()
// 加锁,再次检查是否已经具有锁
s.m.Lock()
new := NewLocker()
s.locks[key] = new
if l2, ok := s.locks[key]; ok {
s.m.Unlock()
new.Lock()
l2.Lock()
fmt.Println("二次检查加锁")
} else {
n := NewLocker()
s.locks[key] = &countLocker{Locker: n, Count: 1}
s.m.Unlock()
fmt.Printf("新锁准备加锁:%p\n", n)
n.Lock()
fmt.Println("初始加锁")
}
}
}
func (s *SourceLocker) Unlock(key string) {
s.m.Lock()
if l, ok := s.locks[key]; ok {
atomic.AddInt32(&l.Count, -1)
fmt.Printf("解锁%p\n", l)
l.Unlock()
// delete(s.locks, key)
fmt.Println("解锁")
if l.Count == 0 {
delete(s.locks, key)
}
}
s.m.Unlock()
}
func (s *SourceLocker) TryLock(key string) bool {
return false
// 加读锁
s.m.RLock()
l, ok := s.locks[key]
if ok {
ret := l.TryLock()
s.m.RUnlock()
return ret
} else {
s.m.RUnlock()
s.m.Lock()
n := NewLocker()
s.locks[key] = &countLocker{Locker: n, Count: 1}
s.m.Unlock()
return n.TryLock()
}
}

View File

@ -3,10 +3,15 @@ package locker
import (
"sync"
"testing"
"time"
)
var sourcekey = "u-0001"
func TestTryLock(t *testing.T) {
}
func TestSourceLocker(t *testing.T) {
l := NewSourceLocker()
@ -27,6 +32,32 @@ func TestSourceLocker(t *testing.T) {
wg.Wait()
t.Log("n:", n)
t.Logf("%+v", l)
}
func TestSourceTryLock(t *testing.T) {
c := 5
n := 0
wg := new(sync.WaitGroup)
wg.Add(c)
l := NewSourceLocker()
for i := 0; i < c; i++ {
go func() {
defer wg.Done()
if l.TryLock(sourcekey) {
n++
time.Sleep(time.Second)
l.Unlock(sourcekey)
}
}()
}
wg.Wait()
t.Log("n:", n)
t.Logf("%+v", l)
}
func BenchmarkSourceLocker(b *testing.B) {

96
locker/synchronizeable.go Normal file
View File

@ -0,0 +1,96 @@
package locker
import (
"log"
"sync"
)
type WithLocker struct {
once sync.Once
mu Locker
}
func (w *WithLocker) Synchronize() {
if w.mu == nil || w.mu == EmptyLocker {
w.mu = NewLocker()
}
}
func (w *WithLocker) Lock() {
w.ensureLocker().Lock()
}
func (w *WithLocker) Unlock() {
w.ensureLocker().Unlock()
}
func (w *WithLocker) TryLock() bool {
return w.ensureLocker().TryLock()
}
func (w *WithLocker) ensureLocker() Locker {
w.once.Do(func() {
if w.mu == nil {
w.mu = EmptyLocker
}
})
return w.mu
}
type WithSpinLocker struct {
WithLocker
}
func (w *WithSpinLocker) Synchronize() {
if w.mu == nil || w.mu == EmptyLocker {
w.mu = NewSpinLocker()
}
}
type WithRWLocker struct {
once sync.Once
mu RWLocker
}
func (w *WithRWLocker) Synchronize() {
if w.mu == nil || w.mu == EmptyLocker {
log.Println("初始化有效锁")
w.mu = NewRWLocker()
}
}
func (w *WithRWLocker) Lock() {
w.ensureLocker().Lock()
}
func (w *WithRWLocker) TryLock() bool {
return w.ensureLocker().TryLock()
}
func (w *WithRWLocker) Unlock() {
w.ensureLocker().Unlock()
}
func (w *WithRWLocker) RLock() {
w.ensureLocker().RLock()
}
func (w *WithRWLocker) TryRLock() bool {
return w.ensureLocker().TryRLock()
}
func (w *WithRWLocker) RUnlock() {
w.ensureLocker().RUnlock()
}
func (w *WithRWLocker) ensureLocker() RWLocker {
w.once.Do(func() {
if w.mu == nil {
log.Println("初始化一个空锁")
w.mu = EmptyLocker
}
})
return w.mu
}

View File

@ -1,5 +1,7 @@
package logx
import "io"
var std = defaultLogger()
func StandardLogger() Logger {
@ -34,4 +36,5 @@ type Logger interface {
Println(args ...any)
Print(args ...any)
Printf(format string, args ...any)
Writer() io.Writer
}

View File

@ -1,3 +1,23 @@
package logx
import (
"io"
"os"
)
type Rotate int
const (
None Rotate = iota // 不分割日志
Size // 按大小分割
Date // 按日期分割
)
type OutputOptions struct {
LogrusOutputOptions
}
func WithFile(filename string) (io.Writer, error) {
mode := os.FileMode(0644)
return os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, mode)
}

1
logx/logger.go Normal file
View File

@ -0,0 +1 @@
package logx

View File

@ -1,16 +0,0 @@
package logx
type loggerBuilder struct {
}
func NewBuilder() *loggerBuilder {
return &loggerBuilder{}
}
func (b *loggerBuilder) WithLogrus() *loggerBuilder {
return b
}
func (b *loggerBuilder) WithLogger() *loggerBuilder {
return b
}

View File

@ -2,14 +2,8 @@ package logx_test
import (
"testing"
"github.com/charlienet/go-mixed/logx"
)
func TestBuilder(t *testing.T) {
logger := logx.NewBuilder().
WithLogrus().
WithLogger()
_ = logger
}

View File

@ -1,6 +1,8 @@
package logx
import (
"io"
"github.com/sirupsen/logrus"
)
@ -37,3 +39,7 @@ func (l *logrusWrpper) WithFields(fields Fields) Logger {
return l
}
func (l *logrusWrpper) Writer() io.Writer {
return l.Entry.Writer()
}

View File

@ -6,6 +6,7 @@ import (
"runtime"
nested "github.com/antonfisher/nested-logrus-formatter"
"github.com/sirupsen/logrus"
)
const defaultTimestampFormat = "2006-01-02 15:04:05.000"
@ -14,6 +15,14 @@ type NestedFormatterOption struct {
Color bool
}
func NewJsonFormatter() logrus.Formatter {
return &logrus.JSONFormatter{}
}
func NewTextFOrmatter() logrus.Formatter {
return &logrus.TextFormatter{}
}
func NewNestedFormatter(option NestedFormatterOption) *nested.Formatter {
return &nested.Formatter{
TimestampFormat: defaultTimestampFormat,

View File

@ -32,6 +32,7 @@ type LogrusOutputOptions struct {
}
type LogrusBackupOptions struct {
BackupType Rotate // 分割类型
MaxSize int // 默认大小100M
MaxAge int // 备份保留天数
MaxBackups int // 备份保留数量
@ -59,6 +60,12 @@ func WithOptions(o LogrusOptions) logrusOption {
}
}
func WithNestedFormatter(o NestedFormatterOption) logrusOption {
return func(logrusLogger *logrus.Logger) {
logrusLogger.Formatter = NewNestedFormatter(o)
}
}
func WithFormatter(formatter logrus.Formatter) logrusOption {
return func(logrusLogger *logrus.Logger) {
logrusLogger.SetFormatter(formatter)

View File

@ -39,9 +39,3 @@ func TestLevel(t *testing.T) {
// logger.SetLevel(l)
logger.Info("bcdefg")
}
func TestMutiWriter(t *testing.T) {
l := NewLogger().AppendLogger()
_ = l
}

View File

@ -1,12 +0,0 @@
package logx
type mutiLogger struct {
}
func NewLogger() *mutiLogger {
return &mutiLogger{}
}
func (w *mutiLogger) AppendLogger() Logger {
return nil
}

26
logx/readme.md Normal file
View File

@ -0,0 +1,26 @@
# 日志记录组件
日志分割及备份
日志可按照日期或大小进行分割,保留的历史日志文件数量由备份数量决定。
1. 按天拆分每天生成新的日志文件名称。格式为file.yyyy-mm-dd.log 其中file和log为配置的日志文件名称。
2. 按大小拆分使用lumberjack组件对日志文件进行分割。
3. 按时间间隔拆分,日志文件按照指定的间隔拆分,
日志输出流
支持控制台和文件输出,可扩展输出组件
``` golang
logx.NewLogger(
WithLevel("debug"),
WithFormatter(),
WithConsole(),
WithRoateBySize(FileRoateSize{
MaxSize
MaxAge
MaxBackups
}),
WithRoateByDate("filename", MaxAge, MaxBackups),
WithFile("filename"))
```

Some files were not shown because too many files have changed in this diff Show More