Files
xiong 603bc24c26
Some checks are pending
ci / release-gate (push) Waiting to run
feat(cache): adopt FusionCache for business caching
2026-08-05 09:30:48 +08:00

101 lines
3.7 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using Npgsql;
using Tiku.Application.Tenancy;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Caching;
using ZiggyCreatures.Caching.Fusion;
namespace Tiku.Infrastructure.Tenancy;
internal sealed record TenantDirectoryCacheEntry(bool Found, TenantDirectoryEntry? Entry);
public sealed class TenantDirectory(
NpgsqlDataSource dataSource,
[FromKeyedServices(BusinessCachingServiceCollectionExtensions.CacheName)]
IFusionCache cache) : ITenantDirectory
{
public Task<TenantDirectoryEntry?> FindByHostAsync(
string host,
CancellationToken cancellationToken = default)
{
const string sql = """
select t.id, t.slug, t.name, t.status, t.mode, d.host
from tenant_domains d
join tenants t on t.id = d.tenant_id
where d.host = @lookup
and d.status = 'active'
and t.status = 'active'
limit 1
""";
return FindAsync(sql, "host", Normalize(host), cancellationToken);
}
public Task<TenantDirectoryEntry?> FindByCodeAsync(
string tenantCode,
CancellationToken cancellationToken = default)
{
const string sql = """
select t.id, t.slug, t.name, t.status, t.mode, null::text as host
from tenants t
where t.slug = @lookup
and t.status = 'active'
limit 1
""";
return FindAsync(sql, "code", Normalize(tenantCode), cancellationToken);
}
private async Task<TenantDirectoryEntry?> FindAsync(
string sql,
string kind,
string lookup,
CancellationToken cancellationToken)
{
var cacheKey = CacheKey(kind, lookup);
var cached = await cache.GetOrSetAsync<TenantDirectoryCacheEntry>(
cacheKey,
async (context, token) =>
{
await using var command = dataSource.CreateCommand(sql);
command.Parameters.AddWithValue("lookup", lookup);
await using var reader = await command.ExecuteReaderAsync(token);
if (!await reader.ReadAsync(token))
{
context.Options.Duration = TimeSpan.FromSeconds(20);
context.Options.MemoryCacheDuration = TimeSpan.FromSeconds(20);
return new TenantDirectoryCacheEntry(false, null);
}
return new TenantDirectoryCacheEntry(true, new TenantDirectoryEntry(
reader.GetGuid(0),
reader.GetString(1),
reader.GetString(2),
ParseEnum<TenantStatus>(reader.GetString(3)),
ParseEnum<TenantMode>(reader.GetString(4)),
reader.IsDBNull(5) ? null : reader.GetString(5)));
},
options =>
{
options.Duration = TimeSpan.FromSeconds(300);
options.MemoryCacheDuration = TimeSpan.FromSeconds(30);
},
token: cancellationToken);
return cached.Entry;
}
private static TEnum ParseEnum<TEnum>(string value)
where TEnum : struct, Enum
{
return Enum.Parse<TEnum>(value.Replace("_", string.Empty, StringComparison.Ordinal), true);
}
private static string Normalize(string value)
{
return value.Trim().ToLowerInvariant();
}
internal static string CacheKey(string kind, string lookup)
{
return $"tenant-directory:v1:{kind}:{Normalize(lookup)}";
}
}