仪表盘和 6 类统计摘要接入 HybridCache(内存 + Redis)。
默认 Redis 缓存 3 分钟、本地缓存 30 秒,可通过环境变量调整。 缓存键包含数据权限范围、有效学院和全部筛选条件,避免跨学院串数据。 Excel 导出保持实时查询,不使用摘要缓存。 无需数据库迁移。
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Infrastructure.Caching;
|
||||
using Jiaowu.Api.Infrastructure.Timetables;
|
||||
@@ -12,6 +13,51 @@ namespace Jiaowu.Api.Tests;
|
||||
|
||||
public sealed class AppCacheTests
|
||||
{
|
||||
[Fact]
|
||||
public void Statistics_keys_isolate_data_scope_college_and_filters()
|
||||
{
|
||||
var firstCollege = Guid.NewGuid();
|
||||
var secondCollege = Guid.NewGuid();
|
||||
var term = Guid.NewGuid().ToString("N");
|
||||
|
||||
var first = AppCacheKeys.Statistics(
|
||||
"grades",
|
||||
"College",
|
||||
firstCollege,
|
||||
term,
|
||||
"-");
|
||||
var otherCollege = AppCacheKeys.Statistics(
|
||||
"grades",
|
||||
"College",
|
||||
secondCollege,
|
||||
term,
|
||||
"-");
|
||||
var otherScope = AppCacheKeys.Statistics(
|
||||
"grades",
|
||||
"All",
|
||||
firstCollege,
|
||||
term,
|
||||
"-");
|
||||
var otherFilter = AppCacheKeys.Statistics(
|
||||
"grades",
|
||||
"College",
|
||||
firstCollege,
|
||||
Guid.NewGuid().ToString("N"),
|
||||
"-");
|
||||
|
||||
Assert.NotEqual(first, otherCollege);
|
||||
Assert.NotEqual(first, otherScope);
|
||||
Assert.NotEqual(first, otherFilter);
|
||||
Assert.Equal(
|
||||
first,
|
||||
AppCacheKeys.Statistics(
|
||||
" GRADES ",
|
||||
"COLLEGE",
|
||||
firstCollege,
|
||||
term,
|
||||
null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Hybrid_cache_reuses_value_and_tag_invalidation_reloads_source()
|
||||
{
|
||||
@@ -122,6 +168,56 @@ public sealed class AppCacheTests
|
||||
Assert.Equal(new TimeOnly(8, 45), result.Slots.Single().EndsAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Analytics_json_round_trips_through_distributed_cache()
|
||||
{
|
||||
IDistributedCache distributedCache = new SharedDistributedCache(
|
||||
new MemoryDistributedCache(
|
||||
Options.Create(new MemoryDistributedCacheOptions())));
|
||||
var keyPrefix = $"tests:{Guid.NewGuid():N}";
|
||||
var source = JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
totals = new { totalCourses = 3 }
|
||||
});
|
||||
|
||||
await using (var writer = CreateProvider(
|
||||
true,
|
||||
distributedCache,
|
||||
keyPrefix))
|
||||
{
|
||||
await writer.GetRequiredService<IAppCache>().GetOrCreateAsync(
|
||||
"statistics:courses",
|
||||
_ => Task.FromResult(source),
|
||||
AppCacheProfile.Analytics,
|
||||
[AppCacheTags.Analytics],
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
await using var reader = CreateProvider(
|
||||
true,
|
||||
distributedCache,
|
||||
keyPrefix);
|
||||
var sourceCalled = false;
|
||||
var result = await reader.GetRequiredService<IAppCache>()
|
||||
.GetOrCreateAsync(
|
||||
"statistics:courses",
|
||||
_ =>
|
||||
{
|
||||
sourceCalled = true;
|
||||
return Task.FromResult(source);
|
||||
},
|
||||
AppCacheProfile.Analytics,
|
||||
[AppCacheTags.Analytics],
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.False(sourceCalled);
|
||||
Assert.Equal(
|
||||
3,
|
||||
result.GetProperty("totals")
|
||||
.GetProperty("totalCourses")
|
||||
.GetInt32());
|
||||
}
|
||||
|
||||
private static ServiceProvider CreateProvider(
|
||||
bool enabled,
|
||||
IDistributedCache? distributedCache = null,
|
||||
|
||||
Reference in New Issue
Block a user