package registerreward import ( "chatapp3-golang/internal/integration" "chatapp3-golang/internal/model" "chatapp3-golang/internal/utils" "context" "errors" "strings" "time" "gorm.io/gorm" ) // resolveRewardConfig 决定本次事件应该发什么奖励,并把解析结果回写日志。 func (s *RegisterRewardService) resolveRewardConfig(ctx context.Context, logRecord *model.ResidentRegisterRewardLog, event registerRewardEvent) (registerRewardResolvedConfig, bool, error) { // 失败重试时优先沿用上一次已经落库的奖励配置,避免配置变更影响补发。 if logRecord.Status == registerRewardStatusFailed && (logRecord.GoldAmount > 0 || logRecord.RewardGroupID != nil) { return registerRewardResolvedConfig{ GoldAmount: logRecord.GoldAmount, RewardGroupID: logRecord.RewardGroupID, }, false, nil } cfg, err := s.loadRegisterRewardConfig(ctx, event.SysOrigin) if err != nil { return registerRewardResolvedConfig{}, false, err } if cfg == nil || !cfg.Enabled { err = s.updateLogStatus( ctx, logRecord.ID, registerRewardStatusSkipped, "register reward config missing or disabled", registerRewardResolvedConfig{}, ) return registerRewardResolvedConfig{}, err == nil, err } resolved := registerRewardResolvedConfig{ GoldAmount: cfg.GoldAmount, RewardGroupID: cfg.RewardGroupID, DailyLimit: cfg.DailyLimit, } // 配置存在但没有任何奖励内容时,直接标记跳过。 if resolved.GoldAmount <= 0 && resolved.RewardGroupID == nil { err = s.updateLogStatus( ctx, logRecord.ID, registerRewardStatusSkipped, "register reward config has no reward payload", resolved, ) return registerRewardResolvedConfig{}, err == nil, err } if err := s.updateLogStatus(ctx, logRecord.ID, registerRewardStatusPending, "", resolved); err != nil { return registerRewardResolvedConfig{}, false, err } return resolved, false, nil } // loadRegisterRewardConfig 按系统读取注册奖励配置。 func (s *RegisterRewardService) loadRegisterRewardConfig(ctx context.Context, sysOrigin string) (*model.ResidentRegisterRewardConfig, error) { var cfg model.ResidentRegisterRewardConfig err := s.repo.DB.WithContext(ctx). Where("sys_origin = ?", strings.ToUpper(strings.TrimSpace(sysOrigin))). First(&cfg).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } if err != nil { return nil, err } return &cfg, nil } // dispatchGold 调用钱包接口发放金币奖励。 func (s *RegisterRewardService) dispatchGold(ctx context.Context, event registerRewardEvent, resolved registerRewardResolvedConfig) error { if resolved.GoldAmount <= 0 { return nil } return s.java.ChangeGoldBalance(ctx, integration.GoldReceiptCommand{ ReceiptType: "INCOME", UserID: event.UserID, SysOrigin: event.SysOrigin, EventID: event.EventID + ":gold", Remark: registerRewardGoldDesc, Amount: integration.NewPennyAmountPayloadFromDollar(resolved.GoldAmount), CloseDelayAsset: false, OpUserType: "APP", CustomizeOrigin: registerRewardGoldOrigin, CustomizeOriginDesc: registerRewardGoldDesc, }) } // dispatchProps 调用 Java 奖励接口发放道具奖励。 func (s *RegisterRewardService) dispatchProps(ctx context.Context, logID int64, event registerRewardEvent, resolved registerRewardResolvedConfig) error { if resolved.RewardGroupID == nil { return nil } return s.java.GrantProps(ctx, integration.GrantPropsRequest{ TrackID: logID, AcceptUserID: event.UserID, SysOrigin: event.SysOrigin, Origin: registerRewardPropsOrigin, SourceGroupID: *resolved.RewardGroupID, }) } // getOrCreateLog 基于事件 ID 做幂等,返回现有日志或新建日志。 func (s *RegisterRewardService) getOrCreateLog(ctx context.Context, event registerRewardEvent, streamMessageID string) (*model.ResidentRegisterRewardLog, bool, error) { var record model.ResidentRegisterRewardLog err := s.repo.DB.WithContext(ctx). Where("event_id = ?", event.EventID). First(&record).Error if err == nil { if record.StreamMessageID != streamMessageID { if updateErr := s.repo.DB.WithContext(ctx). Model(&model.ResidentRegisterRewardLog{}). Where("id = ?", record.ID). Updates(map[string]any{ "stream_message_id": streamMessageID, "update_time": time.Now(), }).Error; updateErr == nil { record.StreamMessageID = streamMessageID } } return &record, isRegisterRewardTerminalStatus(record.Status), nil } if !errors.Is(err, gorm.ErrRecordNotFound) { return nil, false, err } id, idErr := utils.NextID() if idErr != nil { return nil, false, idErr } now := time.Now() record = model.ResidentRegisterRewardLog{ ID: id, EventID: event.EventID, StreamMessageID: streamMessageID, SysOrigin: event.SysOrigin, UserID: event.UserID, Status: registerRewardStatusPending, CreateTime: now, UpdateTime: now, } if err := s.repo.DB.WithContext(ctx).Create(&record).Error; err != nil { if strings.Contains(strings.ToLower(err.Error()), "duplicate") { return s.getOrCreateLog(ctx, event, streamMessageID) } return nil, false, err } return &record, false, nil } // updateLogStatus 回写处理状态、错误信息以及最终奖励内容。 func (s *RegisterRewardService) updateLogStatus(ctx context.Context, logID int64, status, errorMessage string, resolved registerRewardResolvedConfig) error { return s.repo.DB.WithContext(ctx). Model(&model.ResidentRegisterRewardLog{}). Where("id = ?", logID). Updates(map[string]any{ "status": status, "error_message": truncateRegisterRewardMessage(errorMessage), "gold_amount": resolved.GoldAmount, "reward_group_id": resolved.RewardGroupID, "update_time": time.Now(), }).Error }