|
|
@@ -7,6 +7,7 @@ using Kitty_Fall.Apis.Common;
|
|
|
using Kitty_Fall.Apis.Language;
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
+using Microsoft.EntityFrameworkCore.Storage;
|
|
|
using Newtonsoft.Json;
|
|
|
using System.Globalization;
|
|
|
|
|
|
@@ -773,8 +774,14 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
.OrderByDescending(x => x.Id)
|
|
|
.FirstOrDefaultAsync();
|
|
|
|
|
|
+ // Keep the eligibility check and reward writes atomic across API instances.
|
|
|
+ await using var rewardTransaction = await dbContext.Database.BeginTransactionAsync();
|
|
|
+ if (!await LockRewardSubscriberAsync(msisdn))
|
|
|
+ return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, Lang.errorOccurred, new { });
|
|
|
+ now = MyanmarClock.Now;
|
|
|
+
|
|
|
var session = await dbContext.GamePlaySessions
|
|
|
- .FirstOrDefaultAsync(x => x.RootTransId == request.transId);
|
|
|
+ .FirstOrDefaultAsync(x => x.RootTransId == request.transId && x.Msisdn == msisdn);
|
|
|
|
|
|
// Chi cho ghi ket qua khi phien da start va da tru luot.
|
|
|
if (session == null || session.TurnDeducted != true)
|
|
|
@@ -790,6 +797,7 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
session.LastActionTime = now;
|
|
|
session.UpdatedTime = now;
|
|
|
await dbContext.SaveChangesAsync();
|
|
|
+ await rewardTransaction.CommitAsync();
|
|
|
var totalTurn = await GetAvailableTurnAsync(msisdn, now);
|
|
|
|
|
|
return CommonLogic.BuildResponse(
|
|
|
@@ -838,6 +846,10 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
.ThenBy(x => x.RewardOptionId)
|
|
|
.ToListAsync();
|
|
|
|
|
|
+ var alreadyWonExternalReward = await DailyLevelRewardPolicy.ExternalWinsForDay(
|
|
|
+ dbContext.GameLevelAttempts.AsNoTracking(), msisdn, levelNo.Value, now).AnyAsync();
|
|
|
+ rewardOptions = DailyLevelRewardPolicy.EligibleOptions(rewardOptions, alreadyWonExternalReward);
|
|
|
+
|
|
|
var rewardBudget = await GetDailyExternalRewardBudgetAsync(now);
|
|
|
var usedRewardBudget = await GetDailyExternalRewardCostAsync(now);
|
|
|
rewardOptions = rewardOptions
|
|
|
@@ -991,6 +1003,7 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
|
|
|
await UpsertMonthlyRankingAsync(msisdn, account?.Id, mode.ModeId, awardedScore + rewardScoreDelta, session.MaxLevelReached, now);
|
|
|
await dbContext.SaveChangesAsync();
|
|
|
+ await rewardTransaction.CommitAsync();
|
|
|
|
|
|
// Response cho popup LEVEL COMPLETE chi tra thuong cua level vua
|
|
|
// hoan thanh. Tong diem van duoc luu trong session/ranking va chi
|
|
|
@@ -1021,6 +1034,24 @@ namespace Kitty_Fall.Apis.Business.Game
|
|
|
return CommonLogic.BuildResponse(url, json, CommonErrorCode.Error, Lang.errorOccurred, new { });
|
|
|
}
|
|
|
|
|
|
+ private async Task<bool> LockRewardSubscriberAsync(string msisdn)
|
|
|
+ {
|
|
|
+ // Oracle row locks serialize result submissions for this subscriber, including
|
|
|
+ // submissions from different sessions or different running API instances.
|
|
|
+ await using var command = dbContext.Database.GetDbConnection().CreateCommand();
|
|
|
+ command.Transaction = dbContext.Database.CurrentTransaction!.GetDbTransaction();
|
|
|
+ command.CommandText = "SELECT ID FROM ACCOUNT_USER WHERE MSISDN = :msisdn ORDER BY ID FOR UPDATE";
|
|
|
+ var parameter = command.CreateParameter();
|
|
|
+ parameter.ParameterName = "msisdn";
|
|
|
+ parameter.Value = msisdn;
|
|
|
+ command.Parameters.Add(parameter);
|
|
|
+
|
|
|
+ await using var reader = await command.ExecuteReaderAsync();
|
|
|
+ var found = false;
|
|
|
+ while (await reader.ReadAsync()) found = true;
|
|
|
+ return found;
|
|
|
+ }
|
|
|
+
|
|
|
private async Task<decimal> NextDecimalIdAsync(string type)
|
|
|
{
|
|
|
var sequence = type switch
|