chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
209
internal/app/privacy/cache.go
Normal file
209
internal/app/privacy/cache.go
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
package privacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/readmodelcache"
|
||||
"telesrv/internal/store"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultPrivacyRulesCacheTTL = 24 * time.Hour
|
||||
|
||||
privacySnapshotMaxOwners = 8192
|
||||
)
|
||||
|
||||
var allPrivacyRuleKeys = []domain.PrivacyKey{
|
||||
domain.PrivacyKeyStatusTimestamp,
|
||||
domain.PrivacyKeyChatInvite,
|
||||
domain.PrivacyKeyPhoneCall,
|
||||
domain.PrivacyKeyPhoneP2P,
|
||||
domain.PrivacyKeyForwards,
|
||||
domain.PrivacyKeyProfilePhoto,
|
||||
domain.PrivacyKeyPhoneNumber,
|
||||
domain.PrivacyKeyAddedByPhone,
|
||||
domain.PrivacyKeyVoiceMessages,
|
||||
domain.PrivacyKeyAbout,
|
||||
domain.PrivacyKeyBirthday,
|
||||
domain.PrivacyKeyStarGiftsAutoSave,
|
||||
domain.PrivacyKeyNoPaidMessages,
|
||||
domain.PrivacyKeySavedMusic,
|
||||
}
|
||||
|
||||
// privacyRulesMap 是单个 owner 的全部隐私规则(空 map = 查过且无规则,即负缓存)。
|
||||
type privacyRulesMap map[domain.PrivacyKey]domain.PrivacyRules
|
||||
|
||||
// CachedPrivacyStore 是 account privacy rules 的 owner 级 read-model 缓存,由统一缓存原语承载
|
||||
// (LRU 单条驱逐 / epoch 守卫 / clone)。owner 级、变更稀少:一次性装入某 owner 全部 key,让
|
||||
// projectBatch/CanSeeMatrix 在内存里判 phone/status/photo 可见性,免去反复规划 account_privacy_rules。
|
||||
// 单 owner 走 GetOrLoad,多 owner 走 GetOrLoadBatch(一次 LoadEpoch + 合批 ListPrivacyRules + 写回)。
|
||||
type CachedPrivacyStore struct {
|
||||
inner store.PrivacyStore
|
||||
cache *readmodelcache.Cache[int64, privacyRulesMap]
|
||||
}
|
||||
|
||||
func NewCachedPrivacyStore(inner store.PrivacyStore, ttl time.Duration) *CachedPrivacyStore {
|
||||
if inner == nil {
|
||||
return nil
|
||||
}
|
||||
if ttl <= 0 {
|
||||
ttl = DefaultPrivacyRulesCacheTTL
|
||||
}
|
||||
return &CachedPrivacyStore{
|
||||
inner: inner,
|
||||
cache: readmodelcache.New[int64, privacyRulesMap](readmodelcache.Config[int64, privacyRulesMap]{
|
||||
MaxEntries: privacySnapshotMaxOwners,
|
||||
TTL: ttl,
|
||||
Clone: clonePrivacyRulesMap,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CachedPrivacyStore) GetPrivacyRules(ctx context.Context, ownerUserID int64, key domain.PrivacyKey) (domain.PrivacyRules, bool, error) {
|
||||
if ownerUserID == 0 {
|
||||
return domain.PrivacyRules{}, false, nil
|
||||
}
|
||||
rules, err := c.ownerRules(ctx, ownerUserID)
|
||||
if err != nil {
|
||||
return domain.PrivacyRules{}, false, err
|
||||
}
|
||||
r, ok := rules[key]
|
||||
if !ok {
|
||||
return domain.PrivacyRules{}, false, nil
|
||||
}
|
||||
return cloneRules(r), true, nil
|
||||
}
|
||||
|
||||
func (c *CachedPrivacyStore) SetPrivacyRules(ctx context.Context, rules domain.PrivacyRules) error {
|
||||
if err := c.inner.SetPrivacyRules(ctx, rules); err != nil {
|
||||
return err
|
||||
}
|
||||
c.InvalidateOwners(rules.OwnerUserID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CachedPrivacyStore) ListPrivacyRules(ctx context.Context, ownerUserIDs []int64, keys []domain.PrivacyKey) ([]domain.PrivacyRules, error) {
|
||||
owners := dedupPrivacyOwnerIDs(ownerUserIDs)
|
||||
if len(owners) == 0 || len(keys) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
byOwner, err := c.ownerRulesBatch(ctx, owners)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keySet := make(map[domain.PrivacyKey]struct{}, len(keys))
|
||||
for _, key := range keys {
|
||||
keySet[key] = struct{}{}
|
||||
}
|
||||
out := make([]domain.PrivacyRules, 0, len(owners)*len(keys))
|
||||
for _, owner := range owners {
|
||||
for key, rules := range byOwner[owner] {
|
||||
if _, want := keySet[key]; !want {
|
||||
continue
|
||||
}
|
||||
out = append(out, cloneRules(rules))
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *CachedPrivacyStore) ownerRules(ctx context.Context, ownerUserID int64) (privacyRulesMap, error) {
|
||||
load := func() (privacyRulesMap, error) {
|
||||
list, err := c.inner.ListPrivacyRules(ctx, []int64{ownerUserID}, allPrivacyRuleKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildPrivacyRulesByOwner(list, []int64{ownerUserID})[ownerUserID], nil
|
||||
}
|
||||
if c == nil || c.cache == nil {
|
||||
return load()
|
||||
}
|
||||
return c.cache.GetOrLoad(ctx, ownerUserID, load)
|
||||
}
|
||||
|
||||
func (c *CachedPrivacyStore) ownerRulesBatch(ctx context.Context, owners []int64) (map[int64]privacyRulesMap, error) {
|
||||
loadMissing := func(ctx context.Context, missing []int64) (map[int64]privacyRulesMap, error) {
|
||||
list, err := c.inner.ListPrivacyRules(ctx, missing, allPrivacyRuleKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildPrivacyRulesByOwner(list, missing), nil
|
||||
}
|
||||
if c == nil || c.cache == nil {
|
||||
return loadMissing(ctx, owners)
|
||||
}
|
||||
return c.cache.GetOrLoadBatch(ctx, owners,
|
||||
func(int64) (int64, bool) { return 0, true }, // 纯 TTL,无版本闸门
|
||||
loadMissing,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *CachedPrivacyStore) InvalidateOwners(ids ...int64) {
|
||||
if c == nil || c.cache == nil || len(ids) == 0 {
|
||||
return
|
||||
}
|
||||
nonZero := make([]int64, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if id != 0 {
|
||||
nonZero = append(nonZero, id)
|
||||
}
|
||||
}
|
||||
c.cache.Invalidate(nonZero...)
|
||||
}
|
||||
|
||||
func (c *CachedPrivacyStore) FlushReadModelCache() {
|
||||
if c == nil || c.cache == nil {
|
||||
return
|
||||
}
|
||||
c.cache.Flush()
|
||||
}
|
||||
|
||||
// buildPrivacyRulesByOwner 把扁平规则按 owner 归组;每个 owner 都建一个条目(无规则即空 map),
|
||||
// 这样「查过且无规则」的 owner 也被负缓存,不会反复打后端。
|
||||
func buildPrivacyRulesByOwner(list []domain.PrivacyRules, owners []int64) map[int64]privacyRulesMap {
|
||||
out := make(map[int64]privacyRulesMap, len(owners))
|
||||
for _, owner := range owners {
|
||||
out[owner] = make(privacyRulesMap)
|
||||
}
|
||||
for _, item := range list {
|
||||
if item.OwnerUserID == 0 || item.Key == "" {
|
||||
continue
|
||||
}
|
||||
m, ok := out[item.OwnerUserID]
|
||||
if !ok {
|
||||
m = make(privacyRulesMap)
|
||||
out[item.OwnerUserID] = m
|
||||
}
|
||||
m[item.Key] = cloneRules(item)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func clonePrivacyRulesMap(in privacyRulesMap) privacyRulesMap {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := make(privacyRulesMap, len(in))
|
||||
for key, rules := range in {
|
||||
out[key] = cloneRules(rules)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func dedupPrivacyOwnerIDs(ids []int64) []int64 {
|
||||
seen := make(map[int64]struct{}, len(ids))
|
||||
out := make([]int64, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
out = append(out, id)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue