-// 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.
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
+ }
+}
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
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
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"
}
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