给统计缓存加版本并绑定现有基础数据/课表失效标签,同时让演示数据、人员数据写入主动清理统计缓存;不改统计口径、筛选、导出或页面结构。
This commit is contained in:
@@ -437,6 +437,9 @@ public sealed class PersonnelController(
|
|||||||
await cache.RemoveByTagAsync(
|
await cache.RemoveByTagAsync(
|
||||||
AppCacheTags.Timetables,
|
AppCacheTags.Timetables,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
await cache.RemoveByTagAsync(
|
||||||
|
AppCacheTags.Analytics,
|
||||||
|
cancellationToken);
|
||||||
return Created(string.Empty, new { id });
|
return Created(string.Empty, new { id });
|
||||||
}
|
}
|
||||||
catch (DbUpdateException)
|
catch (DbUpdateException)
|
||||||
@@ -453,6 +456,9 @@ public sealed class PersonnelController(
|
|||||||
await cache.RemoveByTagAsync(
|
await cache.RemoveByTagAsync(
|
||||||
AppCacheTags.Timetables,
|
AppCacheTags.Timetables,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
await cache.RemoveByTagAsync(
|
||||||
|
AppCacheTags.Analytics,
|
||||||
|
cancellationToken);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
catch (DbUpdateException)
|
catch (DbUpdateException)
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ public sealed class PersonnelExcelController(
|
|||||||
await cache.RemoveByTagAsync(
|
await cache.RemoveByTagAsync(
|
||||||
AppCacheTags.Timetables,
|
AppCacheTags.Timetables,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
await cache.RemoveByTagAsync(
|
||||||
|
AppCacheTags.Analytics,
|
||||||
|
cancellationToken);
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
catch (DbUpdateException)
|
catch (DbUpdateException)
|
||||||
|
|||||||
@@ -966,7 +966,11 @@ public sealed class StatisticsController(
|
|||||||
source.Value.GetType());
|
source.Value.GetType());
|
||||||
},
|
},
|
||||||
AppCacheProfile.Analytics,
|
AppCacheProfile.Analytics,
|
||||||
[AppCacheTags.Analytics],
|
[
|
||||||
|
AppCacheTags.Analytics,
|
||||||
|
AppCacheTags.BaseData,
|
||||||
|
AppCacheTags.Timetables
|
||||||
|
],
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ public static class AppCacheKeys
|
|||||||
var filterPart = filters.Length == 0
|
var filterPart = filters.Length == 0
|
||||||
? "all"
|
? "all"
|
||||||
: string.Join(':', filters.Select(Normalize));
|
: 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}";
|
$"college:{effectiveCollegeId?.ToString("N") ?? "all"}:{filterPart}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
using Jiaowu.Api.Domain.Academic;
|
using Jiaowu.Api.Domain.Academic;
|
||||||
|
using Jiaowu.Api.Infrastructure.Caching;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Jiaowu.Api.Infrastructure.Persistence;
|
namespace Jiaowu.Api.Infrastructure.Persistence;
|
||||||
|
|
||||||
public sealed class DemoDataSeeder(
|
public sealed class DemoDataSeeder(
|
||||||
AppDbContext db,
|
AppDbContext db,
|
||||||
|
IAppCache cache,
|
||||||
ILogger<DemoDataSeeder> logger)
|
ILogger<DemoDataSeeder> logger)
|
||||||
{
|
{
|
||||||
// MySql.EntityFrameworkCore 10 cannot type-map parameterized primitive
|
// MySql.EntityFrameworkCore 10 cannot type-map parameterized primitive
|
||||||
@@ -47,6 +49,10 @@ public sealed class DemoDataSeeder(
|
|||||||
await LogSummaryAsync(cancellationToken);
|
await LogSummaryAsync(cancellationToken);
|
||||||
await transaction.CommitAsync(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)
|
private async Task EnsureDatabaseIsEmptyAsync(CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Jiaowu.Api.Domain.Academic;
|
using Jiaowu.Api.Domain.Academic;
|
||||||
|
using Jiaowu.Api.Infrastructure.Caching;
|
||||||
using Jiaowu.Api.Infrastructure.Persistence;
|
using Jiaowu.Api.Infrastructure.Persistence;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -18,6 +19,7 @@ public sealed class DemoDataSeederTests
|
|||||||
|
|
||||||
var seeder = new DemoDataSeeder(
|
var seeder = new DemoDataSeeder(
|
||||||
db,
|
db,
|
||||||
|
NoOpAppCache.Instance,
|
||||||
NullLogger<DemoDataSeeder>.Instance);
|
NullLogger<DemoDataSeeder>.Instance);
|
||||||
|
|
||||||
await seeder.SeedAsync();
|
await seeder.SeedAsync();
|
||||||
@@ -46,6 +48,7 @@ public sealed class DemoDataSeederTests
|
|||||||
|
|
||||||
var seeder = new DemoDataSeeder(
|
var seeder = new DemoDataSeeder(
|
||||||
db,
|
db,
|
||||||
|
NoOpAppCache.Instance,
|
||||||
NullLogger<DemoDataSeeder>.Instance);
|
NullLogger<DemoDataSeeder>.Instance);
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
@@ -70,6 +73,7 @@ public sealed class DemoDataSeederTests
|
|||||||
|
|
||||||
var seeder = new DemoDataSeeder(
|
var seeder = new DemoDataSeeder(
|
||||||
db,
|
db,
|
||||||
|
NoOpAppCache.Instance,
|
||||||
NullLogger<DemoDataSeeder>.Instance);
|
NullLogger<DemoDataSeeder>.Instance);
|
||||||
|
|
||||||
await Assert.ThrowsAsync<KeyNotFoundException>(() => seeder.SeedAsync());
|
await Assert.ThrowsAsync<KeyNotFoundException>(() => seeder.SeedAsync());
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ public sealed class StatisticsControllerTests
|
|||||||
null,
|
null,
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
Assert.Equal(1, TotalCourses(first));
|
Assert.Equal(1, TotalCourses(first));
|
||||||
|
Assert.Equal(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
AppCacheTags.Analytics,
|
||||||
|
AppCacheTags.BaseData,
|
||||||
|
AppCacheTags.Timetables
|
||||||
|
},
|
||||||
|
cache.Tags.Single());
|
||||||
|
|
||||||
db.Courses.Add(CreateCourse("C003", firstCollege, category));
|
db.Courses.Add(CreateCourse("C003", firstCollege, category));
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
@@ -115,6 +123,7 @@ public sealed class StatisticsControllerTests
|
|||||||
|
|
||||||
public int SourceCalls { get; private set; }
|
public int SourceCalls { get; private set; }
|
||||||
public IReadOnlyCollection<string> Keys => values.Keys;
|
public IReadOnlyCollection<string> Keys => values.Keys;
|
||||||
|
public List<IReadOnlyCollection<string>> Tags { get; } = [];
|
||||||
|
|
||||||
public async Task<T> GetOrCreateAsync<T>(
|
public async Task<T> GetOrCreateAsync<T>(
|
||||||
string key,
|
string key,
|
||||||
@@ -127,6 +136,7 @@ public sealed class StatisticsControllerTests
|
|||||||
return (T)value;
|
return (T)value;
|
||||||
|
|
||||||
SourceCalls++;
|
SourceCalls++;
|
||||||
|
Tags.Add(tags.ToArray());
|
||||||
var loaded = await factory(cancellationToken);
|
var loaded = await factory(cancellationToken);
|
||||||
values[key] = loaded!;
|
values[key] = loaded!;
|
||||||
return loaded;
|
return loaded;
|
||||||
|
|||||||
Reference in New Issue
Block a user