forked from xiongyuxing/tiku-backend.net
116 lines
4.5 KiB
C#
116 lines
4.5 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Tiku.Application.Security;
|
|
using System.Diagnostics;
|
|
using System.Text.Json;
|
|
using Tiku.Domain.Operations;
|
|
using Tiku.Infrastructure.Persistence;
|
|
|
|
namespace Tiku.Infrastructure.Tenancy;
|
|
|
|
public sealed class TenantExecutionScope(
|
|
IServiceScopeFactory scopeFactory,
|
|
ILogger<TenantExecutionScope> logger) : ITenantExecutionScope
|
|
{
|
|
public Task ExecuteAsync(
|
|
SystemScopeRequest request,
|
|
Func<IServiceProvider, CancellationToken, Task> operation,
|
|
CancellationToken cancellationToken = default) =>
|
|
ExecuteAsync<object?>(request, async (provider, token) =>
|
|
{
|
|
await operation(provider, token);
|
|
return null;
|
|
}, cancellationToken);
|
|
|
|
public async Task<TResult> ExecuteAsync<TResult>(
|
|
SystemScopeRequest request,
|
|
Func<IServiceProvider, CancellationToken, Task<TResult>> operation,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Validate(request, operation);
|
|
await using var scope = scopeFactory.CreateAsyncScope();
|
|
var initializer = scope.ServiceProvider.GetRequiredService<ITenantContextInitializer>();
|
|
initializer.InitializeSystem(request.TargetTenantId, request.Reason);
|
|
logger.LogWarning(
|
|
"Entering audited system scope. CallerType={CallerType} Caller={Caller} TargetTenantId={TargetTenantId} CorrelationId={CorrelationId} Reason={Reason}",
|
|
request.CallerType,
|
|
request.Caller,
|
|
request.TargetTenantId,
|
|
request.CorrelationId,
|
|
request.Reason);
|
|
var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
|
|
var started = DateTimeOffset.UtcNow;
|
|
var stopwatch = Stopwatch.StartNew();
|
|
await using var transaction = dbContext.Database.IsRelational()
|
|
? await dbContext.Database.BeginTransactionAsync(cancellationToken)
|
|
: null;
|
|
await WriteAuditAsync(dbContext, request, "system_scope.entered", started, null, null, cancellationToken);
|
|
try
|
|
{
|
|
var result = await operation(scope.ServiceProvider, cancellationToken);
|
|
await WriteAuditAsync(
|
|
dbContext, request, "system_scope.completed", started, stopwatch.ElapsedMilliseconds, null, cancellationToken);
|
|
if (transaction is not null)
|
|
{
|
|
await transaction.CommitAsync(cancellationToken);
|
|
}
|
|
return result;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
if (transaction is not null)
|
|
{
|
|
await transaction.RollbackAsync(CancellationToken.None);
|
|
dbContext.ChangeTracker.Clear();
|
|
await WriteAuditAsync(
|
|
dbContext, request, "system_scope.entered", started, null, null, CancellationToken.None);
|
|
}
|
|
await WriteAuditAsync(
|
|
dbContext, request, "system_scope.failed", started, stopwatch.ElapsedMilliseconds,
|
|
exception.GetType().Name, CancellationToken.None);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static void Validate<TResult>(
|
|
SystemScopeRequest request,
|
|
Func<IServiceProvider, CancellationToken, Task<TResult>> operation)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
ArgumentNullException.ThrowIfNull(operation);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(request.Caller);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(request.Reason);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(request.CorrelationId);
|
|
}
|
|
|
|
private static async Task WriteAuditAsync(
|
|
TikuDbContext dbContext,
|
|
SystemScopeRequest request,
|
|
string action,
|
|
DateTimeOffset startedAt,
|
|
long? elapsedMilliseconds,
|
|
string? failureType,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
dbContext.AuditLogs.Add(new AuditLog
|
|
{
|
|
TenantId = request.TargetTenantId,
|
|
Action = action,
|
|
TargetType = "system_scope",
|
|
TargetId = request.CorrelationId,
|
|
Details = JsonSerializer.SerializeToElement(new
|
|
{
|
|
callerType = request.CallerType.ToString(),
|
|
request.Caller,
|
|
request.Reason,
|
|
request.CorrelationId,
|
|
startedAt,
|
|
elapsedMilliseconds,
|
|
failureType
|
|
})
|
|
});
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
}
|