feat(server): 到账通知四接缝(邀请注册/首充/TG/购买开通)——与发放同事务零孤儿

This commit is contained in:
wangjia
2026-07-13 13:49:09 +08:00
parent 6a782a07fd
commit e1e198e169
5 changed files with 176 additions and 5 deletions
+48 -5
View File
@@ -16,14 +16,21 @@ type Granter interface {
GrantRewardTx(ctx context.Context, tx *sql.Tx, userID int64, days int, source, auditAction, ref string) (int64, time.Time, error)
}
// Noticer 抽象 notices.Store 的事务内插入入口(测试可替身;生产传 notices.NewStore(db))。
// reward → notices 单向依赖,不引入 import cycle,故不需要适配器,直接实现即可。
type Noticer interface {
InsertNoticeTx(ctx context.Context, tx *sql.Tx, userID int64, typ, titleZH, titleEN, bodyZH, bodyEN, link string, now time.Time) error
}
type Config struct{ RegDays, PaidDays, TgDays, RegMonthlyCap int }
type Service struct {
db *sql.DB
st *Store
g Granter
cfg Config
now func() time.Time
db *sql.DB
st *Store
g Granter
cfg Config
now func() time.Time
noticer Noticer
rdb *redis.Client
tg tgConfig
@@ -32,6 +39,22 @@ type Service struct {
memTok map[string]int64
}
// SetNoticer 注入到账通知钩子(notices.Store 满足此接口);为 nil 时四接缝跳过(装配前兼容)。
func (s *Service) SetNoticer(n Noticer) { s.noticer = n }
// rewardNoticeTitles 按事件渲染双语标题(reward 类通知无正文,一行标题即可)。
func rewardNoticeTitles(kind string, days int) (zh, en string) {
switch kind {
case "invite_reg":
return fmt.Sprintf("邀请奖励 +%d 天已到账", days), fmt.Sprintf("Invite reward +%d days credited", days)
case "invite_paid":
return fmt.Sprintf("好友首购奖励 +%d 天已到账", days), fmt.Sprintf("Friend's first purchase: +%d days credited", days)
case "task_tg":
return fmt.Sprintf("任务奖励 +%d 天已到账", days), fmt.Sprintf("Task reward +%d days credited", days)
}
return "", ""
}
func NewService(db *sql.DB, st *Store, g Granter, cfg Config, now func() time.Time) *Service {
if now == nil {
now = func() time.Time { return time.Now().UTC() }
@@ -119,6 +142,17 @@ func (s *Service) OnRegister(ctx context.Context, inviteeID int64, inviteCode, d
slog.Warn("reward: grant invitee reg failed", "err", err)
return
}
if s.noticer != nil {
zh, en := rewardNoticeTitles("invite_reg", s.cfg.RegDays)
if err := s.noticer.InsertNoticeTx(ctx, tx, inviterID, "reward", zh, en, "", "", "", now); err != nil {
slog.Warn("reward: insert inviter notice failed", "err", err)
return
}
if err := s.noticer.InsertNoticeTx(ctx, tx, inviteeID, "reward", zh, en, "", "", "", now); err != nil {
slog.Warn("reward: insert invitee notice failed", "err", err)
return
}
}
}
if err := tx.Commit(); err != nil {
slog.Warn("reward: commit failed", "err", err)
@@ -142,6 +176,15 @@ func (s *Service) OnFirstPaidTx(ctx context.Context, tx *sql.Tx, inviteeID int64
if _, _, err := s.g.GrantRewardTx(ctx, tx, inviteeID, s.cfg.PaidDays, "invite", "invite_paid_invitee", fmt.Sprintf("invite:paid:inviter=%d", inviterID)); err != nil {
return err
}
if s.noticer != nil {
zh, en := rewardNoticeTitles("invite_paid", s.cfg.PaidDays)
if err := s.noticer.InsertNoticeTx(ctx, tx, inviterID, "reward", zh, en, "", "", "", now); err != nil {
return err
}
if err := s.noticer.InsertNoticeTx(ctx, tx, inviteeID, "reward", zh, en, "", "", "", now); err != nil {
return err
}
}
return s.st.MarkPaidRewardedTx(ctx, tx, inviteeID, now)
}