├── znet ├── rpc_test.go ├── realip │ ├── cloudflare_test.go │ └── cloudflare.go ├── signal_win.go ├── template.go ├── timeout │ └── timeout.go ├── signal_notwin.go ├── gzip │ ├── util.go │ ├── gzip.go │ └── gzip_test.go ├── handler_test.go ├── template_go_test.go ├── session │ ├── store.go │ └── session.go ├── limiter │ ├── statistics.go │ ├── queue.go │ └── limiter.go ├── cache │ ├── cache_test.go │ └── cache.go ├── doc.go ├── auth │ ├── auth_test.go │ └── auth.go ├── value.go └── rpc.go ├── zstring ├── s_118.go ├── s_109.go ├── md5_test.go ├── s_110.go ├── url_test.go ├── rand_118.go ├── expand_test.go ├── dispose_test.go ├── rand_117.go ├── url.go ├── md5.go ├── snowflake_test.go ├── encoding_test.go ├── template_test.go ├── regex_test.go └── expand.go ├── .cnb.yml ├── zlog ├── color_notwin.go ├── zlogger_test.go ├── color_test.go ├── file_test.go └── color_win.go ├── zcli ├── service_test.go └── signal.go ├── ztype ├── pointer.go ├── pointer_test.go ├── utils.go ├── decimal.go ├── decima_test.go └── pathcache_test.go ├── go.mod ├── zutil ├── utils_118.go ├── runtime_test.go ├── utils_117.go ├── runtime.go ├── options.go ├── once_117.go ├── daemon │ ├── signal_notwin.go │ ├── signal_win.go │ ├── daemon_test.go │ └── signal.go ├── options_test.go ├── utils_nowin.go ├── utils_win.go ├── chan_test.go ├── retry_test.go ├── env_test.go ├── other_test.go ├── once.go └── other.go ├── ztime ├── utils.go ├── utils_test.go ├── local.go ├── local_test.go ├── cron │ └── doc.go └── time.go ├── .gitignore ├── zshell ├── utils.go ├── shell_win.go └── shell_notwin.go ├── zsync ├── pool.go ├── coalescer.go ├── mutex_fallback.go ├── pool_test.go ├── utils.go ├── atomic.go ├── doc.go ├── seqlock.go ├── content_test.go ├── mutex_test.go └── waitgroup_test.go ├── zfile ├── utils.go ├── utils_test.go ├── file_notwin.go ├── file_win.go ├── compress_test.go ├── lock_unix.go ├── handle_test.go ├── memory_test.go ├── lock_test.go ├── lock.go └── lock_windows.go ├── zjson └── pool.go ├── zhttp ├── sse_test.go ├── json_rpc_test.go ├── requester.go └── query_render.go ├── zreflect ├── bench_test.go ├── value_test.go ├── method_test.go ├── type_test.go ├── method.go ├── doc.go ├── value.go ├── type.go ├── quick_test.go └── utils_test.go ├── zerror ├── catch.go ├── format_test.go ├── tag_test.go ├── catch_test.go └── tag.go ├── zdi ├── lazy.go ├── pre.go ├── di_test.go ├── lazy_test.go ├── bind_test.go └── di.go ├── zlocale ├── cache_test.go ├── loader.go └── cache.go ├── zpprof ├── pprof_test.go ├── pprof.go ├── systeminfo.go └── README.md ├── zcache ├── simple_test.go ├── cache.go ├── simple.go └── tofile │ └── tofile_test.go ├── zarray ├── sort_test.go ├── slice_121.go ├── string_test.go ├── map.go ├── string.go ├── hashmap_benchmark_test.go └── sort.go ├── .github └── workflows │ └── go.yml ├── go.sum ├── LICENSE ├── doc.go ├── zpool ├── inject.go ├── release_test.go └── pool_benchmark_test.go └── zvalid ├── json_test.go ├── format_test.go ├── has_test.go ├── format.go └── bind_test.go /znet/rpc_test.go: -------------------------------------------------------------------------------- 1 | package znet 2 | -------------------------------------------------------------------------------- /zstring/s_118.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zstring 5 | -------------------------------------------------------------------------------- /.cnb.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - https://cnb.cool/zls-tools/vm/-/blob/main/tools/sync-github-sohaha.yml 3 | -------------------------------------------------------------------------------- /zlog/color_notwin.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package zlog 5 | 6 | // IsSupportColor IsSupportColor 7 | func IsSupportColor() bool { 8 | return supportColor 9 | } 10 | -------------------------------------------------------------------------------- /znet/realip/cloudflare_test.go: -------------------------------------------------------------------------------- 1 | package realip 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestGetCloudflare(t *testing.T) { 9 | t.Log(GetCloudflare()) 10 | t.Log(GetCloudflare(time.Microsecond)) 11 | } 12 | -------------------------------------------------------------------------------- /zcli/service_test.go: -------------------------------------------------------------------------------- 1 | package zcli 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestService(t *testing.T) { 8 | s, err := LaunchService("test", "", func() { 9 | t.Log("TestService") 10 | }) 11 | t.Log(s, err) 12 | } 13 | -------------------------------------------------------------------------------- /znet/signal_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package znet 5 | 6 | import ( 7 | "errors" 8 | ) 9 | 10 | // Restart Restart 11 | func (e *Engine) Restart() error { 12 | return errors.New("windows does not support") 13 | } 14 | -------------------------------------------------------------------------------- /znet/template.go: -------------------------------------------------------------------------------- 1 | package znet 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Template interface { 8 | Load() error 9 | Render(io.Writer, string, interface{}, ...string) error 10 | } 11 | 12 | func (e *Engine) SetTemplate(v Template) { 13 | e.views = v 14 | } 15 | -------------------------------------------------------------------------------- /ztype/pointer.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package ztype 5 | 6 | // ToPointer returns a pointer to the given value. 7 | // This is a generic function that works with any type T. 8 | func ToPointer[T any](value T) *T { 9 | return &value 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sohaha/zlsgo 2 | 3 | go 1.18 4 | 5 | require ( 6 | golang.org/x/crypto v0.21.0 7 | golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb 8 | golang.org/x/net v0.23.0 9 | golang.org/x/sync v0.1.0 10 | golang.org/x/sys v0.18.0 11 | ) 12 | 13 | require golang.org/x/text v0.14.0 // indirect 14 | -------------------------------------------------------------------------------- /zutil/utils_118.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zutil 5 | 6 | // IfVal Simulate ternary calculations, pay attention to handling no variables or indexing problems 7 | func IfVal[T interface{}](condition bool, trueVal, falseVal T) T { 8 | if condition { 9 | return trueVal 10 | } 11 | return falseVal 12 | } 13 | -------------------------------------------------------------------------------- /zstring/s_109.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.10 2 | // +build !go1.10 3 | 4 | package zstring 5 | 6 | import ( 7 | "bytes" 8 | ) 9 | 10 | // Buffer creates a new empty bytes.Buffer. 11 | // This implementation is for Go versions prior to 1.10. 12 | func Buffer(size ...int) *bytes.Buffer { 13 | b := bytes.NewBufferString("") 14 | return &b 15 | } 16 | -------------------------------------------------------------------------------- /ztime/utils.go: -------------------------------------------------------------------------------- 1 | package ztime 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | func Sleep(ctx context.Context, duration time.Duration) error { 9 | timer := time.NewTimer(duration) 10 | defer timer.Stop() 11 | 12 | select { 13 | case <-ctx.Done(): 14 | return ctx.Err() 15 | case <-timer.C: 16 | return nil 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /zutil/runtime_test.go: -------------------------------------------------------------------------------- 1 | package zutil_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo/zutil" 7 | ) 8 | 9 | func TestGetGid(t *testing.T) { 10 | t.Log(zutil.GetGid()) 11 | c := make(chan struct{}) 12 | go func() { 13 | t.Log(zutil.GetGid()) 14 | c <- struct{}{} 15 | }() 16 | <-c 17 | t.Log(zutil.GetGid()) 18 | } 19 | -------------------------------------------------------------------------------- /zutil/utils_117.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.18 2 | // +build !go1.18 3 | 4 | package zutil 5 | 6 | // IfVal Simulate ternary calculations, pay attention to handling no variables or indexing problems 7 | func IfVal(condition bool, trueVal, falseVal interface{}) interface{} { 8 | if condition { 9 | return trueVal 10 | } 11 | return falseVal 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | .DS_Store 14 | .idea 15 | .vscode 16 | report 17 | gotest 18 | .history 19 | 20 | coverage.txt 21 | test.json 22 | *.yaml 23 | .*/ -------------------------------------------------------------------------------- /zcli/signal.go: -------------------------------------------------------------------------------- 1 | package zcli 2 | 3 | import ( 4 | "github.com/sohaha/zlsgo/zutil/daemon" 5 | ) 6 | 7 | // SingleKillSignal returns a channel that will receive a value when the application 8 | // receives a termination signal (such as SIGINT or SIGTERM). 9 | // This can be used to implement graceful shutdown handling. 10 | func SingleKillSignal() <-chan bool { 11 | return daemon.SingleKillSignal() 12 | } 13 | -------------------------------------------------------------------------------- /zstring/md5_test.go: -------------------------------------------------------------------------------- 1 | package zstring 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | ) 8 | 9 | func TestMd5(t *testing.T) { 10 | tt := zlsgo.NewTest(t) 11 | str := "zlsgo" 12 | tt.Equal("e058a5f5dd76183d00d902c61c250fe3", Md5(str)) 13 | t.Log(Md5File("./md5.go")) 14 | t.Log(Md5File("./md5.go.bak")) 15 | } 16 | 17 | func TestProjectMd5(t *testing.T) { 18 | t.Log(ProjectMd5()) 19 | } 20 | -------------------------------------------------------------------------------- /zstring/s_110.go: -------------------------------------------------------------------------------- 1 | //go:build go1.10 2 | // +build go1.10 3 | 4 | package zstring 5 | 6 | import ( 7 | "strings" 8 | ) 9 | 10 | // Buffer creates a new strings.Builder with optional initial capacity. 11 | // This implementation uses the more efficient strings.Builder available in Go 1.10+. 12 | func Buffer(size ...int) *strings.Builder { 13 | var b strings.Builder 14 | if len(size) > 0 { 15 | b.Grow(size[0]) 16 | } 17 | return &b 18 | } 19 | -------------------------------------------------------------------------------- /zshell/utils.go: -------------------------------------------------------------------------------- 1 | package zshell 2 | 3 | import "os/exec" 4 | 5 | func wrapOptions(cmd *exec.Cmd, opt ...func(o *Options)) { 6 | o := Options{} 7 | for _, v := range opt { 8 | v(&o) 9 | } 10 | 11 | if o.Dir != "" { 12 | cmd.Dir = o.Dir 13 | } else if Dir != "" { 14 | cmd.Dir = Dir 15 | } 16 | 17 | if o.Env != nil { 18 | cmd.Env = append(cmd.Env, o.Env...) 19 | } else if Env != nil { 20 | cmd.Env = append(cmd.Env, Env...) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /zstring/url_test.go: -------------------------------------------------------------------------------- 1 | package zstring 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | ) 8 | 9 | func TestUrl(t *testing.T) { 10 | tt := zlsgo.NewTest(t) 11 | str := "?d=1 &c=2" 12 | res := UrlEncode(str) 13 | t.Log(res) 14 | d, err := UrlDecode(str) 15 | tt.EqualNil(err) 16 | tt.Equal(str, d) 17 | 18 | res = UrlRawEncode(str) 19 | t.Log(res) 20 | d, err = UrlRawDecode(str) 21 | tt.EqualNil(err) 22 | tt.Equal(str, d) 23 | } 24 | -------------------------------------------------------------------------------- /zsync/pool.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zsync 5 | 6 | import "sync" 7 | 8 | type Pool[T any] struct { 9 | p sync.Pool 10 | } 11 | 12 | func NewPool[T any](n func() T) *Pool[T] { 13 | return &Pool[T]{ 14 | p: sync.Pool{ 15 | New: func() any { 16 | return n() 17 | }, 18 | }, 19 | } 20 | } 21 | 22 | func (p *Pool[T]) Get() T { 23 | return p.p.Get().(T) 24 | } 25 | 26 | func (p *Pool[T]) Put(x T) { 27 | p.p.Put(x) 28 | } 29 | -------------------------------------------------------------------------------- /zfile/utils.go: -------------------------------------------------------------------------------- 1 | package zfile 2 | 3 | // File size unit constants for use in size calculations and formatting. 4 | const ( 5 | // BYTE represents 1 byte 6 | BYTE = 1 << (iota * 10) 7 | // KB represents 1 kilobyte (1024 bytes) 8 | KB 9 | // MB represents 1 megabyte (1024 kilobytes) 10 | MB 11 | // GB represents 1 gigabyte (1024 megabytes) 12 | GB 13 | // TB represents 1 terabyte (1024 gigabytes) 14 | TB 15 | // PB represents 1 petabyte (1024 terabytes) 16 | PB 17 | ) 18 | -------------------------------------------------------------------------------- /zjson/pool.go: -------------------------------------------------------------------------------- 1 | package zjson 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // pathCachePool reuses path parsing results 8 | var pathCachePool = sync.Pool{ 9 | New: func() interface{} { 10 | return make([]pathResult, 0, 8) 11 | }, 12 | } 13 | 14 | func getPathCache() []pathResult { 15 | return pathCachePool.Get().([]pathResult)[:0] 16 | } 17 | 18 | func putPathCache(cache []pathResult) { 19 | if cap(cache) > 32 { 20 | return 21 | } 22 | pathCachePool.Put(cache) 23 | } 24 | -------------------------------------------------------------------------------- /ztype/pointer_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package ztype_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/sohaha/zlsgo" 10 | "github.com/sohaha/zlsgo/ztype" 11 | ) 12 | 13 | func TestNewPointer(t *testing.T) { 14 | tt := zlsgo.NewTest(t) 15 | 16 | b := ztype.ToPointer(true) 17 | tt.EqualExit(true, *b) 18 | 19 | b2 := ztype.ToPointer(false) 20 | tt.EqualExit(false, *b2) 21 | 22 | i := ztype.ToPointer(1) 23 | tt.EqualExit(1, *i) 24 | } 25 | -------------------------------------------------------------------------------- /zutil/runtime.go: -------------------------------------------------------------------------------- 1 | package zutil 2 | 3 | import ( 4 | "bytes" 5 | "runtime" 6 | "strconv" 7 | ) 8 | 9 | // GetGid returns the ID of the current goroutine. 10 | // This is useful for debugging and logging purposes to track which goroutine 11 | // is executing a particular piece of code. 12 | func GetGid() uint64 { 13 | b := make([]byte, 64) 14 | runtime.Stack(b, false) 15 | b = bytes.TrimPrefix(b, []byte("goroutine ")) 16 | b = b[:bytes.IndexByte(b, ' ')] 17 | n, _ := strconv.ParseUint(string(b), 10, 64) 18 | return n 19 | } 20 | -------------------------------------------------------------------------------- /zhttp/sse_test.go: -------------------------------------------------------------------------------- 1 | package zhttp 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/sohaha/zlsgo" 8 | ) 9 | 10 | func TestSSE(t *testing.T) { 11 | tt := zlsgo.NewTest(t) 12 | 13 | time.Sleep(time.Second) 14 | 15 | s, err := SSE("http://127.0.0.1:18181/sse", NoRedirect(true)) 16 | tt.NoError(err, true) 17 | i := 0 18 | c, err := s.OnMessage(func(ev *SSEEvent) { 19 | t.Logf("id:%s msg:%s [%s] %s\n", ev.ID, string(ev.Data), ev.Event, ev.Undefined) 20 | i++ 21 | }) 22 | if err != nil { 23 | t.Error(err) 24 | return 25 | } 26 | <-c 27 | tt.Equal(2, i) 28 | } 29 | -------------------------------------------------------------------------------- /zutil/options.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zutil 5 | 6 | // Optional applies a series of option functions to a value of any type. 7 | // This is a generic implementation of the functional options pattern that works with any type. 8 | // It's useful for configuring structs or other values with optional parameters. 9 | // Optional applies configuration functions to a value and returns the modified value. 10 | func Optional[T interface{}](o T, fn ...func(*T)) T { 11 | for _, f := range fn { 12 | if f == nil { 13 | continue 14 | } 15 | f(&o) 16 | } 17 | return o 18 | } 19 | -------------------------------------------------------------------------------- /zutil/once_117.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.18 2 | // +build !go1.18 3 | 4 | package zutil 5 | 6 | import ( 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // Once initialize the singleton 12 | func Once(fn func() interface{}) func() interface{} { 13 | var ( 14 | once sync.Once 15 | ivar interface{} 16 | ) 17 | return func() interface{} { 18 | once.Do(func() { 19 | err := TryCatch(func() error { 20 | ivar = fn() 21 | return nil 22 | }) 23 | if err != nil { 24 | go func() { 25 | time.Sleep(time.Second) 26 | once = sync.Once{} 27 | }() 28 | } 29 | }) 30 | return ivar 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /zreflect/bench_test.go: -------------------------------------------------------------------------------- 1 | package zreflect_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/sohaha/zlsgo/zreflect" 8 | ) 9 | 10 | func BenchmarkZReflect(b *testing.B) { 11 | for i := 0; i < b.N; i++ { 12 | v := zreflect.NewType(zreflect.Demo) 13 | _ = v.NumMethod() 14 | } 15 | } 16 | 17 | func BenchmarkZReflectRaw(b *testing.B) { 18 | for i := 0; i < b.N; i++ { 19 | v := zreflect.NewType(zreflect.Demo) 20 | _ = v.Native().NumMethod() 21 | } 22 | } 23 | 24 | func BenchmarkGReflect(b *testing.B) { 25 | for i := 0; i < b.N; i++ { 26 | v := reflect.TypeOf(zreflect.Demo) 27 | _ = v.NumMethod() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /zutil/daemon/signal_notwin.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package daemon 5 | 6 | import ( 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | ) 11 | 12 | func KillSignal() bool { 13 | sig, stop := SignalChan() 14 | s := <-sig 15 | stop() 16 | return s != syscall.SIGUSR2 17 | } 18 | 19 | func SignalChan() (<-chan os.Signal, func()) { 20 | quit := make(chan os.Signal, 1) 21 | signal.Notify(quit, os.Interrupt, os.Kill, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGUSR2) 22 | return quit, func() { 23 | signal.Stop(quit) 24 | } 25 | } 26 | 27 | func IsSudo() bool { 28 | return isSudo() == nil 29 | } 30 | -------------------------------------------------------------------------------- /zerror/catch.go: -------------------------------------------------------------------------------- 1 | package zerror 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // TryCatch exception capture 8 | func TryCatch(fn func() error) (err error) { 9 | defer func() { 10 | if recoverErr := recover(); recoverErr != nil { 11 | switch e := recoverErr.(type) { 12 | case error: 13 | err = Reuse(e) 14 | case *Error: 15 | err = e 16 | default: 17 | err = Reuse(fmt.Errorf("%v", recoverErr)) 18 | } 19 | } 20 | }() 21 | err = fn() 22 | return 23 | } 24 | 25 | // Panic if error is not nil, usually used in conjunction with TryCatch 26 | func Panic(err error) { 27 | if err != nil { 28 | panic(Reuse(err)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /zerror/format_test.go: -------------------------------------------------------------------------------- 1 | package zerror_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo/zerror" 7 | "github.com/sohaha/zlsgo/zlog" 8 | ) 9 | 10 | func TestFormat(t *testing.T) { 11 | err := newErr() 12 | err = wrap500Err(err) 13 | err = wrap999Err(err) 14 | zlog.Stack(err) 15 | } 16 | 17 | func newErr() error { 18 | e := func() error { 19 | return zerror.New(400, "The is 400") 20 | } 21 | return e() 22 | } 23 | 24 | func wrap500Err(err error) error { 25 | return zerror.Wrap(err, 500, "Wrap 500 NoError") 26 | } 27 | 28 | func wrap999Err(err error) error { 29 | return zerror.Wrap(err, 999, "Unknown NoError") 30 | } 31 | -------------------------------------------------------------------------------- /zreflect/value_test.go: -------------------------------------------------------------------------------- 1 | package zreflect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/sohaha/zlsgo" 8 | ) 9 | 10 | func TestValue(t *testing.T) { 11 | tt := zlsgo.NewTest(t) 12 | 13 | val := ValueOf(Demo) 14 | zval := NewValue(Demo) 15 | gval := NewValue(val) 16 | zzval := NewValue(zval) 17 | 18 | tt.Equal(reflect.Struct, val.Kind()) 19 | tt.Equal(reflect.Struct, zval.Native().Kind()) 20 | tt.Equal(reflect.Struct, gval.Native().Kind()) 21 | tt.Equal(reflect.Struct, zzval.Native().Kind()) 22 | tt.Equal(reflect.Struct, zval.Type().Native().Kind()) 23 | 24 | tt.Log(val.Interface()) 25 | tt.Log(zval.Native().Interface()) 26 | } 27 | -------------------------------------------------------------------------------- /zstring/rand_118.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zstring 5 | 6 | import ( 7 | _ "unsafe" 8 | ) 9 | 10 | // fastrand is linked directly to the Go runtime's internal fast random number generator. 11 | // This provides better performance than crypto/rand for non-cryptographic purposes. 12 | // 13 | //go:noescape 14 | //go:linkname fastrand runtime.fastrand 15 | func fastrand() uint32 16 | 17 | // RandUint32 returns a pseudorandom uint32 value using the Go runtime's internal 18 | // random number generator, which is much faster than crypto/rand for non-security-critical uses. 19 | func RandUint32() uint32 { 20 | return fastrand() 21 | } 22 | -------------------------------------------------------------------------------- /zutil/daemon/signal_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package daemon 5 | 6 | import ( 7 | "os" 8 | "os/exec" 9 | "os/signal" 10 | "syscall" 11 | ) 12 | 13 | func KillSignal() bool { 14 | sig, stop := SignalChan() 15 | <-sig 16 | stop() 17 | return true 18 | } 19 | 20 | func SignalChan() (<-chan os.Signal, func()) { 21 | quit := make(chan os.Signal, 1) 22 | signal.Notify(quit, os.Interrupt, os.Kill, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL) 23 | return quit, func() { 24 | signal.Stop(quit) 25 | } 26 | } 27 | 28 | func IsSudo() bool { 29 | cmd := exec.Command("net", "session") 30 | err := cmd.Run() 31 | return err == nil 32 | } 33 | -------------------------------------------------------------------------------- /zfile/utils_test.go: -------------------------------------------------------------------------------- 1 | package zfile_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | "github.com/sohaha/zlsgo/zfile" 8 | ) 9 | 10 | func Test_Byte(t *testing.T) { 11 | tt := zlsgo.NewTest(t) 12 | tt.Equal(1024, zfile.KB) 13 | tt.Equal(1048576, zfile.MB) 14 | tt.Equal(1073741824, zfile.GB) 15 | tt.Equal(1099511627776, zfile.TB) 16 | 17 | tt.Equal("2.0 KB", zfile.SizeFormat(2*zfile.KB)) 18 | tt.Equal("2.0 MB", zfile.SizeFormat(2*zfile.MB)) 19 | tt.Equal("2.0 GB", zfile.SizeFormat(2*zfile.GB)) 20 | tt.Equal("2.0 TB", zfile.SizeFormat(2*zfile.TB)) 21 | tt.Equal("2.0 PB", zfile.SizeFormat(2*zfile.PB)) 22 | 23 | tt.Equal("-1.0 KB", zfile.SizeFormat(-1024)) 24 | } 25 | -------------------------------------------------------------------------------- /zfile/file_notwin.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package zfile 5 | 6 | import ( 7 | "os" 8 | ) 9 | 10 | // MoveFile moves a file from source to destination path. 11 | // If force is true and destination exists, it will be removed before moving. 12 | // On non-Windows systems, this uses os.Rename which is atomic if both paths 13 | // are on the same filesystem. 14 | func MoveFile(source string, dest string, force ...bool) error { 15 | source = RealPath(source) 16 | dest = RealPath(dest) 17 | if len(force) > 0 && force[0] { 18 | if exist, _ := PathExist(dest); exist != 0 && source != dest { 19 | _ = os.RemoveAll(dest) 20 | } 21 | } 22 | return os.Rename(source, dest) 23 | } 24 | -------------------------------------------------------------------------------- /ztime/utils_test.go: -------------------------------------------------------------------------------- 1 | package ztime 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo" 9 | ) 10 | 11 | func TestSleep(t *testing.T) { 12 | tt := zlsgo.NewTest(t) 13 | 14 | tt.Run("base", func(tt *zlsgo.TestUtil) { 15 | tt.Parallel() 16 | now := time.Now() 17 | Sleep(context.Background(), time.Second/5) 18 | tt.EqualTrue(time.Since(now) >= time.Second/5) 19 | }) 20 | 21 | tt.Run("cancel", func(tt *zlsgo.TestUtil) { 22 | tt.Parallel() 23 | ctx, cancel := context.WithTimeout(context.Background(), time.Second/5) 24 | defer cancel() 25 | now := time.Now() 26 | Sleep(ctx, time.Second) 27 | tt.EqualTrue(time.Since(now) >= time.Second/5) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /zreflect/method_test.go: -------------------------------------------------------------------------------- 1 | package zreflect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/sohaha/zlsgo" 8 | ) 9 | 10 | func TestGetAllMethod(t *testing.T) { 11 | tt := zlsgo.NewTest(t) 12 | i := 0 13 | GetAllMethod(Demo, func(numMethod int, m reflect.Method) error { 14 | t.Log(numMethod, m.Name) 15 | i++ 16 | switch numMethod { 17 | case 0: 18 | tt.Equal("Text", m.Name) 19 | } 20 | return nil 21 | }) 22 | tt.Equal(1, i) 23 | } 24 | 25 | func TestRunAssignMethod(t *testing.T) { 26 | tt := zlsgo.NewTest(t) 27 | i := 0 28 | RunAssignMethod(Demo, func(methodName string) bool { 29 | t.Log("methodName:", methodName) 30 | i++ 31 | return false 32 | }) 33 | tt.Equal(1, i) 34 | } 35 | -------------------------------------------------------------------------------- /zerror/tag_test.go: -------------------------------------------------------------------------------- 1 | package zerror_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/sohaha/zlsgo" 8 | "github.com/sohaha/zlsgo/zerror" 9 | ) 10 | 11 | func TestTag(t *testing.T) { 12 | tt := zlsgo.NewTest(t) 13 | err := errors.New("test") 14 | 15 | zerr := zerror.With(err, "包裹错误", zerror.WrapTag(zerror.NotFound)) 16 | zerr = zerror.With(zerr, "最终错误提示", zerror.WrapTag(zerror.Unauthorized)) 17 | 18 | tt.Equal(zerror.Unauthorized, zerror.GetTag(zerr)) 19 | 20 | e := zerror.InvalidInput.Wrap(err, "输入无效") 21 | e2 := zerror.InvalidInput.Text("输入无效") 22 | tt.Equal(zerror.GetTag(e), zerror.GetTag(e2)) 23 | tt.Equal(zerror.InvalidInput, zerror.GetTag(e2)) 24 | 25 | tt.Logf("%v\n", e) 26 | tt.Logf("%v\n", e2) 27 | } 28 | -------------------------------------------------------------------------------- /zdi/lazy.go: -------------------------------------------------------------------------------- 1 | package zdi 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/sohaha/zlsgo/zreflect" 7 | ) 8 | 9 | // Provide registers a provider function with the injector. 10 | // A provider is a function that, when invoked, returns one or more values to be injected. 11 | func (inj *injector) Provide(provider interface{}, opt ...Option) (override []reflect.Type) { 12 | val := zreflect.ValueOf(provider) 13 | t := val.Type() 14 | numout := t.NumOut() 15 | for i := 0; i < numout; i++ { 16 | out := t.Out(i) 17 | if _, ok := inj.values[out]; ok { 18 | override = append(override, out) 19 | } 20 | if _, ok := inj.providers[out]; ok { 21 | override = append(override, out) 22 | } 23 | inj.providers[out] = val 24 | } 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /zsync/coalescer.go: -------------------------------------------------------------------------------- 1 | package zsync 2 | 3 | import ( 4 | "sync/atomic" 5 | ) 6 | 7 | // NewCoalescer returns a function that coalesces multiple calls into a single 8 | // or a few executions: while one execution is running, further calls schedule 9 | // at least one more run. Thread-safe. 10 | func NewCoalescer(fn func()) func() { 11 | var running uint32 12 | var pending uint32 13 | 14 | return func() { 15 | if !atomic.CompareAndSwapUint32(&running, 0, 1) { 16 | atomic.AddUint32(&pending, 1) 17 | return 18 | } 19 | 20 | defer atomic.StoreUint32(&running, 0) 21 | 22 | for { 23 | if fn != nil { 24 | fn() 25 | } 26 | 27 | if atomic.SwapUint32(&pending, 0) > 0 { 28 | continue 29 | } 30 | 31 | break 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /znet/timeout/timeout.go: -------------------------------------------------------------------------------- 1 | package timeout 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo/znet" 9 | ) 10 | 11 | func New(waitingTime time.Duration, custom ...znet.HandlerFunc) znet.HandlerFunc { 12 | return func(c *znet.Context) { 13 | ctx, cancel := context.WithTimeout(c.Request.Context(), waitingTime) 14 | defer cancel() 15 | 16 | done := make(chan struct{}, 1) 17 | 18 | go func() { 19 | c.Next() 20 | select { 21 | case done <- struct{}{}: 22 | default: 23 | } 24 | }() 25 | 26 | select { 27 | case <-done: 28 | return 29 | case <-ctx.Done(): 30 | if len(custom) > 0 { 31 | custom[0](c) 32 | } else { 33 | c.Abort(http.StatusGatewayTimeout) 34 | } 35 | return 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /zsync/mutex_fallback.go: -------------------------------------------------------------------------------- 1 | //go:build !amd64 && !arm64 && !ppc64 && !ppc64le && !mips64 && !mips64le && !s390x && !riscv64 && !loong64 2 | 3 | package zsync 4 | 5 | import "sync" 6 | 7 | // RBMutex fallback implementation for non-64-bit architectures. 8 | // It preserves the API but uses a plain RWMutex without read-bias optimizations. 9 | type RBMutex struct { 10 | rw sync.RWMutex 11 | } 12 | 13 | type RBToken struct { 14 | p *uint64 15 | } 16 | 17 | func NewRBMutex() *RBMutex { return &RBMutex{} } 18 | 19 | func (mu *RBMutex) RLock() RBToken { 20 | mu.rw.RLock() 21 | return RBToken{} 22 | } 23 | 24 | func (mu *RBMutex) RUnlock(_ RBToken) { mu.rw.RUnlock() } 25 | 26 | func (mu *RBMutex) Lock() { mu.rw.Lock() } 27 | func (mu *RBMutex) Unlock() { mu.rw.Unlock() } 28 | -------------------------------------------------------------------------------- /zstring/expand_test.go: -------------------------------------------------------------------------------- 1 | package zstring 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | ) 8 | 9 | func TestExpand(t *testing.T) { 10 | tt := zlsgo.NewTest(t) 11 | 12 | tt.Equal("hello zlsgo", Expand("hello $world", func(key string) string { 13 | return "zlsgo" 14 | })) 15 | 16 | tt.Equal("hello {zlsgo}", Expand("hello {$world}", func(key string) string { 17 | return "zlsgo" 18 | })) 19 | 20 | tt.Equal("hello zlsgo", Expand("hello ${world}", func(key string) string { 21 | t.Log(key) 22 | return "zlsgo" 23 | })) 24 | 25 | var keys []string 26 | Expand("${a} $b $c.d ${e.f} $1 - ${*}", func(key string) string { 27 | t.Log(key) 28 | keys = append(keys, key) 29 | return "" 30 | }) 31 | tt.Equal([]string{"a", "b", "c", "e.f", "1", "*"}, keys) 32 | } 33 | -------------------------------------------------------------------------------- /zlocale/cache_test.go: -------------------------------------------------------------------------------- 1 | package zlocale 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkCache(b *testing.B) { 9 | b.Run("Legacy", func(b *testing.B) { 10 | i18n := New("en") 11 | 12 | data := make(map[string]string) 13 | for i := 0; i < 1000; i++ { 14 | data[fmt.Sprintf("template_%d", i)] = fmt.Sprintf("Hello {0}, this is template %d", i) 15 | } 16 | 17 | err := i18n.LoadLanguageWithConfig("en", "English", data, NewLegacyCacheAdapter(1000)) 18 | if err != nil { 19 | b.Fatalf("Failed to load language: %v", err) 20 | } 21 | 22 | b.ResetTimer() 23 | b.RunParallel(func(pb *testing.PB) { 24 | for pb.Next() { 25 | for i := 0; i < 10; i++ { 26 | key := fmt.Sprintf("template_%d", i%200) 27 | i18n.T(key, "World") 28 | } 29 | } 30 | }) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /znet/signal_notwin.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package znet 5 | 6 | import ( 7 | "errors" 8 | "os" 9 | "syscall" 10 | 11 | "github.com/sohaha/zlsgo/zutil" 12 | ) 13 | 14 | var isRestarting = zutil.NewBool(false) 15 | 16 | // Restart triggers a server process restart 17 | func (e *Engine) Restart() error { 18 | if !isRestarting.CAS(false, true) { 19 | return errors.New("restart already in progress") 20 | } 21 | 22 | defer isRestarting.Store(false) 23 | 24 | pid := os.Getpid() 25 | proc, err := os.FindProcess(pid) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | return proc.Signal(syscall.SIGUSR2) 31 | } 32 | 33 | // IsRestarting returns whether the server is currently restarting 34 | func (e *Engine) IsRestarting() bool { 35 | return isRestarting.Load() 36 | } 37 | -------------------------------------------------------------------------------- /zpprof/pprof_test.go: -------------------------------------------------------------------------------- 1 | package zpprof 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/sohaha/zlsgo" 10 | "github.com/sohaha/zlsgo/znet" 11 | ) 12 | 13 | func TestListenAndServe(t *testing.T) { 14 | _ = ListenAndServe("127.0.0.1:67890") 15 | } 16 | 17 | func TestRegister(t *testing.T) { 18 | tt := zlsgo.NewTest(t) 19 | r := znet.New("pprof-test") 20 | r.SetMode(znet.DebugMode) 21 | Register(r, "666") 22 | w := httptest.NewRecorder() 23 | req, _ := http.NewRequest("GET", "/debug?token=666", strings.NewReader("")) 24 | r.ServeHTTP(w, req) 25 | t.Log(w.Body.String()) 26 | tt.Equal(200, w.Code) 27 | 28 | w = httptest.NewRecorder() 29 | req, _ = http.NewRequest("GET", "/debug", nil) 30 | r.ServeHTTP(w, req) 31 | t.Log(w.Body.String()) 32 | tt.Equal(401, w.Code) 33 | } 34 | -------------------------------------------------------------------------------- /znet/gzip/util.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "compress/gzip" 5 | "io/ioutil" 6 | ) 7 | 8 | type ( 9 | poolCap struct { 10 | c chan *gzip.Writer 11 | l int 12 | } 13 | // Config gzip configuration 14 | Config struct { 15 | // CompressionLevel gzip compression level to use 16 | CompressionLevel int 17 | // PoolMaxSize maximum number of resource pools 18 | PoolMaxSize int 19 | // MinContentLength minimum content length to trigger gzip, the unit is in byte. 20 | MinContentLength int 21 | } 22 | ) 23 | 24 | func (bp *poolCap) Get() (g *gzip.Writer, err error) { 25 | select { 26 | case g = <-bp.c: 27 | default: 28 | g, err = gzip.NewWriterLevel(ioutil.Discard, bp.l) 29 | } 30 | 31 | return 32 | } 33 | 34 | func (bp *poolCap) Put(g *gzip.Writer) { 35 | select { 36 | case bp.c <- g: 37 | default: 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /zutil/options_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zutil_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/sohaha/zlsgo" 10 | "github.com/sohaha/zlsgo/zutil" 11 | ) 12 | 13 | func TestOptional(t *testing.T) { 14 | tt := zlsgo.NewTest(t) 15 | 16 | o := zutil.Optional(TestSt{Name: "test"}) 17 | tt.Equal("test", o.Name) 18 | tt.Equal(0, o.I) 19 | 20 | o = zutil.Optional(TestSt{Name: "test2"}, func(o *TestSt) { 21 | o.I = 1 22 | }, func(ts *TestSt) { 23 | ts.I = ts.I + 1 24 | }) 25 | tt.Equal("test2", o.Name) 26 | tt.Equal(2, o.I) 27 | 28 | o2 := zutil.Optional(&TestSt{Name: "test"}, func(ts **TestSt) { 29 | (*ts).I = 1 30 | }) 31 | tt.Equal("test", o2.Name) 32 | tt.Equal(1, o2.I) 33 | 34 | o3 := zutil.Optional(&TestSt{Name: "null"}, nil) 35 | tt.Equal("null", o3.Name) 36 | tt.Equal(0, o3.I) 37 | } 38 | -------------------------------------------------------------------------------- /zcache/simple_test.go: -------------------------------------------------------------------------------- 1 | package zcache_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/sohaha/zlsgo" 8 | "github.com/sohaha/zlsgo/zcache" 9 | "github.com/sohaha/zlsgo/ztime" 10 | ) 11 | 12 | func TestSimple(t *testing.T) { 13 | tt := zlsgo.NewTest(t) 14 | 15 | now := ztime.Now() 16 | zcache.Set("TestSimple1", now, time.Second/5) 17 | 18 | v, ok := zcache.GetAny("TestSimple1") 19 | tt.EqualTrue(ok) 20 | tt.Equal(now, v.String()) 21 | 22 | time.Sleep(time.Second / 4) 23 | 24 | vv, ok := zcache.Get("TestSimple1") 25 | t.Log(vv, ok) 26 | tt.EqualTrue(!ok) 27 | 28 | v2, ok := zcache.ProvideGet("TestSimple2", func() (interface{}, bool) { 29 | return now, true 30 | }) 31 | tt.EqualTrue(ok) 32 | tt.Equal(now, v2.(string)) 33 | 34 | zcache.Delete("TestSimple2") 35 | _, ok = zcache.Get("TestSimple2") 36 | tt.EqualTrue(!ok) 37 | } 38 | -------------------------------------------------------------------------------- /zstring/dispose_test.go: -------------------------------------------------------------------------------- 1 | package zstring_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | "github.com/sohaha/zlsgo/zstring" 8 | ) 9 | 10 | func TestFilter(t *testing.T) { 11 | tt := zlsgo.NewTest(t) 12 | f := zstring.NewFilter([]string{"我", "你", "他", "初音", ""}, '#') 13 | res, keywords, found := f.Filter("你是谁,我是谁,他又是谁") 14 | tt.EqualTrue(found) 15 | tt.EqualExit(3, len(keywords)) 16 | tt.EqualExit("#是谁,#是谁,#又是谁", res) 17 | 18 | _, _, found = f.Filter("") 19 | tt.EqualExit(false, found) 20 | 21 | t.Log(2, len(f.Find("你是谁,初音又是谁"))) 22 | t.Log(0, len(f.Find(""))) 23 | } 24 | 25 | func TestReplacer(t *testing.T) { 26 | tt := zlsgo.NewTest(t) 27 | r := zstring.NewReplacer(map[string]string{"你": "初音", "它": "犬夜叉"}) 28 | res := r.Replace("你是谁,我是谁,它又是谁") 29 | tt.EqualExit("初音是谁,我是谁,犬夜叉又是谁", res) 30 | t.Log("", r.Replace("")) 31 | } 32 | -------------------------------------------------------------------------------- /zarray/sort_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zarray_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/sohaha/zlsgo" 10 | "github.com/sohaha/zlsgo/zarray" 11 | ) 12 | 13 | func TestNewSortMap(t *testing.T) { 14 | tt := zlsgo.NewTest(t) 15 | count := 50 16 | arr := make([]int, 0, count) 17 | 18 | m := zarray.NewSortMap[int, int]() 19 | for i := 0; i < count; i++ { 20 | arr = append(arr, i) 21 | m.Set(i, i) 22 | } 23 | 24 | v, has := m.Get(2) 25 | tt.EqualTrue(has) 26 | tt.Equal(2, v) 27 | 28 | res := make([]int, 0, count) 29 | m.ForEach(func(key int, value int) bool { 30 | tt.Equal(key, value) 31 | res = append(res, key) 32 | return true 33 | }) 34 | t.Log(res) 35 | 36 | tt.Equal(arr, res) 37 | tt.Equal(count, m.Len()) 38 | 39 | m.Delete(1, 10, 20, 20) 40 | tt.Equal(count-3, m.Len()) 41 | 42 | t.Log(m.Keys()) 43 | } 44 | -------------------------------------------------------------------------------- /zlog/zlogger_test.go: -------------------------------------------------------------------------------- 1 | package zlog_test 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "testing" 8 | 9 | "github.com/sohaha/zlsgo/zlog" 10 | ) 11 | 12 | type Report struct { 13 | Writer io.Writer 14 | } 15 | 16 | func (w *Report) Write(p []byte) (n int, err error) { 17 | // Here you can initiate an HTTP request to report an error 18 | fmt.Println("Report: ", string(p)) 19 | 20 | return w.Writer.Write(p) 21 | } 22 | 23 | func TestCustomWriter(t *testing.T) { 24 | w := &Report{ 25 | Writer: os.Stdout, 26 | } 27 | 28 | l1 := zlog.NewZLog(w, "[Custom1] ", zlog.BitLevel, zlog.LogDump, true, 3) 29 | l1.Info("Test") 30 | 31 | // or 32 | 33 | l2 := zlog.New("[Custom2] ") 34 | l2.Info("Test") 35 | l2.Writer().Set(w) 36 | l2.Info("Test 2") 37 | 38 | // or 39 | l3 := zlog.New("[Custom3] ") 40 | l3.Info("Test") 41 | l3.Writer().Reset(l2) 42 | l3.Info("Test") 43 | } 44 | -------------------------------------------------------------------------------- /zreflect/type_test.go: -------------------------------------------------------------------------------- 1 | package zreflect_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/sohaha/zlsgo" 8 | "github.com/sohaha/zlsgo/zreflect" 9 | ) 10 | 11 | func TestType(t *testing.T) { 12 | tt := zlsgo.NewTest(t) 13 | 14 | typ := zreflect.TypeOf(zreflect.Demo) 15 | ztyp := zreflect.NewType(zreflect.Demo) 16 | vtyp := zreflect.NewType(zreflect.NewValue(zreflect.Demo)) 17 | zztyp := zreflect.NewType(ztyp) 18 | zgtyp := zreflect.NewType(typ) 19 | atyp := zreflect.NewValue(zreflect.Demo).Type() 20 | 21 | tt.Equal(reflect.Struct, typ.Kind()) 22 | tt.Equal(reflect.Struct, ztyp.Native().Kind()) 23 | tt.Equal(reflect.Struct, vtyp.Native().Kind()) 24 | tt.Equal(reflect.Struct, zztyp.Native().Kind()) 25 | tt.Equal(reflect.Struct, zgtyp.Native().Kind()) 26 | tt.Equal(reflect.Struct, atyp.Native().Kind()) 27 | 28 | tt.Equal(typ.NumMethod(), ztyp.NumMethod()) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /zsync/pool_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zsync 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/sohaha/zlsgo" 10 | ) 11 | 12 | func TestNewPool(t *testing.T) { 13 | tt := zlsgo.NewTest(t) 14 | 15 | tt.Run("base", func(tt *zlsgo.TestUtil) { 16 | pool := NewPool(func() int { 17 | return 1 18 | }) 19 | tt.Equal(1, pool.Get()) 20 | }) 21 | 22 | tt.Run("pointer", func(tt *zlsgo.TestUtil) { 23 | type poolS struct{ V int } 24 | pool := NewPool(func() *poolS { return &poolS{} }) 25 | v := pool.Get() 26 | tt.EqualTrue(v != nil) 27 | v.V = 1 28 | pool.Put(v) 29 | tt.EqualTrue(pool.Get() != nil) 30 | }) 31 | 32 | tt.Run("slice", func(tt *zlsgo.TestUtil) { 33 | pool := NewPool(func() []byte { return make([]byte, 0, 8) }) 34 | v := pool.Get() 35 | tt.EqualTrue(v != nil) 36 | pool.Put(v[:0]) 37 | tt.EqualTrue(pool.Get() != nil) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /zlog/color_test.go: -------------------------------------------------------------------------------- 1 | package zlog 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | zls "github.com/sohaha/zlsgo" 9 | ) 10 | 11 | func TestColor(t *testing.T) { 12 | T := zls.NewTest(t) 13 | testText := "ok" 14 | _ = os.Setenv("ConEmuANSI", "ON") 15 | bl := IsSupportColor() 16 | OutAllColor() 17 | if bl { 18 | T.Equal(fmt.Sprintf("\x1b[%dm%s\x1b[0m", ColorGreen, testText), ColorTextWrap(ColorGreen, testText)) 19 | } else { 20 | T.Equal(testText, ColorTextWrap(ColorGreen, testText)) 21 | } 22 | 23 | DisableColor = true 24 | bl = isSupportColor() 25 | T.Equal(false, bl) 26 | OutAllColor() 27 | T.Equal(testText, ColorTextWrap(ColorGreen, testText)) 28 | } 29 | 30 | func TestTrimAnsi(t *testing.T) { 31 | tt := zls.NewTest(t) 32 | 33 | testText := "ok\x1b[31m" 34 | tt.Equal("ok", TrimAnsi(testText)) 35 | 36 | testText = ColorTextWrap(ColorGreen, "ok") 37 | tt.Equal("ok", TrimAnsi(testText)) 38 | } 39 | -------------------------------------------------------------------------------- /zfile/file_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package zfile 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | ) 10 | 11 | // MoveFile moves a file from source to destination path. 12 | // If force is true and destination exists, it will be removed before moving. 13 | // On Windows systems, this uses syscall.MoveFile which provides better support 14 | // for Windows file paths and attributes. 15 | func MoveFile(source string, dest string, force ...bool) error { 16 | source = RealPath(source) 17 | dest = RealPath(dest) 18 | if len(force) > 0 && force[0] { 19 | if exist, _ := PathExist(dest); exist != 0 && source != dest { 20 | _ = os.RemoveAll(dest) 21 | } 22 | } 23 | from, err := syscall.UTF16PtrFromString(source) 24 | if err != nil { 25 | return err 26 | } 27 | to, err := syscall.UTF16PtrFromString(dest) 28 | if err != nil { 29 | return err 30 | } 31 | return syscall.MoveFile(from, to) 32 | } 33 | -------------------------------------------------------------------------------- /ztime/local.go: -------------------------------------------------------------------------------- 1 | package ztime 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | type LocalTime struct { 10 | time.Time 11 | } 12 | 13 | func (t LocalTime) MarshalJSON() ([]byte, error) { 14 | return []byte(`"` + inlay.FormatTime(t.Time) + `"`), nil 15 | } 16 | 17 | func (t LocalTime) Value() (driver.Value, error) { 18 | if t.Time.IsZero() { 19 | return nil, nil 20 | } 21 | 22 | return t.Time, nil 23 | } 24 | 25 | func (t LocalTime) String() string { 26 | return inlay.FormatTime(t.Time) 27 | } 28 | 29 | func (t LocalTime) Format(layout string) string { 30 | return inlay.FormatTime(t.Time, layout) 31 | } 32 | 33 | func (t *LocalTime) Scan(v interface{}) error { 34 | switch vv := v.(type) { 35 | case time.Time: 36 | *t = LocalTime{Time: vv} 37 | return nil 38 | case LocalTime: 39 | *t = vv 40 | return nil 41 | default: 42 | return fmt.Errorf("expected time.Time, got %T", v) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /zshell/shell_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package zshell 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "os/exec" 10 | "syscall" 11 | 12 | "github.com/sohaha/zlsgo/zutil" 13 | ) 14 | 15 | var chcp = zutil.Once(func() struct{} { 16 | _ = exec.Command("chcp", "65001").Run() 17 | return struct{}{} 18 | }) 19 | 20 | func RunNewProcess(file string, args []string) (pid int, err error) { 21 | return 0, errors.New("windows does not support") 22 | } 23 | 24 | func RunBash(ctx context.Context, command string) (code int, outStr, errStr string, err error) { 25 | return ExecCommand(ctx, []string{ 26 | "cmd", 27 | "/C", 28 | command, 29 | }, nil, nil, nil) 30 | } 31 | 32 | func sysProcAttr(cmd *exec.Cmd) *exec.Cmd { 33 | if cmd.SysProcAttr == nil { 34 | cmd.SysProcAttr = &syscall.SysProcAttr{ 35 | // CreationFlags: 0x08000000, 36 | } 37 | } 38 | 39 | cmd.SysProcAttr.HideWindow = true 40 | return cmd 41 | } 42 | -------------------------------------------------------------------------------- /znet/handler_test.go: -------------------------------------------------------------------------------- 1 | package znet 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/ztime" 10 | "github.com/sohaha/zlsgo/zutil" 11 | ) 12 | 13 | func Test_isModified(t *testing.T) { 14 | tt := zlsgo.NewTest(t) 15 | 16 | now := time.Now() 17 | c := &Context{ 18 | Request: &http.Request{ 19 | Header: http.Header{ 20 | "If-Modified-Since": []string{ztime.In(now).Format("Mon, 02 Jan 2006 15:04:05 GMT")}, 21 | }, 22 | }, 23 | stopHandle: zutil.NewBool(false), 24 | prevData: &PrevData{ 25 | Code: zutil.NewInt32(http.StatusOK), 26 | Type: ContentTypePlain, 27 | }, 28 | header: map[string][]string{}, 29 | } 30 | m := isModified(c, now) 31 | tt.Equal(false, m, true) 32 | 33 | m = isModified(c, now.Add(-time.Second)) 34 | tt.Equal(true, m, true) 35 | 36 | c.Request.Header.Del("If-Modified-Since") 37 | m = isModified(c, now) 38 | tt.Equal(true, m, true) 39 | } 40 | -------------------------------------------------------------------------------- /zarray/slice_121.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | // +build go1.21 3 | 4 | package zarray 5 | 6 | // Intersection returns a new slice containing the common elements from two slices. 7 | // The order of elements in the returned slice is based on the order in list1. 8 | // Duplicate elements in the intersection are removed. 9 | func Intersection[T comparable](list1 []T, list2 []T) []T { 10 | if len(list1) == 0 || len(list2) == 0 { 11 | return []T{} 12 | } 13 | 14 | set2 := make(map[T]struct{}, len(list2)) 15 | for _, item := range list2 { 16 | set2[item] = struct{}{} 17 | } 18 | 19 | result := make([]T, 0, min(len(list1), len(list2))) 20 | seenInResult := make(map[T]struct{}, min(len(list1), len(list2))) 21 | 22 | for _, item := range list1 { 23 | if _, ok := set2[item]; ok { 24 | if _, seen := seenInResult[item]; !seen { 25 | result = append(result, item) 26 | seenInResult[item] = struct{}{} 27 | } 28 | } 29 | } 30 | 31 | return result 32 | } 33 | -------------------------------------------------------------------------------- /zstring/rand_117.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.18 2 | // +build !go1.18 3 | 4 | package zstring 5 | 6 | import ( 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // rngPool is a pool of random number generators to reduce allocation overhead 12 | var rngPool sync.Pool 13 | 14 | // Uint32 generates a pseudorandom uint32 value using a simple xorshift algorithm. 15 | // It initializes the generator state from the current time if needed. 16 | func (r *ru) Uint32() uint32 { 17 | for r.x == 0 { 18 | x := time.Now().UnixNano() 19 | r.x = uint32((x >> 32) ^ x) 20 | } 21 | x := r.x 22 | x ^= x << 13 23 | x ^= x >> 17 24 | x ^= x << 5 25 | r.x = x 26 | return x 27 | } 28 | 29 | // RandUint32 returns a pseudorandom uint32 value. 30 | // It uses a pool of generators to improve performance by reducing allocations. 31 | func RandUint32() uint32 { 32 | v := rngPool.Get() 33 | if v == nil { 34 | v = &ru{} 35 | } 36 | r := v.(*ru) 37 | x := r.Uint32() 38 | rngPool.Put(r) 39 | return x 40 | } 41 | -------------------------------------------------------------------------------- /zsync/utils.go: -------------------------------------------------------------------------------- 1 | package zsync 2 | 3 | import ( 4 | "runtime" 5 | _ "unsafe" 6 | ) 7 | 8 | const ( 9 | cacheLineSize = 64 10 | ) 11 | 12 | // nextPowOf2 returns the next power of 2 greater than or equal to v. 13 | // This is used internally for sizing data structures that perform better 14 | // with power-of-2 sizes, such as hash tables and lock arrays. 15 | func nextPowOf2(v uint32) uint32 { 16 | v-- 17 | v |= v >> 1 18 | v |= v >> 2 19 | v |= v >> 4 20 | v |= v >> 8 21 | v |= v >> 16 22 | v++ 23 | return v 24 | } 25 | 26 | // parallelism returns the effective parallelism level for the current process. 27 | // It returns the minimum of GOMAXPROCS and the number of CPU cores, 28 | // which provides a reasonable estimate of the available concurrent execution capacity. 29 | func parallelism() uint32 { 30 | maxProcs := uint32(runtime.GOMAXPROCS(0)) 31 | numCores := uint32(runtime.NumCPU()) 32 | if maxProcs < numCores { 33 | return maxProcs 34 | } 35 | return numCores 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: UnitTest 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | env: 10 | GO111MODULE: on 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | go: [1.18, 1.21, 1.24] 18 | db: [SQLite3] 19 | steps: 20 | - name: Checkout Code 21 | uses: actions/checkout@v3 22 | 23 | - name: Setup Go ${{ matrix.go }} 24 | uses: actions/setup-go@v2 25 | with: 26 | go-version: ${{ matrix.go }} 27 | 28 | - name: Setup Go Tools 29 | run: | 30 | go mod download 31 | export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}" 32 | 33 | - name: Test 34 | run: go test -test.short ./... -race -coverprofile=coverage.txt -covermode=atomic 35 | 36 | - name: Codecov Report 37 | uses: codecov/codecov-action@v2 38 | with: 39 | token: ${{ secrets.CODECOV_TOKEN }} 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 2 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 3 | golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= 4 | golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 5 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 6 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 7 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 8 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 9 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 10 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 11 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 12 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 13 | -------------------------------------------------------------------------------- /zstring/url.go: -------------------------------------------------------------------------------- 1 | package zstring 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | ) 7 | 8 | // UrlEncode encodes a string for use in a URL query. 9 | // It uses the standard encoding where spaces are converted to '+' characters. 10 | func UrlEncode(str string) string { 11 | return url.QueryEscape(str) 12 | } 13 | 14 | // UrlDecode decodes a URL-encoded string. 15 | // It handles the conversion of '+' characters back to spaces. 16 | func UrlDecode(str string) (string, error) { 17 | return url.QueryUnescape(str) 18 | } 19 | 20 | // UrlRawEncode encodes a string according to RFC 3986. 21 | // Unlike UrlEncode, it converts spaces to '%20' instead of '+'. 22 | func UrlRawEncode(str string) string { 23 | return strings.Replace(url.QueryEscape(str), "+", "%20", -1) 24 | } 25 | 26 | // UrlRawDecode decodes a string that was encoded according to RFC 3986. 27 | // It handles the conversion of '%20' sequences to spaces. 28 | func UrlRawDecode(str string) (string, error) { 29 | return url.QueryUnescape(strings.Replace(str, "%20", "+", -1)) 30 | } 31 | -------------------------------------------------------------------------------- /zutil/utils_nowin.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package zutil 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | const ( 11 | darwinOpenMax = 10240 12 | ) 13 | 14 | func IsDoubleClickStartUp() bool { 15 | return false 16 | } 17 | 18 | func GetParentProcessName() (string, error) { 19 | return "", nil 20 | } 21 | 22 | // MaxRlimit tries to set the resource limit RLIMIT_NOFILE to the max (hard limit) 23 | func MaxRlimit() (int, error) { 24 | var lim syscall.Rlimit 25 | if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { 26 | return 0, err 27 | } 28 | 29 | if lim.Cur >= lim.Max { 30 | return int(lim.Cur), nil 31 | } 32 | 33 | if IsMac() && lim.Max > darwinOpenMax { 34 | lim.Max = darwinOpenMax 35 | } 36 | 37 | oldLimit := lim.Cur 38 | lim.Cur = lim.Max 39 | if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { 40 | return int(oldLimit), err 41 | } 42 | 43 | if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil { 44 | return 0, err 45 | } 46 | 47 | return int(lim.Cur), nil 48 | } 49 | -------------------------------------------------------------------------------- /znet/template_go_test.go: -------------------------------------------------------------------------------- 1 | package znet 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/sohaha/zlsgo" 8 | "github.com/sohaha/zlsgo/zfile" 9 | "github.com/sohaha/zlsgo/zstring" 10 | ) 11 | 12 | func TestHTMLRender(t *testing.T) { 13 | tt := zlsgo.NewTest(t) 14 | 15 | zfile.WriteFile("./testdata/html/partials/footer.html", []byte("

