updated idempotency filter and attribute

master
Young 7 months ago
parent 3d4a8943de
commit 9036afadc5

@ -0,0 +1,12 @@
namespace Infrastructure.Attributes;
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class IdempotencyAttribute(string parameter, int seconds = 5, string message = "invalid request")
: Attribute
{
public string Parameter { get; } = parameter;
public int Seconds { get; } = seconds;
public string Message { get; } = message;
}

@ -0,0 +1,54 @@
using System.Reflection;
using System.Text;
using Infrastructure.Attributes;
using Infrastructure.Repository;
using Infrastructure.Utils;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Mvc;
namespace Infrastructure.Filters;
public class IdempotencyFilter(ILogger<IdempotencyFilter> logger, IRedisBasketRepository redis) : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var idempotencyAttribute =
controllerActionDescriptor.MethodInfo.GetCustomAttribute<IdempotencyAttribute>();
if (idempotencyAttribute is null)
{
await next();
return;
}
if (!context.ActionArguments.TryGetValue(idempotencyAttribute!.Parameter, out var value))
{
logger.LogWarning("idempotency parameter key not found");
await next();
return;
}
var request = context.HttpContext.Request;
var body = value!.Serialize();
var hashBytes = MD5.HashData(Encoding.ASCII.GetBytes(body));
var hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
var redisKey = $"{request.Path.Value}:{hashString}";
if (await redis.Exist(redisKey))
{
logger.LogWarning("invalid request path: {path}remote ip address{ip}", request.Path,
context.HttpContext.GetRequestIp());
var message = new MessageData(false, idempotencyAttribute.Message, 409);
context.Result = new ObjectResult(message) { StatusCode = 200 };
}
else
{
await redis.Set(redisKey, 0, TimeSpan.FromSeconds(idempotencyAttribute!.Seconds));
await next();
}
}
}
}

