游标分页优化
This commit is contained in:
1 parent
f5db2f6e77
commit
0a0713e65b
17 files changed
+448
-156
No files matched your search
@@ -8,7 +8,8 @@ public sealed record CursorPagedResult<T>(
|
|||||||
IReadOnlyCollection<T> Items,
|
IReadOnlyCollection<T> Items,
|
||||||
string? NextCursor,
|
string? NextCursor,
|
||||||
bool HasMore,
|
bool HasMore,
|
||||||
int PageSize);
|
int PageSize,
|
||||||
|
int? Total = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Legacy offset-page response. New list endpoints must use
|
/// Legacy offset-page response. New list endpoints must use
|
||||||
|
|||||||
@@ -113,15 +113,17 @@ public sealed partial class AppUpdatesController(AppDbContext db) : ControllerBa
|
|||||||
|
|
||||||
[Authorize(Roles = SystemRoles.SuperAdmin)]
|
[Authorize(Roles = SystemRoles.SuperAdmin)]
|
||||||
[HttpGet("releases")]
|
[HttpGet("releases")]
|
||||||
public async Task<ActionResult<PagedResult<AppUpdateReleaseItem>>> GetReleases(
|
public async Task<ActionResult<CursorPagedResult<AppUpdateReleaseItem>>> GetReleases(
|
||||||
[FromQuery] int page = 1,
|
[FromQuery] string? cursor = null,
|
||||||
[FromQuery] int pageSize = 20,
|
[FromQuery] int pageSize = 20,
|
||||||
[FromQuery] string? platform = null,
|
[FromQuery] string? platform = null,
|
||||||
[FromQuery] string? channel = null,
|
[FromQuery] string? channel = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (page < 1 || pageSize is < 1 or > 100)
|
if (pageSize is < 1 or > 100)
|
||||||
return ValidationProblem("页码必须大于零,每页数量必须在 1 到 100 之间。");
|
return ValidationProblem("每批数量必须在 1 到 100 之间。");
|
||||||
|
if (!CursorToken.TryDecode<ReleaseCursor>(cursor, out var after))
|
||||||
|
return BadRequest("更新包游标无效。");
|
||||||
|
|
||||||
var query = db.AppUpdateReleases.AsNoTracking().AsQueryable();
|
var query = db.AppUpdateReleases.AsNoTracking().AsQueryable();
|
||||||
if (!string.IsNullOrWhiteSpace(platform))
|
if (!string.IsNullOrWhiteSpace(platform))
|
||||||
@@ -137,11 +139,21 @@ public sealed partial class AppUpdatesController(AppDbContext db) : ControllerBa
|
|||||||
query = query.Where(x => x.Channel == parsedChannel);
|
query = query.Where(x => x.Channel == parsedChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
var total = await query.CountAsync(cancellationToken);
|
if (after is not null)
|
||||||
var rows = await query
|
{
|
||||||
|
query = query.Where(x => (x.PublishedAt ?? x.CreatedAt) < after.SortAt ||
|
||||||
|
(x.PublishedAt ?? x.CreatedAt) == after.SortAt && x.Id.CompareTo(after.Id) < 0);
|
||||||
|
}
|
||||||
|
var keys = await query
|
||||||
.OrderByDescending(x => x.PublishedAt ?? x.CreatedAt)
|
.OrderByDescending(x => x.PublishedAt ?? x.CreatedAt)
|
||||||
.Skip((page - 1) * pageSize)
|
.ThenByDescending(x => x.Id)
|
||||||
.Take(pageSize)
|
.Take(pageSize + 1)
|
||||||
|
.Select(x => new { x.Id, SortAt = x.PublishedAt ?? x.CreatedAt })
|
||||||
|
.ToArrayAsync(cancellationToken);
|
||||||
|
var hasMore = keys.Length > pageSize;
|
||||||
|
var pageIds = keys.Take(pageSize).Select(x => x.Id).ToArray();
|
||||||
|
var rows = await query.WhereIn(pageIds, x => x.Id)
|
||||||
|
.OrderByDescending(x => x.PublishedAt ?? x.CreatedAt).ThenByDescending(x => x.Id)
|
||||||
.Select(x => new AppUpdateReleaseItem(
|
.Select(x => new AppUpdateReleaseItem(
|
||||||
x.Id,
|
x.Id,
|
||||||
x.Platform,
|
x.Platform,
|
||||||
@@ -158,13 +170,16 @@ public sealed partial class AppUpdatesController(AppDbContext db) : ControllerBa
|
|||||||
x.PublishedByUserName,
|
x.PublishedByUserName,
|
||||||
x.PublishedAt))
|
x.PublishedAt))
|
||||||
.ToArrayAsync(cancellationToken);
|
.ToArrayAsync(cancellationToken);
|
||||||
return Ok(new PagedResult<AppUpdateReleaseItem>(
|
var last = keys.Take(pageSize).LastOrDefault();
|
||||||
|
return Ok(new CursorPagedResult<AppUpdateReleaseItem>(
|
||||||
rows,
|
rows,
|
||||||
total,
|
hasMore && last is not null ? CursorToken.Encode(new ReleaseCursor(last.SortAt, last.Id)) : null,
|
||||||
page,
|
hasMore,
|
||||||
pageSize));
|
pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed record ReleaseCursor(DateTime SortAt, Guid Id);
|
||||||
|
|
||||||
[Authorize(Roles = SystemRoles.SuperAdmin)]
|
[Authorize(Roles = SystemRoles.SuperAdmin)]
|
||||||
[Consumes("multipart/form-data")]
|
[Consumes("multipart/form-data")]
|
||||||
[RequestSizeLimit(MaximumBundleBytes + 1024 * 1024)]
|
[RequestSizeLimit(MaximumBundleBytes + 1024 * 1024)]
|
||||||
|
|||||||
@@ -177,12 +177,14 @@ public sealed class CourseAdjustmentsController(
|
|||||||
public async Task<ActionResult> GetMine(
|
public async Task<ActionResult> GetMine(
|
||||||
Guid? academicTermId,
|
Guid? academicTermId,
|
||||||
CourseAdjustmentStatus? status,
|
CourseAdjustmentStatus? status,
|
||||||
int page = 1,
|
string? cursor = null,
|
||||||
int pageSize = 20,
|
int pageSize = 20,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (page < 1 || pageSize is < 1 or > 100)
|
if (pageSize is < 1 or > 100)
|
||||||
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
return ValidationProblem("每批条数应在 1 至 100 之间。");
|
||||||
|
if (!CursorToken.TryDecode<CreatedCursor>(cursor, out var after))
|
||||||
|
return BadRequest("调停课申请游标无效。");
|
||||||
|
|
||||||
var userId = currentUserDataScope.Current.UserId;
|
var userId = currentUserDataScope.Current.UserId;
|
||||||
var source = db.CourseAdjustments.AsNoTracking()
|
var source = db.CourseAdjustments.AsNoTracking()
|
||||||
@@ -193,15 +195,27 @@ public sealed class CourseAdjustmentsController(
|
|||||||
if (status.HasValue)
|
if (status.HasValue)
|
||||||
source = source.Where(x => x.Status == status);
|
source = source.Where(x => x.Status == status);
|
||||||
|
|
||||||
var total = await source.CountAsync(cancellationToken);
|
if (after is not null)
|
||||||
var items = await source
|
{
|
||||||
|
source = source.Where(x => x.CreatedAt < after.CreatedAt ||
|
||||||
|
x.CreatedAt == after.CreatedAt && x.Id.CompareTo(after.Id) < 0);
|
||||||
|
}
|
||||||
|
var keys = await source
|
||||||
.OrderByDescending(x => x.CreatedAt)
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
.ThenByDescending(x => x.Id)
|
.ThenByDescending(x => x.Id)
|
||||||
.Skip((page - 1) * pageSize)
|
.Take(pageSize + 1)
|
||||||
.Take(pageSize)
|
.Select(x => new { x.Id, x.CreatedAt })
|
||||||
.Select(AdjustmentProjection())
|
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
var hasMore = keys.Count > pageSize;
|
||||||
|
var pageIds = keys.Take(pageSize).Select(x => x.Id).ToArray();
|
||||||
|
var items = await source.WhereIn(pageIds, x => x.Id)
|
||||||
|
.OrderByDescending(x => x.CreatedAt).ThenByDescending(x => x.Id)
|
||||||
|
.Select(AdjustmentProjection()).ToListAsync(cancellationToken);
|
||||||
|
var last = keys.Take(pageSize).LastOrDefault();
|
||||||
|
return Ok(new CursorPagedResult<object>(items,
|
||||||
|
hasMore && last is not null
|
||||||
|
? CursorToken.Encode(new CreatedCursor(last.CreatedAt, last.Id)) : null,
|
||||||
|
hasMore, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════ Pending reviews ═══════════════
|
// ═══════════════ Pending reviews ═══════════════
|
||||||
@@ -210,13 +224,15 @@ public sealed class CourseAdjustmentsController(
|
|||||||
[Authorize(Roles = Reviewers)]
|
[Authorize(Roles = Reviewers)]
|
||||||
public async Task<ActionResult> GetPendingReviews(
|
public async Task<ActionResult> GetPendingReviews(
|
||||||
Guid? academicTermId,
|
Guid? academicTermId,
|
||||||
int page = 1,
|
string? cursor = null,
|
||||||
int pageSize = 20,
|
int pageSize = 20,
|
||||||
CancellationToken cancellationToken = default,
|
CancellationToken cancellationToken = default,
|
||||||
Guid? adjustmentId = null)
|
Guid? adjustmentId = null)
|
||||||
{
|
{
|
||||||
if (page < 1 || pageSize is < 1 or > 100)
|
if (pageSize is < 1 or > 100)
|
||||||
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
return ValidationProblem("每批条数应在 1 至 100 之间。");
|
||||||
|
if (!CursorToken.TryDecode<SubmittedCursor>(cursor, out var after))
|
||||||
|
return BadRequest("待审核调停课游标无效。");
|
||||||
|
|
||||||
var scope = currentUserDataScope.Current;
|
var scope = currentUserDataScope.Current;
|
||||||
var source = db.CourseAdjustments.AsNoTracking()
|
var source = db.CourseAdjustments.AsNoTracking()
|
||||||
@@ -230,17 +246,32 @@ public sealed class CourseAdjustmentsController(
|
|||||||
source = source.Where(x =>
|
source = source.Where(x =>
|
||||||
x.TeachingTask!.AcademicTermId == academicTermId);
|
x.TeachingTask!.AcademicTermId == academicTermId);
|
||||||
|
|
||||||
var total = await source.CountAsync(cancellationToken);
|
if (after is not null)
|
||||||
var items = await source
|
{
|
||||||
|
source = source.Where(x => x.SubmittedAt!.Value < after.SubmittedAt ||
|
||||||
|
x.SubmittedAt == after.SubmittedAt && x.Id.CompareTo(after.Id) < 0);
|
||||||
|
}
|
||||||
|
var keys = await source
|
||||||
.OrderByDescending(x => x.SubmittedAt)
|
.OrderByDescending(x => x.SubmittedAt)
|
||||||
.ThenByDescending(x => x.Id)
|
.ThenByDescending(x => x.Id)
|
||||||
.Skip((page - 1) * pageSize)
|
.Take(pageSize + 1)
|
||||||
.Take(pageSize)
|
.Select(x => new { x.Id, SubmittedAt = x.SubmittedAt!.Value })
|
||||||
.Select(AdjustmentProjection())
|
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
var hasMore = keys.Count > pageSize;
|
||||||
|
var pageIds = keys.Take(pageSize).Select(x => x.Id).ToArray();
|
||||||
|
var items = await source.WhereIn(pageIds, x => x.Id)
|
||||||
|
.OrderByDescending(x => x.SubmittedAt).ThenByDescending(x => x.Id)
|
||||||
|
.Select(AdjustmentProjection()).ToListAsync(cancellationToken);
|
||||||
|
var last = keys.Take(pageSize).LastOrDefault();
|
||||||
|
return Ok(new CursorPagedResult<object>(items,
|
||||||
|
hasMore && last is not null
|
||||||
|
? CursorToken.Encode(new SubmittedCursor(last.SubmittedAt, last.Id)) : null,
|
||||||
|
hasMore, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed record CreatedCursor(DateTime CreatedAt, Guid Id);
|
||||||
|
private sealed record SubmittedCursor(DateTime SubmittedAt, Guid Id);
|
||||||
|
|
||||||
// ═══════════════ Detail ═══════════════
|
// ═══════════════ Detail ═══════════════
|
||||||
|
|
||||||
[HttpGet("{id:guid}")]
|
[HttpGet("{id:guid}")]
|
||||||
|
|||||||
Loaded 3 of 17 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user