owpengram-server/internal/rpc/premium_sweeper.go

95 lines
3 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 rpc
import (
"context"
"time"
"github.com/gotd/td/tg"
"go.uber.org/zap"
"telesrv/internal/domain"
)
// RunPremiumSweeper 周期清理到期会员并通知本人在线 session。
//
// premium 下发正确性由 hydration 即时派生premium_expires_at > now保证
// 不依赖本 sweeper这里只负责两件收尾事把过期行清 NULL保持索引/语义
// 干净),以及向该用户全部在线 session 推 updateUser + 最新 self user让在线
// 客户端立即降级 UIupdateUser 无 pts不进 update_events离线设备重连后由
// 任意带 self user 的响应自愈)。
func (r *Router) RunPremiumSweeper(ctx context.Context, interval time.Duration, batch int) {
if interval <= 0 {
interval = time.Minute
}
if batch <= 0 {
batch = 500
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
}
r.sweepExpiredPremium(ctx, batch)
}
}
func (r *Router) sweepExpiredPremium(ctx context.Context, batch int) {
svc, ok := r.deps.Users.(UserPremiumService)
if !ok {
return
}
for {
sweepCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
users, err := svc.SweepExpiredPremium(sweepCtx, r.clock.Now().Unix(), batch)
cancel()
if err != nil {
r.log.Warn("premium sweep failed", zap.Error(err))
return
}
for _, u := range users {
r.pushPremiumStatusUpdate(ctx, u)
}
// 不满一批说明已扫完当前积压;满批则继续,避免长停机后积压跨多个周期。
if len(users) < batch {
return
}
}
}
// viewerPremium 报告 viewer 当前是否有效会员限额双档判断用best-effort
// 服务未接通时按非会员档处理)。
func (r *Router) viewerPremium(ctx context.Context, userID int64) bool {
svc, ok := r.deps.Users.(UserPremiumStatusService)
return ok && svc.PremiumActive(ctx, userID)
}
// NotifyUserChanged 是 Admin 用例层可调用的 domain-only hook账号基础事实
// 变更后失效 RPC 投影缓存,并向本人在线 session 推 updateUser。它不把 tg.*
// 泄漏给 admin/domain/app协议对象只在 rpc 边界内构造。
func (r *Router) NotifyUserChanged(ctx context.Context, u domain.User) error {
if r == nil || u.ID == 0 {
return nil
}
r.invalidateRPCProjectionForUser(u.ID)
r.pushPremiumStatusUpdate(ctx, u)
return nil
}
// pushPremiumStatusUpdate 向用户本人的全部在线 session 推送会员状态变化。
// 授予、到期与 admin 认证变更共用updateUser 触发客户端用随附的 self user
// 对象刷新 premium/verified 等基础 flagTDesktop processUser 按 flag 翻转)。
func (r *Router) pushPremiumStatusUpdate(ctx context.Context, u domain.User) {
if u.ID == 0 {
return
}
pushCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
r.pushUserUpdates(pushCtx, u.ID, &tg.Updates{
Updates: []tg.UpdateClass{&tg.UpdateUser{UserID: u.ID}},
Users: []tg.UserClass{r.tgSelfUser(u)},
Date: int(r.clock.Now().Unix()),
})
}