commit 2006e9eef82a24dc1809b59a318da6b8435d9fd5 Author: ayflying Date: Mon Dec 23 12:00:41 2024 +0800 首次提交 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/goframehelperCache.xml b/.idea/goframehelperCache.xml new file mode 100644 index 0000000..1d86da2 --- /dev/null +++ b/.idea/goframehelperCache.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..909649f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/utility-go.iml b/.idea/utility-go.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/utility-go.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/aycache/cache.go b/aycache/cache.go new file mode 100644 index 0000000..aa21bea --- /dev/null +++ b/aycache/cache.go @@ -0,0 +1,33 @@ +package aycache + +import ( + "github.com/ayflying/utility_go/ayCache/drive" + "github.com/gogf/gf/v2/os/gcache" +) + +type Mod struct { + client *gcache.Cache +} + +//func NewV1(_name ...string) *cache.Mod { +// return pgk.Cache +//} + +func New(_name ...string) gcache.Adapter { + + var cacheAdapterObj gcache.Adapter + var name = "cache" + if len(_name) > 0 { + name = _name[0] + } + switch name { + case "cache": + cacheAdapterObj = drive.NewAdapterMemory() + case "redis": + cacheAdapterObj = drive.NewAdapterRedis() + } + + //var client = gcache.New() + //client.SetAdapter(cacheAdapterObj) + return cacheAdapterObj +} diff --git a/aycache/drive/file.go b/aycache/drive/file.go new file mode 100644 index 0000000..6cd67c7 --- /dev/null +++ b/aycache/drive/file.go @@ -0,0 +1,118 @@ +package drive + +import ( + "context" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/os/gcache" + "time" +) + +type AdapterFile struct { + FilePath string +} + +func (a AdapterFile) Set(ctx context.Context, key interface{}, value interface{}, duration time.Duration) error { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) error { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (ok bool, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) SetIfNotExistFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Get(ctx context.Context, key interface{}) (*gvar.Var, error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) GetOrSetFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) GetOrSetFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Contains(ctx context.Context, key interface{}) (bool, error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Size(ctx context.Context) (size int, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Data(ctx context.Context) (data map[interface{}]interface{}, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Keys(ctx context.Context) (keys []interface{}, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Values(ctx context.Context) (values []interface{}, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Clear(ctx context.Context) error { + //TODO implement me + panic("implement me") +} + +func (a AdapterFile) Close(ctx context.Context) error { + //TODO implement me + panic("implement me") +} + +func NewAdapterFile(filePath string) gcache.Adapter { + return &AdapterFile{ + FilePath: filePath, + } +} diff --git a/aycache/drive/memory.go b/aycache/drive/memory.go new file mode 100644 index 0000000..c2a4df6 --- /dev/null +++ b/aycache/drive/memory.go @@ -0,0 +1,15 @@ +package drive + +import ( + "github.com/gogf/gf/v2/os/gcache" +) + +var adapterMemoryClient = gcache.New() + +// NewAdapterMemory 创建并返回一个新的内存缓存对象。 +func NewAdapterMemory() gcache.Adapter { + //if adapterMemoryClient == nil { + // adapterMemoryClient = gcache.New() + //} + return adapterMemoryClient +} diff --git a/aycache/drive/mencached.go b/aycache/drive/mencached.go new file mode 100644 index 0000000..f8d0d44 --- /dev/null +++ b/aycache/drive/mencached.go @@ -0,0 +1,120 @@ +package drive + +import ( + "context" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/database/gredis" + "github.com/gogf/gf/v2/os/gcache" + "time" +) + +// AdapterRedis is the gcache adapter implements using Redis server. +type AdapterMemcached struct { + //redis *gredis.Redis + //client +} + +func (a AdapterMemcached) Set(ctx context.Context, key interface{}, value interface{}, duration time.Duration) error { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) error { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (ok bool, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) SetIfNotExistFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Get(ctx context.Context, key interface{}) (*gvar.Var, error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) GetOrSetFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) GetOrSetFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Contains(ctx context.Context, key interface{}) (bool, error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Size(ctx context.Context) (size int, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Data(ctx context.Context) (data map[interface{}]interface{}, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Keys(ctx context.Context) (keys []interface{}, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Values(ctx context.Context) (values []interface{}, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Clear(ctx context.Context) error { + //TODO implement me + panic("implement me") +} + +func (a AdapterMemcached) Close(ctx context.Context) error { + //TODO implement me + panic("implement me") +} + +// NewAdapterRedis creates and returns a new memory cache object. +func NewAdapterMemcached(redis *gredis.Redis) gcache.Adapter { + return &AdapterMemcached{} +} diff --git a/aycache/drive/redis.go b/aycache/drive/redis.go new file mode 100644 index 0000000..62f21a9 --- /dev/null +++ b/aycache/drive/redis.go @@ -0,0 +1,18 @@ +package drive + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" +) + +var adapterRedisClient gcache.Adapter +var adapterRedisCache = gcache.New() + +func NewAdapterRedis() gcache.Adapter { + + if adapterRedisClient == nil { + adapterRedisClient = gcache.NewAdapterRedis(g.Redis("cache")) + adapterRedisCache.SetAdapter(adapterRedisClient) + } + return adapterRedisCache +} diff --git a/excel/download.go b/excel/download.go new file mode 100644 index 0000000..aa8aea8 --- /dev/null +++ b/excel/download.go @@ -0,0 +1,53 @@ +package excel + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/xuri/excelize/v2" +) + +func Data2Excel(ctx context.Context, data []map[string]interface{}) (file string) { + + // 创建一个新的 Excel 文件 + f := excelize.NewFile() + var sheetName = "Sheet1" + f.NewSheet(sheetName) + + //// 准备数据 + //data = []map[string]interface{}{ + // {"Name": "Alice", "Age": 30, "City": "New York"}, + // {"Name": "Bob", "Age": 25, "City": "Los Angeles"}, + // {"Name": "Charlie", "Age": 35, "City": "Chicago"}, + //} + + //写入头部 + var colIndex = 0 + var headers []string + for header := range data[0] { + //追加头部名字 + headers = append(headers, header) + cell, _ := excelize.CoordinatesToCellName(colIndex+1, 1) // 表头在第一行 + f.SetCellValue(sheetName, cell, header) + colIndex++ + } + + // 写入数据 + for rowIndex, record := range data { + for colIndex, header := range headers { + cell, _ := excelize.CoordinatesToCellName(colIndex+1, rowIndex+2) // 数据从第二行开始 + f.SetCellValue(sheetName, cell, record[header]) // 通过表头获取数据 + } + } + + // 保存 Excel 文件 + saveName := fmt.Sprintf("runtime/uploads/out_%v.xlsx", gtime.Now().Nanosecond()) + if err := f.SaveAs(saveName); err != nil { + g.Log().Fatal(ctx, err) + } + + //下载excel + //g.RequestFromCtx(ctx).Response.ServeFileDownload(saveName) + return saveName +} diff --git a/excel/excel.go b/excel/excel.go new file mode 100644 index 0000000..73f438c --- /dev/null +++ b/excel/excel.go @@ -0,0 +1,174 @@ +package excel + +import ( + "context" + "github.com/ayflying/excel2json" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + "path" + "strconv" + "strings" + "time" +) + +type FileItem struct { + Name string `json:"name" dc:"配置文件名"` + Filename string `json:"filename" dc:"文件名"` + Tabs []string `json:"tabs" dc:"页签"` + Items []string `json:"items" dc:"道具字段"` + ItemsMap []string `json:"items_map" dc:"道具字段map格式"` + Slice map[string]string `json:"slice" dc:"切片"` +} + +type Excel struct { + Header int //表头行数 + Key int //key列 +} + +func New(header int, key int) *Excel { + return &Excel{ + Header: header, + Key: key, + } +} + +func (s *Excel) ExcelLoad(ctx context.Context, fileItem *FileItem, mainPath string) (runTime time.Duration) { + startTime := gtime.Now() + filepath := path.Join("manifest/game", fileItem.Name) + + //如果filepath文件不存在,跳过 + if !gfile.Exists(path.Join(mainPath, fileItem.Filename)) { + return + } + + //假设我们有一个命令行工具,比如:dir(Windows环境下列出目录内容) + var tempJson []interface{} + for k, v2 := range fileItem.Tabs { + sheet := v2 + if k == 0 { + sheet = v2 + } + + //导出json + excel2json.Excel(path.Join(mainPath, fileItem.Filename), + filepath, s.Header, s.Key, sheet) + + //如果配置了道具字段,则进行转换 + //g.Log().Info(ctx, "当前任务表=%v,items=%v", v.Name, v.Items) + fileBytes := gfile.GetBytes(filepath) + arr, _ := gjson.DecodeToJson(fileBytes) + list := arr.Array() + //格式化item格式 + if len(fileItem.Items) > 0 { + list = s.itemsFormat(list, fileItem.Items) + } + if len(fileItem.ItemsMap) > 0 { + list = s.itemsMapFormat(list, gconv.Strings(fileItem.ItemsMap)) + } + //格式化切片修改 + if len(fileItem.Slice) > 0 { + list = s.sliceFormat(list, fileItem.Slice) + } + + //拼接json + tempJson = append(tempJson, list...) + fileBytes, _ = gjson.MarshalIndent(tempJson, "", "\t") + err := gfile.PutBytes(filepath, fileBytes) + if err != nil { + g.Log().Error(ctx, err) + } + } + + runTime = gtime.Now().Sub(startTime) + return +} + +func (s *Excel) itemsFormat(list []interface{}, Items []string) []interface{} { + for k2, v2 := range list { + for k3, v3 := range v2.(g.Map) { + if gstr.InArray(Items, k3) { + if _, ok := v3.(string); ok { + list[k2].(g.Map)[k3] = s.Spilt2Item(v3.(string)) + + } else { + g.Log().Errorf(gctx.New(), "当前类型断言失败:%v,list=%v", v3, v2) + } + } + } + } + return list +} + +func (s *Excel) itemsMapFormat(list []interface{}, Items []string) []interface{} { + for k2, v2 := range list { + for k3, v3 := range v2.(g.Map) { + if gstr.InArray(Items, k3) { + if _, ok := v3.(string); ok { + get := s.Spilt2Item(v3.(string)) + list[k2].(g.Map)[k3] = s.Items2Map(get) + } else { + g.Log().Errorf(gctx.New(), "当前类型断言失败:%v,list=%v", v3, v2) + } + } + } + } + return list +} + +func (s *Excel) sliceFormat(list []interface{}, Slice map[string]string) []interface{} { + for s1, s2 := range Slice { + for k2, v2 := range list { + for k3, v3 := range v2.(g.Map) { + //判断是否存在 + if s1 != k3 { + continue + } + if gconv.String(v3) == "" { + list[k2].(g.Map)[k3] = []string{} + continue + } + + var parts []string + //断言是否成功 + if get, ok := v3.(string); !ok { + //g.Log().Errorf(gctx.New(), "当前类型断言失败:%v", v3) + parts = []string{gconv.String(v3)} + continue + } else { + parts = strings.Split(get, "|") // 分割字符串 + } + + switch s2 { + case "int": + var temp = make([]int, len(parts)) + for k, v := range parts { + temp[k], _ = strconv.Atoi(v) + } + list[k2].(g.Map)[k3] = temp + case "int64": + var temp = make([]int64, len(parts)) + for k, v := range parts { + temp[k], _ = strconv.ParseInt(v, 10, 64) + } + case "float64": + var temp = make([]float64, len(parts)) + for k, v := range parts { + temp[k], _ = strconv.ParseFloat(v, 64) + } + list[k2].(g.Map)[k3] = temp + default: + list[k2].(g.Map)[k3] = parts + } + + } + + } + } + + return list +} diff --git a/excel/tools.go b/excel/tools.go new file mode 100644 index 0000000..7475e88 --- /dev/null +++ b/excel/tools.go @@ -0,0 +1,49 @@ +package excel + +import ( + "github.com/xuri/excelize/v2" + "log" + "strconv" + "strings" +) + +// Excel2Slice 读取excel文件导入为切片 +func Excel2Slice(filePath string, _sheet ...string) [][]string { + excelObj, err := excelize.OpenFile(filePath) + if err != nil { + log.Fatalf("无法打开Excel文件: %v", err) + } + defer excelObj.Close() + var sheet string + if len(_sheet) == 0 { + sheet = excelObj.GetSheetList()[0] + } else { + sheet = _sheet[0] + } + res, err := excelObj.GetRows(sheet) + + return res +} + +// 字符串转道具类型 +func (s *Excel) Spilt2Item(str string) (result [][]int64) { + parts := strings.Split(str, "|") // 分割字符串 + + for i := 0; i < len(parts)-1; i += 2 { + num1, _ := strconv.ParseInt(parts[i], 10, 64) + num2, _ := strconv.ParseInt(parts[i+1], 10, 64) + + pair := []int64{num1, num2} + result = append(result, pair) + } + return +} + +// 道具格式转map +func (s *Excel) Items2Map(items [][]int64) (list map[int64]int64) { + list = make(map[int64]int64) + for _, v := range items { + list[v[0]] = v[1] + } + return +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..66f5b96 --- /dev/null +++ b/go.mod @@ -0,0 +1,51 @@ +module github.com/ayflying/utility_go + +go 1.23.0 + +require ( + github.com/ayflying/excel2json v1.1.2 + github.com/gogf/gf/v2 v2.8.3 + github.com/minio/minio-go/v7 v7.0.82 + github.com/xuri/excelize/v2 v2.9.0 +) + +require ( + github.com/360EntSecGroup-Skylar/excelize v1.4.1 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grokify/html-strip-tags-go v0.1.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/magiconair/properties v1.8.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect + github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/sdk v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.30.0 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9fe61a5 --- /dev/null +++ b/go.sum @@ -0,0 +1,106 @@ +github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/ayflying/excel2json v1.1.2 h1:ERMDUvN2H/9jQmbFo5Rt1XJAvWdMTzIAsI4p4Fp/wRA= +github.com/ayflying/excel2json v1.1.2/go.mod h1:aNzB271bUAuRq+P4J+S8a3NAltcghLIGR2HdM6akqFU= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gogf/gf/v2 v2.8.3 h1:h9Px3lqJnnH6It0AqHRz4/1hx0JmvaSf1IvUir5x1rA= +github.com/gogf/gf/v2 v2.8.3/go.mod h1:n++xPYGUUMadw6IygLEgGZqc6y6DRLrJKg5kqCrPLWY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4= +github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.82 h1:tWfICLhmp2aFPXL8Tli0XDTHj2VB/fNf0PC1f/i1gRo= +github.com/minio/minio-go/v7 v7.0.82/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00= +github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY= +github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE= +github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE= +github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A= +github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/rank/rank.go b/rank/rank.go new file mode 100644 index 0000000..2b7753f --- /dev/null +++ b/rank/rank.go @@ -0,0 +1,312 @@ +package rank + +import ( + "fmt" + "time" + + "github.com/gogf/gf/v2/database/gredis" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + ctx = gctx.New() +) + +type Mod struct { +} + +type F64CountRank struct { + name string // 排行榜名 + updateTs string // 更新时间key +} + +type Info struct { + Id int64 + Score int64 + Rank int32 + UpdateTs int64 +} + +func New() *Mod { + return &Mod{} +} + +func (s *Mod) Load() { + +} + +// CreateF64CountRank 创建一个排行榜实例 name: [name:赛季] +func (s *Mod) CreateF64CountRank(name string) *F64CountRank { + return &F64CountRank{ + name: fmt.Sprintf("rank:%s:score", name), + updateTs: fmt.Sprintf("rank:%s:updateTs", name), + } +} + +// IncrScore 对指定ID的分数进行增加,并返回增加后的当前分数。 +// 该方法首先更新成员的更新时间戳,然后增加成员的分数。 +// +// 参数: +// +// id - 要操作的成员ID。 +// score - 要增加的分数。 +// +// 返回值: +// +// curScore - 增加分数后的当前分数。 +// err - 操作过程中可能发生的错误。 +// +// IncrScore 先改redis再改cache +// +// @Description: +// @receiver r +// @param id +// @param score +// @return curScore +// @return err +func (r *F64CountRank) IncrScore(id int64, score int64) (curScore float64, err error) { + // 记录当前时间戳,用于更新成员的最新活动时间。 + now := time.Now().UnixMilli() + + // 将成员的更新时间戳加入到Redis的有序集合中,确保成员的排序依据是最新的活动时间。 + _, err = g.Redis().ZAdd(ctx, r.updateTs, &gredis.ZAddOption{}, gredis.ZAddMember{ + Score: float64(now), + Member: id, + }) + + // 增加成员的分数,并返回增加后的当前分数。 + curScore, err = g.Redis().ZIncrBy(ctx, r.name, float64(score), id) + + //如果分数小于0,则删除 + if curScore <= 0 { + err = r.DelScore(id) + } + + return +} + +// 删除当前排行榜 +func (r *F64CountRank) Delete() { + _, err := g.Redis().Del(ctx, r.name) + if err != nil { + g.Log().Error(ctx, "排行榜删除失败:%v", err) + } + _, err = g.Redis().Del(ctx, r.updateTs) + if err != nil { + g.Log().Error(ctx, "排行榜删除失败:%v", err) + } +} + +// DelScore 删除当前分数 +// +// @Description: +// @receiver r +// @param id +// @return err +func (r *F64CountRank) DelScore(id int64) (err error) { + _, err = g.Redis().ZRem(ctx, r.updateTs, id) + _, err = g.Redis().ZRem(ctx, r.name, id) + return +} + +// DelScore 删除当前分数 +// +// @Description: +// @receiver r +// @param id +// @return err +func (r *F64CountRank) DelByRank(start int64, stop int64) (err error) { + var members []int64 + get, err := g.Redis().ZRange(ctx, r.name, start, stop, + gredis.ZRangeOption{ + Rev: true, + }) + err = get.Scan(&members) + if err != nil { + return + } + + for _, member := range members { + _, err = g.Redis().ZRem(ctx, r.updateTs, member) + } + _, err = g.Redis().ZRemRangeByRank(ctx, r.name, start, stop) + return +} + +// updateScore 更新给定ID的分数值。 +// +// 参数: +// +// id - 需要更新分数的实体ID。 +// score - 新的分数值。 +// +// 返回值: +// +// error - 更新过程中可能出现的错误。 +// +// 该方法首先记录当前时间作为更新时间戳,然后将新的分数值添加到排名系统中。 +// 使用Redis的ZAdd方法来确保操作的原子性和一致性。 +// UpdateScore 更新分数 +// +// @Description: +// @receiver r +// @param id +// @param score +// @return err +func (r *F64CountRank) UpdateScore(id int64, score int64) (err error) { + // 获取当前时间戳,以毫秒为单位。 + now := time.Now().UnixMilli() + + // 向更新时间戳的有序集合中添加新的成员和分数,成员为id,分数为当前时间戳。 + _, err = g.Redis().ZAdd(ctx, r.updateTs, &gredis.ZAddOption{}, gredis.ZAddMember{ + Score: float64(now), + Member: id, + }) + + // 向排名的有序集合中添加新的成员和分数,成员为id,分数为传入的score。 + _, err = g.Redis().ZAdd(ctx, r.name, &gredis.ZAddOption{}, gredis.ZAddMember{ + Score: float64(score), + Member: id, + }) + return +} + +//// GetRankInfosV1 获取0~count跳记录 +//func (r *F64CountRank) getRankInfosV1(offset, count int) (list []*RankInfo, err error) { +// /* +// 找到maxRank的玩家的分数 +// 根据分数拿到所有分数大于等于minScore玩家 +// 将这些玩家进行排序 +// 返回maxRank条目 +// */ +// var ( +// minScore int64 // 最低分 +// maxScore int64 +// //zl []redis2.Z +// zl []gredis.ZAddMember +// length int +// ) +// // 拉取所有玩家的更新时间戳 +// zl, err = g.Redis().ZRemRangeByScore(ctx,r.updateTs, strconv.Itoa(0), strconv.Itoa(-1))//ZRemRangeByScore(ctx, r.updateTs, strconv.Itoa(0), strconv.Itoa(-1)) +// //zl, err = rdbV1.ZRangeWithScores(ctx, r.updateTs, 0, -1).Result() +// if err != nil { +// g.Log().Errorf(ctx, "redis err:%v", err) +// return +// } +// if len(zl) == 0 { +// //logs.Infof("empty list") +// return +// } +// tsl := make(map[int64]int64, len(zl)) +// for _, z := range zl { +// id := gconv.Int64(z.Member) //pgk.InterfaceToNumber[uint64](z.Member) +// tsl[id] = int64(z.Score) +// } +// +// // 找到maxRank的玩家的分数 +// zl, err = rdbV1.ZRevRangeByScoreWithScores(ctx, r.name, &redis2.ZRangeBy{ +// Min: "0", +// Max: strconv.Itoa(math.MaxInt), +// Offset: 0, +// Count: int64(count), +// }).Result() +// if err != nil { +// g.Log().Errorf(ctx, "redis err:%v", err) +// return +// } +// if len(zl) == 0 { +// g.Log().Info(ctx, "empty list") +// return +// } +// minScore = int64(zl[len(zl)-1].Score) +// maxScore = int64(zl[0].Score) +// // 根据分数拿到所有分数大于等于minScore玩家 +// zl, err = rdbV1.ZRevRangeByScoreWithScores(ctx, r.name, &redis2.ZRangeBy{ +// Min: fmt.Sprintf("%v", minScore), +// Max: fmt.Sprintf("%v", maxScore), +// }).Result() +// if err != nil { +// g.Log().Errorf(ctx, "redis err:%v", err) +// return +// } +// if len(zl) == 0 { +// g.Log().Info(ctx, "empty list") +// return +// } +// //如果开始已经大于等于总长度,就返回空 +// if offset >= len(zl) { +// return +// } +// list = make([]*RankInfo, len(zl)) +// for i, z := range zl { +// id := gconv.Int64(z.Member) +// list[i] = &RankInfo{ +// Id: id, +// Score: int64(z.Score), +// UpdateTs: tsl[id], +// } +// } +// // 将这些玩家进行排序 +// sort.Slice(list, func(i, j int) bool { +// if list[i].Score != list[j].Score { +// return list[i].Score > list[j].Score +// } else { +// return list[i].UpdateTs < list[j].UpdateTs +// } +// }) +// length = len(list) +// if length > count { +// length = count +// } +// for i := range list { +// info := list[i] +// info.Rank = i + 1 +// } +// +// list = list[offset:length] +// return +//} + +// GetRankInfosNotTs 获取0~count跳记录 不根据更新时间来 +func (r *F64CountRank) GetRankInfosNotTs(offset, count int) (list []*Info, err error) { + var members []int64 + get, err := g.Redis().ZRange(ctx, r.name, int64(offset), int64(count), + gredis.ZRangeOption{ + Rev: true, + }) //.ScanSlice(&members) + err = get.Scan(&members) + if err != nil { + //logs.Withf("redis err:%v", err) + return + } + + list = make([]*Info, len(members)) + for i := range members { + id := members[i] + //id := pgk.InterfaceToNumber[uint64](members[i]) + list[i] = r.GetIdRankNotTs(id) + } + return +} + +// 获取指定id的当前排名 +func (r *F64CountRank) GetIdRankNotTs(id int64) (rankInfo *Info) { + rankInfo = &Info{Id: id} + score, err := g.Redis().ZScore(ctx, r.name, id) + if err != nil { + return + } + rankInfo.Score = int64(score) + if score == 0 { + return + } + + rank, err := g.Redis().ZRevRank(ctx, r.name, id) + if err != nil { + return + } + rankInfo.Rank = int32(rank) + 1 + + return rankInfo +} diff --git a/s3/s3.go b/s3/s3.go new file mode 100644 index 0000000..bc6a9ad --- /dev/null +++ b/s3/s3.go @@ -0,0 +1,270 @@ +package s3 + +import ( + "fmt" + "io" + "log" + "net/url" + "path" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gconv" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +var ( + //client *minio.Client + ctx = gctx.New() +) + +type DataType struct { + AccessKey string `json:"access_key"` + SecretKey string `json:"secret_key"` + Address string `json:"address"` + Ssl bool `json:"ssl"` + Url string `json:"url"` + BucketName string `json:"bucket_name"` + BucketNameCdn string `json:"bucket_name_cdn"` +} + +type Mod struct { + client *minio.Client + cfg DataType +} + +func New(_name ...string) *Mod { + var name string + if len(_name) > 0 { + name = _name[0] + } else { + getName, _ := g.Cfg().Get(ctx, "s3.type") + name = getName.String() + } + + get, err := g.Cfg().Get(ctx, "s3."+name) + if err != nil { + panic(err.Error()) + } + var cfg DataType + get.Scan(&cfg) + + // 使用minio-go创建S3客户端 + obj, err := minio.New( + cfg.Address, + &minio.Options{ + Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""), + Secure: cfg.Ssl, + }, + ) + if err != nil { + log.Fatalln(err) + } + + mod := &Mod{ + client: obj, + cfg: cfg, + } + + return mod +} + +//func (s *Mod) Load() { +// //导入配置 +// get, err := g.Cfg().Get(ctx, "s3.type") +// cfgType := get.String() +// if cfgType == "" { +// cfgType = "default" +// } +// +// cfgData, err := g.Cfg().Get(ctx, "s3."+cfgType) +// if cfgData.IsEmpty() { +// panic("当前配置中未配置s3:" + cfgType) +// } +// +// get, err = g.Cfg().Get(ctx, "s3."+cfgType) +// err = get.Scan(&Cfg) +// +// // 使用minio-go创建S3客户端 +// obj, err := minio.New( +// Cfg.Address, +// &minio.Options{ +// Creds: credentials.NewStaticV4(Cfg.AccessKey, Cfg.SecretKey, ""), +// Secure: Cfg.Ssl, +// }, +// ) +// if err != nil { +// log.Fatalln(err) +// } +// +// client = obj +//} +// +//func (s *Mod) S3(name string) { +// get, err := g.Cfg().Get(ctx, "s3."+name) +// if err != nil { +// panic(err) +// } +// get.Scan(&Cfg) +// +// // 使用minio-go创建S3客户端 +// obj, err := minio.New( +// Cfg.Address, +// &minio.Options{ +// Creds: credentials.NewStaticV4(Cfg.AccessKey, Cfg.SecretKey, ""), +// Secure: Cfg.Ssl, +// }, +// ) +// if err != nil { +// log.Fatalln(err) +// } +// +// client = obj +// +//} + +// GetCfg 获取配置 +func (s *Mod) GetCfg() *DataType { + return &s.cfg +} + +// GetFileUrl 生成指向S3存储桶中指定文件的预签名URL +// +// @Description: 生成一个具有有限有效期的预签名URL,可用于访问S3存储桶中的文件。 +// @receiver s: S3的实例,用于执行S3操作。 +// @param name: 要获取预签名URL的文件名。 +// @param bucketName: 文件所在的存储桶名称。 +// @return presignedURL: 生成的预签名URL,可用于访问文件。 +// @return err: 在获取预签名URL过程中遇到的任何错误。 +func (s *Mod) GetFileUrl(name string, bucketName string, _expires ...time.Duration) (presignedURL *url.URL, err error) { + // 设置预签名URL的有效期为1小时 + expires := time.Hour * 1 + if len(_expires) > 0 { + expires = _expires[0] + } + cacheKey := fmt.Sprintf("s3:%v:%v", name, bucketName) + get, _ := gcache.Get(ctx, cacheKey) + //g.Dump(get.Vars()) + if !get.IsEmpty() { + err = gconv.Struct(get.Val(), &presignedURL) + //presignedURL = + return + } + //expires := time.Duration(604800) + // 调用s3().PresignedGetObject方法生成预签名URL + presignedURL, err = s.client.PresignedGetObject(ctx, bucketName, name, expires, nil) + err = gcache.Set(ctx, cacheKey, presignedURL, expires) + return +} + +// PutFileUrl 生成一个用于上传文件到指定bucket的预签名URL +// +// @Description: +// @receiver s +// @param name 文件名 +// @param bucketName 存储桶名称 +// @return presignedURL 预签名的URL,用于上传文件 +// @return err 错误信息,如果在生成预签名URL时发生错误 +func (s *Mod) PutFileUrl(name string, bucketName string) (presignedURL *url.URL, err error) { + // 设置预签名URL的有效期 + //expires := time.Now().Add(time.Minute * 30).Unix() // 例如:有效期30分钟 + //expires2 := time.Duration(expires) + expires := time.Minute * 10 + // 生成预签名URL + presignedURL, err = s.client.PresignedPutObject(ctx, bucketName, name, expires) + + return +} + +// 获取储存桶列表 +func (s *Mod) ListBuckets() []minio.BucketInfo { + buckets, err := s.client.ListBuckets(ctx) + //g.Dump(buckets) + if err != nil { + //fmt.Println(err) + return nil + } + return buckets +} + +// PutObject 上传文件到指定的存储桶中。 +// +// @Description: 上传一个文件到指定的存储桶。 +// @receiver s *Mod: 表示调用此方法的Mod实例。 +// @param f io.Reader: 文件的读取器,用于读取待上传的文件内容。 +// @param name string: 待上传文件的名称。 +// @param bucketName string: 存储桶的名称。 +// @param _size ...int64: 可选参数,指定上传文件的大小。如果未提供,则默认为-1,表示读取文件直到EOF。 +// @return res minio.UploadInfo: 上传成功后返回的上传信息。 +// @return err error: 如果上传过程中出现错误,则返回错误信息。 +func (s *Mod) PutObject(f io.Reader, name string, bucketName string, _size ...int64) (res minio.UploadInfo, err error) { + // 初始化文件大小为-1,表示将读取文件至结束。 + var size = int64(-1) + // 如果提供了文件大小,则使用提供的大小值。 + if len(_size) > 0 { + size = _size[0] + } + + // 调用client的PutObject方法上传文件,并设置内容类型为"application/octet-stream"。 + res, err = s.client.PutObject(ctx, bucketName, name, f, size, minio.PutObjectOptions{ContentType: "application/octet-stream"}) + if err != nil { + g.Log().Error(ctx, err) + } + return +} + +// RemoveObject 删除文件 +func (s *Mod) RemoveObject(name string, bucketName string) (err error) { + opts := minio.RemoveObjectOptions{ + //GovernanceBypass: true, + //VersionID: "myversionid", + } + err = s.client.RemoveObject(ctx, bucketName, name, opts) + return +} + +// ListObjects 文件列表 +func (s *Mod) ListObjects(bucketName string, prefix string) (res <-chan minio.ObjectInfo, err error) { + res = s.client.ListObjects(ctx, bucketName, minio.ListObjectsOptions{ + Prefix: prefix, + }) + return +} + +// SetBucketPolicy 设置bucket或对象前缀的访问权限 +func (s *Mod) SetBucketPolicy(bucketName string, prefix string) (err error) { + + policy := `{"Version": "2012-10-17","Statement": [{"Action": ["s3:GetObject"],"Effect": "Allow","Principal": {"AWS": ["*"]},"Resource": ["arn:aws:s3:::my-bucketname/*"],"Sid": ""}]}` + + err = s.client.SetBucketPolicy(ctx, bucketName, policy) + return +} + +// GetUrl 获取文件访问地址 +func (s *Mod) GetUrl(filePath string, defaultFile ...string) (url string) { + bucketName := s.cfg.BucketNameCdn + get := s.cfg.Url + + //如果没有图片,返回默认的图片地址 + if filePath == "" && len(defaultFile) > 0 { + filePath = defaultFile[0] + } + + if s.cfg.Ssl { + url = get + filePath + } else { + url = get + path.Join(bucketName, filePath) + } + + return +} + +func (s *Mod) GetPath(url string) (filePath string) { + bucketName := s.cfg.BucketNameCdn + get := s.cfg.Url + + return url[len(get+bucketName)+1:] +} diff --git a/utility.go b/utility.go new file mode 100644 index 0000000..63fab9f --- /dev/null +++ b/utility.go @@ -0,0 +1 @@ +package utility_go