修复统计图
Build and publish Jiaowu packages and container image / Test, package and publish (push) Successful in 34m39s

This commit is contained in:
2026-07-29 11:50:15 +08:00 Unverified
parent 66622ca207
commit 7bcfa1a7a4
2 changed files with 64 additions and 3 deletions
@@ -25,7 +25,12 @@ public sealed class StatisticsController(
SystemRoles.CollegeAdmin + "," +
SystemRoles.Leader;
private Guid? RestrictedCollegeId => currentUserDataScope.Current.RestrictedCollegeId;
// HybridCache may execute its source factory without the request's ambient
// HttpContext. Keep the authorization scope stable for the whole controller
// invocation instead of resolving it again inside that background factory.
private readonly CurrentUserScope currentUser = currentUserDataScope.Current;
private Guid? RestrictedCollegeId => currentUser.RestrictedCollegeId;
private Guid? ResolveCollegeId(Guid? requestedCollegeId)
{
@@ -982,7 +987,7 @@ public sealed class StatisticsController(
params string?[] filters) =>
AppCacheKeys.Statistics(
area,
currentUserDataScope.Current.Scope.ToString(),
currentUser.Scope.ToString(),
effectiveCollegeId,
filters);
@@ -94,6 +94,36 @@ public sealed class StatisticsControllerTests
Assert.Equal(2, cache.SourceCalls);
}
[Fact]
public async Task Course_summary_keeps_request_scope_when_cache_factory_loses_http_context()
{
await using var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(connection)
.Options;
await using var db = new AppDbContext(options);
await db.Database.EnsureCreatedAsync();
var college = new College { Code = "C01", Name = "第一学院" };
var category = new CourseCategory { Code = "CAT", Name = "测试分类" };
db.AddRange(college, category, CreateCourse("C001", college, category));
await db.SaveChangesAsync();
var scope = new MutableDataScope(college.Id);
var cache = new RecordingCache(() => scope.LoseHttpContext());
var controller = new StatisticsController(db, scope, cache);
var result = await controller.GetCourseSummary(
college.Id,
null,
null,
CancellationToken.None);
Assert.Equal(1, TotalCourses(result));
Assert.Equal(1, cache.SourceCalls);
}
private static int TotalCourses(ActionResult<object> result)
{
var json = Assert.IsType<JsonElement>(result.Value);
@@ -117,7 +147,7 @@ public sealed class StatisticsControllerTests
AssessmentMethod = AssessmentMethod.Examination
};
private sealed class RecordingCache : IAppCache
private sealed class RecordingCache(Action? beforeFactory = null) : IAppCache
{
private readonly Dictionary<string, object> values = [];
@@ -137,6 +167,7 @@ public sealed class StatisticsControllerTests
SourceCalls++;
Tags.Add(tags.ToArray());
beforeFactory?.Invoke();
var loaded = await factory(cancellationToken);
values[key] = loaded!;
return loaded;
@@ -157,4 +188,29 @@ public sealed class StatisticsControllerTests
DataScope.College,
new HashSet<string> { SystemRoles.CollegeAdmin });
}
private sealed class MutableDataScope(Guid collegeId) : ICurrentUserDataScope
{
private CurrentUserScope current = CreateCollegeScope(collegeId);
public CurrentUserScope Current => current;
public void LoseHttpContext()
{
current = new CurrentUserScope(
Guid.Empty,
null,
null,
DataScope.Self,
new HashSet<string>());
}
private static CurrentUserScope CreateCollegeScope(Guid value) =>
new(
Guid.NewGuid(),
"学院管理员",
value,
DataScope.College,
new HashSet<string> { SystemRoles.CollegeAdmin });
}
}