603 lines
19 KiB
C#
603 lines
19 KiB
C#
using MAX.Core.DependencyInjection;
|
||
using MAX.Framework.Commons;
|
||
using MAX.Framework.Dtos;
|
||
using Microsoft.Extensions.Logging;
|
||
using Store.Modules.CloudProduct.Dtos;
|
||
using Store.Modules.CloudProduct.Entities;
|
||
using Store.Modules.CloudProduct.Enums;
|
||
using Store.Modules.CloudProduct.Repositorys;
|
||
|
||
namespace Store.Modules.CloudProduct.Services
|
||
{
|
||
/// <summary>
|
||
/// 用户收藏商品业务:收藏/取消收藏(数据库 + 收藏索引同步写入)、收藏分组维护、收藏状态查询、收藏检索与索引重建。
|
||
/// </summary>
|
||
public interface IUserFavoritesService : IScopedDependency
|
||
{
|
||
/// <summary>批量收藏商品,返回本次成功收藏的商品数量(已收藏的商品视为修改收藏分组)</summary>
|
||
Task<int> AddFavoritesAsync(AddUserFavoriteRequest request);
|
||
|
||
/// <summary>批量取消收藏,返回实际取消的条数</summary>
|
||
Task<int> RemoveFavoritesAsync(RemoveUserFavoritesRequest request);
|
||
|
||
/// <summary>创建收藏分组:创建成功返回 true,同名分组已存在返回 false</summary>
|
||
Task<bool> CreateGroupAsync(CreateUserFavoriteGroupRequest request);
|
||
|
||
/// <summary>查询用户的收藏分组(含默认分组)及分组下收藏数量</summary>
|
||
Task<List<UserFavoriteGroupDto>> GetGroupsAsync(string userName);
|
||
|
||
/// <summary>按 GoodsIds 顺序返回每个商品是否已被该用户收藏</summary>
|
||
Task<List<bool>> GetFavoriteStatusAsync(UserFavoriteStatusRequest request);
|
||
|
||
/// <summary>检索用户收藏的商品(标题/销量/价格/类目/分组)</summary>
|
||
Task<(List<UserFavoriteItem> List, long Total)> SearchFavoritesAsync(UserFavoritesSearchRequest request);
|
||
|
||
/// <summary>移除所有用户收藏索引并按数据库数据全量重建,返回重建的文档数</summary>
|
||
Task<long> RebuildSearchIndexAsync();
|
||
}
|
||
|
||
internal class UserFavoritesService : IUserFavoritesService
|
||
{
|
||
/// <summary>未归属任何分组的收藏所使用的分组名(对应 group_id = 0)</summary>
|
||
public const string DefaultGroupName = "默认分组";
|
||
|
||
/// <summary>重建索引时单次 bulk 的文档数,避免一次请求过大</summary>
|
||
private const int RebuildBatchSize = 2000;
|
||
|
||
private readonly UserGoodsFavoriteRepository _favoriteRepository;
|
||
private readonly UserGoodsFavoriteGroupRepository _groupRepository;
|
||
private readonly GoodsRepository _goodsRepository;
|
||
private readonly IUserFavoritesIndexService _indexService;
|
||
private readonly ILogger<UserFavoritesService> _logger;
|
||
|
||
public UserFavoritesService(
|
||
UserGoodsFavoriteRepository favoriteRepository,
|
||
UserGoodsFavoriteGroupRepository groupRepository,
|
||
GoodsRepository goodsRepository,
|
||
IUserFavoritesIndexService indexService,
|
||
ILogger<UserFavoritesService> logger)
|
||
{
|
||
_favoriteRepository = favoriteRepository;
|
||
_groupRepository = groupRepository;
|
||
_goodsRepository = goodsRepository;
|
||
_indexService = indexService;
|
||
_logger = logger;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量收藏商品:先落库(user_goods_favorites),再同步写入收藏索引;
|
||
/// 商品不存在/已删除的编号会被跳过,返回本次成功收藏的商品数量。
|
||
/// 任意一步失败都会回滚本次已写入的数据库记录,保证“数据库中有收藏 = 索引中可搜索到”。
|
||
/// </summary>
|
||
public async Task<int> AddFavoritesAsync(AddUserFavoriteRequest request)
|
||
{
|
||
if (request == null)
|
||
{
|
||
throw new AppException("请求参数不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var userName = request.UserName?.Trim() ?? string.Empty;
|
||
if (string.IsNullOrWhiteSpace(userName))
|
||
{
|
||
throw new AppException("用户名不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var goodsIds = request.GoodsIds?.Where(x => x > 0).Distinct().ToList() ?? new List<long>();
|
||
if (goodsIds.Count == 0)
|
||
{
|
||
throw new AppException("请提供要收藏的商品编号", ResultCode.ValidationError);
|
||
}
|
||
|
||
if (request.GroupId < 0)
|
||
{
|
||
throw new AppException("收藏分组无效", ResultCode.ValidationError);
|
||
}
|
||
|
||
// 1. 校验分组归属(分组ID > 0 时必须是该用户自己创建的分组)
|
||
if (request.GroupId > 0)
|
||
{
|
||
var group = await _groupRepository.Select
|
||
.Where(x => x.Id == request.GroupId && x.UserName == userName)
|
||
.FirstAsync();
|
||
|
||
if (group == null)
|
||
{
|
||
throw new AppException($"收藏分组不存在: {request.GroupId}", ResultCode.ValidationError);
|
||
}
|
||
}
|
||
|
||
var groupName = await ResolveGroupNameAsync(userName, request.GroupId);
|
||
|
||
// 2. 批量取商品快照(不存在/已删除的商品不收藏,直接跳过)
|
||
var goodsList = await _goodsRepository.Select
|
||
.Where(x => goodsIds.Contains(x.GoodsId) && x.Status == 0)
|
||
.ToListAsync();
|
||
|
||
if (goodsList.Count == 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
var goodsMap = new Dictionary<long, GoodsEntity>();
|
||
foreach (var goods in goodsList)
|
||
{
|
||
goodsMap[goods.GoodsId] = goods;
|
||
}
|
||
|
||
// 3. 批量取已存在的收藏记录
|
||
var existingList = await _favoriteRepository.Select
|
||
.Where(x => x.UserName == userName && goodsIds.Contains(x.GoodsId))
|
||
.ToListAsync();
|
||
|
||
var existingMap = new Dictionary<long, UserGoodsFavoriteEntity>();
|
||
foreach (var favorite in existingList)
|
||
{
|
||
existingMap[favorite.GoodsId] = favorite;
|
||
}
|
||
|
||
// 4. 逐条落库:不存在则新增,已存在且分组不同则修改分组
|
||
var insertedIds = new List<long>();
|
||
var changedGroupIds = new List<(long Id, int PreviousGroupId)>();
|
||
var documents = new List<UserFavoritesSearchDocument>();
|
||
|
||
try
|
||
{
|
||
foreach (var goods in goodsMap.Values)
|
||
{
|
||
if (existingMap.TryGetValue(goods.GoodsId, out var favorite) == false)
|
||
{
|
||
favorite = new UserGoodsFavoriteEntity
|
||
{
|
||
UserName = userName,
|
||
GoodsId = goods.GoodsId,
|
||
GroupId = request.GroupId,
|
||
CreatedAt = DateTime.Now,
|
||
};
|
||
|
||
try
|
||
{
|
||
await _favoriteRepository.InsertAsync(favorite);
|
||
insertedIds.Add(favorite.Id);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 并发收藏同一商品时可能命中唯一键 uk_user_goods,回读已存在记录后按“修改分组”处理
|
||
favorite = await _favoriteRepository.Select
|
||
.Where(x => x.UserName == userName && x.GoodsId == goods.GoodsId)
|
||
.FirstAsync();
|
||
|
||
if (favorite == null)
|
||
{
|
||
throw new AppException($"收藏商品失败({goods.GoodsId}): {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
if (favorite.GroupId != request.GroupId)
|
||
{
|
||
changedGroupIds.Add((favorite.Id, favorite.GroupId));
|
||
await _favoriteRepository.Orm.Update<UserGoodsFavoriteEntity>()
|
||
.Set(x => x.GroupId, request.GroupId)
|
||
.Where(x => x.Id == favorite.Id)
|
||
.ExecuteAffrowsAsync();
|
||
favorite.GroupId = request.GroupId;
|
||
}
|
||
|
||
documents.Add(BuildDocument(favorite, goods, groupName));
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
await CompensateWriteFailureAsync(insertedIds, changedGroupIds);
|
||
throw;
|
||
}
|
||
|
||
// 5. 批量同步写入索引
|
||
try
|
||
{
|
||
await _indexService.IndexDocumentsAsync(documents);
|
||
}
|
||
catch
|
||
{
|
||
await CompensateWriteFailureAsync(insertedIds, changedGroupIds);
|
||
throw;
|
||
}
|
||
|
||
return documents.Count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量取消收藏:先删除数据库记录,再删除索引文档;
|
||
/// 索引删除失败时回补数据库记录,避免“库里有收藏但搜索不到”。
|
||
/// </summary>
|
||
public async Task<int> RemoveFavoritesAsync(RemoveUserFavoritesRequest request)
|
||
{
|
||
if (request == null)
|
||
{
|
||
throw new AppException("请求参数不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var userName = request.UserName?.Trim() ?? string.Empty;
|
||
if (string.IsNullOrWhiteSpace(userName))
|
||
{
|
||
throw new AppException("用户名不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var goodsIds = request.GoodsIds?.Where(x => x > 0).Distinct().ToList() ?? new List<long>();
|
||
if (goodsIds.Count == 0)
|
||
{
|
||
throw new AppException("请提供要取消收藏的商品编号", ResultCode.ValidationError);
|
||
}
|
||
|
||
var favorites = await _favoriteRepository.Select
|
||
.Where(x => x.UserName == userName && goodsIds.Contains(x.GoodsId))
|
||
.ToListAsync();
|
||
|
||
if (favorites.Count == 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
await _favoriteRepository.Orm.Delete<UserGoodsFavoriteEntity>()
|
||
.Where(x => x.UserName == userName && goodsIds.Contains(x.GoodsId))
|
||
.ExecuteAffrowsAsync();
|
||
|
||
try
|
||
{
|
||
await _indexService.DeleteDocumentsAsync(
|
||
favorites.Select(x => UserFavoritesSearchDocument.BuildId(x.UserName, x.GoodsId)));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 索引删除失败:回补数据库记录(自增主键会重新生成,检索以“用户名+商品编号”为准,不受影响)
|
||
_logger.LogError(ex, "删除收藏索引失败,回补数据库收藏记录。UserName={UserName} Count={Count}", userName, favorites.Count);
|
||
await _favoriteRepository.InsertAsync(favorites);
|
||
throw;
|
||
}
|
||
|
||
return favorites.Count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建收藏分组:创建成功返回 true,同一用户同名分组已存在返回 false。
|
||
/// </summary>
|
||
public async Task<bool> CreateGroupAsync(CreateUserFavoriteGroupRequest request)
|
||
{
|
||
if (request == null)
|
||
{
|
||
throw new AppException("请求参数不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var userName = request.UserName?.Trim() ?? string.Empty;
|
||
if (string.IsNullOrWhiteSpace(userName))
|
||
{
|
||
throw new AppException("用户名不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var groupName = request.GroupName?.Trim() ?? string.Empty;
|
||
if (string.IsNullOrWhiteSpace(groupName))
|
||
{
|
||
throw new AppException("分组名不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
if (groupName.Length > 255)
|
||
{
|
||
throw new AppException("分组名长度不能超过255个字符", ResultCode.ValidationError);
|
||
}
|
||
|
||
var existing = await _groupRepository.Select
|
||
.Where(x => x.UserName == userName && x.GroupName == groupName)
|
||
.FirstAsync();
|
||
|
||
if (existing != null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
await _groupRepository.InsertAsync(new UserGoodsFavoriteGroupEntity
|
||
{
|
||
UserName = userName,
|
||
GroupName = groupName,
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询用户的收藏分组及分组下收藏数量,默认分组(分组ID=0)存在收藏时一并返回。
|
||
/// </summary>
|
||
public async Task<List<UserFavoriteGroupDto>> GetGroupsAsync(string userName)
|
||
{
|
||
userName = userName?.Trim() ?? string.Empty;
|
||
if (string.IsNullOrWhiteSpace(userName))
|
||
{
|
||
throw new AppException("用户名不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var groups = await _groupRepository.Select
|
||
.Where(x => x.UserName == userName)
|
||
.OrderBy(x => x.Id)
|
||
.ToListAsync();
|
||
|
||
var countList = await _favoriteRepository.Select
|
||
.Where(x => x.UserName == userName)
|
||
.GroupBy(x => x.GroupId)
|
||
.ToListAsync(x => new { GroupId = x.Key, Count = x.Count() });
|
||
|
||
var countMap = countList.ToDictionary(x => x.GroupId, x => (long)x.Count);
|
||
|
||
var result = new List<UserFavoriteGroupDto>();
|
||
|
||
if (countMap.TryGetValue(0, out var defaultCount) && defaultCount > 0)
|
||
{
|
||
result.Add(new UserFavoriteGroupDto
|
||
{
|
||
Id = 0,
|
||
UserName = userName,
|
||
GroupName = DefaultGroupName,
|
||
FavoriteCount = defaultCount,
|
||
});
|
||
}
|
||
|
||
result.AddRange(groups.Select(x => new UserFavoriteGroupDto
|
||
{
|
||
Id = x.Id,
|
||
UserName = x.UserName,
|
||
GroupName = x.GroupName,
|
||
FavoriteCount = countMap.TryGetValue((int)x.Id, out var count) ? count : 0,
|
||
}));
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按 GoodsIds 顺序返回每个商品是否已被该用户收藏(商品编号无效的返回 false)。
|
||
/// 以数据库为准,不依赖索引,索引重建期间结果依然准确。
|
||
/// </summary>
|
||
public async Task<List<bool>> GetFavoriteStatusAsync(UserFavoriteStatusRequest request)
|
||
{
|
||
if (request == null)
|
||
{
|
||
throw new AppException("请求参数不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var userName = request.UserName?.Trim() ?? string.Empty;
|
||
if (string.IsNullOrWhiteSpace(userName))
|
||
{
|
||
throw new AppException("用户名不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var goodsIds = request.GoodsIds ?? new List<long>();
|
||
if (goodsIds.Count == 0)
|
||
{
|
||
return new List<bool>();
|
||
}
|
||
|
||
var validIds = goodsIds.Where(x => x > 0).Distinct().ToList();
|
||
var favoritedIds = new HashSet<long>();
|
||
|
||
if (validIds.Count > 0)
|
||
{
|
||
var favorited = await _favoriteRepository.Select
|
||
.Where(x => x.UserName == userName && validIds.Contains(x.GoodsId))
|
||
.ToListAsync(x => x.GoodsId);
|
||
|
||
favoritedIds = favorited.ToHashSet();
|
||
}
|
||
|
||
// 结果与入参顺序一一对应,前台可直接按下标渲染
|
||
return goodsIds.Select(x => x > 0 && favoritedIds.Contains(x)).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从收藏索引检索当前用户的收藏商品。
|
||
/// </summary>
|
||
public async Task<(List<UserFavoriteItem> List, long Total)> SearchFavoritesAsync(UserFavoritesSearchRequest request)
|
||
{
|
||
if (request == null)
|
||
{
|
||
throw new AppException("请求参数不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
var userName = request.UserName?.Trim() ?? string.Empty;
|
||
if (string.IsNullOrWhiteSpace(userName))
|
||
{
|
||
throw new AppException("用户名不能为空", ResultCode.ValidationError);
|
||
}
|
||
|
||
request.UserName = userName;
|
||
|
||
var searchResult = await _indexService.SearchAsync(request, ParseSortBy(request.SortBy));
|
||
var list = searchResult.Documents.Select(ToItem).ToList();
|
||
return (list, searchResult.Total);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除所有用户收藏索引并重新创建,然后从 user_goods_favorites 关联 goods 全量回填。
|
||
/// </summary>
|
||
public async Task<long> RebuildSearchIndexAsync()
|
||
{
|
||
// 1. 删除并重建索引(清空所有用户收藏索引)
|
||
await _indexService.ClearIndexAsync();
|
||
|
||
// 2. 从数据库读取全部收藏记录
|
||
var favorites = await _favoriteRepository.Select
|
||
.OrderBy(x => x.Id)
|
||
.ToListAsync();
|
||
|
||
if (favorites.Count == 0)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
// 3. 关联 goods 补全商品数据(标题/价格/销量/类目/平台/缩略图)
|
||
var goodsIds = favorites.Select(x => x.GoodsId).Distinct().ToList();
|
||
var goodsList = await _goodsRepository.Select
|
||
.Where(x => goodsIds.Contains(x.GoodsId))
|
||
.ToListAsync();
|
||
|
||
var goodsMap = new Dictionary<long, GoodsEntity>();
|
||
foreach (var goods in goodsList)
|
||
{
|
||
goodsMap[goods.GoodsId] = goods;
|
||
}
|
||
|
||
// 4. 关联分组表补全分组名
|
||
var groupIds = favorites.Where(x => x.GroupId > 0).Select(x => (long)x.GroupId).Distinct().ToList();
|
||
var groupMap = new Dictionary<int, string>();
|
||
|
||
if (groupIds.Count > 0)
|
||
{
|
||
var groups = await _groupRepository.Select
|
||
.Where(x => groupIds.Contains(x.Id))
|
||
.ToListAsync();
|
||
|
||
foreach (var group in groups)
|
||
{
|
||
groupMap[(int)group.Id] = group.GroupName;
|
||
}
|
||
}
|
||
|
||
var documents = new List<UserFavoritesSearchDocument>();
|
||
var missingGoodsCount = 0;
|
||
|
||
foreach (var favorite in favorites)
|
||
{
|
||
if (goodsMap.TryGetValue(favorite.GoodsId, out var goods) == false)
|
||
{
|
||
// 商品已被物理删除,跳过该收藏(不写入索引)
|
||
missingGoodsCount++;
|
||
continue;
|
||
}
|
||
|
||
var groupName = favorite.GroupId <= 0
|
||
? DefaultGroupName
|
||
: (groupMap.TryGetValue(favorite.GroupId, out var name) ? name : string.Empty);
|
||
|
||
documents.Add(BuildDocument(favorite, goods, groupName));
|
||
}
|
||
|
||
// 5. 分批写入索引
|
||
for (var offset = 0; offset < documents.Count; offset += RebuildBatchSize)
|
||
{
|
||
await _indexService.IndexDocumentsAsync(documents.Skip(offset).Take(RebuildBatchSize));
|
||
}
|
||
|
||
_logger.LogInformation(
|
||
"用户收藏索引重建完成:收藏 {FavoriteCount} 条,写入索引 {IndexedCount} 条,商品缺失跳过 {MissingCount} 条。",
|
||
favorites.Count, documents.Count, missingGoodsCount);
|
||
|
||
return documents.Count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 收藏落库成功但索引写入失败时的补偿:撤销本次新增的收藏记录、还原被修改的收藏分组。
|
||
/// </summary>
|
||
private async Task CompensateWriteFailureAsync(List<long> insertedIds, List<(long Id, int PreviousGroupId)> changedGroupIds)
|
||
{
|
||
try
|
||
{
|
||
if (insertedIds.Count > 0)
|
||
{
|
||
await _favoriteRepository.Orm.Delete<UserGoodsFavoriteEntity>()
|
||
.Where(x => insertedIds.Contains(x.Id))
|
||
.ExecuteAffrowsAsync();
|
||
}
|
||
|
||
foreach (var (id, previousGroupId) in changedGroupIds)
|
||
{
|
||
await _favoriteRepository.Orm.Update<UserGoodsFavoriteEntity>()
|
||
.Set(x => x.GroupId, previousGroupId)
|
||
.Where(x => x.Id == id)
|
||
.ExecuteAffrowsAsync();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex,
|
||
"回滚用户收藏记录失败。Inserted={InsertedCount} GroupChanged={GroupChangedCount}",
|
||
insertedIds.Count, changedGroupIds.Count);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取分组名:分组ID <= 0 返回默认分组名,分组不存在返回空串。
|
||
/// </summary>
|
||
private async Task<string> ResolveGroupNameAsync(string userName, int groupId)
|
||
{
|
||
if (groupId <= 0)
|
||
{
|
||
return DefaultGroupName;
|
||
}
|
||
|
||
var group = await _groupRepository.Select
|
||
.Where(x => x.Id == groupId && x.UserName == userName)
|
||
.FirstAsync();
|
||
|
||
return group?.GroupName ?? string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 组装收藏索引文档:收藏记录 + 商品快照 + 分组名。
|
||
/// </summary>
|
||
private static UserFavoritesSearchDocument BuildDocument(UserGoodsFavoriteEntity favorite, GoodsEntity goods, string groupName)
|
||
{
|
||
return new UserFavoritesSearchDocument
|
||
{
|
||
Id = UserFavoritesSearchDocument.BuildId(favorite.UserName, favorite.GoodsId),
|
||
FavId = favorite.Id,
|
||
UserName = favorite.UserName,
|
||
GroupId = favorite.GroupId,
|
||
GroupName = groupName,
|
||
GoodsId = goods.GoodsId,
|
||
Platform = goods.Platform,
|
||
Title = goods.Title ?? string.Empty,
|
||
ThumbUrl = goods.ThumbUrl,
|
||
CatId1 = goods.CatId1,
|
||
CatId2 = goods.CatId2,
|
||
CatId3 = goods.CatId3,
|
||
CatId4 = goods.CatId4,
|
||
MinPrice = goods.MinPrice,
|
||
SoldQuantity = goods.SoldQuantity,
|
||
CreatedAt = favorite.CreatedAt,
|
||
};
|
||
}
|
||
|
||
private static UserFavoriteItem ToItem(UserFavoritesSearchDocument document)
|
||
{
|
||
return new UserFavoriteItem
|
||
{
|
||
Id = document.FavId,
|
||
UserName = document.UserName,
|
||
GroupId = document.GroupId,
|
||
GroupName = document.GroupName,
|
||
GoodsId = document.GoodsId,
|
||
Platform = document.Platform,
|
||
Title = document.Title,
|
||
ThumbUrl = document.ThumbUrl,
|
||
CatId1 = document.CatId1,
|
||
CatId2 = document.CatId2,
|
||
CatId3 = document.CatId3,
|
||
CatId4 = document.CatId4,
|
||
MinPrice = document.MinPrice,
|
||
SoldQuantity = document.SoldQuantity,
|
||
CreatedAt = document.CreatedAt,
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析排序方式,非法值一律按“收藏时间倒序”处理。
|
||
/// </summary>
|
||
private static UserFavoritesSortBy ParseSortBy(string? sortBy)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(sortBy))
|
||
{
|
||
return UserFavoritesSortBy.CreatedTimeDesc;
|
||
}
|
||
|
||
return Enum.TryParse<UserFavoritesSortBy>(sortBy, ignoreCase: true, out var value)
|
||
? value
|
||
: UserFavoritesSortBy.CreatedTimeDesc;
|
||
}
|
||
}
|
||
} |