feat: sync monoforum and collectible emoji status

Sync telesrv bd15657 (feat(account): implement collectible emoji status).

Skipped telesrv docs changes per public sync rules.
This commit is contained in:
A 2026-07-19 20:37:10 +08:00
parent edb7057757
commit 0c99ae0a9d
91 changed files with 4061 additions and 693 deletions

View file

@ -375,17 +375,14 @@ func (s *Service) SweepExpiredPremium(ctx context.Context, now int64, limit int)
return users, nil
}
// UpdateEmojiStatus 更新当前用户 emoji statuspremium 专属;documentID=0 清除)。
// UpdateEmojiStatus 更新当前用户 emoji statuspremium 专属;零值清除)。
// 清除不要求会员(到期降级后客户端仍可显式清掉残留状态)。
func (s *Service) UpdateEmojiStatus(ctx context.Context, userID int64, documentID int64, until int) (domain.User, error) {
self, err := s.loadSelf(ctx, userID)
func (s *Service) UpdateEmojiStatus(ctx context.Context, userID int64, status domain.UserEmojiStatus) (domain.User, error) {
self, err := s.validateEmojiStatusUpdate(ctx, userID, status)
if err != nil {
return domain.User{}, err
}
if documentID != 0 && !self.PremiumActiveAt(time.Now().Unix()) {
return domain.User{}, domain.ErrPremiumRequired
}
u, err := s.users.UpdateEmojiStatus(ctx, self.ID, documentID, until)
u, err := s.users.UpdateEmojiStatus(ctx, self.ID, status)
if err != nil {
return domain.User{}, err
}
@ -393,6 +390,52 @@ func (s *Service) UpdateEmojiStatus(ctx context.Context, userID int64, documentI
return u, nil
}
// UpdateEmojiStatusWithEvent uses the store's aggregate transaction when it
// is available. The bool reports whether the returned event was durably
// appended with dispatch; lightweight memory/test wiring falls back to the
// ordinary state write and lets the RPC's Updates service append the event.
func (s *Service) UpdateEmojiStatusWithEvent(ctx context.Context, userID int64, status domain.UserEmojiStatus, date int, excludeAuthKeyID [8]byte, excludeSessionID int64) (domain.User, domain.UpdateEvent, bool, error) {
self, err := s.validateEmojiStatusUpdate(ctx, userID, status)
if err != nil {
return domain.User{}, domain.UpdateEvent{}, false, err
}
writer, ok := s.users.(store.UserEmojiStatusEventStore)
if !ok {
u, err := s.users.UpdateEmojiStatus(ctx, self.ID, status)
if err == nil {
s.refreshCachedUsers(ctx, u)
}
return u, domain.UpdateEvent{}, false, err
}
event := domain.UpdateEvent{
Type: domain.UpdateEventUserEmojiStatus,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: self.ID},
EmojiStatus: status,
Date: date,
PtsCount: 1,
}
u, event, err := writer.UpdateEmojiStatusWithEvent(ctx, self.ID, status, event, excludeAuthKeyID, excludeSessionID)
if err != nil {
return domain.User{}, domain.UpdateEvent{}, false, err
}
s.refreshCachedUsers(ctx, u)
return u, event, true, nil
}
func (s *Service) validateEmojiStatusUpdate(ctx context.Context, userID int64, status domain.UserEmojiStatus) (domain.User, error) {
self, err := s.loadSelf(ctx, userID)
if err != nil {
return domain.User{}, err
}
if !status.Valid() {
return domain.User{}, domain.ErrStarGiftCollectibleInvalid
}
if !status.Empty() && !self.PremiumActiveAt(time.Now().Unix()) {
return domain.User{}, domain.ErrPremiumRequired
}
return self, nil
}
// UpdateBirthday 设置/清除用户生日account.updateBirthday。零值 Birthday 表示清除。
func (s *Service) UpdateBirthday(ctx context.Context, userID int64, birthday domain.Birthday) (domain.User, error) {
self, err := s.loadSelf(ctx, userID)
@ -513,7 +556,7 @@ func (s *Service) loadBaseUsersByIDs(ctx context.Context, userIDs []int64) ([]do
if s.cache != nil {
if cached, err := s.cache.GetByIDs(ctx, ids); err == nil && len(cached) > 0 {
for id, u := range cached {
if u.ID != 0 {
if u.ID != 0 && u.EmojiStatusCollectible.Empty() {
loaded[id] = u
}
}
@ -561,7 +604,18 @@ func (s *Service) putCachedUsers(ctx context.Context, users ...domain.User) {
if s.cache == nil || len(users) == 0 {
return
}
_ = s.cache.PutMany(ctx, users)
cacheable := make([]domain.User, 0, len(users))
for _, user := range users {
// Collectible ownership may change inside the star-gift aggregate. Keep
// these uncommon users on the authoritative store path so the database
// lifecycle trigger can never be masked by a stale base-user cache entry.
if user.ID != 0 && user.EmojiStatusCollectible.Empty() {
cacheable = append(cacheable, user)
}
}
if len(cacheable) > 0 {
_ = s.cache.PutMany(ctx, cacheable)
}
}
func (s *Service) dropCachedUsers(ctx context.Context, userIDs ...int64) {