Footer

")) 16 | zfile.WriteFile("./testdata/html/partials/header.html", []byte("

Header

")) 17 | zfile.WriteFile("./testdata/html/index.html", []byte(` 18 | {{template "partials/header.html" .}} 19 |

{{.Title}}

20 | {{template "partials/footer.html" .}} 21 | `)) 22 | defer zfile.Rmdir("./testdata/html/") 23 | 24 | engine := newGoTemplate(nil, "./testdata/html") 25 | 26 | err := engine.Load() 27 | tt.NoError(err) 28 | 29 | var buf bytes.Buffer 30 | err = engine.Render(&buf, "index.html", map[string]interface{}{ 31 | "Title": "Hello, World!", 32 | }) 33 | tt.NoError(err) 34 | expect := `

Header

Hello, World!

Footer

` 35 | tt.Equal(expect, zstring.TrimLine(buf.String())) 36 | } 37 | -------------------------------------------------------------------------------- /zutil/daemon/daemon_test.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/sohaha/zlsgo" 8 | ) 9 | 10 | type ss struct { 11 | I int 12 | } 13 | 14 | func (p *ss) Start(s ServiceIface) error { 15 | p.run() 16 | return nil 17 | } 18 | 19 | func (p *ss) run() { 20 | fmt.Println("run") 21 | p.I = p.I + 1 22 | } 23 | 24 | func (p *ss) Stop(s ServiceIface) error { 25 | return nil 26 | } 27 | 28 | func TestDaemon(t *testing.T) { 29 | o := &ss{ 30 | I: 1, 31 | } 32 | s, err := New(o, &Config{ 33 | Name: "zlsgo_daemon_test", 34 | Options: map[string]interface{}{"UserService": false}, 35 | }) 36 | if err != nil { 37 | return 38 | } 39 | t.Log(o.I) 40 | t.Log(err) 41 | _ = s.Install() 42 | err = s.Start() 43 | t.Log(err) 44 | _ = s.Stop() 45 | _ = s.Restart() 46 | t.Log(s.Status()) 47 | _ = s.Uninstall() 48 | t.Log(s.String()) 49 | t.Log(o.I) 50 | } 51 | 52 | func TestUtil(t *testing.T) { 53 | tt := zlsgo.NewTest(t) 54 | tt.Equal(IsPermissionError(ErrNotAnAdministrator), IsPermissionError(ErrNotAnRootUser)) 55 | _ = isSudo() 56 | } 57 | -------------------------------------------------------------------------------- /zarray/string_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zarray_test 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/sohaha/zlsgo" 10 | "github.com/sohaha/zlsgo/zarray" 11 | ) 12 | 13 | func TestSlice(t *testing.T) { 14 | tt := zlsgo.NewTest(t) 15 | tt.Equal([]string{"a", "b", "c"}, zarray.Slice[string]("a,b,c", ",")) 16 | tt.Equal([]int{1, 2, 3}, zarray.Slice[int]("1,2,3", ",")) 17 | tt.Equal([]float64{1.1, 2.2, 3.3}, zarray.Slice[float64]("1.1,2.2,3.3", ",")) 18 | tt.Equal([]string{"1.1", "2.2,3.3"}, zarray.Slice[string]("1.1,2.2,3.3", ",", 2)) 19 | tt.Equal([]int{}, zarray.Slice[int]("", ",")) 20 | } 21 | 22 | func TestJoin(t *testing.T) { 23 | tt := zlsgo.NewTest(t) 24 | tt.Equal("a,b,c", zarray.Join([]string{"a", "b", "c"}, ",")) 25 | tt.Equal("1,2,3", zarray.Join([]int{1, 2, 3}, ",")) 26 | tt.Equal("1.1,2.2,3.3", zarray.Join([]float64{1.1, 2.2, 3.3}, ",")) 27 | tt.Equal("1.1,2.2,3.3", zarray.Join([]string{"1.1", "2.2", "3.3"}, ",")) 28 | tt.Equal("1.1,3.3", zarray.Join([]string{"1.1", "", "3.3"}, ",")) 29 | tt.Equal("", zarray.Join([]string{}, ",")) 30 | } 31 | -------------------------------------------------------------------------------- /zfile/compress_test.go: -------------------------------------------------------------------------------- 1 | package zfile 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | ) 8 | 9 | func TestGz(t *testing.T) { 10 | tt := zlsgo.NewTest(t) 11 | err := WriteFile("./tmp_gz/log.txt", []byte("ok\n")) 12 | tt.EqualNil(err) 13 | err = WriteFile("./tmp_gz/tmp2/log.txt", []byte("ok\n")) 14 | tt.EqualNil(err) 15 | gz := "dd.tar.gz" 16 | err = GzCompress(".", gz) 17 | tt.EqualNil(err) 18 | Rmdir("./tmp_gz") 19 | err = GzDeCompress(gz, "tmp2") 20 | tt.EqualNil(err) 21 | err = GzDeCompress(gz+"1", "tmp2") 22 | tt.Equal(true, err != nil) 23 | 24 | ok := Rmdir("tmp2") 25 | tt.EqualTrue(ok) 26 | Rmdir(gz) 27 | } 28 | 29 | func TestZip(t *testing.T) { 30 | tt := zlsgo.NewTest(t) 31 | zip := "tmp.zip" 32 | err := WriteFile("./tmp/log.txt", []byte("ok\n")) 33 | tt.EqualNil(err) 34 | err = WriteFile("./tmp/tmp2/log.txt", []byte("ok\n")) 35 | tt.EqualNil(err) 36 | err = ZipCompress("./", zip) 37 | tt.EqualNil(err) 38 | tt.EqualNil(ZipDeCompress(zip, "zip")) 39 | tt.EqualTrue(FileExist("./zip/tmp/log.txt")) 40 | Rmdir(zip) 41 | Rmdir("zip") 42 | Rmdir("./tmp") 43 | } 44 | -------------------------------------------------------------------------------- /znet/gzip/gzip.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "strings" 7 | 8 | "github.com/sohaha/zlsgo/znet" 9 | ) 10 | 11 | func Default() znet.HandlerFunc { 12 | return New(Config{ 13 | CompressionLevel: 7, 14 | PoolMaxSize: 1024, 15 | MinContentLength: 1024, 16 | }) 17 | } 18 | 19 | func New(conf Config) znet.HandlerFunc { 20 | pool := &poolCap{ 21 | c: make(chan *gzip.Writer, conf.PoolMaxSize), 22 | l: conf.CompressionLevel, 23 | } 24 | return func(c *znet.Context) { 25 | if !strings.Contains(c.GetHeader("Accept-Encoding"), "gzip") { 26 | c.Next() 27 | } else { 28 | c.Next() 29 | p := c.PrevContent() 30 | if len(p.Content) < conf.MinContentLength { 31 | return 32 | } 33 | 34 | g, err := pool.Get() 35 | if err != nil { 36 | return 37 | } 38 | defer pool.Put(g) 39 | 40 | be := &bytes.Buffer{} 41 | g.Reset(be) 42 | _, err = g.Write(p.Content) 43 | if err != nil { 44 | return 45 | } 46 | _ = g.Flush() 47 | 48 | c.SetHeader("Content-Encoding", "gzip") 49 | c.Byte(p.Code.Load(), be.Bytes()) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /zdi/pre.go: -------------------------------------------------------------------------------- 1 | package zdi 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type PreInvoker interface { 9 | Invoke([]interface{}) ([]reflect.Value, error) 10 | } 11 | 12 | // IsPreInvoker checks if the given handler implements the PreInvoker interface. 13 | // PreInvoker allows for a potentially faster invocation path by bypassing some reflection. 14 | func IsPreInvoker(handler interface{}) bool { 15 | _, ok := handler.(PreInvoker) 16 | return ok 17 | } 18 | 19 | // fast is an internal helper for invoking a PreInvoker. 20 | // It resolves dependencies for the PreInvoker's arguments as interface{} slices 21 | // and then calls its Invoke method. 22 | func (inj *injector) fast(f PreInvoker, t reflect.Type, numIn int) ([]reflect.Value, error) { 23 | var in []interface{} 24 | if numIn > 0 { 25 | in = make([]interface{}, numIn) 26 | var argType reflect.Type 27 | for i := 0; i < numIn; i++ { 28 | argType = t.In(i) 29 | val, ok := inj.Get(argType) 30 | if !ok { 31 | return nil, fmt.Errorf("value not found for type %v", argType) 32 | } 33 | 34 | in[i] = val.Interface() 35 | } 36 | } 37 | return f.Invoke(in) 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 seekwe@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /zshell/shell_notwin.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package zshell 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "io/ioutil" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "strings" 14 | "syscall" 15 | 16 | "github.com/sohaha/zlsgo/zutil" 17 | ) 18 | 19 | var chcp = zutil.Once(func() struct{} { 20 | return struct{}{} 21 | }) 22 | 23 | func RunNewProcess(file string, args []string) (pid int, err error) { 24 | execSpec := &syscall.ProcAttr{ 25 | Env: os.Environ(), 26 | Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}, 27 | } 28 | if tmp, _ := ioutil.TempDir("", ""); tmp != "" { 29 | tmp = filepath.Dir(tmp) 30 | if strings.HasPrefix(file, tmp) { 31 | return 0, errors.New("temporary program does not support startup") 32 | } 33 | } 34 | return syscall.ForkExec(file, args, execSpec) 35 | } 36 | 37 | func RunBash(ctx context.Context, command string) (code int, outStr, errStr string, err error) { 38 | return ExecCommand(ctx, []string{ 39 | "bash", 40 | "-c", 41 | command, 42 | }, nil, nil, nil) 43 | } 44 | 45 | func sysProcAttr(cmd *exec.Cmd) *exec.Cmd { 46 | return cmd 47 | } 48 | -------------------------------------------------------------------------------- /zreflect/method.go: -------------------------------------------------------------------------------- 1 | package zreflect 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // GetAllMethod get all methods of struct 9 | func GetAllMethod(s interface{}, fn func(numMethod int, m reflect.Method) error) error { 10 | typ := ValueOf(s) 11 | if fn == nil { 12 | return nil 13 | } 14 | return ForEachMethod(typ, func(index int, method reflect.Method, value reflect.Value) error { 15 | return fn(index, method) 16 | }) 17 | } 18 | 19 | // RunAssignMethod run assign methods of struct 20 | func RunAssignMethod(st interface{}, filter func(methodName string) bool, args ...interface{}) (err error) { 21 | valueOf := ValueOf(st) 22 | err = GetAllMethod(st, func(numMethod int, m reflect.Method) (err error) { 23 | if !filter(m.Name) { 24 | return nil 25 | } 26 | 27 | var values []reflect.Value 28 | for _, v := range args { 29 | values = append(values, ValueOf(v)) 30 | } 31 | 32 | (func() { 33 | defer func() { 34 | if e := recover(); e != nil { 35 | err = fmt.Errorf("method invocation panic: %v", e) 36 | } 37 | }() 38 | valueOf.Method(numMethod).Call(values) 39 | })() 40 | 41 | return err 42 | }) 43 | 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /znet/session/store.go: -------------------------------------------------------------------------------- 1 | // Package session provides session management for web applications. 2 | // It includes interfaces and implementations for storing and retrieving 3 | // session data with different storage backends. 4 | package session 5 | 6 | import ( 7 | "time" 8 | 9 | "github.com/sohaha/zlsgo/ztype" 10 | ) 11 | 12 | // Session represents a user session with key-value storage capabilities. 13 | // It provides methods to manage session data and control session lifecycle. 14 | type Session interface { 15 | ID() string 16 | Get(key string) ztype.Type 17 | Set(key string, value interface{}) 18 | Delete(key string) error 19 | Save() error 20 | Destroy() error 21 | ExpiresAt() time.Time 22 | } 23 | 24 | // Store defines the interface for session storage backends. 25 | // Implementations of Store are responsible for creating, retrieving, 26 | // and managing session data in various storage systems. 27 | type Store interface { 28 | New(sessionID string, expiresAt time.Time) (Session, error) 29 | Get(sessionID string) (Session, error) 30 | Save(session Session) error 31 | Delete(sessionID string) error 32 | Collect() error 33 | Renew(sessionID string, expiresAt time.Time) error 34 | } 35 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package zlsgo is golang daily development of some commonly used functions set 3 | 4 | See https://docs.73zls.com/zlsgo/#/ 5 | 6 | znet(Web development) https://pkg.go.dev/github.com/sohaha/zlsgo/znet 7 | 8 | ztype(Type correlation) https://pkg.go.dev/github.com/sohaha/zlsgo/ztype 9 | 10 | zpool(Goroutine pool) https://pkg.go.dev/github.com/sohaha/zlsgo/zpool 11 | 12 | zfile(File operation) https://pkg.go.dev/github.com/sohaha/zlsgo/zfile 13 | 14 | zcache(Cache) https://pkg.go.dev/github.com/sohaha/zlsgo/zcache 15 | 16 | zdi(Dependency injection) https://pkg.go.dev/github.com/sohaha/zlsgo/zdi 17 | 18 | zlog(Log) https://pkg.go.dev/github.com/sohaha/zlsgo/zlog 19 | 20 | zhttp(Http Client) https://pkg.go.dev/github.com/sohaha/zlsgo/zhttp 21 | 22 | ztime(Time) https://pkg.go.dev/github.com/sohaha/zlsgo/ztime 23 | 24 | zcli(Command line) https://pkg.go.dev/github.com/sohaha/zlsgo/zcli 25 | 26 | zshell(Shell) https://pkg.go.dev/github.com/sohaha/zlsgo/zsh 27 | 28 | zarray(Array) https://pkg.go.dev/github.com/sohaha/zlsgo/zarray 29 | 30 | zsync(Sync) https://pkg.go.dev/github.com/sohaha/zlsgo/zsync 31 | 32 | zdb(Database) https://pkg.go.dev/github.com/zlsgo/zdb 33 | */ 34 | package zlsgo 35 | -------------------------------------------------------------------------------- /zfile/lock_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package zfile 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | ) 10 | 11 | // errLocked is the error returned when a file is already locked by another process. 12 | // On Unix systems, this corresponds to syscall.EWOULDBLOCK. 13 | var errLocked = syscall.EWOULDBLOCK 14 | 15 | // lockFile acquires an exclusive, non-blocking lock on the given file. 16 | // On Unix systems, this uses syscall.Flock with LOCK_EX|LOCK_NB flags. 17 | // Returns errLocked if the file is already locked by another process. 18 | func lockFile(f *os.File) error { 19 | err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 20 | if err != nil { 21 | if err == syscall.EWOULDBLOCK { 22 | return errLocked 23 | } 24 | return &os.PathError{Op: "lock", Path: f.Name(), Err: err} 25 | } 26 | return nil 27 | } 28 | 29 | // unlockFile releases a lock previously acquired with lockFile. 30 | // On Unix systems, this uses syscall.Flock with LOCK_UN flag. 31 | func unlockFile(f *os.File) error { 32 | err := syscall.Flock(int(f.Fd()), syscall.LOCK_UN) 33 | if err != nil { 34 | return &os.PathError{Op: "unlock", Path: f.Name(), Err: err} 35 | } 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /zpool/inject.go: -------------------------------------------------------------------------------- 1 | package zpool 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/sohaha/zlsgo/zdi" 7 | ) 8 | 9 | type ( 10 | invokerPre func() error 11 | ) 12 | 13 | func (i invokerPre) Invoke(_ []interface{}) ([]reflect.Value, error) { 14 | return nil, i() 15 | } 16 | 17 | var ( 18 | _ zdi.PreInvoker = (*invokerPre)(nil) 19 | ) 20 | 21 | func invokeHandler(v []reflect.Value, err error) error { 22 | if err != nil { 23 | return err 24 | } 25 | for i := range v { 26 | val := v[i].Interface() 27 | switch e := val.(type) { 28 | case error: 29 | return e 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | func (wp *WorkPool) Injector() zdi.TypeMapper { 36 | return wp.injector 37 | } 38 | 39 | func (wp *WorkPool) handlerFunc(h Task) (fn taskfn) { 40 | switch v := h.(type) { 41 | case func(): 42 | return func() error { 43 | v() 44 | return nil 45 | } 46 | case func() error: 47 | return func() error { 48 | return invokeHandler(wp.injector.Invoke(invokerPre(v))) 49 | } 50 | case zdi.PreInvoker: 51 | return func() error { 52 | err := invokeHandler(wp.injector.Invoke(v)) 53 | return err 54 | } 55 | default: 56 | return func() error { 57 | return invokeHandler(wp.injector.Invoke(v)) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /zvalid/json_test.go: -------------------------------------------------------------------------------- 1 | package zvalid 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | "github.com/sohaha/zlsgo/zjson" 8 | ) 9 | 10 | func TestValids(t *testing.T) { 11 | tt := zlsgo.NewTest(t) 12 | 13 | j := zjson.Parse(`{"name":"zls","age":18}`) 14 | rules := map[string]Engine{ 15 | "name": New().MinLength(3), 16 | "age": New().MinInt(18), 17 | } 18 | 19 | err := JSON(j, rules) 20 | tt.NoError(err) 21 | 22 | j = zjson.Parse(`{"age":8}`) 23 | err = JSON(j, rules) 24 | t.Log(err) 25 | tt.EqualTrue(err != nil) 26 | 27 | j = zjson.Parse(`{"name":8}`) 28 | err = JSON(j, rules) 29 | t.Log(err) 30 | tt.EqualTrue(err != nil) 31 | 32 | j = zjson.Parse(``) 33 | err = JSON(j, map[string]Engine{ 34 | "password": New().Required("密码不能为空"), 35 | }) 36 | t.Log(err) 37 | tt.EqualTrue(err != nil) 38 | 39 | j = zjson.Parse(`{"password":"123456"}`) 40 | err = JSON(j, map[string]Engine{ 41 | "password": New().Required("密码不能为空").StrongPassword("密码必须是强密码"), 42 | }) 43 | t.Log(err) 44 | tt.EqualTrue(err != nil) 45 | 46 | j = zjson.Parse(`{"password":"123456Abc."}`) 47 | err = JSON(j, map[string]Engine{ 48 | "password": New().Required("密码不能为空").StrongPassword("密码必须是强密码"), 49 | }) 50 | tt.NoError(err) 51 | 52 | } 53 | -------------------------------------------------------------------------------- /zvalid/format_test.go: -------------------------------------------------------------------------------- 1 | package zvalid 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | ) 8 | 9 | func TestFormat(t *testing.T) { 10 | tt := zlsgo.NewTest(t) 11 | str := Text(" is test ").Trim().Value() 12 | tt.Equal("is test", str) 13 | 14 | str = Text(" is test ").RemoveSpace().Value() 15 | tt.Equal("istest", str) 16 | 17 | str = Text("is test is").Replace("is", "yes", 1).Value() 18 | tt.Equal("yes test is", str) 19 | 20 | str = Text("is test is").ReplaceAll("is", "yes").Value() 21 | tt.Equal("yes test yes", str) 22 | 23 | str = Text("is js").XSSClean().Value() 24 | tt.Equal("is js", str) 25 | 26 | str = Text("hello_world").SnakeCaseToCamelCase(false).Value() 27 | tt.Equal("helloWorld", str) 28 | 29 | str = Text("hello_world").SnakeCaseToCamelCase(true).Value() 30 | tt.Equal("HelloWorld", str) 31 | 32 | str = Text("hello-world").SnakeCaseToCamelCase(true, "-").Value() 33 | tt.Equal("HelloWorld", str) 34 | 35 | str = Text("HelloWorld").CamelCaseToSnakeCase().Value() 36 | tt.Equal("hello_world", str) 37 | 38 | str = Text("helloWorld").CamelCaseToSnakeCase().Value() 39 | tt.Equal("hello_world", str) 40 | 41 | str = Text("helloWorld").CamelCaseToSnakeCase("-").Value() 42 | tt.Equal("hello-world", str) 43 | } 44 | -------------------------------------------------------------------------------- /ztime/local_test.go: -------------------------------------------------------------------------------- 1 | package ztime_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/ztime" 10 | ) 11 | 12 | type demo struct { 13 | BirthdayLocal ztime.LocalTime 14 | Birthday time.Time 15 | Name string 16 | } 17 | 18 | func TestLocalTime(t *testing.T) { 19 | tt := zlsgo.NewTest(t) 20 | 21 | ztime.SetTimeZone(0) 22 | 23 | now := time.Now() 24 | lt := ztime.LocalTime{now} 25 | tt.Equal(now.Unix(), lt.Unix()) 26 | 27 | j, err := json.Marshal(lt) 28 | tt.NoError(err) 29 | tt.Log(string(j)) 30 | 31 | nj, err := json.Marshal(now) 32 | tt.NoError(err) 33 | tt.Log(string(nj)) 34 | 35 | data := demo{Name: "anna", BirthdayLocal: lt, Birthday: now} 36 | dj, err := json.Marshal(data) 37 | tt.NoError(err) 38 | tt.Log(string(dj)) 39 | 40 | v, err := lt.Value() 41 | tt.NoError(err) 42 | tt.Log(v) 43 | 44 | nt, _ := ztime.Parse("2021-01-01 00:00:00") 45 | err = lt.Scan(nt) 46 | tt.NoError(err) 47 | 48 | j2, err := lt.MarshalJSON() 49 | tt.NoError(err) 50 | tt.Log(string(j2)) 51 | tt.EqualTrue(string(j2) != string(nj)) 52 | 53 | lt3 := ztime.LocalTime{} 54 | lt3.Scan(data.Birthday) 55 | tt.Log(lt3.String()) 56 | 57 | lt3.Scan(data.BirthdayLocal) 58 | tt.Log(lt3.String()) 59 | } 60 | -------------------------------------------------------------------------------- /zstring/md5.go: -------------------------------------------------------------------------------- 1 | package zstring 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | "os" 9 | ) 10 | 11 | // ProjectMd5 calculates the MD5 hash of the current executable. 12 | // This is useful for checking if the application binary has been modified. 13 | func ProjectMd5() string { 14 | d, _ := Md5File(os.Args[0]) 15 | return d 16 | } 17 | 18 | // Md5 calculates the MD5 hash of a string. 19 | // It returns the hash as a hexadecimal encoded string. 20 | func Md5(s string) string { 21 | return Md5Byte(String2Bytes(s)) 22 | } 23 | 24 | // Md5Byte calculates the MD5 hash of a byte slice. 25 | // It returns the hash as a hexadecimal encoded string. 26 | func Md5Byte(s []byte) string { 27 | h := md5.New() 28 | _, _ = h.Write(s) 29 | return hex.EncodeToString(h.Sum(nil)) 30 | } 31 | 32 | // Md5File calculates the MD5 hash of a file at the given path. 33 | // It returns the hash as a hexadecimal encoded string and any error encountered. 34 | func Md5File(path string) (encrypt string, err error) { 35 | f, err := os.Open(path) 36 | if err != nil { 37 | return "", err 38 | } 39 | // r := bufio.NewReader(f) 40 | h := md5.New() 41 | _, err = io.Copy(h, f) 42 | _ = f.Close() 43 | if err != nil { 44 | return "", err 45 | } 46 | return fmt.Sprintf("%x", h.Sum(nil)), nil 47 | } 48 | -------------------------------------------------------------------------------- /zerror/catch_test.go: -------------------------------------------------------------------------------- 1 | package zerror_test 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "testing" 7 | 8 | "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/zerror" 10 | ) 11 | 12 | func TestTryCatch(t *testing.T) { 13 | tt := zlsgo.NewTest(t) 14 | err := zerror.TryCatch(func() error { 15 | zerror.Panic(zerror.New(500, "测试")) 16 | return nil 17 | }) 18 | tt.EqualTrue(err != nil) 19 | tt.Equal("测试", err.Error()) 20 | t.Logf("%+v", err) 21 | code, _ := zerror.UnwrapCode(err) 22 | tt.Equal(zerror.ErrCode(500), code) 23 | 24 | err = zerror.TryCatch(func() error { 25 | panic("测试") 26 | return nil 27 | }) 28 | tt.Equal("测试", err.Error()) 29 | t.Logf("%+v", err) 30 | } 31 | 32 | func BenchmarkTryCatch_normal(b *testing.B) { 33 | for i := 0; i < b.N; i++ { 34 | _ = zerror.TryCatch(func() error { 35 | e := strconv.Itoa(i) 36 | _ = e 37 | return nil 38 | }) 39 | } 40 | } 41 | 42 | func BenchmarkTryCatch_panic(b *testing.B) { 43 | for i := 0; i < b.N; i++ { 44 | _ = zerror.TryCatch(func() error { 45 | e := strconv.Itoa(i) 46 | panic(e) 47 | }) 48 | } 49 | } 50 | 51 | func BenchmarkTryCatch_error(b *testing.B) { 52 | for i := 0; i < b.N; i++ { 53 | _ = zerror.TryCatch(func() error { 54 | e := strconv.Itoa(i) 55 | return errors.New(e) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /znet/limiter/statistics.go: -------------------------------------------------------------------------------- 1 | package limiter 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/sohaha/zlsgo/znet" 8 | ) 9 | 10 | // Remaining Remaining visits 11 | func (r *Rule) Remaining(key interface{}) []int { 12 | arr := make([]int, 0, len(r.rules)) 13 | for i := range r.rules { 14 | arr = append(arr, r.rules[i].remainingVisits(key)) 15 | } 16 | return arr 17 | } 18 | 19 | // RemainingVisitsByIP Remaining Visits IP 20 | func (r *Rule) RemainingVisitsByIP(ip string) []int { 21 | ipUint, _ := znet.IPToLong(ip) 22 | if ipUint == 0 { 23 | return []int{} 24 | } 25 | return r.Remaining(ipUint) 26 | } 27 | 28 | // GetOnline Get all current online users 29 | func (r *Rule) GetOnline() []string { 30 | var insertIgnoreString = func(s []string, v string) []string { 31 | for _, val := range s { 32 | if val == v { 33 | return s 34 | } 35 | } 36 | s = append(s, v) 37 | return s 38 | } 39 | var users []string 40 | for i := range r.rules { 41 | f := func(k, v interface{}) bool { 42 | var user string 43 | switch v := k.(type) { 44 | case uint: 45 | user, _ = znet.LongToIP(v) 46 | default: 47 | user = fmt.Sprint(k) 48 | } 49 | users = insertIgnoreString(users, user) 50 | return true 51 | } 52 | r.rules[i].usedRecordsIndex.Range(f) 53 | } 54 | sort.Strings(users) 55 | return users 56 | } 57 | -------------------------------------------------------------------------------- /zdi/di_test.go: -------------------------------------------------------------------------------- 1 | package zdi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | "github.com/sohaha/zlsgo/zdi" 8 | "github.com/sohaha/zlsgo/ztime" 9 | ) 10 | 11 | type testSt struct { 12 | Msg string `di:""` 13 | Num int 14 | } 15 | 16 | func TestBase(t *testing.T) { 17 | tt := zlsgo.NewTest(t) 18 | di := zdi.New() 19 | 20 | now := ztime.Now() 21 | test1 := &testSt{Msg: now, Num: 666} 22 | override := di.Maps(test1, testSt2{Name: "main"}) 23 | tt.Equal(0, len(override)) 24 | 25 | tt.Run("TestSetParent", func(tt *zlsgo.TestUtil) { 26 | ndi := zdi.New(di) 27 | ndi.Map(testSt2{Name: "Current"}) 28 | _, err := ndi.Invoke(func(t2 testSt2, t1 *testSt) { 29 | tt.Equal("Current", t2.Name) 30 | tt.Equal(666, t1.Num) 31 | tt.Equal(now, t1.Msg) 32 | t.Log(t2, t1) 33 | }) 34 | tt.NoError(err) 35 | }) 36 | } 37 | 38 | func TestMultiple(t *testing.T) { 39 | tt := zlsgo.NewTest(t) 40 | di := zdi.New() 41 | 42 | test1 := &testSt{Num: 1} 43 | test2 := &testSt{Num: 2} 44 | test3 := &testSt{Num: 3} 45 | 46 | di.Maps(test1, test2) 47 | di.Map(test3) 48 | 49 | _, err := di.Invoke(func(test *testSt) { 50 | t.Log(test) 51 | }) 52 | tt.NoError(err) 53 | 54 | err = di.InvokeWithErrorOnly(func(test []*testSt) error { 55 | t.Log(test) 56 | return nil 57 | }) 58 | tt.Log(err) 59 | tt.EqualTrue(err != nil) 60 | } 61 | -------------------------------------------------------------------------------- /zhttp/json_rpc_test.go: -------------------------------------------------------------------------------- 1 | package zhttp 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/sohaha/zlsgo" 8 | "github.com/sohaha/zlsgo/zlog" 9 | ) 10 | 11 | func TestNewJSONRPC(t *testing.T) { 12 | tt := zlsgo.NewTest(t) 13 | 14 | client, err := NewJSONRPC("18181", "/__rpc", func(o *JSONRPCOptions) { 15 | o.Timeout = time.Second * 10 16 | o.Retry = true 17 | o.RetryDelay = time.Second * 6 18 | // o.TlsConfig = &tls.Config{} 19 | }) 20 | tt.NoError(err) 21 | 22 | res, err := Get("http://127.0.0.1:18181/__rpc") 23 | tt.NoError(err) 24 | tt.Equal([]interface{}{"int", "*zhttp.Result"}, res.JSON("Cal\\.Square").Value()) 25 | zlog.Debug(res) 26 | 27 | var result Result 28 | err = client.Call("Cal.Square", 12, &result) 29 | tt.NoError(err) 30 | tt.Equal(144, result.Ans) 31 | tt.Equal(12, result.Num) 32 | t.Log(result, err) 33 | 34 | res, err = Post("http://127.0.0.1:18181/__rpc", BodyJSON(map[string]interface{}{ 35 | "method": "Cal.Square", 36 | "params": []int{12}, 37 | "id": 1, 38 | })) 39 | tt.NoError(err) 40 | tt.Equal(144, res.JSON("result.Ans").Int()) 41 | zlog.Debug(res, err) 42 | 43 | res, err = Put("http://127.0.0.1:18181/__rpc", BodyJSON(map[string]interface{}{ 44 | "method": "Cal.Square", 45 | "params": []int{12}, 46 | "id": 1, 47 | })) 48 | t.Log(err) 49 | t.Log(res) 50 | 51 | _ = client.Close() 52 | } 53 | -------------------------------------------------------------------------------- /zdi/lazy_test.go: -------------------------------------------------------------------------------- 1 | package zdi_test 2 | 3 | import ( 4 | "github.com/sohaha/zlsgo/ztype" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/zdi" 10 | "github.com/sohaha/zlsgo/ztime" 11 | ) 12 | 13 | func TestProvide(t *testing.T) { 14 | tt := zlsgo.NewTest(t) 15 | di := zdi.New() 16 | 17 | val := ztime.Now() 18 | 19 | di.Provide(func() *testSt { 20 | tt.Log("init testSt") 21 | return &testSt{Msg: val, Num: 666} 22 | }) 23 | 24 | di.Provide(func(ts *testSt) *testSt2 { 25 | tt.Log("init testSt2") 26 | return &testSt2{Name: "2->" + ts.Msg} 27 | }) 28 | 29 | override := di.Provide(func() time.Time { 30 | tt.Log("init time") 31 | return time.Now() 32 | }) 33 | tt.Log(override) 34 | 35 | // overwrite the previous time.Time, *testSt 36 | override = di.Provide(func() (time.Time, *testSt) { 37 | tt.Log("init time2") 38 | return time.Now(), &testSt{Msg: val, Num: 999} 39 | }) 40 | tt.Log(override) 41 | 42 | _, err := di.Invoke(func(t2 *testSt2, t1 *testSt, now time.Time) { 43 | tt.Log(t2.Name, t1.Num, now) 44 | tt.Equal(val, t1.Msg) 45 | }) 46 | tt.NoError(err) 47 | 48 | di.Provide(func() *ztype.Type { 49 | tt.Log("test panic") 50 | panic("panic") 51 | return nil 52 | }) 53 | 54 | _, err = di.Invoke(func(typ *ztype.Type) { 55 | tt.Log(typ) 56 | }) 57 | tt.EqualTrue(err != nil) 58 | t.Log(err) 59 | } 60 | -------------------------------------------------------------------------------- /zhttp/requester.go: -------------------------------------------------------------------------------- 1 | package zhttp 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func DisableChunked(enable ...bool) { 8 | std.DisableChunked(enable...) 9 | } 10 | 11 | func Get(url string, v ...interface{}) (*Res, error) { 12 | return std.Get(url, v...) 13 | } 14 | 15 | func Post(url string, v ...interface{}) (*Res, error) { 16 | return std.Post(url, v...) 17 | } 18 | 19 | func Put(url string, v ...interface{}) (*Res, error) { 20 | return std.Put(url, v...) 21 | } 22 | 23 | func Head(url string, v ...interface{}) (*Res, error) { 24 | return std.Head(url, v...) 25 | } 26 | 27 | func Options(url string, v ...interface{}) (*Res, error) { 28 | return std.Options(url, v...) 29 | } 30 | 31 | func Delete(url string, v ...interface{}) (*Res, error) { 32 | return std.Delete(url, v...) 33 | } 34 | 35 | func Patch(url string, v ...interface{}) (*Res, error) { 36 | return std.Patch(url, v...) 37 | } 38 | 39 | func Connect(url string, v ...interface{}) (*Res, error) { 40 | return std.Connect(url, v...) 41 | } 42 | 43 | func Trace(url string, v ...interface{}) (*Res, error) { 44 | return std.Trace(url, v...) 45 | } 46 | 47 | func Do(method, rawurl string, v ...interface{}) (resp *Res, err error) { 48 | return std.Do(method, rawurl, v...) 49 | } 50 | 51 | func DoRetry(attempt int, sleep time.Duration, fn func() (*Res, error)) (*Res, error) { 52 | return std.DoRetry(attempt, sleep, fn) 53 | } 54 | -------------------------------------------------------------------------------- /zutil/utils_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package zutil 5 | 6 | import ( 7 | "errors" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | func IsDoubleClickStartUp() bool { 13 | if name, err := GetParentProcessName(); err == nil && name == "explorer.exe" { 14 | return true 15 | } 16 | return false 17 | } 18 | 19 | func GetParentProcessName() (string, error) { 20 | snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0) 21 | if err != nil { 22 | return "", err 23 | } 24 | defer syscall.CloseHandle(snapshot) 25 | var procEntry syscall.ProcessEntry32 26 | procEntry.Size = uint32(unsafe.Sizeof(procEntry)) 27 | if err = syscall.Process32First(snapshot, &procEntry); err != nil { 28 | return "", err 29 | } 30 | var ( 31 | pid = uint32(syscall.Getpid()) 32 | pName = make(map[uint32]string, 32) 33 | parentId = uint32(1<<32 - 1) 34 | ) 35 | for { 36 | pName[procEntry.ProcessID] = syscall.UTF16ToString(procEntry.ExeFile[:]) 37 | if procEntry.ProcessID == pid { 38 | parentId = procEntry.ParentProcessID 39 | } 40 | if s, ok := pName[parentId]; ok { 41 | return s, nil 42 | } 43 | err = syscall.Process32Next(snapshot, &procEntry) 44 | if err != nil { 45 | return "", err 46 | } 47 | } 48 | } 49 | 50 | // MaxRlimit (not relevant on Windows) 51 | func MaxRlimit() (int, error) { 52 | return 0, errors.New("not relevant on Windows") 53 | } 54 | -------------------------------------------------------------------------------- /ztype/utils.go: -------------------------------------------------------------------------------- 1 | package ztype 2 | 3 | import ( 4 | "reflect" 5 | "time" 6 | 7 | "github.com/sohaha/zlsgo/zreflect" 8 | ) 9 | 10 | // GetType Get variable type 11 | func GetType(s interface{}) string { 12 | var varType string 13 | switch s.(type) { 14 | case int: 15 | varType = "int" 16 | case int8: 17 | varType = "int8" 18 | case int16: 19 | varType = "int16" 20 | case int32: 21 | varType = "int32" 22 | case int64: 23 | varType = "int64" 24 | case uint: 25 | varType = "uint" 26 | case uint8: 27 | varType = "uint8" 28 | case uint16: 29 | varType = "uint16" 30 | case uint32: 31 | varType = "uint32" 32 | case uint64: 33 | varType = "uint64" 34 | case float32: 35 | varType = "float32" 36 | case float64: 37 | varType = "float64" 38 | case bool: 39 | varType = "bool" 40 | case string: 41 | varType = "string" 42 | case []byte: 43 | varType = "[]byte" 44 | default: 45 | if s == nil { 46 | return "nil" 47 | } 48 | v := zreflect.TypeOf(s) 49 | if v.Kind() == reflect.Invalid { 50 | return "invalid" 51 | } 52 | varType = v.String() 53 | } 54 | return varType 55 | } 56 | 57 | func reflectPtr(r reflect.Value) reflect.Value { 58 | if r.Kind() == reflect.Ptr { 59 | r = r.Elem() 60 | } 61 | return r 62 | } 63 | 64 | func parsePath(path string, v interface{}) (interface{}, bool) { 65 | return executeCompiledPath(compilePath(path), v) 66 | } 67 | 68 | var timeType = reflect.TypeOf(time.Time{}) 69 | -------------------------------------------------------------------------------- /zstring/snowflake_test.go: -------------------------------------------------------------------------------- 1 | package zstring 2 | 3 | import ( 4 | "os" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/sohaha/zlsgo" 10 | ) 11 | 12 | func TestId(t *testing.T) { 13 | var g sync.WaitGroup 14 | var testSum = 10000 15 | var ids = struct { 16 | data map[int64]*interface{} 17 | sync.RWMutex 18 | }{ 19 | data: make(map[int64]*interface{}, testSum), 20 | } 21 | 22 | tt := zlsgo.NewTest(t) 23 | w, err := NewIDWorker(0) 24 | tt.EqualNil(err) 25 | 26 | g.Add(testSum) 27 | for i := 0; i < testSum; i++ { 28 | go func(t *testing.T) { 29 | id, err := w.ID() 30 | tt.EqualNil(err) 31 | ids.Lock() 32 | if _, ok := ids.data[id]; ok { 33 | t.Error("repeated") 34 | os.Exit(1) 35 | } 36 | ids.data[id] = new(interface{}) 37 | ids.Unlock() 38 | g.Done() 39 | }(t) 40 | } 41 | g.Wait() 42 | 43 | w, err = NewIDWorker(2) 44 | tt.EqualNil(err) 45 | id, _ := w.ID() 46 | tim, ts, workerId, seq := ParseID(id) 47 | tt.EqualNil(err) 48 | t.Log(id, tim, ts, workerId, seq) 49 | } 50 | 51 | func TestIdWorker_timeReGen(t *testing.T) { 52 | tt := zlsgo.NewTest(t) 53 | w, err := NewIDWorker(0) 54 | tt.EqualNil(err) 55 | 56 | t.Log(w.ID()) 57 | 58 | g := w.timeGen() 59 | now := time.Now() 60 | reG := w.timeReGen(g + 1) 61 | t.Log(g, reG) 62 | v := time.Since(now).Nanoseconds() 63 | 64 | g = w.timeGen() 65 | now = time.Now() 66 | reG = w.timeReGen(g) 67 | t.Log(g, reG) 68 | 69 | t.Log(v, time.Since(now).Nanoseconds()) 70 | } 71 | -------------------------------------------------------------------------------- /zlog/file_test.go: -------------------------------------------------------------------------------- 1 | package zlog 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/sohaha/zlsgo" 10 | "github.com/sohaha/zlsgo/zfile" 11 | "github.com/sohaha/zlsgo/ztime" 12 | ) 13 | 14 | func TestMain(m *testing.M) { 15 | m.Run() 16 | zfile.Rmdir("tmp2/") 17 | } 18 | 19 | func TestLogFile(T *testing.T) { 20 | t := zlsgo.NewTest(T) 21 | ResetFlags(BitLevel | BitMicroSeconds) 22 | defer zfile.Rmdir("tmp2/") 23 | logPath := "./tmp2/log.log" 24 | SetSaveFile(logPath) 25 | Success("ok1") 26 | var ws sync.WaitGroup 27 | for i := range make([]uint8, 100) { 28 | ws.Add(1) 29 | go func(i int) { 30 | Info(i) 31 | ws.Done() 32 | }(i) 33 | } 34 | 35 | Success("ok2") 36 | ws.Wait() 37 | time.Sleep(time.Second * 2) 38 | 39 | t.Equal(true, zfile.FileExist(logPath)) 40 | 41 | SetSaveFile("tmp2/ll.log", true) 42 | Success("ok3") 43 | Error("err3") 44 | time.Sleep(time.Second * 2) 45 | t.EqualTrue(zfile.DirExist("tmp2/ll")) 46 | Discard() 47 | } 48 | 49 | func TestSetSaveFile(t *testing.T) { 50 | log := New("TestSetSaveFile ") 51 | log.SetFile("tmp2/test.log") 52 | defer zfile.Rmdir("tmp2/") 53 | log.Success("ok") 54 | go func() { 55 | log.SetFile("tmp2/test2.log", true) 56 | for i := 0; i < 100; i++ { 57 | log.Success("ok2-" + strconv.Itoa(i)) 58 | } 59 | }() 60 | time.Sleep(time.Second * 2) 61 | t.Log(zfile.FileSize("tmp2/test.log")) 62 | t.Log(zfile.FileSize("tmp2/test2/" + ztime.Now("Y-m-d") + ".log")) 63 | } 64 | -------------------------------------------------------------------------------- /zsync/atomic.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zsync 5 | 6 | import ( 7 | "sync/atomic" 8 | 9 | "github.com/sohaha/zlsgo/zutil" 10 | ) 11 | 12 | // AtomicValue is the generic version of [atomic.Value]. 13 | type AtomicValue[T any] struct { 14 | _ zutil.Nocmp 15 | v atomic.Value 16 | } 17 | 18 | type wrappedValue[T any] struct{ v T } 19 | 20 | func NewValue[T any](v T) *AtomicValue[T] { 21 | av := &AtomicValue[T]{} 22 | av.v.Store(wrappedValue[T]{v}) 23 | return av 24 | } 25 | 26 | // Load returns the value set by the most recent Store. 27 | // It returns the zero value for T if the value is empty. 28 | func (v *AtomicValue[T]) Load() T { 29 | x := v.v.Load() 30 | if x != nil { 31 | return x.(wrappedValue[T]).v 32 | } 33 | var zero T 34 | return zero 35 | } 36 | 37 | // Store sets the value of the Value to x. 38 | func (v *AtomicValue[T]) Store(x T) { 39 | v.v.Store(wrappedValue[T]{x}) 40 | } 41 | 42 | // Swap stores new into Value and returns the previous value. 43 | // It returns the zero value for T if the value was not set before. 44 | func (v *AtomicValue[T]) Swap(x T) (old T) { 45 | oldV := v.v.Swap(wrappedValue[T]{x}) 46 | if oldV != nil { 47 | return oldV.(wrappedValue[T]).v 48 | } 49 | var zero T 50 | return zero 51 | } 52 | 53 | // CAS executes the compare-and-swap operation for the Value. 54 | func (v *AtomicValue[T]) CAS(oldV, newV T) (swapped bool) { 55 | return v.v.CompareAndSwap(wrappedValue[T]{oldV}, wrappedValue[T]{newV}) 56 | } 57 | -------------------------------------------------------------------------------- /zsync/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package zsync provides enhanced synchronization primitives for concurrent programming in Go. 3 | 4 | The package extends the standard library's sync package with additional features 5 | and optimizations for common concurrency patterns. Key components include: 6 | 7 | - WaitGroup: An extended wait group with error handling and concurrency limiting 8 | - RBMutex: A reader-biased reader/writer mutual exclusion lock optimized for read-heavy workloads 9 | - Promise: A Go implementation of the Promise pattern for asynchronous operations 10 | - Context utilities: Tools for working with and combining multiple contexts 11 | 12 | These primitives are designed to simplify concurrent programming while maintaining 13 | high performance and safety. 14 | 15 | Example usage of WaitGroup with concurrency limiting: 16 | 17 | wg := zsync.NewWaitGroup(10) // Limit to 10 concurrent goroutines 18 | for i := 0; i < 100; i++ { 19 | wg.Go(func() { 20 | // This work will be limited to 10 concurrent executions 21 | // ... 22 | }) 23 | } 24 | err := wg.Wait() // Wait for all goroutines to complete 25 | 26 | Example usage of Promise: 27 | 28 | p := zsync.NewPromise(func() (string, error) { 29 | // Perform async work 30 | return "result", nil 31 | }) 32 | 33 | // Chain promises 34 | p2 := p.Then(func(result string) (string, error) { 35 | return result + " processed", nil 36 | }) 37 | 38 | // Wait for the result 39 | result, err := p2.Done() 40 | */ 41 | package zsync 42 | -------------------------------------------------------------------------------- /zpprof/pprof.go: -------------------------------------------------------------------------------- 1 | // Package zpprof provides a register for zweb framework to use net/http/pprof easily. 2 | package zpprof 3 | 4 | import ( 5 | "net/http" 6 | 7 | "github.com/sohaha/zlsgo/znet" 8 | ) 9 | 10 | // Register Registration routing 11 | func Register(r *znet.Engine, token string) (RouterGroup *znet.Engine) { 12 | 13 | // go tool pprof http://127.0.0.1:8081/debug/pprof/profile 14 | // go tool pprof -alloc_space http://127.0.0.1:8081/debug/pprof/heap 15 | // go tool pprof -inuse_space http://127.0.0.1:8081/debug/pprof/heap 16 | 17 | RouterGroup = r.Group("/debug", func(g *znet.Engine) { 18 | g.Use(authDebug(token), func(c *znet.Context) { 19 | c.Next() 20 | c.Abort(200) 21 | }) 22 | g.GET("", infoHandler) 23 | g.GET("/", redirectPprof) 24 | g.GET("/pprof", redirectPprof) 25 | g.GET("/pprof/", indexHandler) 26 | g.GET("/pprof/allocs", allocsHandler) 27 | g.GET("/pprof/mutex", mutexHandler) 28 | g.GET("/pprof/heap", heapHandler) 29 | g.GET("/pprof/goroutine", goroutineHandler) 30 | g.GET("/pprof/block", blockHandler) 31 | g.GET("/pprof/threadcreate", threadCreateHandler) 32 | g.GET("/pprof/cmdline", cmdlineHandler) 33 | g.GET("/pprof/profile", profileHandler) 34 | g.GET("/pprof/symbol", symbolHandler) 35 | g.POST("/pprof/symbol", symbolHandler) 36 | g.GET("/pprof/trace", traceHandler) 37 | }) 38 | return 39 | } 40 | 41 | func ListenAndServe(addr ...string) error { 42 | a := "localhost:8082" 43 | if len(addr) > 0 { 44 | a = addr[0] 45 | } 46 | return http.ListenAndServe(a, nil) 47 | } 48 | -------------------------------------------------------------------------------- /zlog/color_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package zlog 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | var ( 11 | winEnable bool 12 | procSetConsoleMode *syscall.LazyProc 13 | ) 14 | 15 | func init() { 16 | if supportColor || isMsystem { 17 | return 18 | } 19 | kernel32 := syscall.NewLazyDLL("kernel32.dll") 20 | procSetConsoleMode = kernel32.NewProc("SetConsoleMode") 21 | 22 | winEnable = tryApplyOnCONOUT() 23 | if !winEnable { 24 | winEnable = tryApplyStdout() 25 | } 26 | } 27 | 28 | func tryApplyOnCONOUT() bool { 29 | outHandle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0) 30 | if err != nil { 31 | return false 32 | } 33 | 34 | err = EnableTerminalProcessing(outHandle, true) 35 | if err != nil { 36 | return false 37 | } 38 | 39 | return true 40 | } 41 | 42 | func tryApplyStdout() bool { 43 | err := EnableTerminalProcessing(syscall.Stdout, true) 44 | if err != nil { 45 | return false 46 | } 47 | 48 | return true 49 | } 50 | 51 | func EnableTerminalProcessing(stream syscall.Handle, enable bool) error { 52 | var mode uint32 53 | err := syscall.GetConsoleMode(stream, &mode) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | if enable { 59 | mode |= 0x4 60 | } else { 61 | mode &^= 0x4 62 | } 63 | 64 | ret, _, err := procSetConsoleMode.Call(uintptr(stream), uintptr(mode)) 65 | if ret == 0 { 66 | return err 67 | } 68 | 69 | return nil 70 | } 71 | 72 | // IsSupportColor IsSupportColor 73 | func IsSupportColor() bool { 74 | return supportColor || winEnable 75 | } 76 | -------------------------------------------------------------------------------- /zsync/seqlock.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zsync 5 | 6 | import ( 7 | "runtime" 8 | "sync/atomic" 9 | "unsafe" 10 | ) 11 | 12 | // SeqLockT is a typed sequence lock that avoids interface conversions on the hot path. 13 | // It provides the same semantics as the untyped SeqLock but returns/accepts T directly. 14 | type SeqLock[T any] struct { 15 | ptr unsafe.Pointer 16 | seq uint64 17 | _pad [56]byte 18 | _pad2 [56]byte 19 | } 20 | 21 | // NewSeqLock creates a typed sequence lock. 22 | func NewSeqLock[T any]() *SeqLock[T] { return &SeqLock[T]{} } 23 | 24 | // Write publishes a new value with seqlock semantics. 25 | func (s *SeqLock[T]) Write(v T) { 26 | atomic.AddUint64(&s.seq, 1) 27 | nv := new(T) 28 | *nv = v 29 | atomic.StorePointer(&s.ptr, unsafe.Pointer(nv)) 30 | atomic.AddUint64(&s.seq, 1) 31 | } 32 | 33 | // Read returns a consistent snapshot if the sequence was stable. 34 | // It may spin briefly under write contention. 35 | func (s *SeqLock[T]) Read() (T, bool) { 36 | var zero T 37 | for spin := 0; ; spin++ { 38 | seq1 := atomic.LoadUint64(&s.seq) 39 | if seq1&1 != 0 { // writer active 40 | if spin&15 == 15 { 41 | runtime.Gosched() 42 | } 43 | continue 44 | } 45 | p := atomic.LoadPointer(&s.ptr) 46 | if p == nil { 47 | if seq1 == atomic.LoadUint64(&s.seq) { 48 | return zero, false 49 | } 50 | continue 51 | } 52 | v := *(*T)(p) 53 | if seq1 == atomic.LoadUint64(&s.seq) { 54 | return v, true 55 | } 56 | if spin&15 == 15 { 57 | runtime.Gosched() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /zarray/map.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zarray 5 | 6 | import "errors" 7 | 8 | // Keys extracts all keys from a map and returns them as a slice. 9 | // The order of the keys in the resulting slice is not guaranteed. 10 | func Keys[K comparable, V any](in map[K]V) []K { 11 | result := make([]K, 0, len(in)) 12 | 13 | for k := range in { 14 | result = append(result, k) 15 | } 16 | 17 | return result 18 | } 19 | 20 | // Values extracts all values from a map and returns them as a slice. 21 | // The order of the values in the resulting slice is not guaranteed. 22 | func Values[K comparable, V any](in map[K]V) []V { 23 | result := make([]V, 0, len(in)) 24 | 25 | for _, v := range in { 26 | result = append(result, v) 27 | } 28 | 29 | return result 30 | } 31 | 32 | // IndexMap indexes a slice of Maps into a map based on a key function. 33 | func IndexMap[K comparable, V any](arr []V, toKey func(V) (K, V)) (map[K]V, error) { 34 | if len(arr) == 0 { 35 | return make(map[K]V), nil 36 | } 37 | 38 | data := make(map[K]V, len(arr)) 39 | for _, item := range arr { 40 | key, value := toKey(item) 41 | if _, exists := data[key]; exists { 42 | return nil, errors.New("key is not unique") 43 | } 44 | data[key] = value 45 | } 46 | return data, nil 47 | } 48 | 49 | // FlatMap flattens a map of Maps into a single slice of Maps. 50 | func FlatMap[K comparable, V any](m map[K]V, fn func(key K, value V) V) []V { 51 | data := make([]V, 0, len(m)) 52 | for k := range m { 53 | data = append(data, fn(k, m[k])) 54 | } 55 | return data 56 | } 57 | -------------------------------------------------------------------------------- /zhttp/query_render.go: -------------------------------------------------------------------------------- 1 | package zhttp 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | 7 | "golang.org/x/net/html" 8 | ) 9 | 10 | func (e Els) ForEach(f func(index int, el QueryHTML) bool) { 11 | for i, v := range e { 12 | if !f(i, v) { 13 | break 14 | } 15 | } 16 | } 17 | 18 | func (r QueryHTML) String() string { 19 | return r.HTML(true) 20 | } 21 | 22 | func (r QueryHTML) Exist() bool { 23 | return r.node != nil && r.node.Data != "" 24 | } 25 | 26 | func (r QueryHTML) Attr(key string) string { 27 | return r.Attrs()[key] 28 | } 29 | 30 | func (r QueryHTML) Attrs() map[string]string { 31 | node := r.getNode() 32 | if node.Type != html.ElementNode || len(node.Attr) == 0 { 33 | return make(map[string]string) 34 | } 35 | return getAttrValue(node.Attr) 36 | } 37 | 38 | func (r QueryHTML) Name() string { 39 | return r.getNode().Data 40 | } 41 | 42 | func (r QueryHTML) Text(trimSpace ...bool) string { 43 | text := getElText(r, false) 44 | if len(trimSpace) > 0 && trimSpace[0] { 45 | text = strings.TrimSpace(text) 46 | } 47 | return text 48 | } 49 | 50 | func (r QueryHTML) FullText(trimSpace ...bool) string { 51 | text := getElText(r, true) 52 | if len(trimSpace) > 0 && trimSpace[0] { 53 | text = strings.TrimSpace(text) 54 | } 55 | return text 56 | } 57 | 58 | func (r QueryHTML) HTML(trimSpace ...bool) string { 59 | var b bytes.Buffer 60 | if err := html.Render(&b, r.getNode()); err != nil { 61 | return "" 62 | } 63 | text := b.String() 64 | if len(trimSpace) > 0 && trimSpace[0] { 65 | text = strings.TrimSpace(text) 66 | } 67 | return text 68 | } 69 | -------------------------------------------------------------------------------- /znet/cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache_test 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | "time" 8 | 9 | "github.com/sohaha/zlsgo" 10 | "github.com/sohaha/zlsgo/znet" 11 | "github.com/sohaha/zlsgo/znet/cache" 12 | "github.com/sohaha/zlsgo/zstring" 13 | "github.com/sohaha/zlsgo/zsync" 14 | ) 15 | 16 | var ( 17 | r *znet.Engine 18 | ) 19 | 20 | func init() { 21 | r = znet.New() 22 | r.SetMode(znet.ProdMode) 23 | 24 | r.GET("/cache", func(c *znet.Context) { 25 | c.String(200, zstring.Rand(10)) 26 | }, cache.New(func(conf *cache.Config) { 27 | conf.Expiration = 10 * time.Second 28 | conf.Custom = func(c *znet.Context) (key string, expiration time.Duration) { 29 | return cache.QueryKey(c), 0 30 | } 31 | })) 32 | } 33 | 34 | func qvalue() string { 35 | q := map[string]string{ 36 | "b": "2", 37 | "a": "1", 38 | "c": "3", 39 | } 40 | 41 | val := "" 42 | for k, v := range q { 43 | val += k + "=" + v + "&" 44 | } 45 | return val 46 | } 47 | 48 | func TestCache(t *testing.T) { 49 | tt := zlsgo.NewTest(t) 50 | 51 | w := httptest.NewRecorder() 52 | 53 | req, _ := http.NewRequest("GET", "/cache?"+qvalue(), nil) 54 | r.ServeHTTP(w, req) 55 | str := w.Body 56 | 57 | var wg zsync.WaitGroup 58 | for i := 0; i < 5; i++ { 59 | wg.Go(func() { 60 | w := httptest.NewRecorder() 61 | url := "/cache?" + qvalue() 62 | req, _ := http.NewRequest("GET", url, nil) 63 | r.ServeHTTP(w, req) 64 | tt.Equal(200, w.Code) 65 | tt.Equal(10, w.Body.Len()) 66 | tt.Equal(str, w.Body) 67 | }) 68 | } 69 | _ = wg.Wait() 70 | } 71 | -------------------------------------------------------------------------------- /znet/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package znet provides a lightweight and high-performance HTTP web framework. 3 | 4 | It features a flexible router with middleware support, context-based request handling, 5 | and various utilities for building web applications and RESTful APIs. 6 | 7 | Basic Usage: 8 | 9 | package main 10 | 11 | import ( 12 | "github.com/sohaha/zlsgo/znet" 13 | ) 14 | 15 | func main() { 16 | r := znet.New() // Create a new router instance 17 | 18 | r.SetMode(znet.DebugMode) // Enable debug mode for development 19 | 20 | // Define a simple route 21 | r.GET("/", func(c znet.Context) { 22 | c.String(200, "hello world") 23 | }) 24 | 25 | // Define a route with path parameters 26 | r.GET("/users/:id", func(c znet.Context) { 27 | id := c.Param("id") 28 | c.JSON(200, map[string]interface{}{ 29 | "id": id, 30 | "message": "User details", 31 | }) 32 | }) 33 | 34 | // Start the HTTP server 35 | znet.Run() 36 | } 37 | 38 | The framework provides the following key features: 39 | 40 | 1. Routing: Support for RESTful routes with path parameters and wildcards 41 | 2. Middleware: Request processing pipeline with before and after handlers 42 | 3. Context: Request-scoped context with helper methods for request/response handling 43 | 4. Rendering: Support for various response formats (JSON, HTML, etc.) 44 | 5. Validation: Request data validation with customizable rules 45 | 6. Plugins: Extensions for common web features (CORS, GZIP, Rate Limiting, etc.) 46 | */ 47 | package znet 48 | -------------------------------------------------------------------------------- /zcache/cache.go: -------------------------------------------------------------------------------- 1 | // Package zcache provides caching functionality with support for expiration, callbacks, 2 | // and various cache management strategies. It offers both in-memory and persistent 3 | // caching solutions for Go applications. 4 | package zcache 5 | 6 | import ( 7 | "errors" 8 | "sync" 9 | 10 | "github.com/sohaha/zlsgo/zutil" 11 | ) 12 | 13 | var ( 14 | // ErrKeyNotFound is returned when a requested key does not exist in the cache 15 | ErrKeyNotFound = errors.New("key is not in cache") 16 | // ErrKeyNotFoundAndNotCallback is returned when a key is not found and no callback function is provided 17 | ErrKeyNotFoundAndNotCallback = errors.New("key is not in cache and no callback is set") 18 | // Internal cache registry for named cache tables 19 | cache = make(map[string]*Table) 20 | mutex sync.RWMutex 21 | ) 22 | 23 | // New creates or retrieves a named cache table with optional access counting. 24 | // If a table with the specified name already exists, it is returned. 25 | // If accessCount is true, the cache will track the number of times each item is accessed. 26 | // 27 | // Deprecated: please use zcache.NewFast instead 28 | func New(table string, accessCount ...bool) *Table { 29 | mutex.Lock() 30 | t, ok := cache[table] 31 | 32 | if !ok { 33 | t, ok = cache[table] 34 | if !ok { 35 | t = &Table{ 36 | name: table, 37 | items: make(map[string]*Item), 38 | } 39 | t.accessCount = zutil.NewBool(len(accessCount) > 0 && accessCount[0]) 40 | cache[table] = t 41 | } 42 | } 43 | 44 | mutex.Unlock() 45 | return t 46 | } 47 | -------------------------------------------------------------------------------- /zpool/release_test.go: -------------------------------------------------------------------------------- 1 | package zpool 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/zfile" 10 | "github.com/sohaha/zlsgo/zutil" 11 | ) 12 | 13 | func TestPoolRelease(t *testing.T) { 14 | tt := zlsgo.NewTest(t) 15 | 16 | p := New(10) 17 | _ = p.PreInit() 18 | for i := 0; i < 4; i++ { 19 | _ = p.Do(func() { 20 | time.Sleep(time.Second) 21 | }) 22 | } 23 | tt.Equal(uint(10), p.Cap()) 24 | timer, mem := zutil.WithRunContext(func() { 25 | p.Close() 26 | }) 27 | 28 | tt.EqualTrue(timer >= time.Second-100*time.Millisecond) 29 | t.Log(timer.String(), zfile.SizeFormat(mem)) 30 | tt.Equal(uint(0), p.Cap()) 31 | } 32 | 33 | func TestPoolAutoRelease(t *testing.T) { 34 | tt := zlsgo.NewTest(t) 35 | 36 | var g sync.WaitGroup 37 | p := New(10) 38 | p.releaseTime = time.Second / 2 39 | _ = p.PreInit() 40 | 41 | for i := 0; i < 4; i++ { 42 | g.Add(1) 43 | _ = p.Do(func() { 44 | g.Done() 45 | }) 46 | } 47 | g.Wait() 48 | tt.Equal(uint(10), p.Cap()) 49 | time.Sleep(time.Second) 50 | 51 | tt.EqualTrue(p.Cap() <= uint(1)) 52 | 53 | for i := 0; i < 6; i++ { 54 | g.Add(1) 55 | _ = p.Do(func() { 56 | time.Sleep(time.Second) 57 | tt.Equal(uint(6), p.Cap()) 58 | g.Done() 59 | }) 60 | } 61 | g.Wait() 62 | tt.Equal(uint(6), p.Cap()) 63 | 64 | time.Sleep(time.Second) 65 | 66 | for i := 0; i < 6; i++ { 67 | g.Add(1) 68 | _ = p.Do(func() { 69 | g.Done() 70 | }) 71 | } 72 | g.Wait() 73 | tt.EqualTrue(p.Cap() >= uint(1)) 74 | time.Sleep(time.Second) 75 | tt.EqualTrue(p.Cap() <= uint(1)) 76 | } 77 | -------------------------------------------------------------------------------- /zsync/content_test.go: -------------------------------------------------------------------------------- 1 | package zsync 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/sohaha/zlsgo" 10 | ) 11 | 12 | func TestMergeContext(t *testing.T) { 13 | tt := zlsgo.NewTest(t) 14 | 15 | { 16 | ctx1 := context.Background() 17 | ctx2 := context.WithValue(context.Background(), "key", "value2") 18 | ctx3, cancel3 := context.WithCancel(context.Background()) 19 | 20 | ctx := MergeContext(ctx1, ctx2, ctx3) 21 | tt.Equal(reflect.TypeOf(ctx), reflect.TypeOf(&mergeContext{})) 22 | tt.Equal("value2", ctx.Value("key")) 23 | 24 | now := time.Now() 25 | go func() { 26 | time.Sleep(time.Second / 5) 27 | cancel3() 28 | }() 29 | 30 | <-ctx.Done() 31 | 32 | tt.EqualTrue(time.Since(now).Seconds() > 0.2) 33 | } 34 | 35 | { 36 | ctx1 := context.Background() 37 | ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second/5) 38 | defer cancel2() 39 | ctx := MergeContext(ctx1, ctx2) 40 | now := time.Now() 41 | select { 42 | case <-ctx.Done(): 43 | tt.EqualTrue(ctx.Err() != nil) 44 | } 45 | tt.EqualTrue(time.Since(now).Seconds() > 0.2) 46 | } 47 | 48 | { 49 | ctx1 := context.Background() 50 | ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second/5) 51 | ctx3, cancel3 := context.WithTimeout(context.Background(), time.Second/10) 52 | defer cancel2() 53 | defer cancel3() 54 | ctx := MergeContext(ctx1, ctx2, ctx3) 55 | now := time.Now() 56 | select { 57 | case <-ctx.Done(): 58 | tt.EqualTrue(ctx.Err() != nil) 59 | } 60 | tt.EqualTrue(time.Since(now).Seconds() > 0.1) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /znet/realip/cloudflare.go: -------------------------------------------------------------------------------- 1 | package realip 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "strings" 7 | "time" 8 | 9 | "github.com/sohaha/zlsgo/zhttp" 10 | "github.com/sohaha/zlsgo/zsync" 11 | ) 12 | 13 | func GetCloudflare(timeout ...time.Duration) []string { 14 | const ( 15 | cfIPv4CIDRsEndpoint = "https://www.cloudflare.com/ips-v4" 16 | cfIPv6CIDRsEndpoint = "https://www.cloudflare.com/ips-v6" 17 | ) 18 | 19 | var ( 20 | t time.Duration 21 | wg zsync.WaitGroup 22 | mu = zsync.NewRBMutex() 23 | cfCIDRs = make([]string, 0) 24 | ) 25 | if len(timeout) > 0 { 26 | t = timeout[0] 27 | } else { 28 | t = time.Second * 5 29 | } 30 | 31 | ctx, cancel := context.WithTimeout(context.Background(), t) 32 | defer cancel() 33 | 34 | for _, v := range []string{cfIPv4CIDRsEndpoint, cfIPv6CIDRsEndpoint} { 35 | endpoint := v 36 | wg.Go(func() { 37 | resp, err := zhttp.Get(endpoint, ctx) 38 | if err == nil && resp.StatusCode() == http.StatusOK { 39 | mu.Lock() 40 | cfCIDRs = append(cfCIDRs, strings.Split(resp.String(), "\n")...) 41 | mu.Unlock() 42 | } 43 | }) 44 | } 45 | 46 | wg.Wait() 47 | 48 | if len(cfCIDRs) == 0 { 49 | cfCIDRs = []string{"173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22", "103.31.4.0/22", "141.101.64.0/18", "108.162.192.0/18", "190.93.240.0/20", "188.114.96.0/20", "197.234.240.0/22", "198.41.128.0/17", "162.158.0.0/15", "104.16.0.0/13", "104.24.0.0/14", "172.64.0.0/13", "131.0.72.0/22", "2400:cb00::/32", "2606:4700::/32", "2803:f800::/32", "2405:b500::/32", "2405:8100::/32", "2a06:98c0::/29", "2c0f:f248::/32"} 50 | } 51 | 52 | return cfCIDRs 53 | } 54 | -------------------------------------------------------------------------------- /zreflect/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package zreflect provides enhanced reflection capabilities for Go programs. 3 | 4 | This package extends Go's standard reflect package with additional utilities 5 | and safer access to reflection features. It includes tools for working with 6 | unexported fields, iterating through struct fields, and simplifying common 7 | reflection operations. 8 | 9 | Key features: 10 | 11 | - Access to unexported struct fields (with caution) 12 | - Simplified iteration over struct fields and methods 13 | - Enhanced type and value handling 14 | - Utilities for working with struct tags 15 | - Performance optimizations for reflection operations 16 | 17 | Example usage for iterating through struct fields: 18 | 19 | type Person struct { 20 | Name string `json:"name"` 21 | Age int `json:"age"` 22 | Address struct { 23 | City string `json:"city"` 24 | State string `json:"state"` 25 | } `json:"address"` 26 | } 27 | 28 | person := Person{Name: "John", Age: 30} 29 | 30 | // Iterate through all fields in the struct 31 | err := zreflect.ForEachValue(reflect.ValueOf(person), func(parent []string, index int, tag string, field reflect.StructField, val reflect.Value) error { 32 | fmt.Printf("Field: %s, Tag: %s, Value: %v\n", field.Name, tag, val.Interface()) 33 | return nil 34 | }) 35 | 36 | // Access unexported fields (use with caution) 37 | val, err := zreflect.GetUnexportedField(reflect.ValueOf(someStruct), "privateField") 38 | 39 | // Set unexported fields (use with caution) 40 | err = zreflect.SetUnexportedField(reflect.ValueOf(someStruct), "privateField", newValue) 41 | */ 42 | package zreflect 43 | -------------------------------------------------------------------------------- /zcache/simple.go: -------------------------------------------------------------------------------- 1 | package zcache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/sohaha/zlsgo/ztype" 7 | ) 8 | 9 | // simple is a global cache instance for convenient access to caching functionality 10 | var simple = NewFast() 11 | 12 | // Set adds or updates an item in the global cache with the specified key, value, and optional expiration. 13 | // If no expiration is provided, the item will not expire. 14 | func Set(key string, val interface{}, expiration ...time.Duration) { 15 | simple.Set(key, val, expiration...) 16 | } 17 | 18 | // Delete removes an item with the specified key from the global cache. 19 | func Delete(key string) { 20 | simple.Delete(key) 21 | } 22 | 23 | // Get retrieves an item from the global cache by its key. 24 | // Returns the item's value and a boolean indicating whether the item was found. 25 | func Get(key string) (interface{}, bool) { 26 | return simple.Get(key) 27 | } 28 | 29 | // GetAny retrieves an item from the global cache and wraps it in a ztype.Type for type conversion. 30 | // Returns the wrapped value and a boolean indicating whether the item was found. 31 | func GetAny(key string) (ztype.Type, bool) { 32 | return simple.GetAny(key) 33 | } 34 | 35 | // ProvideGet retrieves an item from the global cache, or computes and stores it if not present. 36 | // If the item doesn't exist, the provide function is called to generate the value. 37 | // Returns the item's value and a boolean indicating whether the item was found or created. 38 | func ProvideGet(key string, provide func() (interface{}, bool), expiration ...time.Duration) (interface{}, bool) { 39 | return simple.ProvideGet(key, provide, expiration...) 40 | } 41 | -------------------------------------------------------------------------------- /zstring/encoding_test.go: -------------------------------------------------------------------------------- 1 | package zstring_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | "github.com/sohaha/zlsgo/zfile" 8 | "github.com/sohaha/zlsgo/zhttp" 9 | "github.com/sohaha/zlsgo/zstring" 10 | ) 11 | 12 | func TestBase64(t *testing.T) { 13 | tt := zlsgo.NewTest(t) 14 | str := "hi,是我" 15 | strbyte := []byte(str) 16 | s := zstring.Base64Encode(strbyte) 17 | deByte, err := zstring.Base64Decode(s) 18 | tt.EqualNil(err) 19 | tt.Equal(strbyte, deByte) 20 | 21 | s2 := zstring.Base64EncodeString(str) 22 | tt.Equal(zstring.Bytes2String(s), s2) 23 | 24 | de, err := zstring.Base64DecodeString(s2) 25 | tt.EqualNil(err) 26 | tt.Equal(str, de) 27 | 28 | de, _ = zstring.Base64DecodeString(string(s)) 29 | tt.Equal(str, de) 30 | 31 | } 32 | 33 | type testSt struct { 34 | Name string 35 | } 36 | 37 | func TestSerialize(t *testing.T) { 38 | tt := zlsgo.NewTest(t) 39 | test := &testSt{"hi"} 40 | 41 | s, err := zstring.Serialize(test) 42 | tt.EqualNil(err) 43 | 44 | v, err := zstring.UnSerialize(s, &testSt{}) 45 | tt.EqualNil(err) 46 | 47 | test2, ok := v.(*testSt) 48 | tt.EqualTrue(ok) 49 | tt.Equal(test.Name, test2.Name) 50 | } 51 | 52 | func TestImg2Base64(t *testing.T) { 53 | tt := zlsgo.NewTest(t) 54 | res, err := zhttp.Get("https://seekwe.73zls.com/signed/https:%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa4bcc6b2-32ef-4a7d-ba1c-65a0330f632d%2Flogo.png") 55 | if err == nil { 56 | file := "tmp/logo.png" 57 | err = res.ToFile(file) 58 | if err == nil { 59 | s, err := zstring.Img2Base64(file) 60 | tt.EqualNil(err) 61 | t.Log(s) 62 | zfile.Rmdir("tmp") 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /zutil/chan_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zutil_test 5 | 6 | import ( 7 | "testing" 8 | "time" 9 | 10 | "github.com/sohaha/zlsgo" 11 | "github.com/sohaha/zlsgo/zsync" 12 | "github.com/sohaha/zlsgo/zutil" 13 | ) 14 | 15 | func TestNewChanUnbounded(t *testing.T) { 16 | tt := zlsgo.NewTest(t) 17 | ch := zutil.NewChan[any]() 18 | 19 | var wg zsync.WaitGroup 20 | 21 | for i := 0; i < 10; i++ { 22 | i := i 23 | wg.Go(func() { 24 | ch.In() <- i 25 | }) 26 | } 27 | 28 | go func() { 29 | _ = wg.Wait() 30 | ch.Close() 31 | }() 32 | 33 | time.Sleep(time.Second / 4) 34 | tt.EqualTrue(ch.Len() >= 10) 35 | 36 | for v := range ch.Out() { 37 | t.Log(v, ch.Len()) 38 | } 39 | } 40 | 41 | func TestNewChanUnbuffered(t *testing.T) { 42 | tt := zlsgo.NewTest(t) 43 | ch := zutil.NewChan[any](0) 44 | 45 | var wg zsync.WaitGroup 46 | 47 | for i := 0; i < 10; i++ { 48 | i := i 49 | wg.Go(func() { 50 | ch.In() <- i 51 | }) 52 | } 53 | 54 | go func() { 55 | _ = wg.Wait() 56 | ch.Close() 57 | }() 58 | 59 | time.Sleep(time.Second / 4) 60 | tt.Equal(0, ch.Len()) 61 | 62 | for v := range ch.Out() { 63 | t.Log(v, ch.Len()) 64 | } 65 | } 66 | 67 | func TestNewChanBuffered(t *testing.T) { 68 | tt := zlsgo.NewTest(t) 69 | ch := zutil.NewChan[any](3) 70 | 71 | var wg zsync.WaitGroup 72 | 73 | for i := 0; i < 10; i++ { 74 | i := i 75 | wg.Go(func() { 76 | ch.In() <- i 77 | }) 78 | } 79 | 80 | go func() { 81 | _ = wg.Wait() 82 | ch.Close() 83 | }() 84 | 85 | time.Sleep(time.Second / 4) 86 | tt.EqualTrue(ch.Len() >= 3) 87 | 88 | for v := range ch.Out() { 89 | t.Log(v, ch.Len()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /znet/auth/auth_test.go: -------------------------------------------------------------------------------- 1 | package auth_test 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/sohaha/zlsgo" 10 | "github.com/sohaha/zlsgo/znet" 11 | "github.com/sohaha/zlsgo/znet/auth" 12 | "github.com/sohaha/zlsgo/zutil" 13 | ) 14 | 15 | var authHandler = auth.New(auth.Accounts{ 16 | "admin": "123", 17 | "admin2": "456", 18 | }) 19 | 20 | func TestNew(t *testing.T) { 21 | tt := zlsgo.NewTest(t) 22 | r := server().(*znet.Engine) 23 | 24 | w1 := newRequest(r, "/auth1", nil) 25 | tt.Equal(http.StatusUnauthorized, w1.Code) 26 | 27 | w2 := newRequest(r, "/auth2", []string{"admin", "123"}) 28 | tt.Equal(http.StatusOK, w2.Code) 29 | tt.Equal("admin", w2.Body.String()) 30 | 31 | w3 := newRequest(r, "/auth3", []string{"admin2", "456"}) 32 | tt.Equal(http.StatusOK, w3.Code) 33 | tt.Equal("admin2", w3.Body.String()) 34 | 35 | w4 := newRequest(r, "/auth4", []string{"admin3", "456"}) 36 | tt.Equal(http.StatusUnauthorized, w4.Code) 37 | tt.Equal("", w4.Body.String()) 38 | 39 | } 40 | 41 | var server = zutil.Once(func() interface{} { 42 | r := znet.New() 43 | // r.SetMode(znet.DebugMode) 44 | return r 45 | }) 46 | 47 | func newRequest(r *znet.Engine, path string, account []string) *httptest.ResponseRecorder { 48 | r.GET(path, func(c *znet.Context) { 49 | c.String(200, c.MustValue(auth.UserKey, "").(string)) 50 | }, authHandler) 51 | w := httptest.NewRecorder() 52 | req, _ := http.NewRequest("GET", path, nil) 53 | if len(account) == 2 { 54 | req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(account[0]+":"+account[1]))) 55 | } 56 | r.ServeHTTP(w, req) 57 | return w 58 | } 59 | -------------------------------------------------------------------------------- /zarray/string.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zarray 5 | 6 | import ( 7 | "strings" 8 | 9 | "github.com/sohaha/zlsgo/zstring" 10 | "github.com/sohaha/zlsgo/ztype" 11 | ) 12 | 13 | // Slice splits a string by the specified separator and converts each part to type T. 14 | // Empty parts after trimming whitespace are excluded from the result. 15 | // If n is provided, the string will be split into at most n parts. 16 | // Returns an empty slice if the input string is empty. 17 | func Slice[T comparable](s, sep string, n ...int) []T { 18 | if s == "" { 19 | return []T{} 20 | } 21 | 22 | var ss []string 23 | if len(n) > 0 { 24 | ss = strings.SplitN(s, sep, n[0]) 25 | } else { 26 | ss = strings.Split(s, sep) 27 | } 28 | res := make([]T, len(ss)) 29 | ni := make([]uint32, 0, len(ss)) 30 | for i := range ss { 31 | if v := strings.TrimSpace(ss[i]); v != "" { 32 | ztype.To(v, &res[i]) 33 | } else { 34 | ni = append(ni, uint32(i)) 35 | } 36 | } 37 | 38 | for i := range ni { 39 | res = append(res[:ni[i]], res[ni[i]+1:]...) 40 | } 41 | return res 42 | } 43 | 44 | // Join concatenates the elements of a slice into a single string with the specified separator. 45 | // Empty string elements are excluded from the result. 46 | // Returns an empty string if the input slice is empty. 47 | func Join[T comparable](s []T, sep string) string { 48 | if len(s) == 0 { 49 | return "" 50 | } 51 | 52 | b := zstring.Buffer(len(s)) 53 | for i := 0; i < len(s); i++ { 54 | v := ztype.ToString(s[i]) 55 | if v == "" { 56 | continue 57 | } 58 | b.WriteString(v) 59 | if i < len(s)-1 { 60 | b.WriteString(sep) 61 | } 62 | } 63 | 64 | return b.String() 65 | } 66 | -------------------------------------------------------------------------------- /zutil/retry_test.go: -------------------------------------------------------------------------------- 1 | package zutil 2 | 3 | import ( 4 | "errors" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | 9 | "github.com/sohaha/zlsgo" 10 | ) 11 | 12 | func TestRetry(tt *testing.T) { 13 | t := zlsgo.NewTest(tt) 14 | 15 | t.Run("Success", func(t *zlsgo.TestUtil) { 16 | i := 0 17 | now := time.Now() 18 | err := DoRetry(5, func() error { 19 | if i < 3 { 20 | i++ 21 | return errors.New("error") 22 | } 23 | return nil 24 | }, func(rc *RetryConf) { 25 | rc.Interval = time.Second / 5 26 | }) 27 | t.NoError(err) 28 | t.EqualTrue(time.Since(now).Seconds() < 1) 29 | t.Equal(3, i) 30 | }) 31 | 32 | t.Run("Success BackOffDelay", func(t *zlsgo.TestUtil) { 33 | rand.Seed(12345) 34 | i := 0 35 | now := time.Now() 36 | err := DoRetry(5, func() error { 37 | t.Log(i, time.Since(now).Seconds()) 38 | if i < 3 { 39 | i++ 40 | return errors.New("error") 41 | } 42 | return nil 43 | }, func(rc *RetryConf) { 44 | rc.BackOffDelay = true 45 | rc.Interval = time.Second / 5 46 | }) 47 | t.NoError(err) 48 | t.EqualTrue(time.Since(now).Seconds() < 4) 49 | t.EqualTrue(time.Since(now).Seconds() > 1.0) 50 | t.Equal(3, i) 51 | }) 52 | 53 | t.Run("Failed", func(t *zlsgo.TestUtil) { 54 | i := 0 55 | now := time.Now() 56 | err := DoRetry(5, func() error { 57 | i++ 58 | return errors.New("error") 59 | }, func(rc *RetryConf) { 60 | rc.Interval = time.Second / 5 61 | }) 62 | t.EqualTrue(err != nil) 63 | t.EqualTrue(time.Since(now).Seconds() > 1) 64 | t.Equal(6, i) 65 | }) 66 | } 67 | 68 | func Test_backOffDelay(t *testing.T) { 69 | for i := 0; i < 10; i++ { 70 | t.Log(BackOffDelay(i, time.Second, time.Minute)) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /znet/limiter/queue.go: -------------------------------------------------------------------------------- 1 | package limiter 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type circleQueue struct { 10 | slice []int64 11 | maxSize int 12 | head int 13 | tail int 14 | sync.RWMutex 15 | } 16 | 17 | // newCircleQueue Initialize ring queue 18 | func newCircleQueue(size int) *circleQueue { 19 | var c circleQueue 20 | c.maxSize = size + 1 21 | c.slice = make([]int64, c.maxSize) 22 | return &c 23 | } 24 | 25 | func (c *circleQueue) push(val int64) (err error) { 26 | if c.isFull() { 27 | return errors.New("queue is full") 28 | } 29 | c.slice[c.tail] = val 30 | c.tail = (c.tail + 1) % c.maxSize 31 | return 32 | } 33 | 34 | func (c *circleQueue) pop() (val int64, err error) { 35 | if c.isEmpty() { 36 | return 0, errors.New("queue is empty") 37 | } 38 | c.Lock() 39 | defer c.Unlock() 40 | val = c.slice[c.head] 41 | c.head = (c.head + 1) % c.maxSize 42 | return 43 | } 44 | 45 | func (c *circleQueue) isFull() bool { 46 | return (c.tail+1)%c.maxSize == c.head 47 | } 48 | 49 | func (c *circleQueue) isEmpty() bool { 50 | return c.tail == c.head 51 | } 52 | 53 | func (c *circleQueue) usedSize() int { 54 | c.RLock() 55 | defer c.RUnlock() 56 | return (c.tail + c.maxSize - c.head) % c.maxSize 57 | } 58 | 59 | func (c *circleQueue) unUsedSize() int { 60 | return c.maxSize - 1 - c.usedSize() 61 | } 62 | 63 | func (c *circleQueue) size() int { 64 | return c.maxSize - 1 65 | } 66 | 67 | func (c *circleQueue) deleteExpired() { 68 | now := time.Now().UnixNano() 69 | size := c.usedSize() 70 | if size == 0 { 71 | return 72 | } 73 | for i := 0; i < size; i++ { 74 | if now > c.slice[c.head] { 75 | _, _ = c.pop() 76 | } else { 77 | return 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /zstring/template_test.go: -------------------------------------------------------------------------------- 1 | package zstring_test 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "testing" 7 | 8 | zls "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/zstring" 10 | "github.com/sohaha/zlsgo/zutil" 11 | ) 12 | 13 | func TestNewTemplate(t *testing.T) { 14 | tt := zls.NewTest(t) 15 | 16 | tmpl, err := zstring.NewTemplate("hello {name}", "{", "}") 17 | tt.NoError(err) 18 | 19 | w := zutil.GetBuff() 20 | defer zutil.PutBuff(w) 21 | 22 | _, err = tmpl.Process(w, func(w io.Writer, tag string) (int, error) { 23 | return w.Write([]byte("Go")) 24 | }) 25 | tt.NoError(err) 26 | tt.Equal("hello Go", w.String()) 27 | 28 | err = tmpl.ResetTemplate("The best {n} {say}") 29 | tt.NoError(err) 30 | 31 | w.Reset() 32 | _, err = tmpl.Process(w, func(w io.Writer, tag string) (int, error) { 33 | switch tag { 34 | case "say": 35 | return w.Write([]byte("!!!")) 36 | } 37 | return w.Write([]byte("Go")) 38 | }) 39 | tt.NoError(err) 40 | tt.Equal("The best Go !!!", w.String()) 41 | } 42 | 43 | func BenchmarkTemplate(b *testing.B) { 44 | tmpl, _ := zstring.NewTemplate("hello {name}", "{", "}") 45 | 46 | b.Run("Buffer", func(b *testing.B) { 47 | b.RunParallel(func(pb *testing.PB) { 48 | for pb.Next() { 49 | var w strings.Builder 50 | _, _ = tmpl.Process(&w, func(w io.Writer, tag string) (int, error) { 51 | return w.Write([]byte("Go")) 52 | }) 53 | } 54 | }) 55 | }) 56 | 57 | b.Run("GetBuff", func(b *testing.B) { 58 | b.RunParallel(func(pb *testing.PB) { 59 | for pb.Next() { 60 | w := zutil.GetBuff() 61 | _, _ = tmpl.Process(w, func(w io.Writer, tag string) (int, error) { 62 | return w.Write([]byte("Go")) 63 | }) 64 | zutil.PutBuff(w) 65 | } 66 | }) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /zutil/env_test.go: -------------------------------------------------------------------------------- 1 | package zutil_test 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/zfile" 10 | "github.com/sohaha/zlsgo/zutil" 11 | ) 12 | 13 | func TestOs(T *testing.T) { 14 | t := zlsgo.NewTest(T) 15 | 16 | osName := runtime.GOOS 17 | t.Log(osName) 18 | isWin := zutil.IsWin() 19 | t.Log("isWin", isWin) 20 | isLinux := zutil.IsLinux() 21 | t.Log("isLinux", isLinux) 22 | isMac := zutil.IsMac() 23 | t.Log("isMac", isMac) 24 | 25 | is32bit := zutil.Is32BitArch() 26 | t.Log(is32bit) 27 | } 28 | 29 | func TestEnv(T *testing.T) { 30 | t := zlsgo.NewTest(T) 31 | t.Log(zutil.Getenv("HOME")) 32 | t.Log(zutil.Getenv("myos")) 33 | t.Log(zutil.Getenv("我不存在", "66")) 34 | _ = os.Setenv("TEST_EMPTY_VAR", "") 35 | defer os.Unsetenv("TEST_EMPTY_VAR") 36 | t.Equal("", zutil.Getenv("TEST_EMPTY_VAR", "default")) 37 | t.Equal("default", zutil.Getenv("NON_EXISTENT_VAR", "default")) 38 | _ = os.Setenv("TEST_SET_VAR", "actual_value") 39 | defer os.Unsetenv("TEST_SET_VAR") 40 | t.Equal("actual_value", zutil.Getenv("TEST_SET_VAR", "default")) 41 | } 42 | 43 | func TestGOROOT(t *testing.T) { 44 | t.Log(zutil.GOROOT()) 45 | } 46 | 47 | func TestLoadenv(t *testing.T) { 48 | tt := zlsgo.NewTest(t) 49 | _ = zfile.WriteFile(".env", []byte("myos=linux\n name=zls \n\n time=\"2024-11-14 23:59:01\" \n#comment='comment'\n description=\"hello world\"")) 50 | defer zfile.Rmdir(".env") 51 | 52 | tt.NoError(zutil.Loadenv()) 53 | 54 | tt.Equal("linux", zutil.Getenv("myos")) 55 | tt.Equal("zls", zutil.Getenv("name")) 56 | tt.Equal("2024-11-14 23:59:01", zutil.Getenv("time")) 57 | tt.Equal("", zutil.Getenv("comment")) 58 | tt.Equal("hello world", zutil.Getenv("description")) 59 | } 60 | -------------------------------------------------------------------------------- /zerror/tag.go: -------------------------------------------------------------------------------- 1 | package zerror 2 | 3 | type TagKind string 4 | 5 | const ( 6 | // Empty error 7 | None TagKind = "" 8 | // Internal errors, This means that some invariants expected by the underlying system have been broken 9 | Internal TagKind = "INTERNAL" 10 | // The operation was cancelled, typically by the caller 11 | Cancelled TagKind = "CANCELLED" 12 | // The client specified an invalid argument 13 | InvalidInput TagKind = "INVALID_INPUT" 14 | // Some requested entity was not found 15 | NotFound TagKind = "NOT_FOUND" 16 | // The caller does not have permission to execute the specified operation 17 | PermissionDenied TagKind = "PERMISSION_DENIED" 18 | // The request does not have valid authentication credentials for the operation 19 | Unauthorized TagKind = "UNAUTHORIZED" 20 | ) 21 | 22 | func (t TagKind) Wrap(err error, text string) error { 23 | return With(err, text, WrapTag(t)) 24 | } 25 | func (t TagKind) Text(text string) error { 26 | return &withTag{ 27 | wrapErr: &Error{errText: &text}, 28 | tag: t, 29 | } 30 | } 31 | 32 | type withTag struct { 33 | wrapErr error 34 | tag TagKind 35 | } 36 | 37 | func (e *withTag) Error() string { 38 | return e.wrapErr.Error() 39 | } 40 | 41 | func WrapTag(tag TagKind) External { 42 | return func(err error) error { 43 | return &withTag{ 44 | wrapErr: err, 45 | tag: tag, 46 | } 47 | } 48 | } 49 | 50 | func GetTag(err error) TagKind { 51 | if err == nil { 52 | return None 53 | } 54 | 55 | for err != nil { 56 | if f, ok := err.(*withTag); ok { 57 | return f.tag 58 | } 59 | 60 | if e, ok := err.(*Error); ok { 61 | err = e.wrapErr 62 | if err == nil { 63 | err = e.err 64 | } 65 | } else { 66 | break 67 | } 68 | } 69 | 70 | return None 71 | } 72 | -------------------------------------------------------------------------------- /znet/limiter/limiter.go: -------------------------------------------------------------------------------- 1 | package limiter 2 | 3 | import ( 4 | "net/http" 5 | "sort" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo/znet" 9 | ) 10 | 11 | // Rule user access control strategy 12 | type Rule struct { 13 | rules []*singleRule 14 | } 15 | 16 | // New Newlimiter 17 | func New(allowed uint64, overflow ...func(c *znet.Context)) znet.HandlerFunc { 18 | r := NewRule() 19 | f := func(c *znet.Context) { 20 | c.String(http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests)) 21 | } 22 | if len(overflow) > 0 { 23 | f = overflow[0] 24 | } 25 | r.AddRule(time.Second, int(allowed)) 26 | return func(c *znet.Context) { 27 | if !r.AllowVisitByIP(c.GetClientIP()) { 28 | f(c) 29 | c.Abort() 30 | return 31 | } 32 | c.Next() 33 | } 34 | } 35 | 36 | // NewRule Custom limiter rule 37 | func NewRule() *Rule { 38 | return &Rule{} 39 | } 40 | 41 | // AddRule increase user access control strategy 42 | // If less than 1s, please use golang.org/x/time/rate 43 | func (r *Rule) AddRule(exp time.Duration, allowed int, estimated ...int) { 44 | r.rules = append(r.rules, newRule(exp, allowed, estimated...)) 45 | sort.Slice(r.rules, func(i int, j int) bool { 46 | return r.rules[i].defaultExpiration < r.rules[j].defaultExpiration 47 | }) 48 | } 49 | 50 | // AllowVisit Is access allowed 51 | func (r *Rule) AllowVisit(keys ...interface{}) bool { 52 | if len(r.rules) == 0 { 53 | return true 54 | } 55 | for i := range r.rules { 56 | for _, key := range keys { 57 | if !r.rules[i].allowVisit(key) { 58 | return false 59 | } 60 | } 61 | } 62 | return true 63 | } 64 | 65 | // AllowVisitByIP AllowVisit IP 66 | func (r *Rule) AllowVisitByIP(ip string) bool { 67 | i, err := znet.IPToLong(ip) 68 | if err == nil { 69 | return r.AllowVisit(i) 70 | } 71 | 72 | return r.AllowVisit(ip) 73 | } 74 | -------------------------------------------------------------------------------- /zsync/mutex_test.go: -------------------------------------------------------------------------------- 1 | package zsync 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/sohaha/zlsgo" 9 | ) 10 | 11 | func TestRBMutex(t *testing.T) { 12 | tt := zlsgo.NewTest(t) 13 | 14 | var ( 15 | wg WaitGroup 16 | total = 100 17 | counter int 18 | mu = NewRBMutex() 19 | ) 20 | 21 | for i := 0; i < total; i++ { 22 | wg.Go(func() { 23 | mu.Lock() 24 | counter++ 25 | mu.Unlock() 26 | }) 27 | } 28 | wg.Wait() 29 | tt.Equal(total, counter) 30 | 31 | counter = 42 32 | readValues := make([]int, total) 33 | for i := 0; i < total; i++ { 34 | ii := i 35 | wg.Go(func() { 36 | token := mu.RLock() 37 | readValues[ii] = counter 38 | mu.RUnlock(token) 39 | }) 40 | } 41 | wg.Wait() 42 | 43 | for i := 0; i < total; i++ { 44 | tt.Equal(42, readValues[i]) 45 | } 46 | } 47 | 48 | func BenchmarkRBMutexReadOnceAfterWrite(b *testing.B) { 49 | benchmarkReadOnceAfterWrite(b, func() func() { 50 | mu := NewRBMutex() 51 | shared := 0 52 | 53 | mu.Lock() 54 | shared = 42 55 | mu.Unlock() 56 | 57 | return func() { 58 | token := mu.RLock() 59 | _ = shared 60 | mu.RUnlock(token) 61 | } 62 | }) 63 | } 64 | 65 | func BenchmarkRWMutexReadOnceAfterWrite(b *testing.B) { 66 | benchmarkReadOnceAfterWrite(b, func() func() { 67 | var mu sync.RWMutex 68 | shared := 0 69 | 70 | mu.Lock() 71 | shared = 42 72 | mu.Unlock() 73 | 74 | return func() { 75 | mu.RLock() 76 | _ = shared 77 | mu.RUnlock() 78 | } 79 | }) 80 | } 81 | 82 | func benchmarkReadOnceAfterWrite(b *testing.B, setup func() func()) { 83 | readFn := setup() 84 | 85 | b.ReportAllocs() 86 | b.SetParallelism(runtime.GOMAXPROCS(0)) 87 | b.ResetTimer() 88 | 89 | b.RunParallel(func(pb *testing.PB) { 90 | for pb.Next() { 91 | readFn() 92 | } 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /zreflect/value.go: -------------------------------------------------------------------------------- 1 | package zreflect 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | // ValueOf returns a reflect.Value for the specified interface{}. 9 | // This is similar to reflect.ValueOf but with additional handling for nil values. 10 | func ValueOf(v interface{}) reflect.Value { 11 | if v == nil { 12 | return reflect.Value{} 13 | } 14 | valueLayout := (*Value)(unsafe.Pointer(&v)) 15 | value := Value{} 16 | value.typ = valueLayout.typ 17 | value.ptr = valueLayout.ptr 18 | f := flag(toRType(value.typ).Kind()) 19 | if ifaceIndir(value.typ) { 20 | f |= 1 << 7 21 | } 22 | value.flag = f 23 | return value.Native() 24 | } 25 | 26 | // NewValue creates a new Value from the given interface{}. 27 | // It handles various input types including Value, reflect.Value, or any other value, 28 | // converting them to the internal Value representation. 29 | func NewValue(v interface{}) Value { 30 | switch vv := v.(type) { 31 | case Value: 32 | return vv 33 | case reflect.Value: 34 | return *(*Value)(unsafe.Pointer(&vv)) 35 | default: 36 | value := Value{} 37 | if v == nil { 38 | return value 39 | } 40 | valueLayout := (*Value)(unsafe.Pointer(&v)) 41 | value.typ = valueLayout.typ 42 | value.ptr = valueLayout.ptr 43 | f := flag(toRType(value.typ).Kind()) 44 | if ifaceIndir(value.typ) { 45 | f |= 1 << 7 46 | } 47 | value.flag = f 48 | return value 49 | } 50 | } 51 | 52 | // Native converts the internal Value representation to the standard reflect.Value. 53 | // This allows interoperability with the standard reflect package. 54 | func (v Value) Native() reflect.Value { 55 | return *(*reflect.Value)(unsafe.Pointer(&v)) 56 | } 57 | 58 | // Type returns the Type of the value. 59 | // This is equivalent to reflect.Value.Type() but returns the internal Type representation. 60 | func (v Value) Type() Type { 61 | return v.typ 62 | } 63 | -------------------------------------------------------------------------------- /zfile/handle_test.go: -------------------------------------------------------------------------------- 1 | package zfile 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | . "github.com/sohaha/zlsgo" 8 | ) 9 | 10 | func TestCopy(tt *testing.T) { 11 | t := NewTest(tt) 12 | dest := RealPathMkdir("../tmp", true) 13 | defer Rmdir(dest) 14 | err := CopyFile("../doc.go", dest+"tmp.tmp") 15 | t.Equal(nil, err) 16 | err = CopyDir("../znet", dest, func(srcFilePath, destFilePath string) bool { 17 | return srcFilePath == "../znet/timeout/timeout.go" 18 | }) 19 | t.Equal(nil, err) 20 | } 21 | 22 | func TestRW(t *testing.T) { 23 | var err error 24 | var text []byte 25 | tt := NewTest(t) 26 | str := []byte("666") 27 | 28 | _ = WriteFile("./text.txt", str) 29 | text, err = ReadFile("./text.txt") 30 | tt.EqualNil(err) 31 | tt.Equal(str, text) 32 | t.Log(string(text)) 33 | 34 | _ = WriteFile("./text.txt", str, true) 35 | text, err = ReadFile("./text.txt") 36 | tt.EqualNil(err) 37 | t.Log(string(text)) 38 | tt.Equal([]byte("666666"), text) 39 | 40 | _ = WriteFile("./text.txt", str) 41 | text, err = ReadFile("./text.txt") 42 | tt.EqualNil(err) 43 | t.Log(string(text)) 44 | tt.Equal(str, text) 45 | _ = os.Remove("./text.txt") 46 | } 47 | 48 | func TestReadLineFile(t *testing.T) { 49 | _ = WriteFile("./TestReadLineFile.txt", []byte("111\n2222\nTestReadLineFile\n88")) 50 | defer os.Remove("./TestReadLineFile.txt") 51 | tt := NewTest(t) 52 | file := "./TestReadLineFile.txt" 53 | i := 4 54 | err := ReadLineFile(file, func(line int, data []byte) error { 55 | t.Log(line, string(data)) 56 | i-- 57 | return nil 58 | }) 59 | tt.EqualNil(err) 60 | tt.Equal(0, i) 61 | 62 | _ = WriteFile("./TestReadLineFile.txt", []byte("111\n2222\nTestReadLineFile\n88\n")) 63 | i = 5 64 | err = ReadLineFile(file, func(line int, data []byte) error { 65 | t.Log(line, string(data)) 66 | i-- 67 | return nil 68 | }) 69 | tt.EqualNil(err) 70 | tt.Equal(0, i) 71 | } 72 | -------------------------------------------------------------------------------- /zreflect/type.go: -------------------------------------------------------------------------------- 1 | package zreflect 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | _ "unsafe" 7 | ) 8 | 9 | // TypeOf returns the reflection Type of the value v. 10 | // This is similar to reflect.TypeOf but with additional handling for zreflect.Type and zreflect.Value types. 11 | func TypeOf(v interface{}) reflect.Type { 12 | return toRType(NewType(v)) 13 | } 14 | 15 | //go:linkname toRType reflect.toType 16 | //go:noescape 17 | func toRType(Type) reflect.Type 18 | 19 | // NewType creates a new Type from the given value. 20 | // It handles various input types including Type, Value, reflect.Type, reflect.Value, 21 | // or any other value, converting them to the internal Type representation. 22 | func NewType(v interface{}) Type { 23 | switch t := v.(type) { 24 | case Type: 25 | return t 26 | case Value: 27 | return t.typ 28 | case reflect.Type: 29 | return rtypeToType(t) 30 | case reflect.Value: 31 | return (*Value)(unsafe.Pointer(&t)).typ 32 | default: 33 | return (*Value)(unsafe.Pointer(&v)).typ 34 | } 35 | } 36 | 37 | // Native converts the internal Type representation to the standard reflect.Type. 38 | // This allows interoperability with the standard reflect package. 39 | func (t *rtype) Native() reflect.Type { 40 | return toRType(t) 41 | } 42 | 43 | // rtypeToType converts a reflect.Type to the internal Type representation. 44 | // This is an internal helper function used for type conversions. 45 | func rtypeToType(t reflect.Type) Type { 46 | return (Type)(((*Value)(unsafe.Pointer(&t))).ptr) 47 | } 48 | 49 | //go:linkname typeNumMethod reflect.(*rtype).NumMethod 50 | //go:noescape 51 | func typeNumMethod(Type) int 52 | 53 | // NumMethod returns the number of exported methods in the type's method set. 54 | // This is equivalent to reflect.Type.NumMethod() but works on the internal Type representation. 55 | func (t *rtype) NumMethod() int { 56 | return typeNumMethod(t) 57 | } 58 | -------------------------------------------------------------------------------- /zutil/other_test.go: -------------------------------------------------------------------------------- 1 | package zutil_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "unsafe" 7 | 8 | "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/zutil" 10 | ) 11 | 12 | func TestUnescapeHTML(t *testing.T) { 13 | tt := zlsgo.NewTest(t) 14 | s := zutil.UnescapeHTML("") 15 | tt.Log(s) 16 | } 17 | 18 | func TestKeySignature(t *testing.T) { 19 | tt := zlsgo.NewTest(t) 20 | var ptrTarget int 21 | tests := []struct { 22 | name string 23 | input interface{} 24 | want string 25 | assert func(got string) 26 | }{ 27 | {name: "string", input: "a", want: "sa"}, 28 | {name: "int", input: 123, want: "i123"}, 29 | {name: "int8", input: int8(-9), want: "a-9"}, 30 | {name: "int16", input: int16(42), want: "b42"}, 31 | {name: "int32", input: int32(-1024), want: "c-1024"}, 32 | {name: "int64", input: int64(999), want: "d999"}, 33 | {name: "uint", input: uint(7), want: "u7"}, 34 | {name: "uint8", input: uint8(8), want: "v8"}, 35 | {name: "uint16", input: uint16(16), want: "w16"}, 36 | {name: "uint32", input: uint32(32), want: "x32"}, 37 | {name: "uint64", input: uint64(64), want: "y64"}, 38 | {name: "uintptr", input: uintptr(0x123abc), want: "p123abc"}, 39 | {name: "float32", input: float32(1.5), want: "f1.5"}, 40 | {name: "float64", input: float64(-2.25), want: "F-2.25"}, 41 | {name: "complex64", input: complex64(1.25 + 2i), want: "g1.25,2"}, 42 | {name: "complex128", input: complex128(-3.5 + 0.5i), want: "G-3.5,0.5"}, 43 | { 44 | name: "unsafePointer", 45 | input: unsafe.Pointer(&ptrTarget), 46 | assert: func(got string) { 47 | tt.EqualTrue(strings.HasPrefix(got, "P")) 48 | }, 49 | }, 50 | {name: "default", input: struct{}{}, want: "?"}, 51 | } 52 | 53 | for _, tc := range tests { 54 | got := zutil.KeySignature(tc.input) 55 | if tc.assert != nil { 56 | tc.assert(got) 57 | continue 58 | } 59 | tt.Equal(tc.want, got) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /zarray/hashmap_benchmark_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zarray_test 5 | 6 | import ( 7 | "sync" 8 | "sync/atomic" 9 | "testing" 10 | 11 | "github.com/sohaha/zlsgo/zarray" 12 | ) 13 | 14 | const size = 1 << 12 15 | 16 | func BenchmarkGoSyncSet(b *testing.B) { 17 | var m sync.Map 18 | b.RunParallel(func(pb *testing.PB) { 19 | for pb.Next() { 20 | for i := 0; i < size; i++ { 21 | m.Store(i, i) 22 | } 23 | } 24 | }) 25 | } 26 | 27 | func BenchmarkHashMapSet(b *testing.B) { 28 | m := zarray.NewHashMap[int, int]() 29 | b.RunParallel(func(pb *testing.PB) { 30 | for pb.Next() { 31 | for i := 0; i < size; i++ { 32 | m.Set(i, i) 33 | } 34 | } 35 | }) 36 | } 37 | 38 | func BenchmarkGoSyncGet(b *testing.B) { 39 | var m sync.Map 40 | for i := 0; i < size; i++ { 41 | m.Store(i, i) 42 | } 43 | var n int64 44 | b.ResetTimer() 45 | b.RunParallel(func(pb *testing.PB) { 46 | for pb.Next() { 47 | if atomic.CompareAndSwapInt64(&n, 0, 1) { 48 | for pb.Next() { 49 | for i := 0; i < size; i++ { 50 | m.Store(i, i) 51 | } 52 | } 53 | } else { 54 | for pb.Next() { 55 | for i := 0; i < size; i++ { 56 | j, _ := m.Load(i) 57 | if j != i { 58 | b.Fail() 59 | } 60 | } 61 | } 62 | } 63 | } 64 | }) 65 | } 66 | 67 | func BenchmarkHashMapGet(b *testing.B) { 68 | m := zarray.NewHashMap[int, int]() 69 | for i := 0; i < size; i++ { 70 | m.Set(i, i) 71 | } 72 | var n int64 73 | b.ResetTimer() 74 | b.RunParallel(func(pb *testing.PB) { 75 | for pb.Next() { 76 | if atomic.CompareAndSwapInt64(&n, 0, 1) { 77 | for pb.Next() { 78 | for i := 0; i < size; i++ { 79 | m.Set(i, i) 80 | } 81 | } 82 | } else { 83 | for pb.Next() { 84 | for i := 0; i < size; i++ { 85 | j, _ := m.Get(i) 86 | if j != i { 87 | b.Fail() 88 | } 89 | } 90 | } 91 | } 92 | } 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /zreflect/quick_test.go: -------------------------------------------------------------------------------- 1 | package zreflect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/sohaha/zlsgo" 8 | ) 9 | 10 | func TestForEachMethod(t *testing.T) { 11 | tt := zlsgo.NewTest(t) 12 | v := ValueOf(Demo) 13 | err := ForEachMethod(v, func(index int, method reflect.Method, value reflect.Value) error { 14 | tt.Log(index, method.Name, value.Kind()) 15 | return nil 16 | }) 17 | tt.NoError(err) 18 | 19 | v = ValueOf(&Demo) 20 | err = ForEachMethod(v, func(index int, method reflect.Method, value reflect.Value) error { 21 | tt.Log(index, method.Name, value.Kind()) 22 | return nil 23 | }) 24 | tt.NoError(err) 25 | } 26 | 27 | func TestForEach(t *testing.T) { 28 | tt := zlsgo.NewTest(t) 29 | typ := TypeOf(Demo) 30 | 31 | err := ForEach(typ, func(parent []string, index int, tag string, field reflect.StructField) error { 32 | return nil 33 | }) 34 | tt.NoError(err) 35 | err = ForEach(typ, func(parent []string, index int, tag string, field reflect.StructField) error { 36 | tt.Log(parent, index, tag, field.Name) 37 | return SkipChild 38 | }) 39 | tt.NoError(err) 40 | 41 | typ = TypeOf(&Demo) 42 | err = ForEach(typ, func(parent []string, index int, tag string, field reflect.StructField) error { 43 | tt.Log(parent, index, tag, field.Name) 44 | return SkipChild 45 | }) 46 | tt.NoError(err) 47 | } 48 | 49 | func TestForEachValue(t *testing.T) { 50 | tt := zlsgo.NewTest(t) 51 | v := ValueOf(Demo) 52 | 53 | err := ForEachValue(v, func(parent []string, index int, tag string, field reflect.StructField, value reflect.Value) error { 54 | tt.Log(parent, index, tag, field.Name, value.Interface()) 55 | return nil 56 | }) 57 | tt.NoError(err) 58 | 59 | v = ValueOf(&Demo) 60 | err = ForEachValue(v, func(parent []string, index int, tag string, field reflect.StructField, value reflect.Value) error { 61 | tt.Log(parent, index, tag, field.Name, value.Interface()) 62 | return nil 63 | }) 64 | tt.NoError(err) 65 | } 66 | -------------------------------------------------------------------------------- /ztime/cron/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package cron provides functionality similar to Linux crontab for scheduling tasks. 3 | 4 | This package allows users to schedule and manage tasks using standard cron expression syntax, 5 | with support for second-level precision. Key features include: 6 | 7 | - Standard cron expression syntax support 8 | - Second-level precision for task scheduling 9 | - Support for adding, running, and stopping tasks 10 | - Thread-safe task management 11 | 12 | Basic usage example: 13 | 14 | // Create a new job table 15 | crontab := cron.New() 16 | 17 | // Add a task that runs every minute 18 | removeFunc, err := crontab.Add("0 * * * * *", func() { 19 | // Task code goes here 20 | fmt.Println("Running every minute") 21 | }) 22 | if err != nil { 23 | // Handle error 24 | } 25 | 26 | // Start the cron scheduler (non-blocking mode) 27 | crontab.Run() 28 | 29 | // For blocking mode 30 | // crontab.Run(true) 31 | 32 | // Remove a specific task 33 | removeFunc() 34 | 35 | // Stop all tasks 36 | crontab.Stop() 37 | 38 | Cron expression format: 39 | 40 | second minute hour day month weekday 41 | 42 | Field descriptions: 43 | - second: 0-59 44 | - minute: 0-59 45 | - hour: 0-23 46 | - day: 1-31 47 | - month: 1-12 48 | - weekday: 0-6 (0 represents Sunday) 49 | 50 | Supported special characters: 51 | - *: represents all possible values 52 | - ,: used to separate multiple values 53 | - -: represents a range 54 | - /: represents an increment 55 | - L: used in the day field to represent the last day of the month, or in the weekday field to represent the last day of the week 56 | - W: used in the day field to represent the nearest weekday 57 | 58 | Examples: 59 | - "0 0 12 * * *": Run at 12:00 PM every day 60 | - "0 15 10 * * *": Run at 10:15 AM every day 61 | - "0 0/5 * * * *": Run every 5 minutes 62 | - "0 0 12 1 * *": Run at 12:00 PM on the first day of every month 63 | */ 64 | package cron 65 | -------------------------------------------------------------------------------- /zdi/bind_test.go: -------------------------------------------------------------------------------- 1 | package zdi_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/sohaha/zlsgo" 8 | "github.com/sohaha/zlsgo/zdi" 9 | "github.com/sohaha/zlsgo/zlog" 10 | "github.com/sohaha/zlsgo/ztime" 11 | ) 12 | 13 | func TestBind(t *testing.T) { 14 | tt := zlsgo.NewTest(t) 15 | di := zdi.New() 16 | 17 | test1 := testSt{Msg: ztime.Now(), Num: 666} 18 | o := di.Map(test1) 19 | tt.EqualNil(o) 20 | zlog.Dump(test1) 21 | 22 | var test2 testSt 23 | override := di.Resolve(&test2) 24 | 25 | tt.Equal(test1, test2) 26 | zlog.Dump(override, test2) 27 | 28 | var test3 testSt 29 | override = di.Resolve(test3) 30 | tt.EqualTrue(override != nil) 31 | zlog.Dump(override, test3) 32 | 33 | test5 := &testSt{Msg: ztime.Now(), Num: 777} 34 | o = di.Map(test5) 35 | tt.EqualNil(o) 36 | 37 | var test4 *testSt 38 | err := di.Resolve(test4) 39 | tt.EqualTrue(err != nil) 40 | zlog.Dump(err, test4) 41 | 42 | var test6 *testSt 43 | err = di.Resolve(&test6) 44 | tt.NoError(err) 45 | tt.Equal(test5, test6) 46 | zlog.Dump(err, test6) 47 | } 48 | 49 | func TestApply(t *testing.T) { 50 | tt := zlsgo.NewTest(t) 51 | di := zdi.New() 52 | 53 | val := time.Now().String() 54 | o := di.Map(val) 55 | tt.EqualNil(o) 56 | 57 | var v testSt 58 | err := di.Apply(&v) 59 | tt.EqualNil(err) 60 | tt.Logf("%+v %+v\n", val, v) 61 | 62 | var s string 63 | err = di.Apply(&s) 64 | tt.EqualNil(err) 65 | tt.Logf("%+v\n", s) 66 | } 67 | 68 | func TestResolve(t *testing.T) { 69 | tt := zlsgo.NewTest(t) 70 | di := zdi.New() 71 | 72 | val := &testSt{Msg: "TestResolve", Num: 2} 73 | o := di.Map(val) 74 | tt.EqualNil(o) 75 | 76 | var v *testSt 77 | err := di.Resolve(&v) 78 | tt.Logf("%+v %+v\n", val, v) 79 | tt.EqualNil(err) 80 | tt.Equal(val.Msg, v.Msg) 81 | tt.Equal(val.Num, v.Num) 82 | 83 | v = &testSt{} 84 | err = di.Resolve(&v) 85 | tt.Logf("%+v %+v\n", val, v) 86 | tt.EqualNil(err) 87 | tt.Equal(val.Msg, v.Msg) 88 | tt.Equal(val.Num, v.Num) 89 | } 90 | -------------------------------------------------------------------------------- /zfile/memory_test.go: -------------------------------------------------------------------------------- 1 | package zfile 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "strconv" 7 | "sync" 8 | "testing" 9 | 10 | "github.com/sohaha/zlsgo" 11 | ) 12 | 13 | func TestMain(m *testing.M) { 14 | m.Run() 15 | _ = Remove("6.txt") 16 | _ = Remove("7.txt") 17 | _ = Remove("8.txt") 18 | os.Exit(0) 19 | } 20 | 21 | func TestMemoryFile(t *testing.T) { 22 | tt := zlsgo.NewTest(t) 23 | f := NewMemoryFile("6.txt", MemoryFileAutoFlush(1), MemoryFileFlushBefore(func(f *MemoryFile) error { 24 | f.SetName("7.txt") 25 | t.Log(f.Size()) 26 | return nil 27 | })) 28 | 29 | t.Log(f.Name()) 30 | tt.EqualTrue(!f.IsDir()) 31 | t.Log(f.ModTime()) 32 | t.Log(f.Mode()) 33 | t.Log(f.Sys()) 34 | t.Log(f.Size()) 35 | 36 | var wg sync.WaitGroup 37 | for i := 0; i < 1000; i++ { 38 | wg.Add(1) 39 | go func(i int) { 40 | _, err := f.Write([]byte(strconv.Itoa(i) + "\n")) 41 | wg.Done() 42 | tt.NoError(err) 43 | }(i) 44 | } 45 | 46 | b := []byte("--\n") 47 | _, err := f.Write(b) 48 | tt.NoError(err) 49 | t.Log(len(f.Bytes())) 50 | wg.Wait() 51 | t.Log(len(f.Bytes())) 52 | tt.NoError(f.Close()) 53 | t.Log(FileSize("7.txt")) 54 | tt.NoError(f.Close()) 55 | } 56 | 57 | func BenchmarkFileMem6(b *testing.B) { 58 | name := "6.txt" 59 | f := NewMemoryFile(name) 60 | for i := 0; i < b.N; i++ { 61 | _, err := f.Write([]byte(strconv.Itoa(i))) 62 | if err != nil { 63 | b.Fatal(err) 64 | } 65 | } 66 | _ = WriteFile(name, f.Bytes()) 67 | } 68 | 69 | func BenchmarkFileReal8(b *testing.B) { 70 | name := "8.txt" 71 | for i := 0; i < b.N; i++ { 72 | err := WriteFile(name, []byte(strconv.Itoa(i)), true) 73 | if err != nil { 74 | b.Fatal(err) 75 | } 76 | } 77 | } 78 | 79 | func BenchmarkFileBufio7(b *testing.B) { 80 | name := "7.txt" 81 | file, _ := os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0766) 82 | write := bufio.NewWriter(file) 83 | for i := 0; i < b.N; i++ { 84 | _, err := write.Write([]byte(strconv.Itoa(i))) 85 | if err != nil { 86 | b.Fatal(err) 87 | } 88 | } 89 | write.Flush() 90 | } 91 | -------------------------------------------------------------------------------- /znet/gzip/gzip_test.go: -------------------------------------------------------------------------------- 1 | package gzip_test 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/sohaha/zlsgo" 10 | "github.com/sohaha/zlsgo/znet" 11 | "github.com/sohaha/zlsgo/znet/gzip" 12 | "github.com/sohaha/zlsgo/zstring" 13 | ) 14 | 15 | const host = "127.0.0.1" 16 | 17 | var ( 18 | r *znet.Engine 19 | size = 10000 20 | ) 21 | 22 | func init() { 23 | r = znet.New() 24 | r.SetMode(znet.ProdMode) 25 | 26 | r.GET("/gzip", func(c *znet.Context) { 27 | c.String(200, zstring.Rand(size, "abc")) 28 | }, gzip.Default()) 29 | 30 | } 31 | 32 | func TestGzip(t *testing.T) { 33 | tt := zlsgo.NewTest(t) 34 | 35 | go func() { 36 | w := httptest.NewRecorder() 37 | req, _ := http.NewRequest("GET", "/gzip", nil) 38 | r.ServeHTTP(w, req) 39 | tt.Equal(200, w.Code) 40 | tt.Equal(size, w.Body.Len()) 41 | }() 42 | 43 | var g sync.WaitGroup 44 | for i := 0; i < 1000; i++ { 45 | g.Add(1) 46 | go func() { 47 | w := httptest.NewRecorder() 48 | req, _ := http.NewRequest("GET", "/gzip", nil) 49 | req.Header.Add("Accept-Encoding", "gzip") 50 | req.Host = host 51 | r.ServeHTTP(w, req) 52 | tt.Equal(200, w.Code) 53 | tt.EqualTrue(w.Body.Len() > 100) 54 | tt.EqualTrue(w.Body.Len() < size) 55 | g.Done() 56 | }() 57 | } 58 | g.Wait() 59 | } 60 | 61 | func BenchmarkGzipDoNotUse(b *testing.B) { 62 | for i := 0; i < b.N; i++ { 63 | w := httptest.NewRecorder() 64 | req, _ := http.NewRequest("GET", "/gzip", nil) 65 | req.Header.Add("Accept-Encoding1", "not-gzip") 66 | r.ServeHTTP(w, req) 67 | if 200 != w.Code || size != w.Body.Len() { 68 | b.Fail() 69 | } 70 | } 71 | } 72 | 73 | func BenchmarkGzipUse(b *testing.B) { 74 | for i := 0; i < b.N; i++ { 75 | w := httptest.NewRecorder() 76 | req, _ := http.NewRequest("GET", "/gzip", nil) 77 | req.Header.Add("Accept-Encoding", "gzip") 78 | req.Host = host 79 | r.ServeHTTP(w, req) 80 | if 200 != w.Code || size <= w.Body.Len() || 100 >= w.Body.Len() { 81 | b.Fail() 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /zstring/regex_test.go: -------------------------------------------------------------------------------- 1 | package zstring 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/sohaha/zlsgo" 8 | ) 9 | 10 | func TestExtract(t *testing.T) { 11 | tt := zlsgo.NewTest(t) 12 | res, err := RegexExtract(`abc(\d{2}).*(\w)`, "abc123999ok") 13 | tt.Equal(true, err == nil) 14 | tt.Equal([]string{"12", "k"}, res[1:]) 15 | res2, err := RegexExtractAll(`a(\d{2})`, "a123999oa23kdsfsda323") 16 | tt.EqualExit(true, err == nil) 17 | t.Log(res2) 18 | tt.Equal(res2[0][1], "12") 19 | tt.Equal(res2[1][1], "23") 20 | tt.Equal(res2[2][1], "32") 21 | res2, err = RegexExtractAll(`a(\d{2})`, "a123999oa23kdsfsda323", 1) 22 | tt.EqualExit(true, err == nil) 23 | t.Log(res2) 24 | tt.Equal(res2[0][1], "12") 25 | } 26 | 27 | func TestRegex(T *testing.T) { 28 | t := zlsgo.NewTest(T) 29 | t.Equal(true, RegexMatch("是我啊", "这就是我啊!")) 30 | t.Equal(false, RegexMatch("是你呀", "这就是我啊!")) 31 | 32 | phone := "13800138000" 33 | isPhone := RegexMatch(`^1[\d]{10}$`, phone) 34 | t.Equal(true, isPhone) 35 | phone = "1380013800x" 36 | isPhone = RegexMatch(`^1[\d]{10}$`, phone) 37 | t.Equal(false, isPhone) 38 | 39 | t.Equal(2, len(RegexFind(`\d{2}`, "a1b23c456", -1))) 40 | t.Equal(0, len(RegexFind(`\d{2}`, "abc", -1))) 41 | 42 | str, _ := RegexReplace(`b\d{2}`, "a1b23c456", "*") 43 | t.Equal("a1*c456", str) 44 | 45 | strs, _ := RegexSplit(`~m~[0-9]{1,}~m~`, "~m~4~m~~h~1") 46 | t.Equal([]string{"", "~h~1"}, strs) 47 | 48 | str, _ = RegexReplaceFunc(`\w{2}`, "abcd", Ucfirst) 49 | t.Equal("AbCd", str) 50 | 51 | clearRegexpCompile() 52 | 53 | regexCache = map[string]*regexMapStruct{} 54 | clearRegexpCompile() 55 | } 56 | 57 | func BenchmarkRegex1(b *testing.B) { 58 | for i := 0; i < b.N; i++ { 59 | RegexMatch("是我啊", "这就是我啊!") 60 | } 61 | } 62 | 63 | func BenchmarkRegex2(b *testing.B) { 64 | for i := 0; i < b.N; i++ { 65 | r, _ := regexp.Compile("是我啊") 66 | r.Match(String2Bytes("这就是我啊!")) 67 | } 68 | } 69 | 70 | func BenchmarkRegex3(b *testing.B) { 71 | r, _ := regexp.Compile("是我啊") 72 | for i := 0; i < b.N; i++ { 73 | r.Match(String2Bytes("这就是我啊!")) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /zlocale/loader.go: -------------------------------------------------------------------------------- 1 | package zlocale 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Loader provides functionality to load translations from maps 8 | // Simplified version that only supports embedded translations 9 | type Loader struct { 10 | i18n *I18n 11 | } 12 | 13 | // NewLoader creates a new loader for the given i18n instance 14 | func NewLoader(i18n *I18n) *Loader { 15 | return &Loader{i18n: i18n} 16 | } 17 | 18 | // LoadTranslationsFromMap loads translations from a map[string]string 19 | func (l *Loader) LoadTranslationsFromMap(langCode, langName string, translations map[string]string) error { 20 | if translations == nil { 21 | return fmt.Errorf("translations map cannot be nil") 22 | } 23 | 24 | return l.i18n.LoadLanguage(langCode, langName, translations) 25 | } 26 | 27 | // AddCustomTranslation adds a custom translation to an existing language 28 | // This allows runtime extension of the built-in translations 29 | func (l *Loader) AddCustomTranslation(langCode, key, value string) error { 30 | langName := langCode 31 | if name, ok := languageNames[langCode]; ok { 32 | langName = name 33 | } 34 | 35 | customTranslations := map[string]string{key: value} 36 | return l.i18n.LoadLanguage(langCode, langName, customTranslations) 37 | } 38 | 39 | // GetAvailableLanguages returns the list of available language codes 40 | func (l *Loader) GetAvailableLanguages() []string { 41 | languages := l.i18n.GetLoadedLanguages() 42 | result := make([]string, 0, len(languages)) 43 | for code := range languages { 44 | result = append(result, code) 45 | } 46 | return result 47 | } 48 | 49 | // LoadTranslationsFromMap loads translations from a map using the global i18n instance 50 | func LoadTranslationsFromMap(langCode, langName string, translations map[string]string) error { 51 | return NewLoader(getDefault()).LoadTranslationsFromMap(langCode, langName, translations) 52 | } 53 | 54 | // AddCustomTranslation adds a custom translation using the global i18n instance 55 | func AddCustomTranslation(langCode, key, value string) error { 56 | return NewLoader(getDefault()).AddCustomTranslation(langCode, key, value) 57 | } 58 | -------------------------------------------------------------------------------- /ztype/decimal.go: -------------------------------------------------------------------------------- 1 | package ztype 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | var tenToAny = [...]string{0: "0", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7", 8: "8", 9: "9", 10: "a", 11: "b", 12: "c", 13: "d", 14: "e", 15: "f", 16: "g", 17: "h", 18: "i", 19: "j", 20: "k", 21: "l", 22: "m", 23: "n", 24: "o", 25: "p", 26: "q", 27: "r", 28: "s", 29: "t", 30: "u", 31: "v", 32: "w", 33: "x", 34: "y", 35: "z", 36: "A", 37: "B", 38: "C", 39: "D", 40: "E", 41: "F", 42: "G", 43: "H", 44: "I", 45: "J", 46: "K", 47: "L", 48: "M", 49: "N", 50: "O", 51: "P", 52: "Q", 53: "R", 54: "S", 55: "T", 56: "U", 57: "V", 58: "W", 59: "X", 60: "Y", 61: "Z", 62: "_", 63: "-", 64: "|", 65: "<"} 10 | 11 | // DecimalToAny Convert decimal to arbitrary decimal values 12 | func DecimalToAny(value, base int) (newNumStr string) { 13 | if base < 2 { 14 | return 15 | } 16 | if base <= 32 { 17 | return strconv.FormatInt(int64(value), base) 18 | } 19 | var ( 20 | remainder int 21 | remainderString string 22 | ) 23 | for value != 0 { 24 | remainder = value % base 25 | if remainder < 66 && remainder > 9 { 26 | remainderString = tenToAny[remainder] 27 | } else { 28 | remainderString = strconv.FormatInt(int64(remainder), 10) 29 | } 30 | newNumStr = remainderString + newNumStr 31 | value = value / base 32 | } 33 | return 34 | } 35 | 36 | // AnyToDecimal Convert arbitrary decimal values to decimal 37 | func AnyToDecimal(value string, base int) (v int) { 38 | if base < 2 { 39 | return 40 | } 41 | if base <= 32 { 42 | n, _ := strconv.ParseInt(value, base, 64) 43 | v = int(n) 44 | return 45 | } 46 | n := 0.0 47 | nNum := len(strings.Split(value, "")) - 1 48 | for _, v := range strings.Split(value, "") { 49 | tmp := float64(findKey(v)) 50 | if tmp != -1 { 51 | n = n + tmp*math.Pow(float64(base), float64(nNum)) 52 | nNum = nNum - 1 53 | } else { 54 | break 55 | } 56 | } 57 | v = int(n) 58 | return 59 | } 60 | 61 | func findKey(in string) int { 62 | result := -1 63 | for k, v := range tenToAny { 64 | if in == v { 65 | result = k 66 | } 67 | } 68 | return result 69 | } 70 | -------------------------------------------------------------------------------- /zlocale/cache.go: -------------------------------------------------------------------------------- 1 | package zlocale 2 | 3 | import "time" 4 | 5 | // TemplateCache defines the interface for template caching functionality 6 | // This abstraction allows for different cache implementations while maintaining 7 | // a consistent API for the i18n module 8 | type TemplateCache interface { 9 | // Get retrieves a cached template by its key 10 | Get(key string) (*TemplateCacheEntry, bool) 11 | 12 | // Set stores a template with optional expiration 13 | Set(key string, template *TemplateCacheEntry) 14 | 15 | // Delete removes a template from the cache 16 | Delete(key string) 17 | 18 | // Clear removes all templates from the cache 19 | Clear() 20 | 21 | // Count returns the number of cached templates 22 | Count() int 23 | 24 | // Stats returns cache statistics for monitoring 25 | Stats() CacheStats 26 | 27 | // Close cleans up cache resources 28 | Close() 29 | } 30 | 31 | // TemplateCacheEntry represents a cached template with metadata 32 | type TemplateCacheEntry struct { 33 | Template interface{} // The actual template (e.g., *zstring.Template) 34 | Created time.Time // Creation timestamp 35 | Accessed time.Time // Last access timestamp 36 | Hits int64 // Access count 37 | } 38 | 39 | // CacheStats provides statistics about cache performance 40 | type CacheStats struct { 41 | // TotalItems is the number of items currently in the cache 42 | TotalItems int 43 | 44 | // HitCount is the total number of successful cache hits 45 | HitCount int64 46 | 47 | // MissCount is the total number of cache misses 48 | MissCount int64 49 | 50 | // HitRate is the cache hit ratio (0-1) 51 | HitRate float64 52 | 53 | // MemoryUsage is the estimated memory usage in bytes 54 | MemoryUsage int64 55 | 56 | // EvictionCount is the number of items evicted due to expiration or capacity limits 57 | EvictionCount int64 58 | 59 | // IdleDuration shows how long the cache has been idle 60 | IdleDuration time.Duration 61 | 62 | // CleanerLevel indicates the current cleaning intensity level 63 | CleanerLevel int32 64 | 65 | // IsCleanerRunning indicates if background cleaner is active 66 | IsCleanerRunning bool 67 | } 68 | -------------------------------------------------------------------------------- /zstring/expand.go: -------------------------------------------------------------------------------- 1 | package zstring 2 | 3 | // Expand replaces ${var} or $var in the string based on the mapping function. 4 | // It's similar to shell variable expansion, supporting both ${var} and $var syntax. 5 | func Expand(s string, process func(key string) string) string { 6 | var buf []byte 7 | i := 0 8 | for j := 0; j < len(s); j++ { 9 | if s[j] == '$' && j+1 < len(s) { 10 | if buf == nil { 11 | buf = make([]byte, 0, 2*len(s)) 12 | } 13 | buf = append(buf, s[i:j]...) 14 | name, w := getShellName(s[j+1:]) 15 | if name != "" { 16 | buf = append(buf, process(name)...) 17 | } else if w == 0 { 18 | buf = append(buf, s[j]) 19 | } 20 | j += w 21 | i = j + 1 22 | } 23 | } 24 | 25 | if buf == nil { 26 | return s 27 | } 28 | 29 | return Bytes2String(buf) + s[i:] 30 | } 31 | 32 | // getShellName extracts a shell variable name from a string starting with a variable reference. 33 | // It returns the variable name and the number of bytes consumed from the input string. 34 | func getShellName(s string) (string, int) { 35 | switch { 36 | case s[0] == '{': 37 | if len(s) > 2 && isShellSpecialVar(s[1]) && s[2] == '}' { 38 | return s[1:2], 3 39 | } 40 | for i := 1; i < len(s); i++ { 41 | if s[i] == '}' { 42 | if i == 1 { 43 | return "", 2 44 | } 45 | return s[1:i], i + 1 46 | } 47 | } 48 | return "", 1 49 | case isShellSpecialVar(s[0]): 50 | return s[0:1], 1 51 | } 52 | var i int 53 | for i = 0; i < len(s) && isAlphaNum(s[i]); i++ { 54 | } 55 | return s[:i], i 56 | } 57 | 58 | // isShellSpecialVar checks if a character is a special shell variable character. 59 | // Special variables include *, #, $, @, !, ?, -, and digits 0-9. 60 | func isShellSpecialVar(c uint8) bool { 61 | switch c { 62 | case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 63 | return true 64 | } 65 | return false 66 | } 67 | 68 | // isAlphaNum checks if a character is alphanumeric or underscore. 69 | // These characters are valid in variable names. 70 | func isAlphaNum(c uint8) bool { 71 | return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' 72 | } 73 | -------------------------------------------------------------------------------- /zvalid/has_test.go: -------------------------------------------------------------------------------- 1 | package zvalid 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | ) 8 | 9 | func TestRuleHas(t *testing.T) { 10 | var err error 11 | tt := zlsgo.NewTest(t) 12 | 13 | err = New().Verifi("123a").HasLetter().Error() 14 | tt.EqualNil(err) 15 | err = New().Verifi("1").HasLetter().Error() 16 | tt.Equal(true, err != nil) 17 | t.Log(err) 18 | err = New().Verifi("").HasLetter().Error() 19 | tt.Equal(true, err != nil) 20 | 21 | err = New().Verifi("123a").HasLower().Error() 22 | tt.EqualNil(err) 23 | err = New().Verifi("1").HasLower().Error() 24 | tt.Equal(true, err != nil) 25 | err = New().Verifi("").HasLower().Error() 26 | tt.Equal(true, err != nil) 27 | 28 | err = New().Verifi("123aA").HasUpper().Error() 29 | tt.EqualNil(err) 30 | err = New().Verifi("1").HasUpper().Error() 31 | tt.Equal(true, err != nil) 32 | err = New().Verifi("").HasUpper().Error() 33 | tt.Equal(true, err != nil) 34 | 35 | err = New().Verifi("123aA").HasNumber().Error() 36 | tt.EqualNil(err) 37 | err = New().Verifi("a").HasNumber().Error() 38 | tt.Equal(true, err != nil) 39 | err = New().Verifi("").HasNumber().Error() 40 | tt.Equal(true, err != nil) 41 | 42 | err = New().Verifi("123aA.").HasSymbol().Error() 43 | tt.EqualNil(err) 44 | err = New().Verifi("a").HasSymbol().Error() 45 | tt.Equal(true, err != nil) 46 | err = New().Verifi("").HasSymbol().Error() 47 | tt.Equal(true, err != nil) 48 | 49 | err = New().Verifi("123aA.").HasString("aA").Error() 50 | tt.EqualNil(err) 51 | err = New().Verifi("a").HasString("c").Error() 52 | tt.Equal(true, err != nil) 53 | err = New().Verifi("").HasString("a").Error() 54 | tt.Equal(true, err != nil) 55 | 56 | err = New().Verifi("123aA.").HasPrefix("123a").Error() 57 | tt.EqualNil(err) 58 | err = New().Verifi("a").HasPrefix("c").Error() 59 | tt.Equal(true, err != nil) 60 | err = New().Verifi("").HasPrefix("a").Error() 61 | tt.Equal(true, err != nil) 62 | 63 | err = New().Verifi("123aA.").HasSuffix("A.").Error() 64 | tt.EqualNil(err) 65 | err = New().Verifi("a").HasSuffix("c").Error() 66 | tt.Equal(true, err != nil) 67 | err = New().Verifi("").HasSuffix("a").Error() 68 | tt.Equal(true, err != nil) 69 | } 70 | -------------------------------------------------------------------------------- /znet/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "crypto/subtle" 5 | "encoding/base64" 6 | "errors" 7 | "net/http" 8 | "strconv" 9 | 10 | "github.com/sohaha/zlsgo/zerror" 11 | "github.com/sohaha/zlsgo/znet" 12 | "github.com/sohaha/zlsgo/zstring" 13 | ) 14 | 15 | const UserKey = "auth_user" 16 | 17 | type ( 18 | Accounts map[string]string 19 | authPairs []authPair 20 | authPair struct { 21 | value string 22 | user string 23 | } 24 | ) 25 | 26 | func (a authPairs) searchCredential(authValue string) (string, bool) { 27 | if authValue == "" { 28 | return "", false 29 | } 30 | 31 | for _, pair := range a { 32 | if subtle.ConstantTimeCompare(zstring.String2Bytes(pair.value), zstring.String2Bytes(authValue)) == 1 { 33 | return pair.user, true 34 | } 35 | } 36 | return "", false 37 | } 38 | 39 | func New(accounts Accounts) znet.Handler { 40 | return BasicRealm(accounts, "") 41 | } 42 | 43 | func BasicRealm(accounts Accounts, realm string) znet.Handler { 44 | if realm == "" { 45 | realm = "Authorization Required" 46 | } 47 | realm = "Basic realm=" + strconv.Quote(realm) 48 | pairs, err := processAccounts(accounts) 49 | zerror.Panic(err) 50 | 51 | return func(c *znet.Context) { 52 | user, found := pairs.searchCredential(c.GetHeader("Authorization")) 53 | if !found { 54 | c.SetHeader("WWW-Authenticate", realm, true) 55 | c.Abort(http.StatusUnauthorized) 56 | return 57 | } 58 | 59 | c.WithValue(UserKey, user) 60 | c.Next() 61 | } 62 | } 63 | 64 | func processAccounts(accounts Accounts) (authPairs, error) { 65 | length := len(accounts) 66 | if length == 0 { 67 | return nil, errors.New("empty list of authorized credentials") 68 | } 69 | pairs := make(authPairs, 0, length) 70 | for user, password := range accounts { 71 | if user == "" { 72 | return nil, errors.New("user can not be empty") 73 | } 74 | value := authorizationHeader(user, password) 75 | pairs = append(pairs, authPair{ 76 | value: value, 77 | user: user, 78 | }) 79 | } 80 | return pairs, nil 81 | } 82 | 83 | func authorizationHeader(user, password string) string { 84 | base := user + ":" + password 85 | return "Basic " + base64.StdEncoding.EncodeToString(zstring.String2Bytes(base)) 86 | } 87 | -------------------------------------------------------------------------------- /zutil/daemon/signal.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/sohaha/zlsgo/zutil" 7 | ) 8 | 9 | var ( 10 | // singleSignal ensures the signal handler is initialized only once 11 | singleSignal sync.Once 12 | // single is a channel for broadcasting kill signals to multiple subscribers 13 | single = zutil.NewChan[bool]() 14 | // singleNum tracks the number of active subscribers to the kill signal 15 | singleNum uint = 0 16 | // singleLock protects access to the shared signal handling state 17 | singleLock sync.Mutex 18 | ) 19 | 20 | // singelDo initializes the signal handler goroutine if it hasn't been initialized yet. 21 | // The goroutine waits for a kill signal and broadcasts it to all subscribers. 22 | // This is an internal function used by SingleKillSignal and ReSingleKillSignal. 23 | func singelDo() { 24 | singleSignal.Do(func() { 25 | go func() { 26 | kill := KillSignal() 27 | singleLock.Lock() 28 | for { 29 | if singleNum == 0 { 30 | break 31 | } 32 | 33 | singleNum-- 34 | single.In() <- kill 35 | } 36 | single.Close() 37 | singleLock.Unlock() 38 | }() 39 | }) 40 | } 41 | 42 | // SingleKillSignal returns a channel that will receive a value when the process 43 | // receives a termination signal (such as SIGTERM or SIGINT). 44 | // Multiple goroutines can call this function to receive the same signal. 45 | // The channel will receive true if the signal was caught, false otherwise. 46 | // 47 | // Returns: 48 | // - <-chan bool: A channel that will receive a value when a kill signal is received 49 | func SingleKillSignal() <-chan bool { 50 | singleLock.Lock() 51 | defer singleLock.Unlock() 52 | 53 | singleNum++ 54 | singelDo() 55 | 56 | return single.Out() 57 | } 58 | 59 | // ReSingleKillSignal resets the signal handling system if there are no active subscribers. 60 | // This allows the signal handling to be reused after all previous subscribers have been notified. 61 | // If there are still active subscribers, this function does nothing. 62 | func ReSingleKillSignal() { 63 | singleLock.Lock() 64 | defer singleLock.Unlock() 65 | 66 | if singleNum > 0 { 67 | return 68 | } 69 | 70 | single = zutil.NewChan[bool]() 71 | singleSignal = sync.Once{} 72 | 73 | singelDo() 74 | } 75 | -------------------------------------------------------------------------------- /znet/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sort" 5 | "time" 6 | 7 | "github.com/sohaha/zlsgo/zcache" 8 | "github.com/sohaha/zlsgo/znet" 9 | "github.com/sohaha/zlsgo/zstring" 10 | ) 11 | 12 | type ( 13 | // Config configuration 14 | Config struct { 15 | Custom func(c *znet.Context) (key string, expiration time.Duration) 16 | zcache.Options 17 | } 18 | cacheContext struct { 19 | Type string 20 | Content []byte 21 | Code int32 22 | } 23 | ) 24 | 25 | func New(opt ...func(conf *Config)) znet.HandlerFunc { 26 | conf := Config{ 27 | Custom: func(c *znet.Context) (key string, expiration time.Duration) { 28 | return QueryKey(c), 0 29 | }, 30 | } 31 | 32 | cache := zcache.NewFast(func(o *zcache.Options) { 33 | conf.Options = *o 34 | conf.Options.Expiration = time.Minute * 10 35 | for _, f := range opt { 36 | f(&conf) 37 | } 38 | *o = conf.Options 39 | }) 40 | 41 | return func(c *znet.Context) { 42 | key, expiration := conf.Custom(c) 43 | if key == "" { 44 | c.Next() 45 | return 46 | } 47 | 48 | v, ok := cache.ProvideGet(key, func() (interface{}, bool) { 49 | c.Next() 50 | 51 | p := c.PrevContent() 52 | if p.Code.Load() != 0 { 53 | return &cacheContext{ 54 | Code: p.Code.Load(), 55 | Type: p.Type, 56 | Content: p.Content, 57 | }, true 58 | } 59 | return nil, false 60 | }, expiration) 61 | 62 | if !ok { 63 | return 64 | } 65 | 66 | if data, ok := v.(*cacheContext); ok { 67 | p := c.PrevContent() 68 | p.Code.Store(data.Code) 69 | p.Content = data.Content 70 | p.Type = data.Type 71 | c.Abort() 72 | } 73 | } 74 | } 75 | 76 | func QueryKey(c *znet.Context) (key string) { 77 | m := c.GetAllQueryMaps() 78 | mLen := len(m) 79 | if mLen == 0 { 80 | return c.Request.URL.Path 81 | } 82 | 83 | keys := make([]string, 0, mLen) 84 | for k := range m { 85 | keys = append(keys, k) 86 | } 87 | sort.Strings(keys) 88 | 89 | b := zstring.Buffer((len(m) * 4) + 2) 90 | b.WriteString(c.Request.URL.Path) 91 | b.WriteString("?") 92 | for _, k := range keys { 93 | b.WriteString(k) 94 | b.WriteString("=") 95 | b.WriteString(m[k]) 96 | b.WriteString("&") 97 | } 98 | return b.String() 99 | } 100 | -------------------------------------------------------------------------------- /zutil/once.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zutil 5 | 6 | import ( 7 | "errors" 8 | "sync" 9 | ) 10 | 11 | // Once creates a function that ensures the provided initialization function 12 | // is executed only once, regardless of how many times the returned function is called. 13 | // This implements the singleton pattern with built-in error recovery. 14 | // 15 | // If the initialization function panics, the Once state is reset after a delay, 16 | // allowing for a retry on the next call. 17 | func Once[T any](fn func() T) func() T { 18 | once := OnceWithError(func() (T, error) { 19 | return fn(), nil 20 | }) 21 | 22 | return func() T { 23 | resp, _ := once() 24 | return resp 25 | } 26 | } 27 | 28 | // OnceWithError creates a function that ensures the provided initialization function 29 | // is executed only once, regardless of how many times the returned function is called. 30 | // This implements the singleton pattern with built-in error recovery. 31 | func OnceWithError[T any](fn func() (T, error)) func() (T, error) { 32 | var ( 33 | mu sync.Mutex 34 | done = NewUint32(0) 35 | ivar T 36 | ierr error 37 | ) 38 | 39 | return func() (T, error) { 40 | if done.Load() == 1 { 41 | return ivar, ierr 42 | } 43 | 44 | mu.Lock() 45 | defer mu.Unlock() 46 | 47 | if done.Load() == 0 { 48 | ierr = TryCatch(func() error { 49 | ivar, ierr = fn() 50 | return ierr 51 | }) 52 | 53 | if ierr == nil { 54 | done.Store(1) 55 | } 56 | } 57 | 58 | return ivar, ierr 59 | } 60 | } 61 | 62 | // Guard creates a function that ensures mutually exclusive execution of the provided function. 63 | // If the returned function is called while a previous call is still in progress, 64 | // it will return an error instead of executing the function again. 65 | // 66 | // This is useful for preventing concurrent execution of functions that are not thread-safe 67 | // or for rate-limiting access to resources. 68 | func Guard[T any](fn func() T) func() (T, error) { 69 | status := NewBool(false) 70 | return func() (resp T, err error) { 71 | if !status.CAS(false, true) { 72 | return resp, errors.New("already running") 73 | } 74 | defer status.Store(false) 75 | 76 | err = TryCatch(func() error { 77 | resp = fn() 78 | return nil 79 | }) 80 | return resp, err 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /zpool/pool_benchmark_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | go test -bench='.*' -benchmem -run none -race ./zpool 3 | */ 4 | 5 | package zpool_test 6 | 7 | import ( 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "github.com/sohaha/zlsgo/zpool" 13 | ) 14 | 15 | const runCount = 100000 16 | 17 | func demoFunc() { 18 | time.Sleep(time.Duration(10) * time.Millisecond) 19 | } 20 | 21 | func BenchmarkGoroutines(b *testing.B) { 22 | var wg sync.WaitGroup 23 | 24 | b.StartTimer() 25 | for i := 0; i < b.N; i++ { 26 | wg.Add(runCount) 27 | for j := 0; j < runCount; j++ { 28 | go func() { 29 | demoFunc() 30 | wg.Done() 31 | }() 32 | } 33 | wg.Wait() 34 | } 35 | b.StopTimer() 36 | } 37 | 38 | func BenchmarkPoolWorkerNum100(b *testing.B) { 39 | var wg sync.WaitGroup 40 | p := zpool.New(100) 41 | defer p.Close() 42 | _ = p.PreInit() 43 | b.StartTimer() 44 | for i := 0; i < b.N; i++ { 45 | wg.Add(runCount) 46 | for j := 0; j < runCount; j++ { 47 | _ = p.Do(func() { 48 | demoFunc() 49 | wg.Done() 50 | }) 51 | } 52 | wg.Wait() 53 | } 54 | b.StopTimer() 55 | } 56 | 57 | func BenchmarkPoolWorkerNum500(b *testing.B) { 58 | var wg sync.WaitGroup 59 | p := zpool.New(500) 60 | defer p.Close() 61 | 62 | b.StartTimer() 63 | for i := 0; i < b.N; i++ { 64 | wg.Add(runCount) 65 | for j := 0; j < runCount; j++ { 66 | _ = p.Do(func() { 67 | demoFunc() 68 | wg.Done() 69 | }) 70 | } 71 | wg.Wait() 72 | } 73 | b.StopTimer() 74 | } 75 | 76 | func BenchmarkPoolWorkerNum1500(b *testing.B) { 77 | var wg sync.WaitGroup 78 | p := zpool.New(1500) 79 | defer p.Close() 80 | 81 | b.StartTimer() 82 | for i := 0; i < b.N; i++ { 83 | wg.Add(runCount) 84 | for j := 0; j < runCount; j++ { 85 | _ = p.Do(func() { 86 | demoFunc() 87 | wg.Done() 88 | }) 89 | } 90 | wg.Wait() 91 | } 92 | b.StopTimer() 93 | } 94 | 95 | func BenchmarkPoolWorkerNum15000(b *testing.B) { 96 | var wg sync.WaitGroup 97 | p := zpool.New(15000) 98 | defer p.Close() 99 | 100 | b.StartTimer() 101 | for i := 0; i < b.N; i++ { 102 | wg.Add(runCount) 103 | for j := 0; j < runCount; j++ { 104 | _ = p.Do(func() { 105 | demoFunc() 106 | wg.Done() 107 | }) 108 | } 109 | wg.Wait() 110 | } 111 | b.StopTimer() 112 | } 113 | -------------------------------------------------------------------------------- /zpprof/systeminfo.go: -------------------------------------------------------------------------------- 1 | package zpprof 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/sohaha/zlsgo/zfile" 11 | ) 12 | 13 | // SystemInfo SystemInfo 14 | type SystemInfo struct { 15 | ServerName string 16 | Runtime string // runtime duration 17 | GoroutineNum string // goroutine count 18 | CPUNum string // cpu core count 19 | UsedMem string // current memory usage 20 | TotalMem string // total allocated memory 21 | SysMem string // system memory usage 22 | Lookups string // pointer lookup count 23 | Mallocs string // memory allocation count 24 | Frees string // memory release count 25 | LastGCTime string // time since last GC 26 | NextGC string // next GC memory reclaim amount 27 | PauseTotalNs string // total GC pause time 28 | PauseNs string // last GC pause time 29 | HeapInuse string // heap memory in use 30 | } 31 | 32 | func NewSystemInfo(startTime time.Time) *SystemInfo { 33 | var afterLastGC string 34 | mstat := &runtime.MemStats{} 35 | runtime.ReadMemStats(mstat) 36 | costTime := int(time.Since(startTime).Seconds()) 37 | if mstat.LastGC != 0 { 38 | afterLastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(mstat.LastGC))/1000/1000/1000) 39 | } else { 40 | afterLastGC = "0" 41 | } 42 | 43 | serverName, _ := os.Hostname() 44 | 45 | return &SystemInfo{ 46 | ServerName: serverName, 47 | Runtime: fmt.Sprintf("%d天%d小时%d分%d秒", costTime/(3600*24), costTime%(3600*24)/3600, costTime%3600/60, costTime%(60)), 48 | GoroutineNum: strconv.Itoa(runtime.NumGoroutine()), 49 | CPUNum: strconv.Itoa(runtime.NumCPU()), 50 | HeapInuse: zfile.SizeFormat(int64(mstat.HeapInuse)), 51 | UsedMem: zfile.SizeFormat(int64(mstat.Alloc)), 52 | TotalMem: zfile.SizeFormat(int64(mstat.TotalAlloc)), 53 | SysMem: zfile.SizeFormat(int64(mstat.Sys)), 54 | Lookups: strconv.FormatUint(mstat.Lookups, 10), 55 | Mallocs: strconv.FormatUint(mstat.Mallocs, 10), 56 | Frees: strconv.FormatUint(mstat.Frees, 10), 57 | LastGCTime: afterLastGC, 58 | NextGC: zfile.SizeFormat(int64(mstat.NextGC)), 59 | PauseTotalNs: fmt.Sprintf("%.3fs", float64(mstat.PauseTotalNs)/1000/1000/1000), 60 | PauseNs: fmt.Sprintf("%.3fs", float64(mstat.PauseNs[(mstat.NumGC+255)%256])/1000/1000/1000), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /znet/value.go: -------------------------------------------------------------------------------- 1 | package znet 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | 7 | "github.com/sohaha/zlsgo/zreflect" 8 | "github.com/sohaha/zlsgo/zvalid" 9 | ) 10 | 11 | // Content-Type MIME of the most common data formats. 12 | // These constants define the standard MIME types used for HTTP content negotiation. 13 | const ( 14 | // mimeJSON is the MIME type for JSON data 15 | mimeJSON = "application/json" 16 | // mimePlain is the MIME type for plain text 17 | mimePlain = "text/plain" 18 | // mimePOSTForm is the MIME type for URL-encoded form data 19 | mimePOSTForm = "application/x-www-form-urlencoded" 20 | // mimeMultipartPOSTForm is the MIME type for multipart form data (typically used for file uploads) 21 | mimeMultipartPOSTForm = "multipart/form-data" 22 | ) 23 | 24 | // valid is an internal helper function that validates struct fields using the provided validation rules. 25 | // It supports validation for basic types like string, bool, numeric types, etc. 26 | func (c *Context) valid(obj interface{}, v map[string]zvalid.Engine) error { 27 | r := make([]*zvalid.ValidEle, 0, len(v)) 28 | val := zreflect.ValueOf(obj) 29 | if val.Kind() != reflect.Ptr { 30 | return errors.New("result must be a pointer") 31 | } 32 | 33 | val = val.Elem() 34 | typ := zreflect.TypeOf(val) 35 | for i := 0; i < typ.NumField(); i++ { 36 | field := val.Field(i) 37 | name, _ := zreflect.GetStructTag(typ.Field(i)) 38 | switch field.Kind() { 39 | case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: 40 | value := field.Interface() 41 | if rv, ok := v[name]; ok { 42 | r = append(r, zvalid.BatchVar(field, rv.VerifiAny(value))) 43 | } 44 | case reflect.Struct: 45 | case reflect.Slice: 46 | default: 47 | return errors.New("value validation for " + name + " is not supported") 48 | } 49 | } 50 | 51 | return zvalid.Batch(r...) 52 | } 53 | 54 | // BindValid binds request data to the provided object and validates it using the provided validation rules. 55 | // It first binds the data using the appropriate method based on request type, then validates the bound data. 56 | func (c *Context) BindValid(obj interface{}, v map[string]zvalid.Engine) error { 57 | err := c.Bind(obj) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return c.valid(obj, v) 63 | } 64 | -------------------------------------------------------------------------------- /zsync/waitgroup_test.go: -------------------------------------------------------------------------------- 1 | package zsync 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/zlog" 10 | "github.com/sohaha/zlsgo/zutil" 11 | ) 12 | 13 | func TestWaitGroup(t *testing.T) { 14 | t.Run("base", func(t *testing.T) { 15 | tt := zlsgo.NewTest(t) 16 | t.Parallel() 17 | count := zutil.NewInt64(0) 18 | var wg WaitGroup 19 | for i := 0; i < 100; i++ { 20 | wg.Go(func() { 21 | count.Add(1) 22 | }) 23 | } 24 | err := wg.Wait() 25 | tt.NoError(err) 26 | tt.Equal(int64(100), count.Load()) 27 | }) 28 | 29 | t.Run("err", func(t *testing.T) { 30 | tt := zlsgo.NewTest(t) 31 | t.Parallel() 32 | count := zutil.NewInt64(0) 33 | var wg WaitGroup 34 | for i := 0; i < 100; i++ { 35 | ii := i 36 | wg.GoTry(func() { 37 | count.Add(1) 38 | if ii > 0 && ii%5 == 0 { 39 | panic("manual panic") 40 | } 41 | }) 42 | } 43 | err := wg.Wait() 44 | tt.EqualTrue(err != nil) 45 | t.Logf("%+v", err) 46 | zlog.Error(err) 47 | tt.Equal(int64(100), count.Load()) 48 | }) 49 | } 50 | 51 | func TestWaitGroupMax(t *testing.T) { 52 | tt := zlsgo.NewTest(t) 53 | tt.Run("base", func(tt *zlsgo.TestUtil) { 54 | var wg WaitGroup 55 | count := zutil.NewInt64(0) 56 | now := time.Now() 57 | for i := 0; i < 100; i++ { 58 | wg.Go(func() { 59 | count.Add(1) 60 | time.Sleep(time.Second / 5) 61 | }) 62 | } 63 | err := wg.Wait() 64 | tt.NoError(err) 65 | tt.Log("time", time.Since(now)) 66 | tt.EqualTrue(time.Since(now) < time.Second) 67 | tt.Equal(int64(100), count.Load()) 68 | }) 69 | 70 | tt.Run("max", func(tt *zlsgo.TestUtil) { 71 | wg := NewWaitGroup(10) 72 | count := zutil.NewInt64(0) 73 | now := time.Now() 74 | for i := 0; i < 100; i++ { 75 | wg.Go(func() { 76 | count.Add(1) 77 | time.Sleep(time.Second / 5) 78 | }) 79 | } 80 | err := wg.Wait() 81 | tt.NoError(err) 82 | tt.Log("time", time.Since(now)) 83 | tt.EqualTrue(time.Since(now) > time.Second) 84 | tt.Equal(int64(100), count.Load()) 85 | }) 86 | } 87 | 88 | func BenchmarkWaitGroup_Go(b *testing.B) { 89 | var wg sync.WaitGroup 90 | var count int64 91 | b.ResetTimer() 92 | for i := 0; i < b.N; i++ { 93 | wg.Add(1) 94 | go func() { 95 | count++ 96 | wg.Done() 97 | }() 98 | } 99 | wg.Wait() 100 | } 101 | -------------------------------------------------------------------------------- /zutil/other.go: -------------------------------------------------------------------------------- 1 | package zutil 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "unsafe" 7 | ) 8 | 9 | func UnescapeHTML(s string) string { 10 | s = strings.Replace(s, "\\u003c", "<", -1) 11 | s = strings.Replace(s, "\\u003e", ">", -1) 12 | return strings.Replace(s, "\\u0026", "&", -1) 13 | } 14 | 15 | func KeySignature(key interface{}) string { 16 | var b strings.Builder 17 | b.Grow(64) 18 | appendKeyRepr(&b, key) 19 | return b.String() 20 | } 21 | 22 | func appendKeyRepr(b *strings.Builder, key interface{}) { 23 | switch v := key.(type) { 24 | case string: 25 | b.WriteByte('s') 26 | b.WriteString(v) 27 | case int: 28 | b.WriteByte('i') 29 | appendInt(b, int64(v)) 30 | case int8: 31 | b.WriteByte('a') 32 | appendInt(b, int64(v)) 33 | case int16: 34 | b.WriteByte('b') 35 | appendInt(b, int64(v)) 36 | case int32: 37 | b.WriteByte('c') 38 | appendInt(b, int64(v)) 39 | case int64: 40 | b.WriteByte('d') 41 | appendInt(b, v) 42 | case uint: 43 | b.WriteByte('u') 44 | appendUint(b, uint64(v), 10) 45 | case uint8: 46 | b.WriteByte('v') 47 | appendUint(b, uint64(v), 10) 48 | case uint16: 49 | b.WriteByte('w') 50 | appendUint(b, uint64(v), 10) 51 | case uint32: 52 | b.WriteByte('x') 53 | appendUint(b, uint64(v), 10) 54 | case uint64: 55 | b.WriteByte('y') 56 | appendUint(b, v, 10) 57 | case uintptr: 58 | b.WriteByte('p') 59 | appendUint(b, uint64(v), 16) 60 | case unsafe.Pointer: 61 | b.WriteByte('P') 62 | appendUint(b, uint64(uintptr(v)), 16) 63 | case float32: 64 | b.WriteByte('f') 65 | appendFloat(b, float64(v), 32) 66 | case float64: 67 | b.WriteByte('F') 68 | appendFloat(b, v, 64) 69 | case complex64: 70 | b.WriteByte('g') 71 | appendFloat(b, float64(real(v)), 32) 72 | b.WriteByte(',') 73 | appendFloat(b, float64(imag(v)), 32) 74 | case complex128: 75 | b.WriteByte('G') 76 | appendFloat(b, real(v), 64) 77 | b.WriteByte(',') 78 | appendFloat(b, imag(v), 64) 79 | default: 80 | b.WriteByte('?') 81 | } 82 | } 83 | 84 | func appendInt(b *strings.Builder, v int64) { 85 | var buf [32]byte 86 | b.Write(strconv.AppendInt(buf[:0], v, 10)) 87 | } 88 | 89 | func appendUint(b *strings.Builder, v uint64, base int) { 90 | var buf [32]byte 91 | b.Write(strconv.AppendUint(buf[:0], v, base)) 92 | } 93 | 94 | func appendFloat(b *strings.Builder, v float64, bitSize int) { 95 | var buf [64]byte 96 | b.Write(strconv.AppendFloat(buf[:0], v, 'g', -1, bitSize)) 97 | } 98 | -------------------------------------------------------------------------------- /ztime/time.go: -------------------------------------------------------------------------------- 1 | // Package ztime provides time related operations 2 | package ztime 3 | 4 | import ( 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | var inlay = New() 10 | 11 | // Now format current time 12 | func Now(format ...string) string { 13 | return inlay.FormatTime(UnixMicro(Clock()), format...) 14 | } 15 | 16 | // Time With the time zone of the time 17 | func Time(realTime ...bool) time.Time { 18 | if len(realTime) > 0 && realTime[0] { 19 | return inlay.In(time.Now()) 20 | } 21 | return inlay.In(UnixMicro(Clock())) 22 | } 23 | 24 | // SetTimeZone SetTimeZone 25 | func SetTimeZone(zone int) *TimeEngine { 26 | return inlay.SetTimeZone(zone) 27 | } 28 | 29 | // GetTimeZone getTimeZone 30 | func GetTimeZone() *time.Location { 31 | return inlay.GetTimeZone() 32 | } 33 | 34 | // FormatTime format time 35 | func FormatTime(t time.Time, format ...string) string { 36 | return inlay.FormatTime(t, format...) 37 | } 38 | 39 | // FormatTimestamp format timestamp 40 | func FormatTimestamp(timestamp int64, format ...string) string { 41 | return inlay.FormatTimestamp(timestamp, format...) 42 | } 43 | 44 | func Week(t time.Time) int { 45 | return inlay.Week(t) 46 | } 47 | 48 | func MonthRange(year int, month int) (beginTime, endTime int64, err error) { 49 | return inlay.MonthRange(year, month) 50 | } 51 | 52 | // Parse string to time 53 | func Parse(str string, format ...string) (time.Time, error) { 54 | return inlay.Parse(str, format...) 55 | } 56 | 57 | // Unix int to time 58 | func Unix(tt int64) time.Time { 59 | return inlay.Unix(tt) 60 | } 61 | 62 | // UnixMicro int to time 63 | func UnixMicro(tt int64) time.Time { 64 | return inlay.UnixMicro(tt) 65 | } 66 | 67 | // In time to time 68 | func In(tt time.Time) time.Time { 69 | return inlay.In(tt) 70 | } 71 | 72 | var clock int64 73 | 74 | func init() { 75 | atomic.StoreInt64(&clock, time.Now().UnixNano()/1000) 76 | 77 | go func() { 78 | const updateInterval = 10 * time.Millisecond 79 | const microsecondsPerUpdate = int64(10000) 80 | 81 | ticker := time.NewTicker(updateInterval) 82 | defer ticker.Stop() 83 | 84 | for { 85 | atomic.StoreInt64(&clock, time.Now().UnixNano()/1000) 86 | for i := 0; i < 10; i++ { 87 | <-ticker.C 88 | atomic.AddInt64(&clock, microsecondsPerUpdate) 89 | } 90 | <-ticker.C 91 | } 92 | }() 93 | } 94 | 95 | // Clock The current microsecond timestamp has an accuracy of 100ms 96 | func Clock() int64 { 97 | return atomic.LoadInt64(&clock) 98 | } 99 | -------------------------------------------------------------------------------- /znet/session/session.go: -------------------------------------------------------------------------------- 1 | // Package session provides HTTP session management for znet web applications. 2 | // It supports configurable session storage backends and automatic session handling. 3 | package session 4 | 5 | import ( 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo/zdi" 9 | "github.com/sohaha/zlsgo/znet" 10 | "github.com/sohaha/zlsgo/zstring" 11 | "github.com/sohaha/zlsgo/zutil" 12 | ) 13 | 14 | type ( 15 | // Config holds the configuration for session management. 16 | // It allows customization of cookie settings and session behavior. 17 | Config struct { 18 | CookieName string 19 | ExpiresAt time.Duration 20 | AutoRenew bool 21 | } 22 | ) 23 | 24 | // Default creates a new session handler with the default memory store. 25 | // It accepts optional configuration functions to customize the session behavior. 26 | // The returned handler can be used as middleware in znet applications. 27 | func Default(opt ...func(*Config)) znet.Handler { 28 | return New(NewMemoryStore(), opt...) 29 | } 30 | 31 | // New creates a new session handler with the specified store implementation. 32 | // It allows customizing session behavior through configuration options. 33 | // The handler manages session lifecycle including creation, retrieval, and renewal. 34 | func New(stores Store, opt ...func(*Config)) znet.Handler { 35 | conf := zutil.Optional(Config{ 36 | CookieName: "session_id", 37 | ExpiresAt: 30 * time.Minute, 38 | }, opt...) 39 | 40 | return func(c *znet.Context) error { 41 | id := c.GetCookie(conf.CookieName) 42 | if id == "" { 43 | id = zstring.UUID() 44 | } 45 | 46 | s, err := stores.Get(id) 47 | if err != nil { 48 | expiresAt := time.Now().Add(conf.ExpiresAt) 49 | s, err = stores.New(id, expiresAt) 50 | if err != nil { 51 | return err 52 | } 53 | c.SetCookie(conf.CookieName, id, int(conf.ExpiresAt.Seconds())) 54 | } 55 | 56 | _ = c.Injector().Map(s) 57 | 58 | _ = c.Next() 59 | 60 | if conf.AutoRenew && time.Until(s.ExpiresAt()) < conf.ExpiresAt/2 { 61 | stores.Renew(id, time.Now().Add(conf.ExpiresAt)) 62 | } 63 | 64 | return nil 65 | } 66 | } 67 | 68 | // Get retrieves the current session from the context. 69 | // It returns the session if it exists, or an error if no session is found. 70 | // This function is typically used within request handlers to access session data. 71 | func Get(c *znet.Context) (s Session, err error) { 72 | err = c.Injector().(zdi.Invoker).Resolve(&s) 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /zfile/lock_test.go: -------------------------------------------------------------------------------- 1 | package zfile 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo" 9 | ) 10 | 11 | func TestFileLock(t *testing.T) { 12 | tt := zlsgo.NewTest(t) 13 | lockFile := TmpPath() + "/test.lock" 14 | 15 | defer Remove(lockFile) 16 | 17 | lock1 := NewFileLock(lockFile) 18 | lock2 := NewFileLock(lockFile) 19 | 20 | err := lock1.Lock() 21 | tt.NoError(err) 22 | 23 | err = lock1.Lock() 24 | tt.NoError(err) 25 | err = lock2.Lock() 26 | tt.Equal(ErrLocked, err) 27 | 28 | err = lock1.Unlock() 29 | tt.NoError(err) 30 | 31 | err = lock1.Unlock() 32 | tt.Equal(ErrNotLocked, err) 33 | 34 | err = lock2.Lock() 35 | tt.NoError(err) 36 | err = lock2.Unlock() 37 | tt.NoError(err) 38 | 39 | done := make(chan bool) 40 | go func() { 41 | lock := NewFileLock(lockFile) 42 | err := lock.Lock() 43 | tt.NoError(err) 44 | time.Sleep(100 * time.Millisecond) 45 | err = lock.Unlock() 46 | tt.NoError(err) 47 | done <- true 48 | }() 49 | 50 | time.Sleep(50 * time.Millisecond) 51 | lock := NewFileLock(lockFile) 52 | err = lock.Lock() 53 | tt.Equal(ErrLocked, err) 54 | <-done 55 | err = lock.Lock() 56 | tt.NoError(err) 57 | err = lock.Unlock() 58 | tt.NoError(err) 59 | } 60 | 61 | func TestFileLockCleanup(t *testing.T) { 62 | tt := zlsgo.NewTest(t) 63 | lockFile := TmpPath() + "/cleanup.lock" 64 | 65 | lock := NewFileLock(lockFile) 66 | err := lock.Lock() 67 | tt.NoError(err) 68 | 69 | err = lock.Clean() 70 | tt.NoError(err) 71 | tt.EqualTrue(!FileExist(lockFile)) 72 | 73 | err = lock.Clean() 74 | tt.NotNil(err) 75 | } 76 | 77 | func TestFileLockConcurrent(t *testing.T) { 78 | tt := zlsgo.NewTest(t) 79 | lockFile := TmpPath() + "/concurrent.lock" 80 | defer Remove(lockFile) 81 | 82 | const goroutines = 10 83 | var wg sync.WaitGroup 84 | errors := make(chan error, goroutines) 85 | 86 | for i := 0; i < goroutines; i++ { 87 | wg.Add(1) 88 | go func() { 89 | defer wg.Done() 90 | lock := NewFileLock(lockFile) 91 | if err := lock.Lock(); err != nil { 92 | if err != ErrLocked { 93 | errors <- err 94 | } 95 | return 96 | } 97 | time.Sleep(10 * time.Millisecond) 98 | if err := lock.Unlock(); err != nil { 99 | errors <- err 100 | } 101 | }() 102 | } 103 | 104 | wg.Wait() 105 | close(errors) 106 | 107 | for err := range errors { 108 | tt.NoError(err) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /zreflect/utils_test.go: -------------------------------------------------------------------------------- 1 | package zreflect 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sohaha/zlsgo" 9 | ) 10 | 11 | type ( 12 | Child2 struct { 13 | P DemoChildSt 14 | } 15 | 16 | DemoChildSt struct { 17 | ChildName int 18 | } 19 | 20 | DemoSt struct { 21 | Date2 time.Time 22 | Any interface{} 23 | any interface{} 24 | Child3 *DemoChildSt 25 | Remark string `json:"remark"` 26 | note string 27 | Name string `json:"username"` 28 | pri string 29 | Hobby []string 30 | Slice [][]string 31 | Child struct { 32 | Title string `json:"child_user_title"` 33 | DemoChild2 Child2 `json:"demo_child_2"` 34 | IsChildName bool 35 | } `json:"child"` 36 | Year float64 37 | Child2 Child2 38 | child4 DemoChildSt 39 | Age uint 40 | Lovely bool 41 | } 42 | TestSt struct { 43 | Name string 44 | I int `z:"iii"` 45 | Note int `json:"note,omitempty"` 46 | } 47 | ) 48 | 49 | func (d DemoSt) Text() string { 50 | return d.Name + ":" + d.Remark 51 | } 52 | 53 | func TestNonzero(t *testing.T) { 54 | tt := zlsgo.NewTest(t) 55 | 56 | tt.Equal(true, Nonzero(ValueOf(true))) 57 | tt.Equal(false, Nonzero(ValueOf(0))) 58 | tt.Equal(true, Nonzero(ValueOf(1))) 59 | tt.Equal(true, Nonzero(ValueOf("0"))) 60 | tt.Equal(true, Nonzero(ValueOf("1"))) 61 | tt.Equal(true, Nonzero(ValueOf(1.1))) 62 | var s []string 63 | tt.Equal(false, Nonzero(ValueOf(s))) 64 | tt.Equal(true, Nonzero(ValueOf([]string{}))) 65 | tt.Equal(false, Nonzero(ValueOf([...]string{}))) 66 | tt.Equal(true, Nonzero(ValueOf(map[string]string{}))) 67 | tt.Equal(true, Nonzero(ValueOf(map[string]string{"a": "b"}))) 68 | } 69 | 70 | func TestCan(t *testing.T) { 71 | tt := zlsgo.NewTest(t) 72 | 73 | tt.Equal(true, IsLabel(TypeOf(Demo))) 74 | 75 | tt.Equal(false, CanExpand(TypeOf(1))) 76 | tt.Equal(true, CanExpand(TypeOf([]string{}))) 77 | 78 | tt.Equal(true, CanInline(TypeOf(map[string]string{}))) 79 | tt.Equal(true, CanInline(TypeOf([]string{}))) 80 | tt.Equal(true, CanInline(TypeOf("1"))) 81 | tt.Equal(false, CanInline(TypeOf(&Demo))) 82 | tt.Equal(false, CanInline(TypeOf(Demo))) 83 | tt.Equal(false, CanInline(TypeOf(func() {}))) 84 | } 85 | 86 | func TestGetAbbrKind(t *testing.T) { 87 | tt := zlsgo.NewTest(t) 88 | 89 | tt.Equal(reflect.Int, GetAbbrKind(ValueOf(1))) 90 | tt.Equal(reflect.Uint, GetAbbrKind(ValueOf(uint64(1)))) 91 | tt.Equal(reflect.Float64, GetAbbrKind(ValueOf(float32(1)))) 92 | tt.Equal(reflect.Struct, GetAbbrKind(ValueOf(Demo))) 93 | } 94 | -------------------------------------------------------------------------------- /zvalid/format.go: -------------------------------------------------------------------------------- 1 | package zvalid 2 | 3 | import ( 4 | "strings" 5 | 6 | "golang.org/x/crypto/bcrypt" 7 | 8 | "github.com/sohaha/zlsgo/zstring" 9 | ) 10 | 11 | // Trim remove leading and trailing spaces 12 | func (v Engine) Trim() Engine { 13 | return pushQueue(&v, func(v *Engine) *Engine { 14 | if notEmpty(v) { 15 | v.value = zstring.TrimSpace(v.value) 16 | } 17 | 18 | return v 19 | }) 20 | } 21 | 22 | // RemoveSpace remove all spaces 23 | func (v Engine) RemoveSpace() Engine { 24 | return pushQueue(&v, func(v *Engine) *Engine { 25 | if notEmpty(v) { 26 | v.value = strings.Replace(v.value, " ", "", -1) 27 | } 28 | return v 29 | }) 30 | } 31 | 32 | // Replace replace text 33 | func (v Engine) Replace(old, new string, n int) Engine { 34 | return pushQueue(&v, func(v *Engine) *Engine { 35 | if notEmpty(v) { 36 | v.value = strings.Replace(v.value, old, new, n) 37 | } 38 | return v 39 | }) 40 | } 41 | 42 | // ReplaceAll replace all text 43 | func (v Engine) ReplaceAll(old, new string) Engine { 44 | return v.Replace(old, new, -1) 45 | } 46 | 47 | // XSSClean clean html tag 48 | func (v Engine) XSSClean() Engine { 49 | return pushQueue(&v, func(v *Engine) *Engine { 50 | if notEmpty(v) { 51 | v.value = zstring.XSSClean(v.value) 52 | } 53 | return v 54 | }) 55 | } 56 | 57 | // SnakeCaseToCamelCase snakeCase To CamelCase: hello_world => helloWorld 58 | func (v Engine) SnakeCaseToCamelCase(ucfirst bool, delimiter ...string) Engine { 59 | return pushQueue(&v, func(v *Engine) *Engine { 60 | if notEmpty(v) { 61 | v.value = zstring.SnakeCaseToCamelCase(v.value, ucfirst, delimiter...) 62 | } 63 | return v 64 | }) 65 | } 66 | 67 | // CamelCaseToSnakeCase camelCase To SnakeCase helloWorld/HelloWorld => hello_world 68 | func (v Engine) CamelCaseToSnakeCase(delimiter ...string) Engine { 69 | return pushQueue(&v, func(v *Engine) *Engine { 70 | if notEmpty(v) { 71 | v.value = zstring.CamelCaseToSnakeCase(v.value, delimiter...) 72 | } 73 | return v 74 | }) 75 | } 76 | 77 | // EncryptPassword encrypt the password 78 | func (v Engine) EncryptPassword(cost ...int) Engine { 79 | return pushQueue(&v, func(v *Engine) *Engine { 80 | if notEmpty(v) { 81 | bcost := bcrypt.DefaultCost 82 | if len(cost) > 0 { 83 | bcost = cost[0] 84 | } 85 | if bytes, err := bcrypt.GenerateFromPassword(zstring.String2Bytes(v.value), bcost); err == nil { 86 | v.value = zstring.Bytes2String(bytes) 87 | } else { 88 | v.err = err 89 | } 90 | } 91 | return v 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /ztype/decima_test.go: -------------------------------------------------------------------------------- 1 | package ztype_test 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "testing" 7 | 8 | "github.com/sohaha/zlsgo" 9 | "github.com/sohaha/zlsgo/ztype" 10 | ) 11 | 12 | func TestDecimal(tt *testing.T) { 13 | t := zlsgo.NewTest(tt) 14 | 15 | t.Equal("10100", strconv.FormatInt(20, 2)) 16 | t.Equal("10100", fmt.Sprintf("%b", 20)) 17 | t.Equal("10100", ztype.DecimalToAny(20, 2)) 18 | 19 | t.Equal(strconv.FormatInt(20, 2), ztype.DecimalToAny(20, 2)) 20 | t.Equal(strconv.FormatInt(20, 8), ztype.DecimalToAny(20, 8)) 21 | t.Equal(strconv.FormatInt(20, 16), ztype.DecimalToAny(20, 16)) 22 | t.Equal(strconv.FormatInt(20, 32), ztype.DecimalToAny(20, 32)) 23 | t.Equal(strconv.FormatInt(20, 32), ztype.DecimalToAny(20, 31)) 24 | 25 | t.Equal("10100", ztype.DecimalToAny(20, 2)) 26 | t.Equal("2Bi", ztype.DecimalToAny(10000, 62)) 27 | t.Equal(10000, ztype.AnyToDecimal("2Bi", 62)) 28 | t.Equal(0, ztype.AnyToDecimal("20", 0)) 29 | t.Equal(2281, ztype.AnyToDecimal("2Bi", 31)) 30 | 31 | tt.Log(ztype.DecimalToAny(65, 66)) 32 | tt.Log(strconv.FormatInt(65, 9), ztype.DecimalToAny(65, 9)) 33 | t.Equal("10011100010000", ztype.DecimalToAny(10000, 2)) 34 | } 35 | 36 | func BenchmarkDecimalStrconv2(b *testing.B) { 37 | for i := 0; i < b.N; i++ { 38 | v := strconv.FormatInt(20, 2) 39 | _ = v 40 | } 41 | } 42 | 43 | func BenchmarkDecimalZtype2(b *testing.B) { 44 | for i := 0; i < b.N; i++ { 45 | v := ztype.DecimalToAny(20, 2) 46 | _ = v 47 | } 48 | } 49 | 50 | func BenchmarkDecimalStrconv8(b *testing.B) { 51 | for i := 0; i < b.N; i++ { 52 | v := strconv.FormatInt(20, 8) 53 | _, _ = strconv.ParseInt(v, 8, 64) 54 | } 55 | } 56 | 57 | func BenchmarkDecimalZtype8(b *testing.B) { 58 | for i := 0; i < b.N; i++ { 59 | v := ztype.DecimalToAny(20, 8) 60 | _ = ztype.AnyToDecimal(v, 8) 61 | } 62 | } 63 | 64 | func BenchmarkDecimalStrconv16(b *testing.B) { 65 | for i := 0; i < b.N; i++ { 66 | v := strconv.FormatInt(20, 16) 67 | _, _ = strconv.ParseInt(v, 16, 64) 68 | } 69 | } 70 | 71 | func BenchmarkDecimalZtype16(b *testing.B) { 72 | for i := 0; i < b.N; i++ { 73 | v := ztype.DecimalToAny(20, 16) 74 | _ = ztype.AnyToDecimal(v, 16) 75 | } 76 | } 77 | 78 | func BenchmarkDecimalStrconv32(b *testing.B) { 79 | for i := 0; i < b.N; i++ { 80 | v := strconv.FormatInt(20, 32) 81 | _, _ = strconv.ParseInt(v, 32, 64) 82 | } 83 | } 84 | 85 | func BenchmarkDecimalZtype32(b *testing.B) { 86 | for i := 0; i < b.N; i++ { 87 | v := ztype.DecimalToAny(20, 32) 88 | _ = ztype.AnyToDecimal(v, 32) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /zfile/lock.go: -------------------------------------------------------------------------------- 1 | package zfile 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | // ErrLocked is returned when attempting to lock a file that is already locked 11 | ErrLocked = errors.New("file is locked") 12 | // ErrNotLocked is returned when attempting to unlock a file that is not locked 13 | ErrNotLocked = errors.New("file is not locked") 14 | ) 15 | 16 | // FileLock provides cross-process file locking capabilities. 17 | // It can be used to synchronize access to resources between multiple processes. 18 | type FileLock struct { 19 | file *os.File // The locked file handle 20 | path string // Path to the lock file 21 | mu sync.Mutex // Mutex for thread-safety within the process 22 | } 23 | 24 | // NewFileLock creates a new file lock instance for the specified path. 25 | // The lock is not acquired until Lock() is called. 26 | func NewFileLock(path string) *FileLock { 27 | return &FileLock{ 28 | path: RealPath(path), 29 | } 30 | } 31 | 32 | // Lock acquires the file lock, blocking other processes from acquiring it. 33 | // If the lock is already held by another process, it returns ErrLocked. 34 | // If the lock is already held by this instance, it returns nil immediately. 35 | func (l *FileLock) Lock() error { 36 | l.mu.Lock() 37 | defer l.mu.Unlock() 38 | if l.file != nil { 39 | return nil 40 | } 41 | 42 | f, err := os.OpenFile(l.path, os.O_CREATE|os.O_RDWR, 0o666) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | err = lockFile(f) 48 | if err != nil { 49 | f.Close() 50 | if err == errLocked { 51 | return ErrLocked 52 | } 53 | return err 54 | } 55 | 56 | l.file = f 57 | return nil 58 | } 59 | 60 | // Unlock releases the file lock, allowing other processes to acquire it. 61 | // If the lock is not currently held by this instance, it returns ErrNotLocked. 62 | func (l *FileLock) Unlock() error { 63 | l.mu.Lock() 64 | defer l.mu.Unlock() 65 | 66 | if l.file == nil { 67 | return ErrNotLocked 68 | } 69 | 70 | err := unlockFile(l.file) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | err = l.file.Close() 76 | l.file = nil 77 | return err 78 | } 79 | 80 | // Clean releases the lock if held and removes the lock file from the filesystem. 81 | // This should be called when the lock is no longer needed to clean up resources. 82 | func (l *FileLock) Clean() error { 83 | l.mu.Lock() 84 | defer l.mu.Unlock() 85 | 86 | if l.file != nil { 87 | _ = unlockFile(l.file) 88 | _ = l.file.Close() 89 | l.file = nil 90 | } 91 | return Remove(l.path) 92 | } 93 | -------------------------------------------------------------------------------- /zarray/sort.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package zarray 5 | 6 | // SortMaper implements an ordered map that maintains insertion order of keys 7 | // while providing map-like operations for key-value pairs. 8 | type SortMaper[K hashable, V any] struct { 9 | values *Maper[K, V] 10 | keys []K 11 | } 12 | 13 | // NewSortMap creates a new SortMaper with the specified initial capacity. 14 | // The SortMaper maintains the insertion order of keys while providing map operations. 15 | func NewSortMap[K hashable, V any](size ...uintptr) *SortMaper[K, V] { 16 | return &SortMaper[K, V]{ 17 | keys: make([]K, 0), 18 | values: NewHashMap[K, V](size...), 19 | } 20 | } 21 | 22 | // Set adds or updates a key-value pair in the map. 23 | // If the key is new, it is appended to the ordered keys list. 24 | func (s *SortMaper[K, V]) Set(key K, value V) { 25 | if !s.values.Has(key) { 26 | s.keys = append(s.keys, key) 27 | } 28 | s.values.Set(key, value) 29 | } 30 | 31 | // Get retrieves a value by its key. 32 | // Returns the value and a boolean indicating whether the key was found. 33 | func (s *SortMaper[K, V]) Get(key K) (value V, ok bool) { 34 | return s.values.Get(key) 35 | } 36 | 37 | // Has checks if a key exists in the map. 38 | // Returns true if the key exists, false otherwise. 39 | func (s *SortMaper[K, V]) Has(key K) (ok bool) { 40 | for _, v := range s.keys { 41 | if v == key { 42 | return true 43 | } 44 | } 45 | return false 46 | } 47 | 48 | // Delete removes one or more key-value pairs from the map. 49 | // This removes the keys from both the ordered keys list and the underlying map. 50 | func (s *SortMaper[K, V]) Delete(key ...K) { 51 | for i, v := range s.keys { 52 | for _, k := range key { 53 | if v == k { 54 | s.keys = append(s.keys[:i], s.keys[i+1:]...) 55 | break 56 | } 57 | } 58 | } 59 | s.values.Delete(key...) 60 | } 61 | 62 | // Len returns the number of key-value pairs in the map. 63 | func (s *SortMaper[K, V]) Len() int { 64 | return len(s.keys) 65 | } 66 | 67 | // Keys returns all keys in the map in their insertion order. 68 | func (s *SortMaper[K, V]) Keys() []K { 69 | return s.keys 70 | } 71 | 72 | // ForEach iterates through all key-value pairs in the map in insertion order. 73 | // The iteration continues as long as the lambda function returns true. 74 | func (s *SortMaper[K, V]) ForEach(lambda func(K, V) bool) { 75 | for i := range s.keys { 76 | v, ok := s.values.Get(s.keys[i]) 77 | if ok && lambda(s.keys[i], v) { 78 | continue 79 | } 80 | break 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /zdi/di.go: -------------------------------------------------------------------------------- 1 | // Package zdi provides dependency injection 2 | package zdi 3 | 4 | import ( 5 | "reflect" 6 | ) 7 | 8 | type ( 9 | Injector interface { 10 | Invoker 11 | TypeMapper 12 | Set(reflect.Type, reflect.Value) 13 | Get(reflect.Type) (reflect.Value, bool) 14 | SetParent(Injector) 15 | } 16 | Invoker interface { 17 | Apply(Pointer) error 18 | Resolve(...Pointer) error 19 | Invoke(interface{}) ([]reflect.Value, error) 20 | InvokeWithErrorOnly(interface{}) error 21 | } 22 | TypeMapper interface { 23 | Map(interface{}, ...Option) reflect.Type 24 | Maps(...interface{}) []reflect.Type 25 | Provide(interface{}, ...Option) []reflect.Type 26 | } 27 | ) 28 | 29 | type ( 30 | Pointer interface{} 31 | Option func(*mapOption) 32 | mapOption struct { 33 | key reflect.Type 34 | } 35 | injector struct { 36 | values map[reflect.Type]reflect.Value 37 | providers map[reflect.Type]reflect.Value 38 | parent Injector 39 | } 40 | ) 41 | 42 | // New creates and returns a new Injector. 43 | // Optionally, a parent Injector can be provided to enable hierarchical dependency resolution. 44 | func New(parent ...Injector) Injector { 45 | inj := &injector{ 46 | values: make(map[reflect.Type]reflect.Value), 47 | providers: make(map[reflect.Type]reflect.Value), 48 | } 49 | if len(parent) > 0 { 50 | inj.parent = parent[0] 51 | } 52 | return inj 53 | } 54 | 55 | // SetParent sets the parent Injector for the current injector. 56 | // This allows for chaining injectors, enabling a hierarchical lookup for dependencies. 57 | func (inj *injector) SetParent(parent Injector) { 58 | inj.parent = parent 59 | } 60 | 61 | // WithInterface is an option used with Map or Provide methods. 62 | // It specifies the interface type that a concrete type should be mapped to. 63 | // The argument must be a pointer to an interface type, e.g., (*MyInterface)(nil). 64 | func WithInterface(ifacePtr Pointer) Option { 65 | return func(opt *mapOption) { 66 | opt.key = ifeOf(ifacePtr) 67 | } 68 | } 69 | 70 | // ifeOf returns the underlying reflect.Type of an interface pointer. 71 | // It is used internally to extract the interface type for mapping. 72 | // Panics if the provided value is not a pointer to an interface. 73 | func ifeOf(value interface{}) reflect.Type { 74 | t := reflect.TypeOf(value) 75 | for t.Kind() == reflect.Ptr { 76 | t = t.Elem() 77 | } 78 | 79 | if t.Kind() != reflect.Interface { 80 | panic("called inject.key with a value that is not a pointer to an interface. (*MyInterface)(nil)") 81 | } 82 | return t 83 | } 84 | -------------------------------------------------------------------------------- /zvalid/bind_test.go: -------------------------------------------------------------------------------- 1 | package zvalid 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | ) 8 | 9 | func TestVar(t *testing.T) { 10 | tt := zlsgo.NewTest(t) 11 | 12 | var str string 13 | err := Var(&str, Text("is var").RemoveSpace()) 14 | tt.EqualNil(err) 15 | tt.Equal("isvar", str) 16 | 17 | var i int 18 | err = Var(&i, Text("is var").RemoveSpace()) 19 | tt.Equal(true, err != nil) 20 | tt.Equal(0, i) 21 | err = Var(&i, Text("99").RemoveSpace()) 22 | tt.EqualNil(err) 23 | tt.Equal(99, i) 24 | 25 | var iu uint 26 | err = Var(&iu, Text("99").RemoveSpace()) 27 | tt.EqualNil(err) 28 | tt.Equal(uint(99), iu) 29 | 30 | var f32 float32 31 | val := Text("99.0") 32 | err = Var(&f32, val) 33 | tt.EqualNil(err) 34 | tt.Equal(float32(99), f32) 35 | 36 | var sts []string 37 | err = Var(&sts, Text("1,2,3,go").Separator(",")) 38 | tt.EqualNil(err) 39 | tt.Equal([]string{"1", "2", "3", "go"}, sts) 40 | 41 | var data struct { 42 | Name string 43 | } 44 | 45 | err = Batch( 46 | BatchVar(&data.Name, Text("yes name")), 47 | ) 48 | tt.EqualNil(err) 49 | tt.Equal("yes name", data.Name) 50 | } 51 | 52 | func TestVarDefault(t *testing.T) { 53 | tt := zlsgo.NewTest(t) 54 | 55 | var email string 56 | err := Var(&email, Text("email").IsMail()) 57 | t.Log(email, err) 58 | tt.EqualExit(email, "") 59 | tt.EqualTrue(err != nil) 60 | 61 | err = Var(&email, Text("email").IsMail().Default(666)) 62 | t.Log(email, err) 63 | tt.EqualExit(email, "") 64 | tt.EqualTrue(err != nil) 65 | 66 | err = Var(&email, Text("email").Silent().IsMail().Default("qq@qq.com")) 67 | t.Log(email, err) 68 | tt.EqualExit("qq@qq.com", email) 69 | tt.NoError(err) 70 | 71 | err = Var(&email, Text("email").IsMail().Default("qq@qq.com")) 72 | t.Log(email, err) 73 | tt.EqualExit(email, "qq@qq.com") 74 | tt.NoError(err) 75 | 76 | var nu int 77 | err = Var(&nu, Text("Number").IsNumber().Default(123)) 78 | t.Log(nu, err) 79 | tt.NoError(err) 80 | tt.EqualExit(nu, 123) 81 | 82 | var b bool 83 | err = Var(&b, Text("true").IsBool().Default(false)) 84 | t.Log(b, err) 85 | tt.EqualTrue(err == nil) 86 | tt.EqualExit(b, true) 87 | 88 | var i uint 89 | err = Var(&i, Text("true").IsNumber().Default(uint(123))) 90 | t.Log(b, err) 91 | tt.NoError(err) 92 | tt.EqualExit(uint(123), i) 93 | 94 | var f float32 95 | err = Var(&f, Text("true").IsNumber().Default(float32(123))) 96 | t.Log(b, err) 97 | tt.NoError(err) 98 | tt.EqualExit(float32(123), f) 99 | } 100 | -------------------------------------------------------------------------------- /ztype/pathcache_test.go: -------------------------------------------------------------------------------- 1 | package ztype 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sohaha/zlsgo" 7 | ) 8 | 9 | // TestPathParsingCorrectness tests path parsing correctness 10 | func TestPathParsingCorrectness(t *testing.T) { 11 | tt := zlsgo.NewTest(t) 12 | testData := map[string]interface{}{ 13 | "user": map[string]interface{}{ 14 | "profile": map[string]interface{}{ 15 | "name": "John", 16 | "age": 30, 17 | }, 18 | }, 19 | "items": []interface{}{ 20 | map[string]interface{}{"id": 1, "name": "item1"}, 21 | map[string]interface{}{"id": 2, "name": "item2"}, 22 | }, 23 | "simple": "value", 24 | } 25 | 26 | tests := []struct { 27 | expected interface{} 28 | path string 29 | exists bool 30 | skipValue bool 31 | }{ 32 | {path: "user.profile.name", expected: "John", exists: true, skipValue: false}, 33 | {path: "user.profile.age", expected: 30, exists: true, skipValue: false}, 34 | {path: "items.0.id", expected: 1, exists: true, skipValue: false}, 35 | {path: "items.1.name", expected: "item2", exists: true, skipValue: false}, 36 | {path: "simple", expected: "value", exists: true, skipValue: false}, 37 | {path: "user.nonexistent", expected: nil, exists: false, skipValue: false}, 38 | {path: "items.5.id", expected: nil, exists: false, skipValue: false}, 39 | {path: "", expected: testData, exists: true, skipValue: true}, 40 | } 41 | 42 | for _, test := range tests { 43 | result, exists := parsePath(test.path, testData) 44 | tt.Equal(test.exists, exists) 45 | if exists && !test.skipValue { 46 | tt.Equal(test.expected, result) 47 | } 48 | } 49 | } 50 | 51 | // TestPathParsingWithEscapeChars tests escape character handling 52 | func TestPathParsingWithEscapeChars(t *testing.T) { 53 | tt := zlsgo.NewTest(t) 54 | testData := map[string]interface{}{ 55 | "user.name": "John", 56 | "user.email": "john@example.com", 57 | "data": map[string]interface{}{ 58 | "key.with.dots": "value", 59 | }, 60 | } 61 | 62 | tests := []struct { 63 | expected interface{} 64 | path string 65 | exists bool 66 | }{ 67 | {path: "user\\.name", expected: "John", exists: true}, 68 | {path: "user\\.email", expected: "john@example.com", exists: true}, 69 | {path: "data.key\\.with\\.dots", expected: "value", exists: true}, 70 | } 71 | 72 | for _, test := range tests { 73 | result, exists := parsePath(test.path, testData) 74 | tt.Logf("Path: %s, Result: %v, Exists: %v", test.path, result, exists) 75 | tt.Equal(test.exists, exists) 76 | if exists { 77 | tt.Equal(test.expected, result) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /zfile/lock_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package zfile 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | // Windows-specific constants for file locking operations 13 | const ( 14 | // lockfileExclusiveLock requests an exclusive lock 15 | lockfileExclusiveLock = 0x2 16 | // lockfileFailImmediately returns immediately if the lock cannot be acquired 17 | lockfileFailImmediately = 0x1 18 | // errorLockViolation is the error code returned when a lock is already held 19 | errorLockViolation = 0x21 20 | ) 21 | 22 | // errLocked is the error returned when a file is already locked by another process. 23 | // For consistency with Unix implementations, we use syscall.EWOULDBLOCK. 24 | var errLocked = syscall.EWOULDBLOCK 25 | 26 | // Windows API function references for file locking 27 | var ( 28 | // kernel32DLL is a reference to the Windows kernel32.dll library 29 | kernel32DLL = syscall.NewLazyDLL("kernel32.dll") 30 | // procLockFileEx is a reference to the LockFileEx function in kernel32.dll 31 | procLockFileEx = kernel32DLL.NewProc("LockFileEx") 32 | // procUnlockFileEx is a reference to the UnlockFileEx function in kernel32.dll 33 | procUnlockFileEx = kernel32DLL.NewProc("UnlockFileEx") 34 | ) 35 | 36 | // lockFile acquires an exclusive, non-blocking lock on the given file. 37 | // On Windows systems, this uses the LockFileEx Win32 API function. 38 | // Returns errLocked if the file is already locked by another process. 39 | func lockFile(f *os.File) error { 40 | h := syscall.Handle(f.Fd()) 41 | var ol syscall.Overlapped 42 | 43 | ol.Offset = 0 44 | ol.OffsetHigh = 0 45 | 46 | r1, _, err := procLockFileEx.Call( 47 | uintptr(h), 48 | uintptr(lockfileExclusiveLock|lockfileFailImmediately), 49 | 0, 50 | 1, 51 | 0, 52 | uintptr(unsafe.Pointer(&ol)), 53 | ) 54 | 55 | if r1 == 0 { 56 | if e, ok := err.(syscall.Errno); ok && e == errorLockViolation { 57 | return errLocked 58 | } 59 | return &os.PathError{Op: "lock", Path: f.Name(), Err: err} 60 | } 61 | return nil 62 | } 63 | 64 | // unlockFile releases a lock previously acquired with lockFile. 65 | // On Windows systems, this uses the UnlockFileEx Win32 API function. 66 | func unlockFile(f *os.File) error { 67 | h := syscall.Handle(f.Fd()) 68 | var ol syscall.Overlapped 69 | 70 | ol.Offset = 0 71 | ol.OffsetHigh = 0 72 | 73 | r1, _, err := procUnlockFileEx.Call( 74 | uintptr(h), 75 | 0, 76 | 1, 77 | 0, 78 | uintptr(unsafe.Pointer(&ol)), 79 | ) 80 | 81 | if r1 == 0 { 82 | return &os.PathError{Op: "unlock", Path: f.Name(), Err: err} 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /zpprof/README.md: -------------------------------------------------------------------------------- 1 | # zpprof 模块 2 | 3 | `zpprof` 提供了 pprof 处理器注册、系统信息收集等功能,用于应用程序的性能监控和调试。 4 | 5 | ## 使用 Go pprof 6 | 7 | ### 使用方式 1 8 | 9 | ```go 10 | import ( 11 | "github.com/sohaha/zlsgo/znet" 12 | "github.com/sohaha/zlsgo/zpprof" 13 | ) 14 | 15 | func main(){ 16 | r := znet.New("Go") 17 | 18 | // 注册pprof路由,如果 token 设置为空表示不需要验证 token 19 | zpprof.Register(r, "mytoken") 20 | 21 | znet.Run() 22 | } 23 | 24 | // 启动服务后直接访问 http://127.0.0.1:3788/debug?token=mytoken 25 | ``` 26 | 27 | ### 使用方式 2 28 | 29 | ```go 30 | // 使用另外端口(原始版本) 31 | 32 | go zpprof.ListenAndServe("0.0.0.0:8082") 33 | 34 | // 启动服务后直接访问 http://127.0.0.1:8082/debug/pprof/ 35 | ``` 36 | 37 | ### 通过 Web 界面分析 38 | 39 | 查看当前总览:访问 `http://127.0.0.1:3788/debug/pprof/` (如设置了token自行填上) 40 | 41 | ```bash 42 | /debug/pprof/ 43 | 44 | profiles: 45 | 0 block 46 | 5 goroutine 47 | 3 heap 48 | 0 mutex 49 | 9 threadcreate 50 | 51 | full goroutine stack dump 52 | ``` 53 | 54 | 这个页面中有许多子页面。 55 | 56 | - cpu(CPU Profiling): /debug/pprof/profile,默认进行 30s 的 CPU Profiling,得到一个分析用的 profile 文件 57 | 58 | - block(Block Profiling):/debug/pprof/block,查看导致阻塞同步的堆栈跟踪 59 | 60 | - goroutine:/debug/pprof/goroutine,查看当前所有运行的 goroutines 堆栈跟踪 61 | 62 | - heap(Memory Profiling): /debug/pprof/heap,查看活动对象的内存分配情况 63 | 64 | - mutex(Mutex Profiling):/debug/pprof/mutex,查看导致互斥锁的竞争持有者的堆栈跟踪 65 | 66 | - threadcreate:/debug/pprof/threadcreate,查看创建新OS线程的堆栈跟踪 67 | 68 | ### 通过交互式终端分析 69 | 70 | 终端执行 `go tool pprof http://127.0.0.1:3788/debug/pprof/profile?seconds=60` 71 | 72 | 执行该命令后,需等待 60 秒(可调整 seconds 的值),pprof 会进行 CPU Profiling。 73 | 74 | 结束后将默认进入 pprof 的交互式命令模式,可以对分析的结果进行查看或导出。 75 | 76 | 具体可执行 pprof help 查看命令说明 77 | 78 | ### 可视化界面 79 | 80 | 终端执行 `go tool pprof -http=:8080 http://127.0.0.1:3788/debug/pprof/profile?seconds=60` 81 | 82 | ## 实时监控 83 | 84 | statsviz 是一款可视化实时运行时统计,我们可以很方便的集成进来。 85 | 86 | ```go 87 | package main 88 | 89 | import ( 90 | "github.com/arl/statsviz" 91 | "github.com/sohaha/zlsgo/znet" 92 | ) 93 | 94 | func main() { 95 | r := znet.New() 96 | 97 | srv, _ := statsviz.NewServer() 98 | 99 | ws := srv.Ws() 100 | index := srv.Index() 101 | 102 | r.GET(`/debug/statsviz{*:[\S]*}`, func(c *znet.Context) { 103 | q := c.GetParam("*") 104 | if q == "" { 105 | c.Redirect("/debug/statsviz/") 106 | return 107 | } 108 | 109 | if q == "/ws" { 110 | ws(c.Writer, c.Request) 111 | return 112 | } 113 | 114 | index(c.Writer, c.Request) 115 | }) 116 | 117 | // 启动服务后直接访问 http://127.0.0.1:3788/debug/statsviz/ 118 | znet.Run() 119 | } 120 | ``` -------------------------------------------------------------------------------- /zcache/tofile/tofile_test.go: -------------------------------------------------------------------------------- 1 | package tofile 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/sohaha/zlsgo" 8 | "github.com/sohaha/zlsgo/zcache" 9 | "github.com/sohaha/zlsgo/zfile" 10 | ) 11 | 12 | type testSt struct { 13 | Name string 14 | Key int 15 | } 16 | 17 | func TestToFile(t *testing.T) { 18 | tt := zlsgo.NewTest(t) 19 | cache := zcache.New("file") 20 | err := zfile.WriteFile("tmp.json", []byte(`{"tmp1":"XhAAFSp0b2ZpbGUucGVyc2lzdGVuY2VTdP+BAwEBDXBlcnNpc3RlbmNlU3QB/4IAAQMBBERhdGEBEAABCExpZmVTcGFuAQQAARBJbnRlcnZhbExpZmVTcGFuAQIAAABP/4I1AQ4qdG9maWxlLnRlc3RTdP+DAwEBBnRlc3RTdAH/hAABAgEETmFtZQEMAAEDS2V5AQQAAAAW/4QJAQZpc05hbWUAAfsFloLwAAEBAA==","tmp3":"XhAAFSp0b2ZpbGUucGVyc2lzdGVuY2VTdP+BAwEBDXBlcnNpc3RlbmNlU3QB/4IAAQMBBERhdGEBEAABCExpZmVTcGFuAQQAARBJbnRlcnZhbExpZmVTcGFuAQIAAAAg/4IdAQZzdHJpbmcMCwAJaXMgc3RyaW5nAfsBZaC8AAA=","tmp2":"XhAAFSp0b2ZpbGUucGVyc2lzdGVuY2VTdP+BAwEBDXBlcnNpc3RlbmNlU3QB/4IAAQMBBERhdGEBEAABCExpZmVTcGFuAQQAARBJbnRlcnZhbExpZmVTcGFuAQIAAAAW/4ITAQNpbnQEBAD+BTQB+wFloLwAAA=="}`)) 21 | tt.EqualExit(nil, err) 22 | save, err := PersistenceToFile(cache, "tmp.json", false, &testSt{}) 23 | tt.EqualExit(nil, err) 24 | 25 | cache.Set("tmp1", &testSt{ 26 | Name: "isName", 27 | Key: 0, 28 | }, 3, true) 29 | cache.Set("tmp2", 666, 3) 30 | cache.Set("tmp3", "is string", 3) 31 | 32 | cache.ForEach(func(key string, value interface{}) bool { 33 | tt.Log(key) 34 | return true 35 | }) 36 | tmp1, err := cache.Get("tmp1") 37 | tt.EqualExit(nil, err) 38 | tt.Equal("isName", tmp1.(*testSt).Name) 39 | tmp2, err := cache.GetInt("tmp2") 40 | tt.EqualNil(err) 41 | tt.Equal(666, tmp2) 42 | tmp3, err := cache.GetString("tmp3") 43 | tt.EqualNil(err) 44 | tt.Equal("is string", tmp3) 45 | go func() { 46 | time.Sleep(500 * time.Millisecond) 47 | _, _ = cache.Get("tmp1") 48 | }() 49 | time.Sleep(3*time.Second + 300*time.Millisecond) 50 | tmp1, err = cache.Get("tmp1") 51 | tt.EqualNil(err) 52 | t.Log(tmp1) 53 | _, err = cache.GetInt("tmp2") 54 | tt.EqualTrue(err != nil) 55 | _, err = cache.GetString("tmp3") 56 | tt.EqualTrue(err != nil) 57 | 58 | cache.Set("tmp0", 1, 2) 59 | tt.Equal(2, cache.Count()) 60 | zfile.Rmdir("tmp.json") 61 | 62 | err = save() 63 | tt.EqualNil(err) 64 | tt.EqualTrue(zfile.FileExist("tmp.json")) 65 | 66 | _ = zfile.WriteFile("tmp.json", []byte(`{"tmp1":"XhAAFSp0b2ZpbGUucGVyc2lzdGVuY2VTdP+BAwEBDXBlcnNpc3RlbmNlU3QB/4IAAQMBBERhdGEBEAABCExpZmVTcGFuAQQAARBJbnRlcnZhbExpZmVTcGFuAQIAAABP/4I1AQ4qdG9maWxlLnRlc3RTdP+DAwEBBnRlc3RTdAH/hAABAgEETmFtZQEMAAEDS2V"}`)) 67 | _, err = PersistenceToFile(cache, "tmp.json", false) 68 | tt.EqualTrue(err != nil) 69 | 70 | zfile.Rmdir("tmp.json") 71 | } 72 | -------------------------------------------------------------------------------- /znet/rpc.go: -------------------------------------------------------------------------------- 1 | package znet 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/rpc" 7 | "net/rpc/jsonrpc" 8 | 9 | "github.com/sohaha/zlsgo/zreflect" 10 | ) 11 | 12 | // JSONRPCOption defines configuration options for the JSON-RPC handler. 13 | type JSONRPCOption struct { 14 | DisabledHTTP bool // Disables HTTP method handling, only processes RPC calls 15 | Debug bool // Enables debug mode with additional logging and method inspection 16 | } 17 | 18 | // JSONRPC creates a handler that processes JSON-RPC requests. 19 | // It registers the provided receiver objects with the RPC server and returns a handler function 20 | // that can be used with the router. The handler supports both HTTP and WebSocket transports. 21 | func JSONRPC(rcvr map[string]interface{}, opts ...func(o *JSONRPCOption)) func(c *Context) { 22 | o := JSONRPCOption{} 23 | if len(opts) > 0 { 24 | opts[0](&o) 25 | } 26 | 27 | s := rpc.NewServer() 28 | methods := make(map[string][]string, 0) 29 | for name, v := range rcvr { 30 | err := s.RegisterName(name, v) 31 | if err == nil && o.Debug { 32 | typ := zreflect.TypeOf(v) 33 | for m := 0; m < typ.NumMethod(); m++ { 34 | method := typ.Method(m) 35 | mtype := method.Type 36 | mname := method.Name 37 | l := mtype.NumIn() 38 | replyType, argType := "-", "-" 39 | if l > 2 { 40 | replyType = mtype.In(2).String() 41 | } 42 | if l > 1 { 43 | argType = mtype.In(1).String() 44 | } 45 | methods[name+"."+mname] = []string{argType, replyType} 46 | } 47 | } 48 | } 49 | 50 | return func(c *Context) { 51 | req := c.Request 52 | method := req.Method 53 | if o.Debug && method == "GET" { 54 | c.JSON(200, methods) 55 | return 56 | } 57 | 58 | if c.stopHandle.Load() { 59 | return 60 | } 61 | 62 | var codec rpc.ServerCodec 63 | if method == "CONNECT" || (method == "POST" && !o.DisabledHTTP) { 64 | c.stopHandle.Store(true) 65 | c.write() 66 | 67 | if method == "CONNECT" { 68 | conn, _, _ := c.Writer.(http.Hijacker).Hijack() 69 | codec = jsonrpc.NewServerCodec(conn) 70 | _, _ = io.WriteString(conn, "HTTP/1.0 200 Connected to JSON RPC\n\n") 71 | s.ServeCodec(codec) 72 | return 73 | } 74 | 75 | c.Writer.Header().Set("Content-Type", ContentTypeJSON) 76 | var conn io.ReadWriteCloser = struct { 77 | io.Writer 78 | io.ReadCloser 79 | }{ 80 | ReadCloser: c.Request.Body, 81 | Writer: c.Writer, 82 | } 83 | _ = s.ServeRequest(jsonrpc.NewServerCodec(conn)) 84 | return 85 | } 86 | 87 | c.SetContentType(ContentTypePlain) 88 | c.String(http.StatusMethodNotAllowed, "405 must CONNECT\n") 89 | } 90 | } 91 | --------------------------------------------------------------------------------