feat: release v1.0.0
This commit is contained in:
218
internal/pkg/cache/cache.go
vendored
218
internal/pkg/cache/cache.go
vendored
@@ -1,31 +1,219 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"server/internal/db/sqlc"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/maypok86/otter/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
"server/internal/config"
|
||||
)
|
||||
|
||||
type Caches struct {
|
||||
SysUserApisCache *otter.Cache[int32, []db.GetSysUserApisRow]
|
||||
rdb *redis.Client
|
||||
keyPrefix string
|
||||
}
|
||||
|
||||
func NewCaches() *Caches {
|
||||
sysUserApisCache := otter.Must(&otter.Options[int32, []db.GetSysUserApisRow]{
|
||||
MaximumSize: 1_000,
|
||||
})
|
||||
|
||||
func NewCaches(rdb *redis.Client, cfg *config.Config) *Caches {
|
||||
return &Caches{
|
||||
SysUserApisCache: sysUserApisCache,
|
||||
rdb: rdb,
|
||||
keyPrefix: cfg.Redis.KeyPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
// ClearSysUserCache 清理单个用户缓存
|
||||
func (c *Caches) ClearSysUserCache(userID int32) {
|
||||
c.SysUserApisCache.Invalidate(userID)
|
||||
func (c *Caches) key(key string) string {
|
||||
if c.keyPrefix == "" {
|
||||
return key
|
||||
}
|
||||
return c.keyPrefix + key
|
||||
}
|
||||
|
||||
// ClearAllSysUserCache 清理所有用户缓存
|
||||
func (c *Caches) ClearAllSysUserCache() {
|
||||
c.SysUserApisCache.InvalidateAll()
|
||||
func (c *Caches) Set(ctx context.Context, key string, value any, expiration time.Duration) error {
|
||||
return c.rdb.Set(ctx, c.key(key), value, expiration).Err()
|
||||
}
|
||||
|
||||
func (c *Caches) Get(ctx context.Context, key string) (string, bool, error) {
|
||||
val, err := c.rdb.Get(ctx, c.key(key)).Result()
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
return val, true, nil
|
||||
}
|
||||
|
||||
func (c *Caches) SetJSON(ctx context.Context, key string, value any, expiration time.Duration) error {
|
||||
b, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.rdb.Set(ctx, c.key(key), b, expiration).Err()
|
||||
}
|
||||
|
||||
func GetJSON[T any](ctx context.Context, c *Caches, key string) (*T, bool, error) {
|
||||
val, err := c.rdb.Get(ctx, c.key(key)).Bytes()
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
var result T
|
||||
if err = json.Unmarshal(val, &result); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return &result, true, nil
|
||||
}
|
||||
|
||||
func GetOrSetJSON[T any](ctx context.Context, c *Caches, key string, expiration time.Duration, fetch func() (T, error)) (T, error) {
|
||||
var zero T
|
||||
|
||||
cacheKey := c.key(key)
|
||||
|
||||
val, err := c.rdb.Get(ctx, cacheKey).Bytes()
|
||||
if err == nil {
|
||||
var result T
|
||||
if err = json.Unmarshal(val, &result); err == nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
if !errors.Is(err, redis.Nil) {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
result, err := fetch()
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
b, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
_ = c.rdb.Set(ctx, cacheKey, b, expiration).Err()
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Caches) Del(ctx context.Context, keys ...string) error {
|
||||
for i := range keys {
|
||||
keys[i] = c.key(keys[i])
|
||||
}
|
||||
return c.rdb.Del(ctx, keys...).Err()
|
||||
}
|
||||
|
||||
func (c *Caches) Exists(ctx context.Context, key string) (bool, error) {
|
||||
n, err := c.rdb.Exists(ctx, c.key(key)).Result()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
// SAdd 向 Set 中添加成员
|
||||
func (c *Caches) SAdd(ctx context.Context, key string, members ...any) error {
|
||||
return c.rdb.SAdd(ctx, c.key(key), members...).Err()
|
||||
}
|
||||
|
||||
// SMembers 获取 Set 中所有成员
|
||||
func (c *Caches) SMembers(ctx context.Context, key string) ([]string, error) {
|
||||
return c.rdb.SMembers(ctx, c.key(key)).Result()
|
||||
}
|
||||
|
||||
// DelSetMembers 删除 Set 中记录的所有成员对应的缓存 key,并删除 Set 本身
|
||||
func (c *Caches) DelSetMembers(ctx context.Context, key string) error {
|
||||
members, err := c.SMembers(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keys := append(members, key)
|
||||
return c.Del(ctx, keys...)
|
||||
}
|
||||
|
||||
// SRem 从 Set 中移除成员
|
||||
func (c *Caches) SRem(ctx context.Context, key string, members ...any) error {
|
||||
return c.rdb.SRem(ctx, c.key(key), members...).Err()
|
||||
}
|
||||
|
||||
// SIsMember 判断 member 是否在 Set 中
|
||||
func (c *Caches) SIsMember(ctx context.Context, key string, member any) (bool, error) {
|
||||
return c.rdb.SIsMember(ctx, c.key(key), member).Result()
|
||||
}
|
||||
|
||||
// MGet 批量获取多个 key 的值
|
||||
func (c *Caches) MGet(ctx context.Context, keys ...string) (map[string]string, error) {
|
||||
if len(keys) == 0 {
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
|
||||
redisKeys := make([]string, len(keys))
|
||||
for i, key := range keys {
|
||||
redisKeys[i] = c.key(key)
|
||||
}
|
||||
|
||||
values, err := c.rdb.MGet(ctx, redisKeys...).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]string, len(keys))
|
||||
for i, value := range values {
|
||||
if value == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
str, ok := value.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
result[keys[i]] = str
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DelByPrefix 批量删除指定前缀的缓存键
|
||||
func (c *Caches) DelByPrefix(ctx context.Context, prefix string) error {
|
||||
prefix = c.key(prefix)
|
||||
|
||||
if !strings.HasSuffix(prefix, "*") {
|
||||
prefix += "*"
|
||||
}
|
||||
|
||||
var cursor uint64
|
||||
var keys []string
|
||||
var err error
|
||||
|
||||
for {
|
||||
keys, cursor, err = c.rdb.Scan(ctx, cursor, prefix, 100).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(keys) > 0 {
|
||||
if err = c.rdb.Del(ctx, keys...).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cursor == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Caches) Expire(ctx context.Context, key string, expiration time.Duration) error {
|
||||
return c.rdb.Expire(ctx, c.keyPrefix+key, expiration).Err()
|
||||
}
|
||||
|
||||
25
internal/pkg/cache/cachekey/key.go
vendored
Normal file
25
internal/pkg/cache/cachekey/key.go
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package cachekey
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
UserApiPermissionsPattern = "user:api:permissions:*"
|
||||
UserInfoPattern = "user:info:*"
|
||||
)
|
||||
|
||||
func UserApiPermissions(id int32) string {
|
||||
return fmt.Sprintf("user:api:permissions:%d", id)
|
||||
}
|
||||
|
||||
func UserInfo(id int32) string {
|
||||
return fmt.Sprintf("user:info:%d", id)
|
||||
}
|
||||
|
||||
func AuthRefresh(hash string) string {
|
||||
return fmt.Sprintf("auth:refresh:%s", hash)
|
||||
}
|
||||
|
||||
// AuthRefreshUser 反向索引 用于定位refresh token来删除
|
||||
func AuthRefreshUser(userID int32) string {
|
||||
return fmt.Sprintf("auth:refresh:user:%d", userID)
|
||||
}
|
||||
Reference in New Issue
Block a user