feat: 商户冻结/解冻功能及字典缓存重构

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
MSuMshk
2026-02-04 10:46:32 +08:00
parent 754dd788ea
commit f69904e195
54 changed files with 753 additions and 1385 deletions

View File

@@ -1,7 +1,5 @@
using TakeoutSaaS.Application.Dictionary.Abstractions;
using TakeoutSaaS.Application.Dictionary.Contracts;
using TakeoutSaaS.Application.Dictionary.Models;
using TakeoutSaaS.Domain.Dictionary.Entities;
using TakeoutSaaS.Domain.Dictionary.Enums;
using TakeoutSaaS.Domain.Dictionary.Repositories;
using TakeoutSaaS.Domain.Dictionary.ValueObjects;
@@ -16,11 +14,8 @@ namespace TakeoutSaaS.Application.Dictionary.Services;
/// </summary>
public sealed class DictionaryQueryService(
IDictionaryGroupRepository groupRepository,
IDictionaryItemRepository itemRepository,
IDictionaryHybridCache cache)
IDictionaryItemRepository itemRepository)
{
private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(30);
/// <summary>
/// 获取字典分组分页数据。
/// </summary>
@@ -34,87 +29,55 @@ public sealed class DictionaryQueryService(
{
throw new BusinessException(ErrorCodes.ValidationFailed, "Scope=Business 时必须指定 TenantId");
}
// 2. (空行后) 确定作用域与目标租户
// 2. 确定作用域与目标租户
var scope = query.Scope ?? (tenantId == 0 ? DictionaryScope.System : DictionaryScope.Business);
if (scope == DictionaryScope.System)
{
tenantId = 0;
}
// 3. (空行后) 构建缓存键并加载分页数据
// 3. 查询分页数据
var sortDescending = string.Equals(query.SortOrder, "desc", StringComparison.OrdinalIgnoreCase);
var targetTenant = scope == DictionaryScope.System ? 0 : tenantId;
var cacheKey = DictionaryCacheKeys.BuildGroupKey(
var groups = await groupRepository.GetPagedAsync(
targetTenant,
scope,
query.Page,
query.PageSize,
query.Keyword,
query.IsEnabled,
query.Page,
query.PageSize,
query.SortBy,
sortDescending);
var cached = await cache.GetOrCreateAsync<DictionaryGroupPage>(
cacheKey,
CacheTtl,
async token =>
{
var groups = await groupRepository.GetPagedAsync(
targetTenant,
scope,
query.Keyword,
query.IsEnabled,
query.Page,
query.PageSize,
query.SortBy,
sortDescending,
token);
var total = await groupRepository.CountAsync(
targetTenant,
scope,
query.Keyword,
query.IsEnabled,
token);
var items = new List<DictionaryGroupDto>(groups.Count);
foreach (var group in groups)
{
IReadOnlyList<DictionaryItemDto>? groupItems = null;
if (query.IncludeItems)
{
var groupItemEntities = await itemRepository.GetByGroupIdAsync(group.TenantId, group.Id, token);
groupItems = groupItemEntities
.Where(item => item.IsEnabled)
.OrderBy(item => item.SortOrder)
.Select(DictionaryMapper.ToItemDto)
.ToList();
}
items.Add(DictionaryMapper.ToGroupDto(group, groupItems));
}
return new DictionaryGroupPage
{
Items = items,
Page = query.Page,
PageSize = query.PageSize,
TotalCount = total
};
},
sortDescending,
cancellationToken);
var page = cached ?? new DictionaryGroupPage
{
Items = Array.Empty<DictionaryGroupDto>(),
Page = query.Page,
PageSize = query.PageSize,
TotalCount = 0
};
var total = await groupRepository.CountAsync(
targetTenant,
scope,
query.Keyword,
query.IsEnabled,
cancellationToken);
return new PagedResult<DictionaryGroupDto>(page.Items, page.Page, page.PageSize, page.TotalCount);
// 4. 转换为 DTO
var items = new List<DictionaryGroupDto>(groups.Count);
foreach (var group in groups)
{
IReadOnlyList<DictionaryItemDto>? groupItems = null;
if (query.IncludeItems)
{
var groupItemEntities = await itemRepository.GetByGroupIdAsync(group.TenantId, group.Id, cancellationToken);
groupItems = groupItemEntities
.Where(item => item.IsEnabled)
.OrderBy(item => item.SortOrder)
.Select(DictionaryMapper.ToItemDto)
.ToList();
}
items.Add(DictionaryMapper.ToGroupDto(group, groupItems));
}
return new PagedResult<DictionaryGroupDto>(items, query.Page, query.PageSize, total);
}
/// <summary>
@@ -136,28 +99,18 @@ public sealed class DictionaryQueryService(
/// </summary>
public async Task<IReadOnlyList<DictionaryItemDto>> GetItemsByGroupIdAsync(long groupId, CancellationToken cancellationToken = default)
{
var cacheKey = DictionaryCacheKeys.BuildItemKey(groupId);
var cached = await cache.GetOrCreateAsync<IReadOnlyList<DictionaryItemDto>>(
cacheKey,
CacheTtl,
async token =>
{
var group = await groupRepository.GetByIdAsync(groupId, token);
if (group == null)
{
throw new BusinessException(ErrorCodes.NotFound, "字典分组不存在");
}
var group = await groupRepository.GetByIdAsync(groupId, cancellationToken);
if (group == null)
{
throw new BusinessException(ErrorCodes.NotFound, "字典分组不存在");
}
var items = await itemRepository.GetByGroupIdAsync(group.TenantId, groupId, token);
return items
.Where(item => item.IsEnabled)
.OrderBy(item => item.SortOrder)
.Select(DictionaryMapper.ToItemDto)
.ToList();
},
cancellationToken);
return cached ?? Array.Empty<DictionaryItemDto>();
var items = await itemRepository.GetByGroupIdAsync(group.TenantId, groupId, cancellationToken);
return items
.Where(item => item.IsEnabled)
.OrderBy(item => item.SortOrder)
.Select(DictionaryMapper.ToItemDto)
.ToList();
}
/// <summary>
@@ -171,31 +124,20 @@ public sealed class DictionaryQueryService(
}
// 1. 管理端默认读取系统字典TenantId=0
var tenantId = 0;
var normalized = new DictionaryCode(code);
var cacheKey = DictionaryCacheKeys.BuildDictionaryKey(tenantId, normalized);
var cached = await cache.GetOrCreateAsync<IReadOnlyList<DictionaryItemDto>>(
cacheKey,
CacheTtl,
async token =>
{
var systemGroup = await groupRepository.GetByCodeAsync(0, normalized, token);
if (systemGroup == null || !systemGroup.IsEnabled)
{
return Array.Empty<DictionaryItemDto>();
}
var systemGroup = await groupRepository.GetByCodeAsync(0, normalized, cancellationToken);
if (systemGroup == null || !systemGroup.IsEnabled)
{
return Array.Empty<DictionaryItemDto>();
}
var systemItems = await itemRepository.GetByGroupIdAsync(0, systemGroup.Id, token);
return systemItems
.Where(item => item.IsEnabled)
.OrderBy(item => item.SortOrder)
.Select(DictionaryMapper.ToItemDto)
.ToList();
},
cancellationToken);
return cached ?? Array.Empty<DictionaryItemDto>();
var systemItems = await itemRepository.GetByGroupIdAsync(0, systemGroup.Id, cancellationToken);
return systemItems
.Where(item => item.IsEnabled)
.OrderBy(item => item.SortOrder)
.Select(DictionaryMapper.ToItemDto)
.ToList();
}
/// <summary>
@@ -230,12 +172,4 @@ public sealed class DictionaryQueryService(
return result;
}
private sealed class DictionaryGroupPage
{
public IReadOnlyList<DictionaryGroupDto> Items { get; init; } = Array.Empty<DictionaryGroupDto>();
public int Page { get; init; }
public int PageSize { get; init; }
public int TotalCount { get; init; }
}
}