From dc9f6136d696a6cc0308dad14ce664a9de351c50 Mon Sep 17 00:00:00 2001
From: MSuMshk <2039814060@qq.com>
Date: Tue, 30 Dec 2025 19:38:13 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=AD=97=E5=85=B8?=
=?UTF-8?q?=E7=AE=A1=E7=90=86=E5=90=8E=E7=AB=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 39 +-
.../Requests/DictionaryExportRequest.cs | 12 +
.../Requests/DictionaryImportFormRequest.cs | 26 +
.../Controllers/CacheMetricsController.cs | 65 ++
.../Controllers/DictionaryGroupsController.cs | 185 ++++++
.../Controllers/DictionaryItemsController.cs | 77 +++
.../DictionaryOverridesController.cs | 167 +++++
.../appsettings.Development.json | 9 +
.../appsettings.Production.json | 9 +
.../appsettings.Seed.Development.json | 4 +
.../Controllers/DictionaryController.cs | 51 ++
src/Api/TakeoutSaaS.UserApi/Program.cs | 7 +
.../TakeoutSaaS.UserApi.csproj | Bin 3434 -> 3904 bytes
.../Abstractions/ICsvDictionaryParser.cs | 17 +
.../Abstractions/IDictionaryHybridCache.cs | 26 +
.../Abstractions/IJsonDictionaryParser.cs | 17 +
.../Contracts/CreateDictionaryGroupRequest.cs | 5 +
.../Contracts/CreateDictionaryItemRequest.cs | 6 +-
.../Contracts/DictionaryGroupQuery.cs | 30 +
.../Contracts/DictionaryImportRequest.cs | 34 +
.../DictionaryOverrideHiddenItemsRequest.cs | 15 +
.../DictionaryOverrideSortOrderRequest.cs | 15 +
.../Contracts/UpdateDictionaryGroupRequest.cs | 10 +
.../Contracts/UpdateDictionaryItemRequest.cs | 15 +-
.../DictionaryServiceCollectionExtensions.cs | 5 +
.../Dictionary/Models/DictionaryGroupDto.cs | 26 +
.../Models/DictionaryImportResultDto.cs | 53 ++
.../Dictionary/Models/DictionaryImportRow.cs | 47 ++
.../Dictionary/Models/DictionaryItemDto.cs | 13 +-
.../Dictionary/Models/OverrideConfigDto.cs | 36 ++
.../Services/DictionaryAppService.cs | 68 +-
.../Services/DictionaryCacheKeys.cs | 44 ++
.../Services/DictionaryCommandService.cs | 338 ++++++++++
.../Services/DictionaryImportExportService.cs | 480 ++++++++++++++
.../Dictionary/Services/DictionaryMapper.cs | 46 ++
.../Services/DictionaryMergeService.cs | 84 +++
.../Services/DictionaryOverrideService.cs | 322 ++++++++++
.../Services/DictionaryQueryService.cs | 246 +++++++
.../Services/DictionaryValueConverter.cs | 46 ++
.../CreateDictionaryGroupValidator.cs | 25 +
.../Validators/I18nValueValidator.cs | 32 +
.../UpdateDictionaryItemValidator.cs | 23 +
.../Middleware/ExceptionHandlingMiddleware.cs | 45 +-
.../Swagger/SwaggerExtensions.cs | 4 +-
.../TakeoutSaaS.Shared.Web.csproj | 2 +
.../Entities/CacheInvalidationLog.cs | 40 ++
.../Dictionary/Entities/DictionaryGroup.cs | 13 +-
.../Entities/DictionaryImportLog.cs | 65 ++
.../Dictionary/Entities/DictionaryItem.cs | 5 +
.../Entities/TenantDictionaryOverride.cs | 64 ++
.../Enums/CacheInvalidationOperation.cs | 11 +
.../Enums/ConflictResolutionMode.cs | 22 +
.../ICacheInvalidationLogRepository.cs | 29 +
.../IDictionaryGroupRepository.cs | 104 +++
.../IDictionaryImportLogRepository.cs | 22 +
.../Repositories/IDictionaryItemRepository.cs | 64 ++
.../ITenantDictionaryOverrideRepository.cs | 47 ++
.../Dictionary/ValueObjects/DictionaryCode.cs | 91 +++
.../Dictionary/ValueObjects/I18nValue.cs | 151 +++++
.../Caching/CacheMetricsCollector.cs | 212 ++++++
.../Dictionary/Caching/CacheWarmupService.cs | 57 ++
.../Dictionary/Caching/HybridCacheService.cs | 229 +++++++
.../Dictionary/Caching/MemoryCacheService.cs | 82 +++
.../Dictionary/Caching/RedisCacheService.cs | 79 +++
.../DictionaryServiceCollectionExtensions.cs | 57 ++
.../ImportExport/CsvDictionaryParser.cs | 91 +++
.../ImportExport/JsonDictionaryParser.cs | 131 ++++
.../Options/DictionaryCacheWarmupOptions.cs | 12 +
.../Persistence/DictionaryDbContext.cs | 131 +++-
.../Persistence/Seeds/InitialDictionaries.sql | 85 +++
.../CacheInvalidationLogRepository.cs | 59 ++
.../Repositories/DictionaryGroupRepository.cs | 170 +++++
.../DictionaryImportLogRepository.cs | 26 +
.../Repositories/DictionaryItemRepository.cs | 148 +++++
.../Repositories/EfDictionaryRepository.cs | 9 +-
.../TenantDictionaryOverrideRepository.cs | 62 ++
...0044727_UpdateDictionarySchema.Designer.cs | 471 ++++++++++++++
.../20251230044727_UpdateDictionarySchema.cs | 256 ++++++++
...20251230162000_AddCacheInvalidationLogs.cs | 54 ++
.../DictionaryDbContextModelSnapshot.cs | 276 +++++++-
.../App/Dictionary/DictionaryApiTests.cs | 605 ++++++++++++++++++
.../Fixtures/DictionarySqliteTestDatabase.cs | 49 ++
.../Fixtures/TestDictionaryHybridCache.cs | 46 ++
83 files changed, 6901 insertions(+), 50 deletions(-)
create mode 100644 src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryExportRequest.cs
create mode 100644 src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryImportFormRequest.cs
create mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/CacheMetricsController.cs
create mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryGroupsController.cs
create mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryItemsController.cs
create mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryOverridesController.cs
create mode 100644 src/Api/TakeoutSaaS.UserApi/Controllers/DictionaryController.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Abstractions/ICsvDictionaryParser.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Abstractions/IDictionaryHybridCache.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Abstractions/IJsonDictionaryParser.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Contracts/DictionaryImportRequest.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Contracts/DictionaryOverrideHiddenItemsRequest.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Contracts/DictionaryOverrideSortOrderRequest.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryImportResultDto.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryImportRow.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Models/OverrideConfigDto.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryCacheKeys.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryCommandService.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryImportExportService.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryMapper.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryMergeService.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryOverrideService.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryQueryService.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryValueConverter.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Validators/CreateDictionaryGroupValidator.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Validators/I18nValueValidator.cs
create mode 100644 src/Application/TakeoutSaaS.Application/Dictionary/Validators/UpdateDictionaryItemValidator.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Entities/CacheInvalidationLog.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Entities/DictionaryImportLog.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Entities/TenantDictionaryOverride.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Enums/CacheInvalidationOperation.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Enums/ConflictResolutionMode.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Repositories/ICacheInvalidationLogRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Repositories/IDictionaryGroupRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Repositories/IDictionaryImportLogRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Repositories/IDictionaryItemRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/Repositories/ITenantDictionaryOverrideRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/ValueObjects/DictionaryCode.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Dictionary/ValueObjects/I18nValue.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Caching/CacheMetricsCollector.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Caching/CacheWarmupService.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Caching/HybridCacheService.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Caching/MemoryCacheService.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Caching/RedisCacheService.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/ImportExport/CsvDictionaryParser.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/ImportExport/JsonDictionaryParser.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Options/DictionaryCacheWarmupOptions.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Persistence/Seeds/InitialDictionaries.sql
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/CacheInvalidationLogRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/DictionaryGroupRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/DictionaryImportLogRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/DictionaryItemRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Repositories/TenantDictionaryOverrideRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/DictionaryDb/20251230044727_UpdateDictionarySchema.Designer.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/DictionaryDb/20251230044727_UpdateDictionarySchema.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/DictionaryDb/20251230162000_AddCacheInvalidationLogs.cs
create mode 100644 tests/TakeoutSaaS.Integration.Tests/App/Dictionary/DictionaryApiTests.cs
create mode 100644 tests/TakeoutSaaS.Integration.Tests/Fixtures/DictionarySqliteTestDatabase.cs
create mode 100644 tests/TakeoutSaaS.Integration.Tests/Fixtures/TestDictionaryHybridCache.cs
diff --git a/README.md b/README.md
index 4222930..caaabce 100644
--- a/README.md
+++ b/README.md
@@ -6,14 +6,15 @@
### 核心特性
-- 🏢 **多租户架构**:支持多租户数据隔离,SaaS模式运营
-- 🍔 **商家管理**:完善的商家入驻、门店管理、菜品管理功能
-- 📦 **订单管理**:订单全生命周期管理,实时状态跟踪
-🚚 配送管理:配送任务、路线规划、第三方配送对接
-- 💰 **支付集成**:支持微信、支付宝等多种支付方式
-- 🎁 **营销功能**:优惠券、满减活动、会员积分
-- 📊 **数据分析**:实时数据统计、经营报表、趋势分析
-- 🔒 **安全可靠**:JWT认证、权限控制、数据加密
+- **多租户架构**:支持多租户数据隔离,SaaS模式运营
+- **商家管理**:完善的商家入驻、门店管理、菜品管理功能
+- **订单管理**:订单全生命周期管理,实时状态跟踪
+- **配送管理**:配送任务、路线规划、第三方配送对接
+- **支付集成**:支持微信、支付宝等多种支付方式
+- **营销功能**:优惠券、满减活动、会员积分
+- **字典管理**:系统字典、租户覆盖、批量导入导出、缓存监控
+- **数据分析**:实时数据统计、经营报表、趋势分析
+- **安全可靠**:JWT认证、权限控制、数据加密
## 技术栈
@@ -80,8 +81,26 @@ dotnet run
```
访问 API 文档:
-- 管理后台 AdminApi Swagger:http://localhost:5001/swagger
-- 小程序/用户端 MiniApi Swagger:http://localhost:5002/swagger
+- 管理后台 AdminApi Swagger:http://localhost:5001/api/docs
+- 小程序/用户端 MiniApi Swagger:http://localhost:5002/api/docs
+
+## 字典管理
+
+> 最后更新日期:2025-12-30
+
+### 功能概述
+
+- 系统/业务字典分组与字典项管理
+- 租户覆盖:隐藏系统项、自定义字典项、拖拽排序
+- CSV/JSON 批量导入导出
+- 两级缓存(Memory + Redis)与缓存监控指标
+
+### 配置要点
+
+- `ConnectionStrings:Redis`:Redis 连接字符串
+- `Database:DataSources:DictionaryDatabase`:字典库读写连接
+- `Dictionary:Cache:SlidingExpiration`:字典缓存滑动过期
+- `CacheWarmup:DictionaryCodes`:缓存预热字典编码列表
## 公告管理
diff --git a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryExportRequest.cs b/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryExportRequest.cs
new file mode 100644
index 0000000..e8f59dc
--- /dev/null
+++ b/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryExportRequest.cs
@@ -0,0 +1,12 @@
+namespace TakeoutSaaS.AdminApi.Contracts.Requests;
+
+///
+/// 字典导出请求。
+///
+public sealed record DictionaryExportRequest
+{
+ ///
+ /// 导出格式(csv/json)。
+ ///
+ public string? Format { get; init; }
+}
diff --git a/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryImportFormRequest.cs b/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryImportFormRequest.cs
new file mode 100644
index 0000000..7d24ccc
--- /dev/null
+++ b/src/Api/TakeoutSaaS.AdminApi/Contracts/Requests/DictionaryImportFormRequest.cs
@@ -0,0 +1,26 @@
+using System.ComponentModel.DataAnnotations;
+using Microsoft.AspNetCore.Http;
+
+namespace TakeoutSaaS.AdminApi.Contracts.Requests;
+
+///
+/// 字典导入表单请求。
+///
+public sealed record DictionaryImportFormRequest
+{
+ ///
+ /// 导入文件。
+ ///
+ [Required]
+ public required IFormFile File { get; init; }
+
+ ///
+ /// 冲突解决模式(Skip/Overwrite/Append)。
+ ///
+ public string? ConflictMode { get; init; }
+
+ ///
+ /// 文件格式(csv/json)。
+ ///
+ public string? Format { get; init; }
+}
diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/CacheMetricsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/CacheMetricsController.cs
new file mode 100644
index 0000000..31e66d1
--- /dev/null
+++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/CacheMetricsController.cs
@@ -0,0 +1,65 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using TakeoutSaaS.Domain.Dictionary.Entities;
+using TakeoutSaaS.Domain.Dictionary.Repositories;
+using TakeoutSaaS.Infrastructure.Dictionary.Caching;
+using TakeoutSaaS.Shared.Abstractions.Results;
+using TakeoutSaaS.Shared.Web.Api;
+
+namespace TakeoutSaaS.AdminApi.Controllers;
+
+///
+/// 缓存监控指标接口。
+///
+[ApiVersion("1.0")]
+[Authorize(Roles = "PlatformAdmin")]
+[Route("api/admin/v{version:apiVersion}/dictionary/metrics")]
+public sealed class CacheMetricsController(
+ CacheMetricsCollector metricsCollector,
+ ICacheInvalidationLogRepository invalidationLogRepository)
+ : BaseApiController
+{
+ ///
+ /// 获取缓存统计信息。
+ ///
+ [HttpGet("cache-stats")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public ApiResponse GetCacheStats([FromQuery] string? timeRange = "1h")
+ {
+ var window = timeRange?.ToLowerInvariant() switch
+ {
+ "24h" => TimeSpan.FromHours(24),
+ "7d" => TimeSpan.FromDays(7),
+ _ => TimeSpan.FromHours(1)
+ };
+
+ var snapshot = metricsCollector.GetSnapshot(window);
+ return ApiResponse.Ok(snapshot);
+ }
+
+ ///
+ /// 获取缓存失效事件列表。
+ ///
+ [HttpGet("invalidation-events")]
+ [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)]
+ public async Task>> GetInvalidationEvents(
+ [FromQuery] int page = 1,
+ [FromQuery] int pageSize = 20,
+ [FromQuery] DateTime? startDate = null,
+ [FromQuery] DateTime? endDate = null,
+ CancellationToken cancellationToken = default)
+ {
+ var safePage = page <= 0 ? 1 : page;
+ var safePageSize = pageSize <= 0 ? 20 : pageSize;
+
+ var (items, total) = await invalidationLogRepository.GetPagedAsync(
+ safePage,
+ safePageSize,
+ startDate,
+ endDate,
+ cancellationToken);
+
+ var result = new PagedResult(items, safePage, safePageSize, total);
+ return ApiResponse>.Ok(result);
+ }
+}
diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryGroupsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryGroupsController.cs
new file mode 100644
index 0000000..d9f5b3a
--- /dev/null
+++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryGroupsController.cs
@@ -0,0 +1,185 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Net.Http.Headers;
+using System.Net.Mime;
+using TakeoutSaaS.AdminApi.Contracts.Requests;
+using TakeoutSaaS.Application.Dictionary.Contracts;
+using TakeoutSaaS.Application.Dictionary.Models;
+using TakeoutSaaS.Application.Dictionary.Services;
+using TakeoutSaaS.Domain.Dictionary.Enums;
+using TakeoutSaaS.Module.Authorization.Attributes;
+using TakeoutSaaS.Shared.Abstractions.Constants;
+using TakeoutSaaS.Shared.Abstractions.Results;
+using TakeoutSaaS.Shared.Web.Api;
+
+namespace TakeoutSaaS.AdminApi.Controllers;
+
+///
+/// 字典分组管理。
+///
+[ApiVersion("1.0")]
+[Authorize]
+[Route("api/admin/v{version:apiVersion}/dictionary/groups")]
+public sealed class DictionaryGroupsController(
+ DictionaryCommandService commandService,
+ DictionaryQueryService queryService,
+ DictionaryImportExportService importExportService)
+ : BaseApiController
+{
+ ///
+ /// 查询字典分组。
+ ///
+ [HttpGet]
+ [PermissionAuthorize("dictionary:group:read")]
+ [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)]
+ public async Task>> List([FromQuery] DictionaryGroupQuery query, CancellationToken cancellationToken)
+ {
+ var result = await queryService.GetGroupsAsync(query, cancellationToken);
+ return ApiResponse>.Ok(result);
+ }
+
+ ///
+ /// 获取字典分组详情。
+ ///
+ [HttpGet("{groupId:long}")]
+ [PermissionAuthorize("dictionary:group:read")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ApiResponse