给统计缓存加版本并绑定现有基础数据/课表失效标签,同时让演示数据、人员数据写入主动清理统计缓存;不改统计口径、筛选、导出或页面结构。

This commit is contained in:
2026-07-26 21:32:51 +08:00 Unverified
parent 034f1eacb8
commit b39a57ebdf
7 changed files with 35 additions and 2 deletions
@@ -437,6 +437,9 @@ public sealed class PersonnelController(
await cache.RemoveByTagAsync(
AppCacheTags.Timetables,
cancellationToken);
await cache.RemoveByTagAsync(
AppCacheTags.Analytics,
cancellationToken);
return Created(string.Empty, new { id });
}
catch (DbUpdateException)
@@ -453,6 +456,9 @@ public sealed class PersonnelController(
await cache.RemoveByTagAsync(
AppCacheTags.Timetables,
cancellationToken);
await cache.RemoveByTagAsync(
AppCacheTags.Analytics,
cancellationToken);
return NoContent();
}
catch (DbUpdateException)
@@ -147,6 +147,9 @@ public sealed class PersonnelExcelController(
await cache.RemoveByTagAsync(
AppCacheTags.Timetables,
cancellationToken);
await cache.RemoveByTagAsync(
AppCacheTags.Analytics,
cancellationToken);
return Ok(result);
}
catch (DbUpdateException)
@@ -966,7 +966,11 @@ public sealed class StatisticsController(
source.Value.GetType());
},
AppCacheProfile.Analytics,
[AppCacheTags.Analytics],
[
AppCacheTags.Analytics,
AppCacheTags.BaseData,
AppCacheTags.Timetables
],
cancellationToken);
return value;
}
@@ -179,7 +179,7 @@ public static class AppCacheKeys
var filterPart = filters.Length == 0
? "all"
: string.Join(':', filters.Select(Normalize));
return $"statistics:{Normalize(area)}:scope:{Normalize(dataScope)}:" +
return $"statistics:v2:{Normalize(area)}:scope:{Normalize(dataScope)}:" +
$"college:{effectiveCollegeId?.ToString("N") ?? "all"}:{filterPart}";
}
}
@@ -1,11 +1,13 @@
using System.Data;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.Caching;
using Microsoft.EntityFrameworkCore;
namespace Jiaowu.Api.Infrastructure.Persistence;
public sealed class DemoDataSeeder(
AppDbContext db,
IAppCache cache,
ILogger<DemoDataSeeder> logger)
{
// MySql.EntityFrameworkCore 10 cannot type-map parameterized primitive
@@ -47,6 +49,10 @@ public sealed class DemoDataSeeder(
await LogSummaryAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
});
await cache.RemoveByTagAsync(AppCacheTags.BaseData, cancellationToken);
await cache.RemoveByTagAsync(AppCacheTags.Timetables, cancellationToken);
await cache.RemoveByTagAsync(AppCacheTags.Analytics, cancellationToken);
}
private async Task EnsureDatabaseIsEmptyAsync(CancellationToken cancellationToken)
@@ -1,4 +1,5 @@
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
@@ -18,6 +19,7 @@ public sealed class DemoDataSeederTests
var seeder = new DemoDataSeeder(
db,
NoOpAppCache.Instance,
NullLogger<DemoDataSeeder>.Instance);
await seeder.SeedAsync();
@@ -46,6 +48,7 @@ public sealed class DemoDataSeederTests
var seeder = new DemoDataSeeder(
db,
NoOpAppCache.Instance,
NullLogger<DemoDataSeeder>.Instance);
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
@@ -70,6 +73,7 @@ public sealed class DemoDataSeederTests
var seeder = new DemoDataSeeder(
db,
NoOpAppCache.Instance,
NullLogger<DemoDataSeeder>.Instance);
await Assert.ThrowsAsync<KeyNotFoundException>(() => seeder.SeedAsync());
@@ -52,6 +52,14 @@ public sealed class StatisticsControllerTests
null,
CancellationToken.None);
Assert.Equal(1, TotalCourses(first));
Assert.Equal(
new[]
{
AppCacheTags.Analytics,
AppCacheTags.BaseData,
AppCacheTags.Timetables
},
cache.Tags.Single());
db.Courses.Add(CreateCourse("C003", firstCollege, category));
await db.SaveChangesAsync();
@@ -115,6 +123,7 @@ public sealed class StatisticsControllerTests
public int SourceCalls { get; private set; }
public IReadOnlyCollection<string> Keys => values.Keys;
public List<IReadOnlyCollection<string>> Tags { get; } = [];
public async Task<T> GetOrCreateAsync<T>(
string key,
@@ -127,6 +136,7 @@ public sealed class StatisticsControllerTests
return (T)value;
SourceCalls++;
Tags.Add(tags.ToArray());
var loaded = await factory(cancellationToken);
values[key] = loaded!;
return loaded;