From: bruce Date: Wed, 29 Oct 2025 02:50:41 +0000 (+0000) Subject: feat(utils): 1. add convutil.SortSlice. X-Git-Tag: v0.3.4 X-Git-Url: https://git.uzoombox.com/git/?a=commitdiff_plain;ds=sidebyside;p=uzsdk-v0.3.4.git feat(utils): 1. add convutil.SortSlice. feat(uzsdk): 1. add Configuration.Backup. 2. default Configuration.Service.TrustedProxies to ["127.0.0.1", "192.168.8.1"]. --- diff --git a/utils/convutil/sort.go b/utils/convutil/sort.go index cb686f3..f1786c3 100644 --- a/utils/convutil/sort.go +++ b/utils/convutil/sort.go @@ -1,10 +1,13 @@ -// Copyright 2010-2022 nanzoom.com. All Rights Reserved. +// Copyright 2010-2025 nanzoom.com. All Rights Reserved. package convutil import ( + "fmt" "reflect" "sort" + "strings" + "time" ) // SortMap returns an sorted array of map keys. @@ -93,3 +96,104 @@ func SortMapString(v any) []string { sort.Strings(keys) return keys } + +// SortSlice sorts a slice of structs based on a field name and order. +// It now supports basic types, time.Time, and pointers to basic types. +// slicePtr: A pointer to the slice of structs to be sorted. +// fieldName: The name of the struct field to sort by (must be exported). +// ascending: true for ascending order, false for descending order. +func SortSlice(slicePtr any, fieldName string, ascending bool) error { + // 1. 使用反射获取切片的值 + sliceVal := reflect.ValueOf(slicePtr) + if sliceVal.Kind() != reflect.Ptr { + return fmt.Errorf("must provide a pointer to a slice") + } + sliceVal = sliceVal.Elem() + if sliceVal.Kind() != reflect.Slice { + return fmt.Errorf("must provide a pointer to a slice") + } + + // 2. 检查切片是否为空 + if sliceVal.Len() == 0 { + return nil // 空切片无需排序 + } + + // 3. 验证字段是否存在 + firstElem := sliceVal.Index(0) + if firstElem.Kind() != reflect.Struct { + return fmt.Errorf("slice elements must be structs") + } + field := firstElem.FieldByName(fieldName) + if !field.IsValid() { + return fmt.Errorf("field '%s' not found in struct", fieldName) + } + + // 4. 使用 sort.Slice 进行排序 + sort.Slice(sliceVal.Interface(), func(i, j int) bool { + // 获取第 i 个和第 j 个元素的指定字段 + fieldI := sliceVal.Index(i).FieldByName(fieldName) + fieldJ := sliceVal.Index(j).FieldByName(fieldName) + + // 比较函数 + isLess := func() bool { + // --- 处理指针类型 --- + if fieldI.Kind() == reflect.Ptr { + // 规则1: 处理 nil 值 + isNilI := fieldI.IsNil() + isNilJ := fieldJ.IsNil() + + if isNilI && isNilJ { + return false // 两个都为 nil,视为相等,不交换 + } + if isNilI { // i 是 nil, j 不是 + return true // nil 值排在前面 (升序) + } + if isNilJ { // j 是 nil, i 不是 + return false + } + + // 规则2: 两个指针都不为 nil,解引用后比较 + elemI := fieldI.Elem() + elemJ := fieldJ.Elem() + // 现在 elemI 和 elemJ 是指针指向的实际值,继续下面的 switch 逻辑 + return compareValues(elemI, elemJ) + } + + // --- 处理非指针类型 --- + return compareValues(fieldI, fieldJ) + }() + + // 根据排序方向返回结果 + if ascending { + return isLess + } + return !isLess + }) + + return nil +} + +// compareValues is a helper function to compare two reflect.Value. +// It assumes the values are of the same comparable kind. +func compareValues(valI, valJ reflect.Value) bool { + switch valI.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return valI.Int() < valJ.Int() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return valI.Uint() < valJ.Uint() + case reflect.Float32, reflect.Float64: + return valI.Float() < valJ.Float() + case reflect.String: + return strings.Compare(valI.String(), valJ.String()) < 0 + case reflect.Struct: + // 特别处理 time.Time 类型 + if t, ok := valI.Interface().(time.Time); ok { + return t.Before(valJ.Interface().(time.Time)) + } + // 其他结构体不支持比较 + return false + default: + // 对于不支持的类型,默认不交换顺序 + return false + } +} diff --git a/utils/utils.sh b/utils/utils.sh index cb12b6e..374e3e3 100644 --- a/utils/utils.sh +++ b/utils/utils.sh @@ -108,4 +108,4 @@ function exportTableDict() { } # generateSbs "$@" -generateStruct "$@" \ No newline at end of file +generateStruct "$@" diff --git a/uzsdk/configuration.go b/uzsdk/configuration.go index 0517d09..6de0f55 100644 --- a/uzsdk/configuration.go +++ b/uzsdk/configuration.go @@ -17,7 +17,7 @@ type Configuration struct { PublicName string // smartcloud address served by httpd, default: [ServerName], exp: http://192.168.0.6:8080 ServicePath string // urlpath for this service, default: /wservice DataDirectory string // server data directory, default: /data/cloud - TrustedProxies []string // a list of proxies trusted, default: ["127.0.0.1"] + TrustedProxies []string // a list of proxies trusted, default: ["127.0.0.1", "192.168.8.1"] } System struct { DBDriver string // database driver, default: postgres @@ -32,6 +32,22 @@ type Configuration struct { DbgOutput bool // debug: output more details, default: fasle. DbgToken bool // debug: use token '63886e35-7291-4b4d-a66f-fa7431542ace', default: false } + Backup struct { + Resource string // default: [schema]://[user]:[password]@[host]/[bucket]?sslmode=[sslmode] + Storage struct { + Scheme string // schema, default: minio + User string // user, default: 1130787647532715 + Password string // password, default: 7NAIzs5xZoyOGVTC33Fu3psJgXVs4Ws + Host string // host, default: 127.0.0.1:9009 + Bucket string // bucket, default: backup + Sslmode bool // sslmode default: false + } + Notification struct { + Method string // default: webhook + Endpoint string // default: http://127.0.0.1:9000/wservice/storage/event + Arn string // default: arn:minio:sqs::uzoombox:webhook + } + } GZip struct { Enable bool // enable gzip with gin, default: true Level int64 // gzip compress level, default: gzip.DefaultCompression @@ -72,10 +88,10 @@ func (setting *Configuration) Check() { setting.Service.DataDirectory = "/data/uzoombox/cloud" } if len(setting.Service.TrustedProxies) == 0 { - setting.Service.TrustedProxies = append(setting.Service.TrustedProxies, "127.0.0.1") + setting.Service.TrustedProxies = append(setting.Service.TrustedProxies, "127.0.0.1", "192.168.8.1") } - // // Configuration.System + // Configuration.System if setting.System.DBDriver == "" { setting.System.DBDriver = "postgres" } @@ -106,6 +122,39 @@ func (setting *Configuration) Check() { setting.System.CheckTime = (3600 >> 1) } + // Configuration.Backup + if setting.Backup.Storage.Scheme == "" { + setting.Backup.Storage.Scheme = "minio" + } + if setting.Backup.Storage.User == "" { + setting.Backup.Storage.User = "1130787647532715" + } + if setting.Backup.Storage.Password == "" { + setting.Backup.Storage.Password = "7NAIzs5xZoyOGVTC33Fu3psJgXVs4Ws" + } + if setting.Backup.Storage.Host == "" { + setting.Backup.Storage.Host = "127.0.0.1:9009" + } + if setting.Backup.Storage.Bucket == "" { + setting.Backup.Storage.Bucket = "backup" + } + if !setting.Backup.Storage.Sslmode { + setting.Backup.Storage.Sslmode = false + } + storage := &setting.Backup.Storage + setting.Backup.Resource = fmt.Sprintf("%v://%v:%v@%v/%v?sslmode=%v", + storage.Scheme, storage.User, storage.Password, storage.Host, storage.Bucket, storage.Sslmode) + + if setting.Backup.Notification.Method == "" { + setting.Backup.Notification.Method = "webhook" + } + if setting.Backup.Notification.Endpoint == "" { + setting.Backup.Notification.Endpoint = setting.Service.ServerName + setting.Service.ServicePath + "/storage/event" + } + if setting.Backup.Notification.Arn == "" { + setting.Backup.Notification.Arn = fmt.Sprintf("arn:minio:sqs::uzoombox:%v", setting.Backup.Notification.Method) + } + // Configuration.GZip if setting.GZip.Enable { setting.GZip.Enable = true diff --git a/version.go b/version.go index f453987..4e4dc57 100644 --- a/version.go +++ b/version.go @@ -2,4 +2,4 @@ package uzsdk // Version is the current opensdk's version. -const Version = "0.3.3" +const Version = "0.3.4"