@ -0,0 +1,228 @@
using System.Text;
using Infrastructure.Utils;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using StackExchange.Redis;
namespace Infrastructure.Repository;
public interface IRedisBasketRepository
{
Task<string> GetValue(string key);
Task<T?> Get<T>(string key);
Task<List<T>> GetValues<T>(string[] keys) where T : class;
Task Set(string key, object? value, TimeSpan cacheTime);
Task<bool> SetValues(Dictionary<string, object> valuePairs, TimeSpan cacheTime);
Task<bool> Exist(string key);
Task Remove(string key);
Task Clear();
Task<RedisValue[]> ListRangeAsync(string redisKey);
Task<long> ListLeftPushAsync(string redisKey, string redisValue, int db = -1);
Task<long> ListRightPushAsync(string redisKey, string redisValue, int db = -1);
Task<long> ListRightPushAsync(string redisKey, IEnumerable<string> redisValue, int db = -1);
Task<T> ListLeftPopAsync<T>(string redisKey, int db = -1) where T : class;
Task<T> ListRightPopAsync<T>(string redisKey, int db = -1) where T : class;
Task<string> ListLeftPopAsync(string redisKey, int db = -1);
Task<string> ListRightPopAsync(string redisKey, int db = -1);
Task<long> ListLengthAsync(string redisKey, int db = -1);
Task<IEnumerable<string>> ListRangeAsync(string redisKey, int db = -1);
Task<IEnumerable<string>> ListRangeAsync(string redisKey, int start, int stop, int db = -1);
Task<long> ListDelRangeAsync(string redisKey, string redisValue, long type = 0, int db = -1);
Task ListClearAsync(string redisKey, int db = -1);
}
public class RedisBasketRepository(ILogger<RedisBasketRepository> logger, ConnectionMultiplexer redis)
: IRedisBasketRepository
{
private readonly IDatabase _database = redis.GetDatabase();
private IServer GetServer()
{
var endpoint = redis.GetEndPoints();
return redis.GetServer(endpoint.First());
}
public async Task Clear()
{
foreach (var endPoint in redis.GetEndPoints())
{
var server = GetServer();
foreach (var key in server.Keys())
{
await _database.KeyDeleteAsync(key);
}
}
}
public async Task<bool> Exist(string key)
{
return await _database.KeyExistsAsync(key);
}
public async Task<string> GetValue(string key)
{
return await _database.StringGetAsync(key);
}
public async Task Remove(string key)
{
await _database.KeyDeleteAsync(key);
}
public async Task Set(string key, object value, TimeSpan cacheTime)
{
if (value != null)
{
if (value is string cacheValue)
{
await _database.StringSetAsync(key, cacheValue, cacheTime);
}
else
{
var jsonString = value.Serialize();
var buffer = Encoding.UTF8.GetBytes(jsonString);
await _database.StringSetAsync(key, buffer, cacheTime);
}
}
}
public async Task<bool> SetValues(Dictionary<string, object> valuePairs, TimeSpan cacheTime)
{
var transaction = _database.CreateTransaction();
foreach (var pair in valuePairs)
{
if (pair.Value is string value)
{
await _database.StringSetAsync(pair.Key, value, cacheTime);
}
else
{
var jsonString = pair.Value.Serialize();
var buffer = Encoding.UTF8.GetBytes(jsonString);
await _database.StringSetAsync(pair.Key, buffer, cacheTime);
}
}
return await transaction.ExecuteAsync();
}
public async Task<TEntity?> Get<TEntity>(string key)
{
var value = await _database.StringGetAsync(key);
if (value.HasValue)
{
var jsonString = Encoding.UTF8.GetString(value);
return jsonString.Deserialize<TEntity>();
}
else
{
return default;
}
}
public async Task<List<T>> GetValues<T>(string[] keys) where T : class
{
var redisKeys = keys.Select(k => new RedisKey(k)).ToArray();
var redisValues = await _database.StringGetAsync(redisKeys);
var result = new List<T>();
foreach (var value in redisValues)
{
if (value.HasValue)
{
result.Add(SerializeExtension.Deserialize<T>(value));
}
}
return result;
}
public async Task<RedisValue[]> ListRangeAsync(string redisKey)
{
return await _database.ListRangeAsync(redisKey);
}
public async Task<long> ListLeftPushAsync(string redisKey, string redisValue, int db = -1)
{
return await _database.ListLeftPushAsync(redisKey, redisValue);
}
public async Task<long> ListRightPushAsync(string redisKey, string redisValue, int db = -1)
{
return await _database.ListRightPushAsync(redisKey, redisValue);
}
public async Task<long> ListRightPushAsync(string redisKey, IEnumerable<string> redisValue, int db = -1)
{
var redislist = redisValue.Select(r => (RedisValue)r).ToArray();
return await _database.ListRightPushAsync(redisKey, redislist);
}
public async Task<T> ListLeftPopAsync<T>(string redisKey, int db = -1) where T : class
{
var value = await _database.ListLeftPopAsync(redisKey);
return SerializeExtension.Deserialize<T>(await _database.ListLeftPopAsync(redisKey));
}
public async Task<T> ListRightPopAsync<T>(string redisKey, int db = -1) where T : class
{
return SerializeExtension.Deserialize<T>(await _database.ListRightPopAsync(redisKey));
}
public async Task<string> ListLeftPopAsync(string redisKey, int db = -1)
{
return await _database.ListLeftPopAsync(redisKey);
}
public async Task<string> ListRightPopAsync(string redisKey, int db = -1)
{
return await _database.ListRightPopAsync(redisKey);
}
public async Task<long> ListLengthAsync(string redisKey, int db = -1)
{
return await _database.ListLengthAsync(redisKey);
}
public async Task<IEnumerable<string>> ListRangeAsync(string redisKey, int db = -1)
{
var result = await _database.ListRangeAsync(redisKey);
return result.Select(o => o.ToString());
}
public async Task<IEnumerable<string>> ListRangeAsync(string redisKey, int start, int stop, int db = -1)
{
var result = await _database.ListRangeAsync(redisKey, start, stop);
return result.Select(o => o.ToString());
}
public async Task<long> ListDelRangeAsync(string redisKey, string redisValue, long type = 0, int db = -1)
{
return await _database.ListRemoveAsync(redisKey, redisValue, type);
}
public async Task ListClearAsync(string redisKey, int db = -1)
{
await _database.ListTrimAsync(redisKey, 1, 0);
}
}

@ -0,0 +1,71 @@
using SqlSugar;
namespace Infrastructure.Repository;
public interface IUnitOfWork
{
SqlSugarScope DbClient { get; }
void BeginTransaction();
void CommitTransaction();
void RollbackTransaction();
}
public class UnitOfWork : IUnitOfWork
{
private int _count;
private readonly SqlSugarScope? _dbClient;
public SqlSugarScope DbClient => _dbClient!;
public UnitOfWork(ISqlSugarClient sqlSugarClient)
{
if (sqlSugarClient is SqlSugarScope scope)
{
_dbClient = scope;
}
}
public void BeginTransaction()
{
lock (this)
{
_count++;
_dbClient?.BeginTran();
}
}
public void CommitTransaction()
{
lock (this)
{
_count--;
if (_count != 0)
{
return;
}
try
{
_dbClient?.CommitTran();
}
catch (Exception e)
{
Console.WriteLine(e);
_dbClient?.RollbackTran();
}
}
}
public void RollbackTransaction()
{
lock (this)
{
_count--;
_dbClient?.RollbackTran();
}
}
}
Loading…
Cancel
Save