109 lines
3.1 KiB
Go
109 lines
3.1 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
redislib "gateway/internal/library/redis"
|
|
permission "gateway/internal/model/permission/domain"
|
|
domrepo "gateway/internal/model/permission/domain/repository"
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
)
|
|
|
|
// CasbinRedisAdapter is a tenant-scoped Redis-backed Casbin policy store.
|
|
// Layout:
|
|
//
|
|
// permission:casbin:rules:{tenant_id} → Redis Set of JSON-encoded
|
|
// []string rule rows.
|
|
//
|
|
// Atomicity: SaveAll uses a Pipelined DEL+SADD; AddPolicy/RemovePolicy
|
|
// rely on Redis Set semantics (idempotent inserts, single-shot removes).
|
|
type CasbinRedisAdapter struct {
|
|
client *redis.Redis
|
|
}
|
|
|
|
// NewCasbinRedisAdapter returns a CasbinPolicyAdapter backed by Redis.
|
|
func NewCasbinRedisAdapter(client *redislib.Client) (domrepo.CasbinPolicyAdapter, error) {
|
|
if client == nil || client.Zero() == nil {
|
|
return nil, fmt.Errorf("permission: redis client is required for casbin adapter")
|
|
}
|
|
return &CasbinRedisAdapter{client: client.Zero()}, nil
|
|
}
|
|
|
|
func (a *CasbinRedisAdapter) key(tenantID string) string {
|
|
return permission.GetCasbinRulesRedisKey(tenantID)
|
|
}
|
|
|
|
// LoadAll returns every rule for tenantID.
|
|
func (a *CasbinRedisAdapter) LoadAll(ctx context.Context, tenantID string) ([][]string, error) {
|
|
raw, err := a.client.SmembersCtx(ctx, a.key(tenantID))
|
|
if err != nil && !errors.Is(err, redis.Nil) {
|
|
return nil, err
|
|
}
|
|
rules := make([][]string, 0, len(raw))
|
|
for _, item := range raw {
|
|
var rule []string
|
|
if err := json.Unmarshal([]byte(item), &rule); err != nil {
|
|
continue
|
|
}
|
|
if len(rule) == 0 {
|
|
continue
|
|
}
|
|
rules = append(rules, rule)
|
|
}
|
|
return rules, nil
|
|
}
|
|
|
|
// SaveAll replaces all rules for tenantID with rules atomically.
|
|
func (a *CasbinRedisAdapter) SaveAll(ctx context.Context, tenantID string, rules [][]string) error {
|
|
key := a.key(tenantID)
|
|
encoded := make([]any, 0, len(rules))
|
|
for _, rule := range rules {
|
|
raw, err := json.Marshal(rule)
|
|
if err != nil {
|
|
return fmt.Errorf("permission: marshal casbin rule: %w", err)
|
|
}
|
|
encoded = append(encoded, string(raw))
|
|
}
|
|
return a.client.PipelinedCtx(ctx, func(p redis.Pipeliner) error {
|
|
if err := p.Del(ctx, key).Err(); err != nil {
|
|
return err
|
|
}
|
|
if len(encoded) == 0 {
|
|
return nil
|
|
}
|
|
return p.SAdd(ctx, key, encoded...).Err()
|
|
})
|
|
}
|
|
|
|
// AddPolicy inserts a single rule (idempotent).
|
|
func (a *CasbinRedisAdapter) AddPolicy(ctx context.Context, tenantID string, rule []string) error {
|
|
raw, err := json.Marshal(rule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = a.client.SaddCtx(ctx, a.key(tenantID), string(raw))
|
|
return err
|
|
}
|
|
|
|
// RemovePolicy removes a single rule.
|
|
func (a *CasbinRedisAdapter) RemovePolicy(ctx context.Context, tenantID string, rule []string) error {
|
|
raw, err := json.Marshal(rule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = a.client.SremCtx(ctx, a.key(tenantID), string(raw))
|
|
return err
|
|
}
|
|
|
|
// Clear empties all rules for tenantID.
|
|
func (a *CasbinRedisAdapter) Clear(ctx context.Context, tenantID string) error {
|
|
_, err := a.client.DelCtx(ctx, a.key(tenantID))
|
|
return err
|
|
}
|
|
|
|
var _ domrepo.CasbinPolicyAdapter = (*CasbinRedisAdapter)(nil)
|