feat: 增加分页排序与FluentValidation

This commit is contained in:
2025-12-02 10:50:43 +08:00
parent 93141fbf0c
commit 97bf6cacb0
63 changed files with 904 additions and 49 deletions

View File

@@ -111,4 +111,9 @@ public sealed class StoreDto
/// 支持配送。
/// </summary>
public bool SupportsDelivery { get; init; }
/// <summary>
/// 创建时间。
/// </summary>
public DateTime CreatedAt { get; init; }
}

View File

@@ -72,6 +72,7 @@ public sealed class CreateStoreCommandHandler(IStoreRepository storeRepository,
DeliveryRadiusKm = store.DeliveryRadiusKm,
SupportsDineIn = store.SupportsDineIn,
SupportsPickup = store.SupportsPickup,
SupportsDelivery = store.SupportsDelivery
SupportsDelivery = store.SupportsDelivery,
CreatedAt = store.CreatedAt
};
}

View File

@@ -47,6 +47,7 @@ public sealed class GetStoreByIdQueryHandler(
DeliveryRadiusKm = store.DeliveryRadiusKm,
SupportsDineIn = store.SupportsDineIn,
SupportsPickup = store.SupportsPickup,
SupportsDelivery = store.SupportsDelivery
SupportsDelivery = store.SupportsDelivery,
CreatedAt = store.CreatedAt
};
}

View File

@@ -28,9 +28,27 @@ public sealed class SearchStoresQueryHandler(
stores = stores.Where(x => x.MerchantId == request.MerchantId.Value).ToList();
}
return stores
.Select(MapToDto)
var sorted = ApplySorting(stores, request.SortBy, request.SortDescending);
var paged = sorted
.Skip((request.Page - 1) * request.PageSize)
.Take(request.PageSize)
.ToList();
return paged.Select(MapToDto).ToList();
}
private static IOrderedEnumerable<Domain.Stores.Entities.Store> ApplySorting(
IReadOnlyCollection<Domain.Stores.Entities.Store> stores,
string? sortBy,
bool sortDescending)
{
return sortBy?.ToLowerInvariant() switch
{
"name" => sortDescending ? stores.OrderByDescending(x => x.Name) : stores.OrderBy(x => x.Name),
"code" => sortDescending ? stores.OrderByDescending(x => x.Code) : stores.OrderBy(x => x.Code),
"status" => sortDescending ? stores.OrderByDescending(x => x.Status) : stores.OrderBy(x => x.Status),
_ => sortDescending ? stores.OrderByDescending(x => x.CreatedAt) : stores.OrderBy(x => x.CreatedAt)
};
}
private static StoreDto MapToDto(Domain.Stores.Entities.Store store) => new()
@@ -54,6 +72,7 @@ public sealed class SearchStoresQueryHandler(
DeliveryRadiusKm = store.DeliveryRadiusKm,
SupportsDineIn = store.SupportsDineIn,
SupportsPickup = store.SupportsPickup,
SupportsDelivery = store.SupportsDelivery
SupportsDelivery = store.SupportsDelivery,
CreatedAt = store.CreatedAt
};
}

View File

@@ -82,6 +82,7 @@ public sealed class UpdateStoreCommandHandler(
DeliveryRadiusKm = store.DeliveryRadiusKm,
SupportsDineIn = store.SupportsDineIn,
SupportsPickup = store.SupportsPickup,
SupportsDelivery = store.SupportsDelivery
SupportsDelivery = store.SupportsDelivery,
CreatedAt = store.CreatedAt
};
}

View File

@@ -18,4 +18,24 @@ public sealed class SearchStoresQuery : IRequest<IReadOnlyList<StoreDto>>
/// 状态过滤。
/// </summary>
public StoreStatus? Status { get; init; }
/// <summary>
/// 页码。
/// </summary>
public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 20;
/// <summary>
/// 排序字段name/code/status/createdAt
/// </summary>
public string? SortBy { get; init; }
/// <summary>
/// 是否倒序。
/// </summary>
public bool SortDescending { get; init; } = true;
}

View File

@@ -0,0 +1,29 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Commands;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 创建门店命令验证器。
/// </summary>
public sealed class CreateStoreCommandValidator : AbstractValidator<CreateStoreCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public CreateStoreCommandValidator()
{
RuleFor(x => x.MerchantId).GreaterThan(0);
RuleFor(x => x.Code).NotEmpty().MaximumLength(32);
RuleFor(x => x.Name).NotEmpty().MaximumLength(128);
RuleFor(x => x.Phone).MaximumLength(32);
RuleFor(x => x.ManagerName).MaximumLength(64);
RuleFor(x => x.Province).MaximumLength(64);
RuleFor(x => x.City).MaximumLength(64);
RuleFor(x => x.District).MaximumLength(64);
RuleFor(x => x.Address).MaximumLength(256);
RuleFor(x => x.Announcement).MaximumLength(512);
RuleFor(x => x.Tags).MaximumLength(256);
RuleFor(x => x.DeliveryRadiusKm).GreaterThanOrEqualTo(0);
}
}

View File

@@ -0,0 +1,20 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Queries;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 门店列表查询验证器。
/// </summary>
public sealed class SearchStoresQueryValidator : AbstractValidator<SearchStoresQuery>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public SearchStoresQueryValidator()
{
RuleFor(x => x.Page).GreaterThan(0);
RuleFor(x => x.PageSize).InclusiveBetween(1, 200);
RuleFor(x => x.SortBy).MaximumLength(64);
}
}

View File

@@ -0,0 +1,30 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Stores.Commands;
namespace TakeoutSaaS.Application.App.Stores.Validators;
/// <summary>
/// 更新门店命令验证器。
/// </summary>
public sealed class UpdateStoreCommandValidator : AbstractValidator<UpdateStoreCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
public UpdateStoreCommandValidator()
{
RuleFor(x => x.StoreId).GreaterThan(0);
RuleFor(x => x.MerchantId).GreaterThan(0);
RuleFor(x => x.Code).NotEmpty().MaximumLength(32);
RuleFor(x => x.Name).NotEmpty().MaximumLength(128);
RuleFor(x => x.Phone).MaximumLength(32);
RuleFor(x => x.ManagerName).MaximumLength(64);
RuleFor(x => x.Province).MaximumLength(64);
RuleFor(x => x.City).MaximumLength(64);
RuleFor(x => x.District).MaximumLength(64);
RuleFor(x => x.Address).MaximumLength(256);
RuleFor(x => x.Announcement).MaximumLength(512);
RuleFor(x => x.Tags).MaximumLength(256);
RuleFor(x => x.DeliveryRadiusKm).GreaterThanOrEqualTo(0);
}
}