Initial commit

This commit is contained in:
2026-05-20 16:13:58 +08:00
commit ceffbe382e
500 changed files with 157469 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
package redis
import (
"errors"
"fmt"
"github.com/go-redis/redis"
)
var StatRds *Manager
type Manager struct {
client *redis.Client
options *redis.Options
}
// Redis 配置项
type Options = redis.Options
func NewManagerWithUrl(url string, fn func(opt *Options)) (*Manager, error) {
opts, err := redis.ParseURL(url)
if err != nil {
return nil, err
}
if opts == nil {
err = errors.New(fmt.Sprintf("NewManagerWithUrl failed url %s", url))
return nil, err
}
if fn != nil {
fn(opts)
}
return &Manager{options: opts}, nil
}
+73
View File
@@ -0,0 +1,73 @@
package redis
import (
"fmt"
"time"
)
const (
AdvertiseClickDayHashTemplate = "stat-center.advertise.day.click.%s" // 广告点击
AdvertiseClickDayHashIdHourSep = "@"
IpLimiterKeyTemplate = "stat-center.%s:%d.ip.limiter.key#%s"
IpLimiterDayRecordKey = "stat-center.ip.limiter.day.record.key"
DownloadCheckBotMsgHashKeyTmpl = "stat-center.download.check.bot.msg.hash.key.%s" // 下载提示消息
ServerStartTimeHashKeyTemplate = "stat-center.server.start_time.hash.key.%d"
ServerKeepAliveHashKeyTemplate = "stat-center.server.keepalive.hash.key.%d"
ServerCloseFlagHashKeyTemplate = "stat-center.server.close.flag.hash.key.%d"
)
func GetServerStartTimeHashKey(index int) string {
return fmt.Sprintf(ServerStartTimeHashKeyTemplate, index)
}
func GetServerKeepAliveHashKey(index int) string {
return fmt.Sprintf(ServerKeepAliveHashKeyTemplate, index)
}
func GetServerCloseFlagHashKey(index int) string {
return fmt.Sprintf(ServerCloseFlagHashKeyTemplate, index)
}
func GetDownloadCheckBotMsgHashKey() string {
return fmt.Sprintf(DownloadCheckBotMsgHashKeyTmpl, time.Now().Format("2006-02-01-15"))
}
func GetIpLimitDayKey(serverIp string, serverPort int) string {
ts := time.Now().Local().Format("2006-02-01")
return fmt.Sprintf(IpLimiterKeyTemplate, serverIp, serverPort, ts)
}
func GetIpLimitSpecifiedDayKey(serverIp string, serverPort int, day time.Time) string {
ts := day.Local().Format("2006-02-01")
return fmt.Sprintf(IpLimiterKeyTemplate, serverIp, serverPort, ts)
}
func GetAdvertiseClickDayHash(now time.Time) *Hash {
t := now.Local().Format("2006-02-01")
return NewHash(StatRds.Client(), fmt.Sprintf(AdvertiseClickDayHashTemplate, t))
}
func GetAdvertiseClickHourName(appId int32, adsId int64, hour int) string {
return fmt.Sprintf("%d%s%d%s%d", appId, AdvertiseClickDayHashIdHourSep, adsId, AdvertiseClickDayHashIdHourSep, hour)
}
const (
NameSource = "name:source" //姓名池
NameSourceExpire = 2 * 24 * time.Hour
AppClickDaySetTemplate = "nh.app.day.click.%s" // app点击
AppClickDaySetIdHourSep = "@"
)
func GetAppClickDaySet(now time.Time) *Hash {
t := now.Local().Format("2006-02-01")
return NewHash(StatRds.Client(), fmt.Sprintf(AppClickDaySetTemplate, t))
}
func GetAppClickHourName(appId string, hour int) string {
return fmt.Sprintf("%s%s%d", appId, AppClickDaySetIdHourSep, hour)
}
func GetAppClickHourNameByAppId(appId int64, parentAppId int64, hour int) string {
return fmt.Sprintf("%s_%s%s%d", appId, parentAppId, AppClickDaySetIdHourSep, hour)
}
+107
View File
@@ -0,0 +1,107 @@
package redis
import (
"time"
"github.com/go-redis/redis"
)
//key: {name:value, name:value, name:value}
type Hash struct {
key string
client *redis.Client
}
func NewHash(client *redis.Client, key string) *Hash {
return &Hash{
key: key,
client: client,
}
}
func (s *Hash) Key() string {
return s.key
}
// Expire 设置集合过期时间
func (s *Hash) Expire(expiration time.Duration) (bool, error) {
return s.client.Expire(s.key, expiration).Result()
}
// Delete 删除一个或多个 Name
func (s *Hash) Delete(names ...string) (int64, error) {
return s.client.HDel(s.key, names...).Result()
}
// Exists 检测Name是否在哈希表中
func (s *Hash) Exists(name string) (bool, error) {
return s.client.HExists(s.key, name).Result()
}
// NameValues 获取所有哈希表数据
func (s *Hash) NameValues() (map[string]string, error) {
return s.client.HGetAll(s.key).Result()
}
// Names 获取所有哈希表的 Name
func (s *Hash) Names() ([]string, error) {
return s.client.HKeys(s.key).Result()
}
// Values 获取所有哈希表的 Total
func (s *Hash) Values() ([]string, error) {
return s.client.HVals(s.key).Result()
}
// SetNX 当name不存在时才向哈希表中设置一对Name:Total
func (s *Hash) SetNX(name string, value interface{}) (bool, error) {
return s.client.HSetNX(s.key, name, value).Result()
}
// Set 向哈希表中设置一对Name:Total
func (s *Hash) Set(name string, value interface{}) (bool, error) {
return s.client.HSet(s.key, name, value).Result()
}
// Get 获取哈希表中Name对应的Value
func (s *Hash) Get(name string) (string, error) {
return s.client.HGet(s.key, name).Result()
}
// IncInt 返回累加之后的新值
func (s *Hash) IncInt(name string, value int64) (int64, error) {
return s.client.HIncrBy(s.key, name, value).Result()
}
// IncFloat 返回累加之后的新值
func (s *Hash) IncFloat(name string, value float64) (float64, error) {
return s.client.HIncrByFloat(s.key, name, value).Result()
}
// Len 返回哈希表的大小
func (s *Hash) Len() (int64, error) {
return s.client.HLen(s.key).Result()
}
// Scan 扫描哈希表数据
func (s *Hash) Scan(match string, piece int64, callback func(name, value string)) error {
proc := func(l []string) {
for i := 0; i+1 < len(l); i += 2 {
callback(l[i], l[i+1])
}
}
lst, cur, err := s.client.HScan(s.key, 0, match, piece).Result()
if err != nil {
return err
}
proc(lst)
for cur > 0 {
lst, cur, err = s.client.HScan(s.key, cur, match, piece).Result()
if err != nil {
return err
}
proc(lst)
}
return nil
}
+110
View File
@@ -0,0 +1,110 @@
package redis
import (
_ "embed"
"encoding/json"
"errors"
"fmt"
"sync"
"time"
"github.com/go-redis/redis"
)
//go:embed limit.lua
var lua string
const (
SUCCESS = "success"
FAILED = "failed"
)
type Bucket struct {
Name string `json:"na"`
Capacity int64 `json:"ca"`
Rate int64 `json:"ra"`
Size int64 `json:"si"`
GenerateAt int64 `json:"ge_at"`
CreatedAt int64 `json:"cr_at"`
UpdatedAt int64 `json:"up_at"`
PassedCount int64 `json:"pa_co"`
RejectCount int64 `json:"re_co"`
PassedHours map[int64]int64 `json:"pa_ho"`
RejectHours map[int64]int64 `json:"re_ho"`
}
type BucketList []*Bucket
func (a BucketList) Len() int { return len(a) }
func (a BucketList) Less(i, j int) bool {
return a[i].PassedCount > a[j].PassedCount
}
func (a BucketList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type TokenBucket struct {
keyGen func() string
capacity int64
rate int64
script *redis.Script
client *redis.Client
expire time.Duration
lock sync.RWMutex
curKey string
}
func NewTokenBucket(client *redis.Client, keyGen func() string, capacity, rate int64, expire time.Duration) *TokenBucket {
return &TokenBucket{
expire: expire,
capacity: capacity,
rate: rate,
client: client,
script: redis.NewScript(lua),
keyGen: keyGen,
}
}
func (s *TokenBucket) TakeTokens(name string, count int64) error {
key := s.keyGen()
now := time.Now().Local()
cmd := []string{
key, //key
name, //name
fmt.Sprintf("%d", s.capacity),
fmt.Sprintf("%d", s.rate),
fmt.Sprintf("%d", count),
fmt.Sprintf("%d", now.UnixMilli()),
fmt.Sprintf("%d", now.Hour()),
}
ret, err := s.script.Run(s.client, cmd).String()
if err != nil {
return err
}
if ret != SUCCESS {
return errors.New("sorry, I can't work anymore~")
}
var same = false
if s.lock.TryRLock() {
same = key == s.curKey
s.lock.RUnlock()
} else {
return nil
}
if !same {
if s.lock.TryLock() {
if ok, err := s.client.Expire(key, s.expire).Result(); err == nil && ok {
s.curKey = key
}
s.lock.Unlock()
}
}
return nil
}
func (s *TokenBucket) Traverse(callback func(key string, item *Bucket)) error {
return NewHash(s.client, s.keyGen()).Scan("", 1000, func(name, value string) {
bucket := Bucket{}
if err := json.Unmarshal([]byte(value), &bucket); err == nil {
callback(name, &bucket)
}
})
}
+90
View File
@@ -0,0 +1,90 @@
local CAPACITY = "ca"
local RATE = "ra"
local SIZE = "si"
local GENERATE_AT = "ge_at"
local CREATED_AT = "cr_at"
local UPDATED_AT = "up_at"
local PASSED_COUNT = "pa_co"
local REJECT_COUNT = "re_co"
local PASSED_HOURS = "pa_ho"
local REJECT_HOURS = "re_ho"
local SUCCESS = "success"
local FAILED = "failed"
local sKey = tostring(KEYS[1] or "")
local sName = tostring(KEYS[2] or "")
local iCapacity = tonumber(KEYS[3] or 0)
local iRate = tonumber(KEYS[4] or 0)
local iCount = tonumber(KEYS[5] or 0)
local iNow = tonumber(KEYS[6] or 0)
local sHour = tostring(KEYS[7] or "")
if sKey == "" or sName == "" or sHour == "" then
return FAILED .. " param error1"
end
if iCapacity <= 0 or iRate <= 0 or iCount <= 0 or iNow <= 0 then
return FAILED .. "param error2"
end
local bucket = {}
local data = redis.pcall("HGET", sKey, sName)
local init = false
if data == nil or data == false or data == "" then
init = true
else
bucket = cjson.decode(data)
if bucket == nil then
init = true
end
end
if init then
bucket[CAPACITY] = iCapacity
bucket[RATE] = iRate
bucket[SIZE] = iCapacity
bucket[GENERATE_AT] = iNow
bucket[CREATED_AT] = iNow
bucket[UPDATED_AT] = iNow
bucket[PASSED_COUNT] = 0
bucket[REJECT_COUNT] = 0
bucket[PASSED_HOURS] = {}
bucket[REJECT_HOURS] = {}
else
bucket[CAPACITY] = iCapacity
bucket[RATE] = iRate
end
local bk_size = tonumber(bucket[SIZE] or 0)
local bk_capacity = tonumber(bucket[CAPACITY] or 0)
if bk_size < bk_capacity then
local bk_last = tonumber(bucket[GENERATE_AT] or 0)
local dura = iNow - bk_last
local incr = math.floor(dura * iRate / 1000)
if incr >= 1 then
bk_size = math.min(bk_size + incr, bk_capacity)
bucket[SIZE] = bk_size
bucket[GENERATE_AT] = iNow
end
end
local message = FAILED
if iCount <= bk_size then
bucket[SIZE] = bk_size - iCount
bucket[PASSED_COUNT] = tonumber(bucket[PASSED_COUNT] or 0) + 1
local hours = bucket[PASSED_HOURS]
if hours == nil then
hours = {}
end
hours[sHour] = tonumber(hours[sHour] or 0) + 1
bucket[PASSED_HOURS] = hours
message = SUCCESS
else
bucket[REJECT_COUNT] = tonumber(bucket[REJECT_COUNT] or 0) + 1
local hours = bucket[REJECT_HOURS]
if hours == nil then
hours = {}
end
hours[sHour] = tonumber(hours[sHour] or 0) + 1
bucket[REJECT_HOURS] = hours
message = FAILED
end
bucket[UPDATED_AT] = iNow
data = cjson.encode(bucket)
redis.pcall("HSET", sKey, sName, data)
return message
+28
View File
@@ -0,0 +1,28 @@
package redis
import (
"fmt"
"testing"
"time"
)
func TestF2(t *testing.T) {
ts := time.Now().Local().Format("2006-02-01-15")
t.Log(ts)
}
func TestScan(t *testing.T) {
mgr, err := NewManagerWithUrl("redis://127.0.0.1:6379/1", nil)
if err != nil {
t.Error(err)
}
set := NewHash(mgr.Client(), "set-test-key")
for i := 0; i < 1000; i++ {
set.Set(fmt.Sprintf("name:%d", i), fmt.Sprintf("value:%d", i))
}
err = set.Scan("", 20, func(name, value string) {
t.Logf("N: %s, V:%s\n", name, value)
})
t.Log(err)
}
+34
View File
@@ -0,0 +1,34 @@
package redis
import (
"github.com/go-redis/redis"
"log"
)
func (m *Manager) Client() *redis.Client {
if m.client == nil {
m.initConnect()
}
return m.client
}
func (m *Manager) Close() {
if m.client != nil {
m.client.Close()
m.client = nil
}
}
func (m *Manager) initConnect() error {
return m.Connect(m.options)
}
func (m *Manager) Connect(opts *Options) error {
c := redis.NewClient(opts)
if err := c.Ping().Err(); err != nil {
log.Panicf("[error] ping redis err %+v\n", err)
return err
}
m.client = c
return nil
}
+93
View File
@@ -0,0 +1,93 @@
package redis
import (
"errors"
"time"
goredis "github.com/go-redis/redis"
)
func (m *Manager) Get(key string) (*string, error) {
if key == "" {
return nil, errors.New("key is empty")
}
data, err := m.Client().Get(key).Result()
if err != nil {
if err == goredis.Nil {
return nil, nil
}
return nil, err
} else {
return &data, nil
}
}
func (m *Manager) Set(key string, value interface{}, expire time.Duration) error {
if err := m.Client().Set(key, value, expire).Err(); err != nil {
return err
}
return nil
}
func (m *Manager) Del(keys ...string) error {
if err := m.Client().Del(keys...).Err(); err != nil {
return err
}
return nil
}
func (m *Manager) GetBytes(key string) ([]byte, error) {
cmd := m.Client().Get(key)
if err := cmd.Err(); err != nil {
return []byte{}, err
}
return cmd.Bytes()
}
func (m *Manager) SAdd(key string, members ...interface{}) error {
if err := m.Client().SAdd(key, members...).Err(); err != nil {
return err
}
return nil
}
func (m *Manager) Exists(keys ...string) (bool, error) {
cmd := m.Client().Exists(keys...)
if err := cmd.Err(); err != nil {
return false, err
}
return cmd.Val() > 0, nil
}
func (m *Manager) Expire(key string, expire time.Duration) error {
if err := m.Client().Expire(key, expire).Err(); err != nil {
return err
}
return nil
}
func (m *Manager) Keys(pattern string) ([]string, error) {
list := m.Client().Keys(pattern)
if err := list.Err(); err != nil {
return []string{}, err
}
return list.Val(), nil
}
func (m *Manager) Incr(pattern string) (int64, error) {
val, err := m.Client().Incr(pattern).Result()
if err != nil {
return 0, err
}
return val, nil
}
//获取key的剩余生存时间
func (m *Manager) TTL(pattern string) time.Duration {
durCmd := m.Client().TTL(pattern)
if durCmd != nil {
return durCmd.Val()
}
return 0
}