优化服务器端分页

This commit is contained in:
biss committed 2026-08-11 11:52:43 +08:00
1 parent 83103eebb8
commit 5d7322507b
6 files changed
+225 -50

No files matched your search

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth; using Jiaowu.Api.Infrastructure.Auth;
@@ -176,8 +177,13 @@ public sealed class CourseAdjustmentsController(
public async Task<ActionResult> GetMine( public async Task<ActionResult> GetMine(
Guid? academicTermId, Guid? academicTermId,
CourseAdjustmentStatus? status, CourseAdjustmentStatus? status,
CancellationToken cancellationToken) int page = 1,
int pageSize = 20,
CancellationToken cancellationToken = default)
{ {
if (page < 1 || pageSize is < 1 or > 100)
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
var userId = currentUserDataScope.Current.UserId; var userId = currentUserDataScope.Current.UserId;
var source = db.CourseAdjustments.AsNoTracking() var source = db.CourseAdjustments.AsNoTracking()
.Where(x => x.ApplicantUserId == userId); .Where(x => x.ApplicantUserId == userId);
@@ -187,10 +193,15 @@ public sealed class CourseAdjustmentsController(
if (status.HasValue) if (status.HasValue)
source = source.Where(x => x.Status == status); source = source.Where(x => x.Status == status);
return Ok(await source var total = await source.CountAsync(cancellationToken);
var items = await source
.OrderByDescending(x => x.CreatedAt) .OrderByDescending(x => x.CreatedAt)
.ThenByDescending(x => x.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(AdjustmentProjection()) .Select(AdjustmentProjection())
.ToListAsync(cancellationToken)); .ToListAsync(cancellationToken);
return Ok(new PagedResult<object>(items, total, page, pageSize));
} }
// ═══════════════ Pending reviews ═══════════════ // ═══════════════ Pending reviews ═══════════════
@@ -199,8 +210,13 @@ public sealed class CourseAdjustmentsController(
[Authorize(Roles = Reviewers)] [Authorize(Roles = Reviewers)]
public async Task<ActionResult> GetPendingReviews( public async Task<ActionResult> GetPendingReviews(
Guid? academicTermId, Guid? academicTermId,
CancellationToken cancellationToken) int page = 1,
int pageSize = 20,
CancellationToken cancellationToken = default)
{ {
if (page < 1 || pageSize is < 1 or > 100)
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
var scope = currentUserDataScope.Current; var scope = currentUserDataScope.Current;
var source = db.CourseAdjustments.AsNoTracking() var source = db.CourseAdjustments.AsNoTracking()
.Where(x => x.Status == CourseAdjustmentStatus.Submitted); .Where(x => x.Status == CourseAdjustmentStatus.Submitted);
@@ -211,10 +227,15 @@ public sealed class CourseAdjustmentsController(
source = source.Where(x => source = source.Where(x =>
x.TeachingTask!.AcademicTermId == academicTermId); x.TeachingTask!.AcademicTermId == academicTermId);
return Ok(await source var total = await source.CountAsync(cancellationToken);
var items = await source
.OrderByDescending(x => x.SubmittedAt) .OrderByDescending(x => x.SubmittedAt)
.ThenByDescending(x => x.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(AdjustmentProjection()) .Select(AdjustmentProjection())
.ToListAsync(cancellationToken)); .ToListAsync(cancellationToken);
return Ok(new PagedResult<object>(items, total, page, pageSize));
} }
// ═══════════════ Detail ═══════════════ // ═══════════════ Detail ═══════════════
@@ -17,17 +17,53 @@ public sealed class StudentStatusChangesController(
ICurrentUserDataScope currentUserDataScope) : ControllerBase ICurrentUserDataScope currentUserDataScope) : ControllerBase
{ {
[HttpGet] [HttpGet]
public async Task<ActionResult> Get(CancellationToken token) public async Task<ActionResult<StudentStatusChangePage>> Get(
int page = 1,
int pageSize = 20,
CancellationToken token = default)
{ {
if (page < 1 || pageSize is < 1 or > 100)
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
var source = ScopedChanges().AsNoTracking(); var source = ScopedChanges().AsNoTracking();
return Ok(await source.OrderByDescending(x => x.SubmittedAt).Select(x => new var userScope = currentUserDataScope.Current;
{ var total = await source.CountAsync(token);
x.Id, x.StudentId, x.Student!.StudentNumber, x.Student.Name, var actionableTotal = await source.CountAsync(change =>
ClassName = x.Student.AdministrativeClass!.Name, (change.State == StudentStatusChangeState.Submitted &&
CollegeName = x.Student.AdministrativeClass.Major!.College!.Name, userScope.IsInRole(SystemRoles.Counselor)) ||
x.Type, x.OriginalStatus, x.TargetStatus, x.Reason, x.State, (change.State == StudentStatusChangeState.CounselorApproved &&
x.ReviewComment, x.SubmittedAt, x.ReviewedAt, x.ApprovedAt userScope.IsInRole(SystemRoles.CollegeAdmin)) ||
}).ToListAsync(token)); (change.State == StudentStatusChangeState.CollegeApproved &&
(userScope.IsInRole(SystemRoles.AcademicAdmin) ||
userScope.IsInRole(SystemRoles.SuperAdmin))), token);
var finishedTotal = await source.CountAsync(change =>
change.State == StudentStatusChangeState.Approved ||
change.State == StudentStatusChangeState.Rejected ||
change.State == StudentStatusChangeState.Cancelled, token);
var items = await source
.OrderByDescending(x => x.SubmittedAt)
.ThenByDescending(x => x.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new StudentStatusChangeListItem(
x.Id,
x.StudentId,
x.Student!.StudentNumber,
x.Student.Name,
x.Student.AdministrativeClass!.Name,
x.Student.AdministrativeClass.Major!.College!.Name,
x.Type,
x.OriginalStatus,
x.TargetStatus,
x.Reason,
x.State,
x.ReviewComment,
x.SubmittedAt,
x.ReviewedAt,
x.ApprovedAt))
.ToListAsync(token);
return Ok(new StudentStatusChangePage(
items, total, page, pageSize, actionableTotal, finishedTotal));
} }
[HttpGet("options")] [HttpGet("options")]
@@ -199,6 +235,31 @@ public sealed class StudentStatusChangesController(
}); });
} }
public sealed record StudentStatusChangePage(
IReadOnlyCollection<StudentStatusChangeListItem> Items,
int Total,
int Page,
int PageSize,
int ActionableTotal,
int FinishedTotal);
public sealed record StudentStatusChangeListItem(
Guid Id,
Guid StudentId,
string StudentNumber,
string Name,
string ClassName,
string CollegeName,
StudentStatusChangeType Type,
StudentStatus OriginalStatus,
StudentStatus TargetStatus,
string Reason,
StudentStatusChangeState State,
string? ReviewComment,
DateTime SubmittedAt,
DateTime? ReviewedAt,
DateTime? ApprovedAt);
public sealed record StudentStatusChangeRequest( public sealed record StudentStatusChangeRequest(
StudentStatusChangeType Type, StudentStatusChangeType Type,
[Required, MinLength(10), MaxLength(1000)] string Reason); [Required, MinLength(10), MaxLength(1000)] string Reason);
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth; using Jiaowu.Api.Infrastructure.Auth;
@@ -46,17 +47,26 @@ public sealed class TeacherCourseApplicationsController(
[Authorize(Roles = SystemRoles.Teacher)] [Authorize(Roles = SystemRoles.Teacher)]
public async Task<ActionResult> GetMine( public async Task<ActionResult> GetMine(
Guid? academicTermId, Guid? academicTermId,
CancellationToken cancellationToken) int page = 1,
int pageSize = 20,
CancellationToken cancellationToken = default)
{ {
if (page < 1 || pageSize is < 1 or > 100)
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
var teacher = await CurrentTeacherAsync(cancellationToken); var teacher = await CurrentTeacherAsync(cancellationToken);
if (teacher is null) return ConflictProblem("当前账号尚未关联在职教师档案。"); if (teacher is null) return ConflictProblem("当前账号尚未关联在职教师档案。");
var source = db.TeacherCourseApplications.AsNoTracking() var source = db.TeacherCourseApplications.AsNoTracking()
.Where(x => x.TeacherId == teacher.Id); .Where(x => x.TeacherId == teacher.Id);
if (academicTermId.HasValue) if (academicTermId.HasValue)
source = source.Where(x => x.AcademicTermId == academicTermId); source = source.Where(x => x.AcademicTermId == academicTermId);
return Ok(await source var total = await source.CountAsync(cancellationToken);
var items = await source
.OrderByDescending(x => x.AcademicTerm!.StartDate) .OrderByDescending(x => x.AcademicTerm!.StartDate)
.ThenBy(x => x.Course!.Code) .ThenBy(x => x.Course!.Code)
.ThenBy(x => x.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new .Select(x => new
{ {
x.Id, x.Id,
@@ -72,7 +82,8 @@ public sealed class TeacherCourseApplicationsController(
x.SubmittedAt, x.SubmittedAt,
x.ReviewedAt x.ReviewedAt
}) })
.ToListAsync(cancellationToken)); .ToListAsync(cancellationToken);
return Ok(new PagedResult<object>(items, total, page, pageSize));
} }
[HttpPost("mine")] [HttpPost("mine")]
@@ -144,15 +155,24 @@ public sealed class TeacherCourseApplicationsController(
public async Task<ActionResult> GetReviews( public async Task<ActionResult> GetReviews(
Guid? academicTermId, Guid? academicTermId,
TeacherCourseApplicationStatus? status, TeacherCourseApplicationStatus? status,
CancellationToken cancellationToken) int page = 1,
int pageSize = 20,
CancellationToken cancellationToken = default)
{ {
if (page < 1 || pageSize is < 1 or > 100)
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
var source = ScopedApplications().AsNoTracking(); var source = ScopedApplications().AsNoTracking();
if (academicTermId.HasValue) if (academicTermId.HasValue)
source = source.Where(x => x.AcademicTermId == academicTermId); source = source.Where(x => x.AcademicTermId == academicTermId);
if (status.HasValue) source = source.Where(x => x.Status == status); if (status.HasValue) source = source.Where(x => x.Status == status);
return Ok(await source var total = await source.CountAsync(cancellationToken);
var items = await source
.OrderBy(x => x.Status) .OrderBy(x => x.Status)
.ThenByDescending(x => x.SubmittedAt) .ThenByDescending(x => x.SubmittedAt)
.ThenBy(x => x.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new .Select(x => new
{ {
x.Id, x.Id,
@@ -172,7 +192,8 @@ public sealed class TeacherCourseApplicationsController(
x.SubmittedAt, x.SubmittedAt,
x.ReviewedAt x.ReviewedAt
}) })
.ToListAsync(cancellationToken)); .ToListAsync(cancellationToken);
return Ok(new PagedResult<object>(items, total, page, pageSize));
} }
[HttpGet("assignment-options")] [HttpGet("assignment-options")]
Loaded 3 of 6 files, more files were not shown because too many files have changed in this diff. Show more