Refactor user name generation

main
ilitirit 3 years ago
parent fd4f3df9ff
commit 4930a52e9d

@ -1,8 +1,12 @@
using Expedience.Api.Encryption;
using Expedience.Models;
using System;
using System.Net;
using System.Text.Json;
using Expedience.Api.Encryption;
using Expedience.Infrastructure;
using Expedience.Infrastructure.Concurrency;
using Expedience.Infrastructure.Models;
using MassTransit;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
namespace Expedience.Api.Controllers
{
@ -13,12 +17,20 @@ namespace Expedience.Api.Controllers
private readonly ILogger<ExpedienceController> _logger;
private readonly IPublishEndpoint _publisher;
private readonly IDecryptor _decrypytor;
private readonly IDistributedLock _distributedLock;
private readonly IServiceScopeFactory _serviceScopeFactory;
public ExpedienceController(ILogger<ExpedienceController> logger, IPublishEndpoint publisher, IDecryptor decrypytor)
public ExpedienceController(ILogger<ExpedienceController> logger,
IPublishEndpoint publisher,
IDecryptor decrypytor,
IDistributedLock distributedLock,
IServiceScopeFactory serviceScopeFactory)
{
_logger = logger;
_publisher = publisher;
_decrypytor = decrypytor;
_distributedLock = distributedLock;
_serviceScopeFactory = serviceScopeFactory;
}
[HttpPost("DutyCompletion")]
@ -30,7 +42,7 @@ namespace Expedience.Api.Controllers
foreach (var payload in encryptedPayloads)
{
var decryptedData = _decrypytor.Decrypt(payload);
var completionResult = JsonSerializer.Deserialize<DutyCompletionResult>(decryptedData)!;
var completionResult = JsonSerializer.Deserialize<Models.DutyCompletionResult>(decryptedData)!;
completionResult.UploadDateUtc = utcNow;
await _publisher.Publish(completionResult);
}
@ -42,5 +54,61 @@ namespace Expedience.Api.Controllers
return Ok();
}
[HttpGet("UserName/{worldId}/{userHash}")]
public async Task<ActionResult> GetUserName(int worldId, string userHash, CancellationToken cancellationToken)
{
using var scope = _serviceScopeFactory.CreateScope();
using var dbContext = scope.ServiceProvider.GetRequiredService<ExpedienceContext>();
var lockKey = $"{worldId}-{userHash}";
if (_distributedLock.AcquireLock(lockKey, TimeSpan.FromSeconds(10)))
{
try
{
var user = dbContext.Users.FirstOrDefault(x => x.UserHash == userHash && x.WorldId == worldId);
if (user == null)
{
string userName;
var isDuplicate = false;
do
{
userName = UserNameGenerator.Generate();
isDuplicate = dbContext.Users.Any(x => x.WorldId == worldId && x.UserName == userName);
await Task.Delay(20, cancellationToken); // Don't hog the CPU
}
while (isDuplicate == false);
user = new User
{
UserHash = userHash,
WorldId = worldId,
UserName = userName,
CreatedAt = DateTime.UtcNow,
};
dbContext.Users.Add(user);
await dbContext.SaveChangesAsync(cancellationToken);
}
return Ok(user.UserName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error obtaining user name for World Id {worldId} and hash {userHash}: {errorMessage}", ex.Message);
}
finally
{
_distributedLock.ReleaseLock(lockKey);
}
}
else
{
_logger.LogError("Could not acquire lock for {lockKey}", lockKey);
}
return StatusCode(500);
}
}
}

@ -3,16 +3,16 @@ using Expedience.Infrastructure;
using Expedience.Api.Encryption;
using MassTransit;
using Microsoft.EntityFrameworkCore;
using Expedience.Infrastructure.Concurrency;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("Expedience");
builder.Services.AddDbContext<ExpedienceContext>(options =>
options.UseNpgsql(connectionString).UseLowerCaseNamingConvention());
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
@ -34,6 +34,9 @@ builder.Services.AddMassTransit(opt =>
});
});
builder.Services.AddEnyimMemcached();
builder.Services.AddSingleton<IDistributedLock>();
builder.Services.AddSingleton<IDecryptor, Decryptor>();
var app = builder.Build();

@ -0,0 +1,47 @@
using System;
using Enyim.Caching;
using Enyim.Caching.Memcached;
using Microsoft.Extensions.Logging;
namespace Expedience.Infrastructure.Concurrency
{
public interface IDistributedLock
{
bool AcquireLock(string lockKey, TimeSpan timeout);
void ReleaseLock(string lockKey);
}
public class DistributedLock
{
private readonly IMemcachedClient _memcachedClient;
public DistributedLock(ILogger<DistributedLock> logger, IMemcachedClient memcachedClient)
{
_memcachedClient = memcachedClient;
}
public bool AcquireLock(string lockKey, TimeSpan timeout, TimeSpan lockExpiration)
{
var startTime = DateTime.UtcNow;
while (true)
{
if (_memcachedClient.Store(StoreMode.Add, lockKey, true, lockExpiration))
{
return true; // Lock acquired
}
if (DateTime.UtcNow - startTime > timeout)
{
return false; // Timeout
}
Thread.Sleep(100); // Wait and try again
}
}
public void ReleaseLock(string lockKey)
{
_memcachedClient.Remove(lockKey);
}
}
}

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EnyimMemcachedCore" Version="2.6.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.3" />
</ItemGroup>

Loading…
Cancel
Save