|
|
@@ -54,45 +54,50 @@ namespace Kitty_Fall.Woker.Services
|
|
|
var dbContext = scope.ServiceProvider.GetRequiredService<ModelContext>();
|
|
|
|
|
|
var batchSize = GetInt("RewardWorker:BatchSize", 20);
|
|
|
- var maxRetry = GetInt("RewardWorker:MaxRetry", 3);
|
|
|
- var retryAfterMinutes = GetInt("RewardWorker:RetryAfterMinutes", 1);
|
|
|
- var retryBefore = DateTime.Now.AddMinutes(-retryAfterMinutes);
|
|
|
-
|
|
|
- // Lay cac log thuong can tra qua WS. SCORE la diem noi bo nen API da xu ly.
|
|
|
- var rewardLogs = await dbContext.GameRewardLogs
|
|
|
- .Where(x => (x.Status == StatusNew || x.Status == StatusFailed)
|
|
|
- && x.RetryCount < maxRetry
|
|
|
- && (x.RewardType == "DATA" || x.RewardType == "LOYALTY")
|
|
|
- && (x.UpdatedTime == null || x.UpdatedTime <= retryBefore))
|
|
|
+
|
|
|
+ // Chi lay NEW de moi reward chi duoc tu dong goi WS mot lan.
|
|
|
+ // FAILED va PROCESSING (ke ca sau restart) phai duoc admin doi soat thu cong,
|
|
|
+ // tuyet doi khong tu retry vi ket qua phia WS co the da thanh cong.
|
|
|
+ var rewardLogIds = await dbContext.GameRewardLogs
|
|
|
+ .AsNoTracking()
|
|
|
+ .Where(x => x.Status == StatusNew
|
|
|
+ && (x.RewardType == "DATA" || x.RewardType == "LOYALTY"))
|
|
|
.OrderBy(x => x.CreatedTime)
|
|
|
.ThenBy(x => x.RewardLogId)
|
|
|
+ .Select(x => x.RewardLogId)
|
|
|
.Take(batchSize)
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
|
- foreach (var rewardLog in rewardLogs)
|
|
|
+ foreach (var rewardLogId in rewardLogIds)
|
|
|
{
|
|
|
if (cancellationToken.IsCancellationRequested) return;
|
|
|
- await ProcessRewardLogAsync(dbContext, rewardLog, maxRetry, cancellationToken);
|
|
|
+
|
|
|
+ var now = DateTime.Now;
|
|
|
+ // Claim bang UPDATE co dieu kien de hai instance worker khong the cung tra.
|
|
|
+ var claimed = await dbContext.GameRewardLogs
|
|
|
+ .Where(x => x.RewardLogId == rewardLogId && x.Status == StatusNew)
|
|
|
+ .ExecuteUpdateAsync(setters => setters
|
|
|
+ .SetProperty(x => x.Status, StatusProcessing)
|
|
|
+ .SetProperty(x => x.RequestTime, now)
|
|
|
+ .SetProperty(x => x.UpdatedTime, now), cancellationToken);
|
|
|
+ if (claimed != 1) continue;
|
|
|
+
|
|
|
+ var rewardLog = await dbContext.GameRewardLogs
|
|
|
+ .FirstAsync(x => x.RewardLogId == rewardLogId, cancellationToken);
|
|
|
+ await ProcessRewardLogAsync(dbContext, rewardLog, cancellationToken);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private async Task ProcessRewardLogAsync(
|
|
|
ModelContext dbContext,
|
|
|
GameRewardLog rewardLog,
|
|
|
- int maxRetry,
|
|
|
CancellationToken cancellationToken)
|
|
|
{
|
|
|
var now = DateTime.Now;
|
|
|
|
|
|
- // Khoa mem log de tranh 2 vong worker cung tra mot reward.
|
|
|
- rewardLog.Status = StatusProcessing;
|
|
|
- rewardLog.RequestTime = now;
|
|
|
- rewardLog.UpdatedTime = now;
|
|
|
- await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
-
|
|
|
try
|
|
|
{
|
|
|
- var limitResult = await ApplyLimitBeforePayoutAsync(dbContext, rewardLog, maxRetry, now, cancellationToken);
|
|
|
+ var limitResult = await ApplyLimitBeforePayoutAsync(dbContext, rewardLog, now, cancellationToken);
|
|
|
if (!limitResult.CanPayout)
|
|
|
{
|
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
@@ -129,7 +134,10 @@ namespace Kitty_Fall.Woker.Services
|
|
|
else
|
|
|
{
|
|
|
rewardLog.RetryCount++;
|
|
|
- rewardLog.Status = rewardLog.RetryCount >= maxRetry ? StatusFailed : StatusNew;
|
|
|
+ rewardLog.Status = StatusFailed;
|
|
|
+ rewardLog.ResponseMessage = Truncate(
|
|
|
+ $"Manual review required; automatic retry is disabled. {rewardLog.ResponseMessage}",
|
|
|
+ 500);
|
|
|
}
|
|
|
|
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
@@ -139,10 +147,12 @@ namespace Kitty_Fall.Woker.Services
|
|
|
log.Error($"Process reward log error. rewardLogId={rewardLog.RewardLogId}", ex);
|
|
|
|
|
|
rewardLog.RetryCount++;
|
|
|
- rewardLog.Status = rewardLog.RetryCount >= maxRetry ? StatusFailed : StatusNew;
|
|
|
+ rewardLog.Status = StatusFailed;
|
|
|
rewardLog.ResponseTime = DateTime.Now;
|
|
|
rewardLog.ResponseCode = "EXCEPTION";
|
|
|
- rewardLog.ResponseMessage = Truncate(ex.Message, 500);
|
|
|
+ rewardLog.ResponseMessage = Truncate(
|
|
|
+ $"Manual review required; automatic retry is disabled. {ex.Message}",
|
|
|
+ 500);
|
|
|
rewardLog.UpdatedTime = DateTime.Now;
|
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
}
|
|
|
@@ -151,7 +161,6 @@ namespace Kitty_Fall.Woker.Services
|
|
|
private async Task<LimitCheckResult> ApplyLimitBeforePayoutAsync(
|
|
|
ModelContext dbContext,
|
|
|
GameRewardLog rewardLog,
|
|
|
- int maxRetry,
|
|
|
DateTime now,
|
|
|
CancellationToken cancellationToken)
|
|
|
{
|
|
|
@@ -206,7 +215,7 @@ namespace Kitty_Fall.Woker.Services
|
|
|
StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
rewardLog.Status = StatusFailed;
|
|
|
- rewardLog.RetryCount = (short)maxRetry;
|
|
|
+ rewardLog.RetryCount++;
|
|
|
rewardLog.ResponseTime = now;
|
|
|
rewardLog.ResponseCode = "INVALID_CROSS_TYPE_FALLBACK";
|
|
|
rewardLog.ResponseMessage = $"Cross-type fallback is not allowed: {rewardLog.RewardType} -> {limitConfig.FallbackRewardType}";
|
|
|
@@ -223,7 +232,7 @@ namespace Kitty_Fall.Woker.Services
|
|
|
// Vuot han muc va khong co fallback thi danh dau failed, khong retry lap lai.
|
|
|
rewardLog.LimitConfigId = limitConfig.LimitConfigId;
|
|
|
rewardLog.Status = StatusFailed;
|
|
|
- rewardLog.RetryCount = (short)maxRetry;
|
|
|
+ rewardLog.RetryCount++;
|
|
|
rewardLog.ResponseTime = now;
|
|
|
rewardLog.ResponseCode = "LIMIT_EXCEEDED";
|
|
|
rewardLog.ResponseMessage = $"Reward limit exceeded: {limitConfig.LimitCode}";
|