fix: sync contact phone privacy disclosure

This commit is contained in:
iamxvbaba 2026-07-24 11:56:58 +08:00
parent 0e2fcdf9c8
commit e1a95c7318
19 changed files with 789 additions and 50 deletions

View file

@ -419,6 +419,7 @@ func (f *fakeDialogReadModelCache) flushCount() int {
type fakePrivacyReadModelCache struct {
mu sync.Mutex
ids []int64
warmed []int64
flushes int
}
@ -428,6 +429,13 @@ func (f *fakePrivacyReadModelCache) InvalidateOwners(ids ...int64) {
f.ids = append(f.ids, ids...)
}
func (f *fakePrivacyReadModelCache) WarmOwners(_ context.Context, ids ...int64) error {
f.mu.Lock()
defer f.mu.Unlock()
f.warmed = append(f.warmed, ids...)
return nil
}
func (f *fakePrivacyReadModelCache) FlushReadModelCache() {
f.mu.Lock()
defer f.mu.Unlock()
@ -440,6 +448,12 @@ func (f *fakePrivacyReadModelCache) idsSnapshot() []int64 {
return append([]int64(nil), f.ids...)
}
func (f *fakePrivacyReadModelCache) warmedSnapshot() []int64 {
f.mu.Lock()
defer f.mu.Unlock()
return append([]int64(nil), f.warmed...)
}
func (f *fakePrivacyReadModelCache) flushCount() int {
f.mu.Lock()
defer f.mu.Unlock()
@ -509,6 +523,9 @@ func TestReadModelChangeListenerInvalidatesAccountCaches(t *testing.T) {
if len(privacy.ids) != 1 || privacy.ids[0] != 21 {
t.Fatalf("privacy invalidations = %v, want [21]", privacy.ids)
}
if warmed := privacy.warmedSnapshot(); len(warmed) != 1 || warmed[0] != 21 {
t.Fatalf("privacy warms = %v, want [21]", warmed)
}
listener.handlePayload(`{"model":"dialog_light","owner_user_id":22,"peer_type":"user","peer_id":32,"version":4}`)
if len(dialogs.owners) != 1 || dialogs.owners[0] != 22 || dialogs.keys[0] != (domain.Peer{Type: domain.PeerTypeUser, ID: 32}) {

View file

@ -3,6 +3,7 @@ package postgres
import (
"context"
"encoding/json"
"time"
"github.com/jackc/pgx/v5"
"go.uber.org/zap"
@ -13,6 +14,8 @@ import (
const readModelChangeNotifyChannel = "telesrv_read_model_changed"
const privacyReadModelWarmTimeout = 5 * time.Second
// ReadModelCacheSet 是 read_model_versions 通知可失效的进程内投影缓存集合。
// 后续新增 read model 时,把缓存接到这里即可复用同一条 LISTEN 连接。
type ReadModelCacheSet struct {
@ -90,6 +93,14 @@ type PrivacyReadModelCache interface {
FlushReadModelCache()
}
// PrivacyReadModelWarmer lets the low-frequency change stream rebuild owner
// snapshots after invalidation, so the next user projection does not own a
// synchronous database miss. It is optional; caches without it remain
// cache-aside and only receive invalidation.
type PrivacyReadModelWarmer interface {
WarmOwners(context.Context, ...int64) error
}
type ProfilePhotoReadModelCache interface {
InvalidateOwner(domain.PeerType, int64)
FlushReadModelCache()
@ -393,6 +404,15 @@ func (l *ReadModelChangeListener) handlePayload(payload string) {
l.caches.RPCProjections.InvalidateRPCProjectionReadModelForUser(evt.OwnerUserID)
l.caches.RPCProjections.InvalidateRPCProjectionReadModelForViewer(evt.OwnerUserID)
}
if warmer, ok := l.caches.Privacy.(PrivacyReadModelWarmer); ok && evt.OwnerUserID != 0 {
ctx, cancel := context.WithTimeout(context.Background(), privacyReadModelWarmTimeout)
err := warmer.WarmOwners(ctx, evt.OwnerUserID)
cancel()
if err != nil {
l.log.Warn("warm privacy read model after change",
zap.Int64("owner_user_id", evt.OwnerUserID), zap.Error(err))
}
}
case "dialog_light":
if peerType, ok := readModelPeerType(evt.PeerType); ok && evt.OwnerUserID != 0 && evt.PeerID != 0 {
peer := domain.Peer{Type: peerType, ID: evt.PeerID}

View file

@ -1126,7 +1126,7 @@ WHERE sv.owner_peer_type = $1
OR lower(COALESCE(c.contact_last_name, u.last_name)) LIKE $7 ESCAPE '\'
OR lower(trim(COALESCE(NULLIF(c.contact_first_name, ''), u.first_name) || ' ' || COALESCE(c.contact_last_name, u.last_name))) LIKE $7 ESCAPE '\'
OR lower(u.username) LIKE $7 ESCAPE '\'
OR lower(COALESCE(NULLIF(c.contact_phone, ''), u.phone)) LIKE $7 ESCAPE '\'
OR lower(c.contact_phone) LIKE $7 ESCAPE '\'
)`, string(req.Owner.Type), req.Owner.ID, int32(req.StoryID), req.ViewerUserID, req.JustContacts, querySet, queryLike).Scan(&count); err != nil {
return domain.StoryViewList{}, fmt.Errorf("count story views: %w", err)
}
@ -1160,7 +1160,7 @@ WHERE sv.owner_peer_type = $1
OR lower(COALESCE(c.contact_last_name, u.last_name)) LIKE $7 ESCAPE '\'
OR lower(trim(COALESCE(NULLIF(c.contact_first_name, ''), u.first_name) || ' ' || COALESCE(c.contact_last_name, u.last_name))) LIKE $7 ESCAPE '\'
OR lower(u.username) LIKE $7 ESCAPE '\'
OR lower(COALESCE(NULLIF(c.contact_phone, ''), u.phone)) LIKE $7 ESCAPE '\'
OR lower(c.contact_phone) LIKE $7 ESCAPE '\'
)
AND (
NOT $9::boolean