shanshanzhong 4ad384b01a
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m20s
fix(redis): 修复缓存设置和清除逻辑的问题
修复Redis缓存设置未设置TTL的问题,使用节点拉取间隔加60秒作为TTL
修复ClearServerAllCache中重复添加keys的问题
修复ClearServerCache中未使用cursor参数的问题
优化ClearServerAllCache以支持清除多种前缀的缓存
2025-11-23 23:39:30 -08:00

250 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package node
import (
"context"
"fmt"
"strings"
"github.com/perfect-panel/server/pkg/tool"
"gorm.io/gorm"
)
type customServerLogicModel interface {
FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error)
FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error)
ClearNodeCache(ctx context.Context, params *FilterNodeParams) error
CountNodesByIdsAndTags(ctx context.Context, nodeIds []int64, tags []string) (int64, error)
ClearServerAllCache(ctx context.Context) error
}
const (
// ServerUserListCacheKey Server User List Cache Key
ServerUserListCacheKey = "server:user:"
// ServerConfigCacheKey Server Config Cache Key
ServerConfigCacheKey = "server:config:"
)
// FilterParams Filter Server Params
type FilterParams struct {
Page int
Size int
Ids []int64 // Server IDs
Search string
}
type FilterNodeParams struct {
Page int // Page Number
Size int // Page Size
NodeId []int64 // Node IDs
ServerId []int64 // Server IDs
Tag []string // Tags
Search string // Search Address or Name
Protocol string // Protocol
Preload bool // Preload Server
Enabled *bool // Enabled
}
// FilterServerList Filter Server List
func (m *customServerModel) FilterServerList(ctx context.Context, params *FilterParams) (int64, []*Server, error) {
var servers []*Server
var total int64
query := m.WithContext(ctx).Model(&Server{})
if params == nil {
params = &FilterParams{
Page: 1,
Size: 10,
}
}
if params.Search != "" {
s := "%" + params.Search + "%"
query = query.Where("`name` LIKE ? OR `address` LIKE ?", s, s)
}
if len(params.Ids) > 0 {
query = query.Where("id IN ?", params.Ids)
}
err := query.Count(&total).Order("sort ASC").Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&servers).Error
return total, servers, err
}
func (m *customServerModel) QueryServerList(ctx context.Context, ids []int64) (servers []*Server, err error) {
query := m.WithContext(ctx).Model(&Server{})
err = query.Where("id IN (?)", ids).Find(&servers).Error
return
}
// FilterNodeList Filter Node List
func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error) {
var nodes []*Node
var total int64
query := m.WithContext(ctx).Model(&Node{})
if params == nil {
params = &FilterNodeParams{
Page: 1,
Size: 10,
}
}
if params.Search != "" {
s := "%" + params.Search + "%"
query = query.Where("`name` LIKE ? OR `address` LIKE ? OR `tags` LIKE ? OR `port` LIKE ? ", s, s, s, s)
}
if len(params.NodeId) > 0 {
query = query.Where("id IN ?", params.NodeId)
}
if len(params.ServerId) > 0 {
query = query.Where("server_id IN ?", params.ServerId)
}
if len(params.Tag) > 0 {
query = query.Scopes(InSet("tags", params.Tag))
}
if params.Protocol != "" {
query = query.Where("protocol = ?", params.Protocol)
}
if params.Enabled != nil {
query = query.Where("enabled = ?", *params.Enabled)
}
if params.Preload {
query = query.Preload("Server")
}
err := query.Count(&total).Order("sort ASC").Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&nodes).Error
return total, nodes, err
}
// ClearNodeCache Clear Node Cache
func (m *customServerModel) ClearNodeCache(ctx context.Context, params *FilterNodeParams) error {
_, nodes, err := m.FilterNodeList(ctx, params)
if err != nil {
return err
}
var cacheKeys []string
for _, node := range nodes {
cacheKeys = append(cacheKeys, fmt.Sprintf("%s%d", ServerUserListCacheKey, node.ServerId))
if node.Protocol != "" {
var cursor uint64
for {
keys, newCursor, err := m.Cache.Scan(ctx, cursor, fmt.Sprintf("%s%d*", ServerConfigCacheKey, node.ServerId), 100).Result()
if err != nil {
return err
}
if len(keys) > 0 {
cacheKeys = append(cacheKeys, keys...)
}
cursor = newCursor
if cursor == 0 {
break
}
}
}
}
if len(cacheKeys) > 0 {
cacheKeys = tool.RemoveDuplicateElements(cacheKeys...)
return m.Cache.Del(ctx, cacheKeys...).Err()
}
return nil
}
// ClearServerCache Clear Server Cache
func (m *customServerModel) ClearServerCache(ctx context.Context, serverId int64) error {
var cacheKeys []string
cacheKeys = append(cacheKeys, fmt.Sprintf("%s%d", ServerUserListCacheKey, serverId))
var cursor uint64
for {
keys, newCursor, err := m.Cache.Scan(ctx, cursor, fmt.Sprintf("%s%d*", ServerConfigCacheKey, serverId), 100).Result()
if err != nil {
return err
}
if len(keys) > 0 {
cacheKeys = append(cacheKeys, keys...)
}
cursor = newCursor
if cursor == 0 {
break
}
}
if len(cacheKeys) > 0 {
cacheKeys = tool.RemoveDuplicateElements(cacheKeys...)
return m.Cache.Del(ctx, cacheKeys...).Err()
}
return nil
}
func (m *customServerModel) ClearServerAllCache(ctx context.Context) error {
var cursor uint64
var keys []string
prefixes := []string{ServerConfigCacheKey + "*", ServerUserListCacheKey + "*"}
for _, prefix := range prefixes {
cursor = 0
for {
scanKeys, newCursor, err := m.Cache.Scan(ctx, cursor, prefix, 999).Result()
if err != nil {
m.Logger.Error(ctx, fmt.Sprintf("ClearServerAllCache err:%v", err))
break
}
m.Logger.Info(ctx, fmt.Sprintf("ClearServerAllCache query keys:%v", scanKeys))
keys = append(keys, scanKeys...)
cursor = newCursor
if cursor == 0 {
break
}
}
}
if len(keys) > 0 {
m.Logger.Info(ctx, fmt.Sprintf("ClearServerAllCache keys:%v", keys))
return m.Cache.Del(ctx, keys...).Err()
}
return nil
}
// InSet 支持多值 OR 查询
func InSet(field string, values []string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if len(values) == 0 {
return db
}
conds := make([]string, len(values))
args := make([]interface{}, len(values))
for i, v := range values {
conds[i] = "FIND_IN_SET(?, " + field + ")"
args[i] = v
}
// 用括号包裹 OR 条件,保证外层 AND 不受影响
return db.Where("("+strings.Join(conds, " OR ")+")", args...)
}
}
// CountNodesByIdsAndTags 根据节点ID和标签计算启用的节点数量
func (m *customServerModel) CountNodesByIdsAndTags(ctx context.Context, nodeIds []int64, tags []string) (int64, error) {
var count int64
query := m.WithContext(ctx).Model(&Node{}).Where("enabled = ?", true)
// 如果有节点ID或标签添加相应的查询条件
if len(nodeIds) > 0 || len(tags) > 0 {
subQuery := m.WithContext(ctx).Model(&Node{}).Where("enabled = ?", true)
if len(nodeIds) > 0 && len(tags) > 0 {
// 节点ID和标签都存在时使用OR条件
subQuery = subQuery.Where("id IN ? OR ?", nodeIds, InSet("tags", tags))
} else if len(nodeIds) > 0 {
// 只有节点ID
subQuery = subQuery.Where("id IN ?", nodeIds)
} else {
// 只有标签
subQuery = subQuery.Scopes(InSet("tags", tags))
}
query = subQuery
}
// 打印sql
fmt.Println(query.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx }))
err := query.Count(&count).Error
return count, err
}