├── 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 |
--------------------------------------------------------------------------------