255 lines
7.8 KiB
Go
255 lines
7.8 KiB
Go
package roomturnoverreward
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/common"
|
|
"chatapp3-golang/internal/integration"
|
|
"chatapp3-golang/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// GetClaimStatus 返回当前用户是否存在可手动领取的房间流水奖励。
|
|
func (s *Service) GetClaimStatus(ctx context.Context, user common.AuthUser) (*RoomTurnoverRewardClaimStatusResponse, error) {
|
|
if user.UserID <= 0 {
|
|
return nil, NewAppError(http.StatusUnauthorized, "invalid_user", "user is required")
|
|
}
|
|
sysOrigin := s.normalizeSysOrigin(user.SysOrigin)
|
|
resp := &RoomTurnoverRewardClaimStatusResponse{
|
|
CanClaim: false,
|
|
Reason: "no_claimable_reward",
|
|
SysOrigin: sysOrigin,
|
|
AutoRewardEnabled: true,
|
|
}
|
|
|
|
configRow, ok, err := s.loadConfigRow(ctx, sysOrigin)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
resp.Reason = "config_not_found"
|
|
return resp, nil
|
|
}
|
|
resp.AutoRewardEnabled = configRow.AutoRewardEnabled
|
|
if configRow.AutoRewardEnabled {
|
|
resp.Reason = "auto_reward_enabled"
|
|
return resp, nil
|
|
}
|
|
|
|
record, ok, err := s.findClaimableRewardRecord(ctx, sysOrigin, user.UserID, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
return resp, nil
|
|
}
|
|
view := toRecordView(record)
|
|
resp.CanClaim = true
|
|
resp.Reason = "available"
|
|
resp.Record = &view
|
|
return resp, nil
|
|
}
|
|
|
|
// ClaimReward 手动领取一条已结算但未发放的房间流水奖励。
|
|
func (s *Service) ClaimReward(ctx context.Context, user common.AuthUser, req RoomTurnoverRewardClaimRequest) (*RoomTurnoverRewardClaimResponse, error) {
|
|
if s.repo.Java == nil {
|
|
return nil, NewAppError(http.StatusServiceUnavailable, "java_gateway_unavailable", "java gateway is unavailable")
|
|
}
|
|
if user.UserID <= 0 {
|
|
return nil, NewAppError(http.StatusUnauthorized, "invalid_user", "user is required")
|
|
}
|
|
|
|
sysOrigin := s.normalizeSysOrigin(user.SysOrigin)
|
|
configRow, ok, err := s.loadConfigRow(ctx, sysOrigin)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
return nil, NewAppError(http.StatusNotFound, "room_turnover_reward_config_not_found", "config not found")
|
|
}
|
|
if configRow.AutoRewardEnabled {
|
|
return nil, NewAppError(http.StatusConflict, "manual_claim_disabled", "manual claim is disabled when auto reward is enabled")
|
|
}
|
|
|
|
recordID := req.RecordID.Int64()
|
|
var saved model.RoomTurnoverRewardRecord
|
|
claimed := false
|
|
var dispatchErr error
|
|
|
|
err = s.repo.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
record, found, findErr := s.lockRewardRecord(ctx, tx, sysOrigin, user.UserID, recordID)
|
|
if findErr != nil {
|
|
return findErr
|
|
}
|
|
if !found {
|
|
return NewAppError(http.StatusNotFound, "no_claimable_reward", "no claimable reward")
|
|
}
|
|
if record.Status == roomTurnoverRewardStatusSuccess {
|
|
saved = record
|
|
return nil
|
|
}
|
|
if record.Status != roomTurnoverRewardStatusPending && record.Status != roomTurnoverRewardStatusFailed {
|
|
return NewAppError(http.StatusConflict, "reward_not_claimable", "reward is not claimable")
|
|
}
|
|
|
|
dispatchErr = s.dispatchRewardRecord(ctx, &record)
|
|
now := time.Now()
|
|
if dispatchErr != nil {
|
|
retryCount := record.RetryCount + 1
|
|
updates := map[string]any{
|
|
"status": roomTurnoverRewardStatusFailed,
|
|
"retry_count": retryCount,
|
|
"last_error": truncateString(dispatchErr.Error(), 1024),
|
|
"update_time": now,
|
|
}
|
|
if err := tx.Model(&model.RoomTurnoverRewardRecord{}).
|
|
Where("id = ?", record.ID).
|
|
Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
record.Status = roomTurnoverRewardStatusFailed
|
|
record.RetryCount = retryCount
|
|
record.LastError = truncateString(dispatchErr.Error(), 1024)
|
|
record.UpdateTime = now
|
|
saved = record
|
|
return nil
|
|
}
|
|
|
|
retryCount := record.RetryCount
|
|
if record.Status == roomTurnoverRewardStatusFailed {
|
|
retryCount++
|
|
}
|
|
if err := tx.Model(&model.RoomTurnoverRewardRecord{}).
|
|
Where("id = ?", record.ID).
|
|
Updates(map[string]any{
|
|
"status": roomTurnoverRewardStatusSuccess,
|
|
"retry_count": retryCount,
|
|
"last_error": "",
|
|
"sent_at": now,
|
|
"update_time": now,
|
|
}).Error; err != nil {
|
|
return err
|
|
}
|
|
record.Status = roomTurnoverRewardStatusSuccess
|
|
record.RetryCount = retryCount
|
|
record.LastError = ""
|
|
record.SentAt = &now
|
|
record.UpdateTime = now
|
|
saved = record
|
|
claimed = true
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if dispatchErr != nil {
|
|
return nil, NewAppError(http.StatusBadGateway, "room_turnover_reward_claim_failed", dispatchErr.Error())
|
|
}
|
|
|
|
if claimed {
|
|
s.markSourceSent(ctx, saved)
|
|
}
|
|
return &RoomTurnoverRewardClaimResponse{
|
|
Claimed: claimed,
|
|
Record: toRecordView(saved),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) dispatchRewardRecord(ctx context.Context, record *model.RoomTurnoverRewardRecord) error {
|
|
if record == nil {
|
|
return nil
|
|
}
|
|
return s.repo.Java.ChangeGoldBalance(ctx, integration.GoldReceiptCommand{
|
|
ReceiptType: "INCOME",
|
|
UserID: record.OwnerUserID,
|
|
SysOrigin: record.SysOrigin,
|
|
EventID: record.TrackID,
|
|
Origin: roomTurnoverRewardGoldOrigin,
|
|
Remark: fmt.Sprintf("room turnover reward %s room %d", record.CycleKey, record.RoomID),
|
|
Amount: integration.NewPennyAmountPayloadFromDollar(record.RewardGold),
|
|
CloseDelayAsset: false,
|
|
OpUserType: "APP",
|
|
})
|
|
}
|
|
|
|
func (s *Service) markSourceSent(ctx context.Context, record model.RoomTurnoverRewardRecord) {
|
|
sourceID := strings.TrimSpace(record.SourceID)
|
|
if sourceID == "" {
|
|
return
|
|
}
|
|
marked, err := s.repo.Java.MarkRoomTurnoverRewardCoinsSent(ctx, sourceID)
|
|
if err != nil {
|
|
log.Printf("mark room turnover reward source failed after manual claim. recordId=%d sourceId=%s err=%v", record.ID, sourceID, err)
|
|
return
|
|
}
|
|
if !marked {
|
|
log.Printf("room turnover reward source was not modified after manual claim. recordId=%d sourceId=%s", record.ID, sourceID)
|
|
}
|
|
}
|
|
|
|
func (s *Service) loadConfigRow(ctx context.Context, sysOrigin string) (model.RoomTurnoverRewardConfig, bool, error) {
|
|
var row model.RoomTurnoverRewardConfig
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ?", s.normalizeSysOrigin(sysOrigin)).
|
|
First(&row).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return model.RoomTurnoverRewardConfig{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return model.RoomTurnoverRewardConfig{}, false, err
|
|
}
|
|
return row, true, nil
|
|
}
|
|
|
|
func (s *Service) findClaimableRewardRecord(ctx context.Context, sysOrigin string, userID int64, recordID int64) (model.RoomTurnoverRewardRecord, bool, error) {
|
|
query := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ? AND owner_user_id = ?", s.normalizeSysOrigin(sysOrigin), userID).
|
|
Where("status IN ?", []string{roomTurnoverRewardStatusPending, roomTurnoverRewardStatusFailed})
|
|
if recordID > 0 {
|
|
query = query.Where("id = ?", recordID)
|
|
}
|
|
var record model.RoomTurnoverRewardRecord
|
|
err := query.
|
|
Order("period_end_at desc").
|
|
Order("id desc").
|
|
First(&record).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return model.RoomTurnoverRewardRecord{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return model.RoomTurnoverRewardRecord{}, false, err
|
|
}
|
|
return record, true, nil
|
|
}
|
|
|
|
func (s *Service) lockRewardRecord(ctx context.Context, tx *gorm.DB, sysOrigin string, userID int64, recordID int64) (model.RoomTurnoverRewardRecord, bool, error) {
|
|
query := tx.WithContext(ctx).
|
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
Where("sys_origin = ? AND owner_user_id = ?", s.normalizeSysOrigin(sysOrigin), userID)
|
|
if recordID > 0 {
|
|
query = query.Where("id = ?", recordID)
|
|
} else {
|
|
query = query.Where("status IN ?", []string{roomTurnoverRewardStatusPending, roomTurnoverRewardStatusFailed})
|
|
}
|
|
var record model.RoomTurnoverRewardRecord
|
|
err := query.
|
|
Order("period_end_at desc").
|
|
Order("id desc").
|
|
First(&record).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return model.RoomTurnoverRewardRecord{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return model.RoomTurnoverRewardRecord{}, false, err
|
|
}
|
|
return record, true, nil
|
|
}
|