171 lines
5.8 KiB
Go

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
}