♻️ Refactor cache system with redis
🐛 Add lock to check in prevent multiple at the same time
This commit is contained in:
@ -344,7 +344,7 @@ public class AccountController(
|
||||
if (!isAvailable)
|
||||
return BadRequest("Check-in is not available for today.");
|
||||
|
||||
var needsCaptcha = events.CheckInDailyDoAskCaptcha(currentUser);
|
||||
var needsCaptcha = await events.CheckInDailyDoAskCaptcha(currentUser);
|
||||
return needsCaptcha switch
|
||||
{
|
||||
true when string.IsNullOrWhiteSpace(captchaToken) => StatusCode(423,
|
||||
|
@ -1,11 +1,13 @@
|
||||
using System.Globalization;
|
||||
using DysonNetwork.Sphere.Activity;
|
||||
using DysonNetwork.Sphere.Connection;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using DysonNetwork.Sphere.Wallet;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NodaTime;
|
||||
using Org.BouncyCastle.Asn1.X509;
|
||||
|
||||
namespace DysonNetwork.Sphere.Account;
|
||||
|
||||
@ -13,24 +15,25 @@ public class AccountEventService(
|
||||
AppDatabase db,
|
||||
ActivityService act,
|
||||
WebSocketService ws,
|
||||
IMemoryCache cache,
|
||||
ICacheService cache,
|
||||
PaymentService payment,
|
||||
IStringLocalizer<Localization.AccountEventResource> localizer
|
||||
)
|
||||
{
|
||||
private static readonly Random Random = new();
|
||||
private const string StatusCacheKey = "account_status_";
|
||||
private const string StatusCacheKey = "AccountStatus_";
|
||||
|
||||
public void PurgeStatusCache(Guid userId)
|
||||
{
|
||||
var cacheKey = $"{StatusCacheKey}{userId}";
|
||||
cache.Remove(cacheKey);
|
||||
cache.RemoveAsync(cacheKey);
|
||||
}
|
||||
|
||||
public async Task<Status> GetStatus(Guid userId)
|
||||
{
|
||||
var cacheKey = $"{StatusCacheKey}{userId}";
|
||||
if (cache.TryGetValue(cacheKey, out Status? cachedStatus))
|
||||
var cachedStatus = await cache.GetAsync<Status>(cacheKey);
|
||||
if (cachedStatus is not null)
|
||||
{
|
||||
cachedStatus!.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId);
|
||||
return cachedStatus;
|
||||
@ -46,7 +49,8 @@ public class AccountEventService(
|
||||
if (status is not null)
|
||||
{
|
||||
status.IsOnline = !status.IsInvisible && isOnline;
|
||||
cache.Set(cacheKey, status, TimeSpan.FromMinutes(5));
|
||||
await cache.SetWithGroupsAsync(cacheKey, status, [$"{AccountService.AccountCachePrefix}{status.AccountId}"],
|
||||
TimeSpan.FromMinutes(5));
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -101,17 +105,18 @@ public class AccountEventService(
|
||||
}
|
||||
|
||||
private const int FortuneTipCount = 7; // This will be the max index for each type (positive/negative)
|
||||
private const string CaptchaCacheKey = "checkin_captcha_";
|
||||
private const string CaptchaCacheKey = "CheckInCaptcha_";
|
||||
private const int CaptchaProbabilityPercent = 20;
|
||||
|
||||
public bool CheckInDailyDoAskCaptcha(Account user)
|
||||
public async Task<bool> CheckInDailyDoAskCaptcha(Account user)
|
||||
{
|
||||
var cacheKey = $"{CaptchaCacheKey}{user.Id}";
|
||||
if (cache.TryGetValue(cacheKey, out bool? needsCaptcha))
|
||||
var needsCaptcha = await cache.GetAsync<bool?>(cacheKey);
|
||||
if (needsCaptcha is not null)
|
||||
return needsCaptcha!.Value;
|
||||
|
||||
var result = Random.Next(100) < CaptchaProbabilityPercent;
|
||||
cache.Set(cacheKey, result, TimeSpan.FromHours(24));
|
||||
await cache.SetAsync(cacheKey, result, TimeSpan.FromHours(24));
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -132,8 +137,14 @@ public class AccountEventService(
|
||||
return lastDate < currentDate;
|
||||
}
|
||||
|
||||
public const string CheckInLockKey = "CheckInLock_";
|
||||
|
||||
public async Task<CheckInResult> CheckInDaily(Account user)
|
||||
{
|
||||
var lockKey = $"{CheckInLockKey}{user.Id}";
|
||||
var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(10), TimeSpan.Zero);
|
||||
if (lk is null) throw new InvalidOperationException("Check-in was in progress.");
|
||||
|
||||
var cultureInfo = new CultureInfo(user.Language, false);
|
||||
CultureInfo.CurrentCulture = cultureInfo;
|
||||
CultureInfo.CurrentUICulture = cultureInfo;
|
||||
@ -201,6 +212,7 @@ public class AccountEventService(
|
||||
ActivityVisibility.Friends
|
||||
);
|
||||
|
||||
await lk.ReleaseAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ using System.Globalization;
|
||||
using System.Reflection;
|
||||
using DysonNetwork.Sphere.Localization;
|
||||
using DysonNetwork.Sphere.Permission;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using EFCore.BulkExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
@ -12,20 +13,15 @@ namespace DysonNetwork.Sphere.Account;
|
||||
|
||||
public class AccountService(
|
||||
AppDatabase db,
|
||||
IMemoryCache cache,
|
||||
ICacheService cache,
|
||||
IStringLocalizerFactory factory
|
||||
)
|
||||
{
|
||||
public const string AccountCachePrefix = "Account_";
|
||||
|
||||
public async Task PurgeAccountCache(Account account)
|
||||
{
|
||||
cache.Remove($"UserFriends_{account.Id}");
|
||||
|
||||
var sessions = await db.AuthSessions.Where(e => e.Account.Id == account.Id).Select(e => e.Id)
|
||||
.ToListAsync();
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
cache.Remove($"Auth_{session}");
|
||||
}
|
||||
await cache.RemoveGroupAsync($"{AccountCachePrefix}{account.Id}");
|
||||
}
|
||||
|
||||
public async Task<Account?> LookupAccount(string probe)
|
||||
|
@ -1,11 +1,13 @@
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Account;
|
||||
|
||||
public class RelationshipService(AppDatabase db, IMemoryCache cache)
|
||||
public class RelationshipService(AppDatabase db, ICacheService cache)
|
||||
{
|
||||
private const string UserFriendsCacheKeyPrefix = "UserFriends_";
|
||||
|
||||
public async Task<bool> HasExistingRelationship(Guid accountId, Guid relatedId)
|
||||
{
|
||||
var count = await db.AccountRelationships
|
||||
@ -49,8 +51,8 @@ public class RelationshipService(AppDatabase db, IMemoryCache cache)
|
||||
db.AccountRelationships.Add(relationship);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
cache.Remove($"UserFriends_{relationship.AccountId}");
|
||||
cache.Remove($"UserFriends_{relationship.RelatedId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relationship.AccountId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relationship.RelatedId}");
|
||||
|
||||
return relationship;
|
||||
}
|
||||
@ -89,8 +91,8 @@ public class RelationshipService(AppDatabase db, IMemoryCache cache)
|
||||
db.AccountRelationships.Remove(relationship);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
cache.Remove($"UserFriends_{accountId}");
|
||||
cache.Remove($"UserFriends_{relatedId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}");
|
||||
}
|
||||
|
||||
public async Task<Relationship> AcceptFriendRelationship(
|
||||
@ -119,8 +121,8 @@ public class RelationshipService(AppDatabase db, IMemoryCache cache)
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
cache.Remove($"UserFriends_{relationship.AccountId}");
|
||||
cache.Remove($"UserFriends_{relationship.RelatedId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relationship.AccountId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relationship.RelatedId}");
|
||||
|
||||
return relationshipBackward;
|
||||
}
|
||||
@ -133,21 +135,27 @@ public class RelationshipService(AppDatabase db, IMemoryCache cache)
|
||||
relationship.Status = status;
|
||||
db.Update(relationship);
|
||||
await db.SaveChangesAsync();
|
||||
cache.Remove($"UserFriends_{accountId}");
|
||||
cache.Remove($"UserFriends_{relatedId}");
|
||||
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}");
|
||||
await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}");
|
||||
|
||||
return relationship;
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> ListAccountFriends(Account account)
|
||||
{
|
||||
if (!cache.TryGetValue($"UserFriends_{account.Id}", out List<Guid>? friends))
|
||||
string cacheKey = $"{UserFriendsCacheKeyPrefix}{account.Id}";
|
||||
var friends = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||
|
||||
if (friends == null)
|
||||
{
|
||||
friends = await db.AccountRelationships
|
||||
.Where(r => r.RelatedId == account.Id)
|
||||
.Where(r => r.Status == RelationshipStatus.Friends)
|
||||
.Select(r => r.AccountId)
|
||||
.ToListAsync();
|
||||
cache.Set($"UserFriends_{account.Id}", friends, TimeSpan.FromHours(1));
|
||||
|
||||
await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
return friends ?? [];
|
||||
|
Reference in New Issue
Block a user