From 148475fa436ca6efa50ccc1dcf5118e502c4d05a Mon Sep 17 00:00:00 2001
From: MSuMshk <2039814060@qq.com>
Date: Tue, 2 Dec 2025 09:04:37 +0800
Subject: [PATCH] feat: migrate snowflake ids and refresh migrations
---
AGENTS.md | 150 +
Document/10_设计期DbContext配置指引.md | 42 +
Document/11_SystemTodo.md | 92 +-
Document/12_BusinessTodo.md | 89 +-
Document/13_AppSeed说明.md | 52 +
.../Controllers/AuthController.cs | 2 +-
.../Controllers/DictionaryController.cs | 20 +-
.../Controllers/MerchantsController.cs | 72 +
src/Api/TakeoutSaaS.AdminApi/Program.cs | 4 +
.../TakeoutSaaS.AdminApi.csproj | 4 +
.../appsettings.Development.json | 34 +
.../Controllers/MeController.cs | 2 +-
...pApplicationServiceCollectionExtensions.cs | 23 +
.../Commands/CreateMerchantCommand.cs | 53 +
.../App/Merchants/Dto/MerchantDto.cs | 63 +
.../Handlers/CreateMerchantCommandHandler.cs | 54 +
.../Handlers/GetMerchantByIdQueryHandler.cs | 42 +
.../Handlers/SearchMerchantsQueryHandler.cs | 42 +
.../Merchants/Queries/GetMerchantByIdQuery.cs | 15 +
.../Merchants/Queries/SearchMerchantsQuery.cs | 16 +
.../Abstractions/IDictionaryAppService.cs | 8 +-
.../Abstractions/IDictionaryCache.cs | 6 +-
.../Contracts/CreateDictionaryItemRequest.cs | 5 +-
.../Dictionary/Models/DictionaryGroupDto.cs | 6 +-
.../Dictionary/Models/DictionaryItemDto.cs | 9 +-
.../Services/DictionaryAppService.cs | 38 +-
.../Abstractions/IAdminAuthService.cs | 2 +-
.../Identity/Abstractions/IMiniAuthService.cs | 2 +-
.../Abstractions/IRefreshTokenStore.cs | 2 +-
.../Identity/Contracts/CurrentUserProfile.cs | 6 +-
.../Identity/Models/RefreshTokenDescriptor.cs | 2 +-
.../Identity/Services/AdminAuthService.cs | 2 +-
.../Identity/Services/MiniAuthService.cs | 6 +-
.../Messaging/Events/OrderCreatedEvent.cs | 4 +-
.../Messaging/Events/PaymentSucceededEvent.cs | 4 +-
.../Sms/Services/VerificationCodeService.cs | 4 +-
.../Storage/Services/FileStorageService.cs | 2 +-
.../TakeoutSaaS.Application.csproj | 1 +
.../Entities/AuditableEntityBase.cs | 6 +-
.../Entities/EntityBase.cs | 2 +-
.../Entities/IAuditableEntity.cs | 6 +-
.../Entities/IMultiTenantEntity.cs | 2 +-
.../Entities/MultiTenantEntityBase.cs | 2 +-
.../Ids/IIdGenerator.cs | 13 +
.../Ids/IdGeneratorOptions.cs | 26 +
.../Results/ApiResponse.cs | 61 +-
.../Security/ICurrentUserAccessor.cs | 2 +-
.../Serialization/SnowflakeIdJsonConverter.cs | 52 +
.../Tenancy/ITenantProvider.cs | 2 +-
.../Tenancy/TenantContext.cs | 8 +-
.../Ids/SnowflakeIdGenerator.cs | 111 +
.../Middleware/CorrelationIdMiddleware.cs | 9 +-
.../Security/ClaimsPrincipalExtensions.cs | 10 +-
.../HttpContextCurrentUserAccessor.cs | 8 +-
.../Analytics/Entities/MetricAlertRule.cs | 2 +-
.../Analytics/Entities/MetricSnapshot.cs | 2 +-
.../Coupons/Entities/Coupon.cs | 6 +-
.../CustomerService/Entities/ChatMessage.cs | 4 +-
.../CustomerService/Entities/ChatSession.cs | 6 +-
.../CustomerService/Entities/SupportTicket.cs | 6 +-
.../CustomerService/Entities/TicketComment.cs | 4 +-
.../Deliveries/Entities/DeliveryEvent.cs | 2 +-
.../Deliveries/Entities/DeliveryOrder.cs | 2 +-
.../Repositories/IDeliveryRepository.cs | 42 +
.../Dictionary/Entities/DictionaryItem.cs | 2 +-
.../Repositories/IDictionaryRepository.cs | 8 +-
.../Distribution/Entities/AffiliateOrder.cs | 6 +-
.../Distribution/Entities/AffiliatePartner.cs | 2 +-
.../Distribution/Entities/AffiliatePayout.cs | 2 +-
.../Engagement/Entities/CheckInRecord.cs | 4 +-
.../Engagement/Entities/CommunityComment.cs | 6 +-
.../Engagement/Entities/CommunityPost.cs | 2 +-
.../Engagement/Entities/CommunityReaction.cs | 4 +-
.../GroupBuying/Entities/GroupOrder.cs | 6 +-
.../GroupBuying/Entities/GroupParticipant.cs | 6 +-
.../Identity/Entities/IdentityUser.cs | 2 +-
.../Repositories/IIdentityUserRepository.cs | 2 +-
.../Repositories/IMiniUserRepository.cs | 4 +-
.../Inventory/Entities/InventoryAdjustment.cs | 4 +-
.../Inventory/Entities/InventoryBatch.cs | 4 +-
.../Inventory/Entities/InventoryItem.cs | 4 +-
.../Membership/Entities/MemberGrowthLog.cs | 2 +-
.../Membership/Entities/MemberPointLedger.cs | 4 +-
.../Membership/Entities/MemberProfile.cs | 4 +-
.../Merchants/Entities/MerchantContract.cs | 2 +-
.../Merchants/Entities/MerchantDocument.cs | 2 +-
.../Merchants/Entities/MerchantStaff.cs | 6 +-
.../Repositories/IMerchantRepository.cs | 63 +
.../Navigation/Entities/MapLocation.cs | 2 +-
.../Navigation/Entities/NavigationRequest.cs | 4 +-
.../Ordering/Entities/CartItem.cs | 6 +-
.../Ordering/Entities/CartItemAddon.cs | 4 +-
.../Ordering/Entities/CheckoutSession.cs | 4 +-
.../Ordering/Entities/ShoppingCart.cs | 4 +-
.../Orders/Entities/Order.cs | 4 +-
.../Orders/Entities/OrderItem.cs | 4 +-
.../Orders/Entities/OrderStatusHistory.cs | 4 +-
.../Orders/Entities/RefundRequest.cs | 2 +-
.../Orders/Repositories/IOrderRepository.cs | 69 +
.../Payments/Entities/PaymentRecord.cs | 2 +-
.../Payments/Entities/PaymentRefundRecord.cs | 4 +-
.../Repositories/IPaymentRepository.cs | 42 +
.../Products/Entities/Product.cs | 4 +-
.../Products/Entities/ProductAddonGroup.cs | 2 +-
.../Products/Entities/ProductAddonOption.cs | 2 +-
.../Entities/ProductAttributeGroup.cs | 7 +-
.../Entities/ProductAttributeOption.cs | 2 +-
.../Products/Entities/ProductCategory.cs | 2 +-
.../Products/Entities/ProductMediaAsset.cs | 2 +-
.../Products/Entities/ProductPricingRule.cs | 7 +-
.../Products/Entities/ProductSku.cs | 7 +-
.../Repositories/IProductRepository.cs | 103 +
.../Queues/Entities/QueueTicket.cs | 2 +-
.../Reservations/Entities/Reservation.cs | 2 +-
.../Stores/Entities/Store.cs | 2 +-
.../Stores/Entities/StoreBusinessHour.cs | 2 +-
.../Stores/Entities/StoreDeliveryZone.cs | 7 +-
.../Stores/Entities/StoreEmployeeShift.cs | 4 +-
.../Stores/Entities/StoreHoliday.cs | 2 +-
.../Stores/Entities/StoreTable.cs | 4 +-
.../Stores/Entities/StoreTableArea.cs | 7 +-
.../Stores/Repositories/IStoreRepository.cs | 93 +
.../Tenants/Entities/Tenant.cs | 2 +-
.../Tenants/Entities/TenantSubscription.cs | 4 +-
.../AppServiceCollectionExtensions.cs | 48 +
.../20251201044927_InitialApp.Designer.cs | 949 -
.../Migrations/20251201044927_InitialApp.cs | 497 -
...51201055852_ExpandDomainSchema.Designer.cs | 4330 ---
.../20251201055852_ExpandDomainSchema.cs | 2206 --
.../20251201094254_AddEntityComments.cs | 22401 ----------------
.../App/Options/AppSeedOptions.cs | 29 +
.../App/Options/DictionarySeedGroupOptions.cs | 51 +
.../App/Options/DictionarySeedItemOptions.cs | 39 +
.../App/Options/TenantSeedOptions.cs | 46 +
.../App/Persistence/AppDataSeeder.cs | 301 +
.../App/Persistence/TakeoutAppDbContext.cs | 6 +-
.../App/Repositories/EfDeliveryRepository.cs | 71 +
.../App/Repositories/EfMerchantRepository.cs | 116 +
.../App/Repositories/EfOrderRepository.cs | 133 +
.../App/Repositories/EfPaymentRepository.cs | 71 +
.../App/Repositories/EfProductRepository.cs | 227 +
.../App/Repositories/EfStoreRepository.cs | 174 +
.../DatabaseServiceCollectionExtensions.cs | 13 +
.../Common/Persistence/AppDbContext.cs | 38 +-
.../DesignTimeDbContextFactoryBase.cs | 4 +-
.../Persistence/TenantAwareDbContext.cs | 8 +-
...251201042346_InitialDictionary.Designer.cs | 172 -
.../20251201042346_InitialDictionary.cs | 101 -
.../20251201094456_AddEntityComments.cs | 599 -
.../Persistence/DictionaryDbContext.cs | 6 +-
.../Repositories/EfDictionaryRepository.cs | 10 +-
.../Services/DistributedDictionaryCache.cs | 10 +-
...20251201042324_InitialIdentity.Designer.cs | 152 -
.../20251201042324_InitialIdentity.cs | 94 -
.../20251201094410_AddEntityComments.cs | 581 -
.../Identity/Options/AdminSeedOptions.cs | 4 +-
.../Persistence/EfIdentityUserRepository.cs | 2 +-
.../Persistence/EfMiniUserRepository.cs | 6 +-
.../Persistence/IdentityDataSeeder.cs | 4 +-
.../Identity/Persistence/IdentityDbContext.cs | 6 +-
.../Services/RedisRefreshTokenStore.cs | 2 +-
...51202005208_InitSnowflake_App.Designer.cs} | 1928 +-
.../20251202005208_InitSnowflake_App.cs | 2550 ++
...5247_InitSnowflake_Dictionary.Designer.cs} | 54 +-
...20251202005247_InitSnowflake_Dictionary.cs | 106 +
.../DictionaryDbContextModelSnapshot.cs | 50 +-
...005226_InitSnowflake_Identity.Designer.cs} | 54 +-
.../20251202005226_InitSnowflake_Identity.cs | 99 +
.../IdentityDbContextModelSnapshot.cs | 50 +-
.../TakeoutAppDbContextModelSnapshot.cs | 1924 +-
.../TakeoutSaaS.Infrastructure.csproj | 1 +
.../TenantProvider.cs | 4 +-
.../TenantResolutionMiddleware.cs | 8 +-
.../TenantResolutionOptions.cs | 12 +-
174 files changed, 8020 insertions(+), 34278 deletions(-)
create mode 100644 AGENTS.md
create mode 100644 Document/13_AppSeed说明.md
create mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs
create mode 100644 src/Application/TakeoutSaaS.Application/App/Extensions/AppApplicationServiceCollectionExtensions.cs
create mode 100644 src/Application/TakeoutSaaS.Application/App/Merchants/Commands/CreateMerchantCommand.cs
create mode 100644 src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantDto.cs
create mode 100644 src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/CreateMerchantCommandHandler.cs
create mode 100644 src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantByIdQueryHandler.cs
create mode 100644 src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/SearchMerchantsQueryHandler.cs
create mode 100644 src/Application/TakeoutSaaS.Application/App/Merchants/Queries/GetMerchantByIdQuery.cs
create mode 100644 src/Application/TakeoutSaaS.Application/App/Merchants/Queries/SearchMerchantsQuery.cs
create mode 100644 src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IIdGenerator.cs
create mode 100644 src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IdGeneratorOptions.cs
create mode 100644 src/Core/TakeoutSaaS.Shared.Abstractions/Serialization/SnowflakeIdJsonConverter.cs
create mode 100644 src/Core/TakeoutSaaS.Shared.Kernel/Ids/SnowflakeIdGenerator.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Deliveries/Repositories/IDeliveryRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Payments/Repositories/IPaymentRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs
create mode 100644 src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.Designer.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201055852_ExpandDomainSchema.Designer.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201055852_ExpandDomainSchema.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201094254_AddEntityComments.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/AppSeedOptions.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/DictionarySeedGroupOptions.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/DictionarySeedItemOptions.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Options/TenantSeedOptions.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/AppDataSeeder.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfDeliveryRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfOrderRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfPaymentRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfProductRepository.cs
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfStoreRepository.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.Designer.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201042346_InitialDictionary.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Dictionary/Migrations/20251201094456_AddEntityComments.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.Designer.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201042324_InitialIdentity.cs
delete mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Migrations/20251201094410_AddEntityComments.cs
rename src/Infrastructure/TakeoutSaaS.Infrastructure/{App/Migrations/20251201094254_AddEntityComments.Designer.cs => Migrations/20251202005208_InitSnowflake_App.Designer.cs} (79%)
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/20251202005208_InitSnowflake_App.cs
rename src/Infrastructure/TakeoutSaaS.Infrastructure/{Dictionary/Migrations/20251201094456_AddEntityComments.Designer.cs => Migrations/DictionaryDb/20251202005247_InitSnowflake_Dictionary.Designer.cs} (83%)
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/DictionaryDb/20251202005247_InitSnowflake_Dictionary.cs
rename src/Infrastructure/TakeoutSaaS.Infrastructure/{Dictionary/Migrations => Migrations/DictionaryDb}/DictionaryDbContextModelSnapshot.cs (84%)
rename src/Infrastructure/TakeoutSaaS.Infrastructure/{Identity/Migrations/20251201094410_AddEntityComments.Designer.cs => Migrations/IdentityDb/20251202005226_InitSnowflake_Identity.Designer.cs} (81%)
create mode 100644 src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20251202005226_InitSnowflake_Identity.cs
rename src/Infrastructure/TakeoutSaaS.Infrastructure/{Identity/Migrations => Migrations/IdentityDb}/IdentityDbContextModelSnapshot.cs (82%)
rename src/Infrastructure/TakeoutSaaS.Infrastructure/{App => }/Migrations/TakeoutAppDbContextModelSnapshot.cs (79%)
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..6ff2f4f
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,150 @@
+# Repository expectations
+# 编程规范_FOR_AI(TakeoutSaaS) - 终极完全体
+
+> **核心指令**:你是一个高级 .NET 架构师。本文件是你生成代码的最高宪法。当用户需求与本规范冲突时,请先提示用户,除非用户强制要求覆盖。
+
+## 0. AI 交互核心约束 (元规则)
+1. **语言**:必须使用**中文**回复和编写注释。
+2. **文件完整性**:
+ * **严禁**随意删除现有代码逻辑。
+ * **严禁**修改文件编码(保持 UTF-8 无 BOM)。
+ * PowerShell 读取命令必须带 `-Encoding UTF8`。
+3. **Git 原子性**:每个独立的功能点或 Bug 修复完成后,必须提示用户进行 Git 提交。
+4. **无乱码承诺**:确保所有输出(控制台、日志、API响应)无乱码。
+5. **不确定的处理**:如果你通过上下文找不到某些配置(如数据库连接串格式),**请直接询问用户**,不要瞎编。
+
+## 1. 技术栈详细版本
+| 组件 | 版本/选型 | 用途说明 |
+| :--- | :--- | :--- |
+| **Runtime** | .NET 10 | 核心运行时 |
+| **API** | ASP.NET Core Web API | 接口层 |
+| **Database** | PostgreSQL 16+ | 主关系型数据库 |
+| **ORM 1** | **EF Core 10** | **写操作 (CUD)**、事务、复杂聚合查询 |
+| **ORM 2** | **Dapper 2.1+** | **纯读操作 (R)**、复杂报表、大批量查询 |
+| **Cache** | Redis 7.0+ | 分布式缓存、Session |
+| **MQ** | RabbitMQ 3.12+ | 异步解耦 (MassTransit) |
+| **Libs** | MediatR, Serilog, FluentValidation | CQRS, 日志, 验证 |
+
+## 2. 命名与风格 (严格匹配)
+* **C# 代码**:
+ * 类/接口/方法/属性:`PascalCase` (如 `OrderService`)
+ * **布尔属性**:必须加 `Is` 或 `Has` 前缀 (如 `IsDeleted`, `HasPayment`)
+ * 私有字段:`_camelCase` (如 `_orderRepository`)
+ * 参数/变量:`camelCase` (如 `orderId`)
+* **PostgreSQL 数据库**:
+ * 表名:`snake_case` + **复数** (如 `merchant_orders`)
+ * 列名:`snake_case` (如 `order_no`, `is_active`)
+ * 主键:`id` (类型 `bigint`)
+* **文件规则**:
+ * **一个文件一个类**。文件名必须与类名完全一致。
+
+## 3. 分层架构 (Clean Architecture)
+**你生成的代码必须严格归类到以下目录:**
+* **`src/Api`**: 仅负责路由与 DTO 转换,**禁止**包含业务逻辑。
+* **`src/Application`**: 业务编排层。必须使用 **CQRS** (`IRequestHandler`) 和 **Mediator**。
+* **`src/Domain`**: 核心领域层。包含实体、枚举、领域异常。**禁止**依赖 EF Core 等外部库。
+* **`src/Infrastructure`**: 基础设施层。实现仓储、数据库上下文、第三方服务。
+
+## 4. 注释与文档
+* **强制 XML 注释**:所有 `public` 的类、方法、属性必须有 ``。
+* **步骤注释**:超过 5 行的业务逻辑,必须分步注释:
+ ```csharp
+ // 1. 验证库存
+ // 2. 扣减余额
+ ```
+* **Swagger**:必须开启 JWT 鉴权按钮,Request/Response 示例必须清晰。
+
+## 5. 异常处理 (防御性编程)
+* **禁止空 Catch**:严禁 `catch (Exception) {}`,必须记录日志或抛出。
+* **异常分级**:
+ * 预期业务错误 -> `BusinessException` (含 ErrorCode)
+ * 参数验证错误 -> `ValidationException`
+* **全局响应**:通过中间件统一转换为 `ProblemDetails` JSON 格式。
+
+## 6. 异步与日志
+* **全异步**:所有 I/O 操作必须 `await`。**严禁** `.Result` 或 `.Wait()`。
+* **结构化日志**:
+ * ❌ `_logger.LogInfo("订单 " + id + " 创建成功");`
+ * ✅ `_logger.LogInformation("订单 {OrderId} 创建成功", id);`
+* **脱敏**:严禁打印密码、密钥、支付凭证等敏感信息。
+
+## 7. 依赖注入 (DI)
+* **构造函数注入**:统一使用构造函数注入。
+* **禁止项**:
+ * ❌ 禁止使用 `[Inject]` 属性注入。
+ * ❌ 禁止使用 `ServiceLocator` (服务定位器模式)。
+ * ❌ 禁止在静态类中持有 ServiceProvider。
+
+## 8. 数据访问规范 (重点执行)
+### 8.1 Entity Framework Core (写/事务)
+1. **无跟踪查询**:只读查询**必须**加 `.AsNoTracking()`。
+2. **杜绝 N+1**:严禁在 `foreach` 循环中查询数据库。必须使用 `.Include()`。
+3. **复杂查询**:关联表超过 2 层时,考虑使用 `.AsSplitQuery()`。
+
+### 8.2 Dapper (读/报表)
+1. **SQL 注入防御**:**严禁**拼接 SQL 字符串。必须使用参数化查询 (`@Param`)。
+2. **字段映射**:注意 PostgreSQL (`snake_case`) 与 C# (`PascalCase`) 的映射配置。
+
+## 9. 多租户与 ID 策略
+* **ID 生成**:
+ * **强制**使用 **雪花算法 (Snowflake ID)**。
+ * 类型:C# `long` <-> DB `bigint`。
+ * **禁止**使用 UUID 或 自增 INT。
+* **租户隔离**:
+ * 所有业务表必须包含 `tenant_id`。
+ * 写入时自动填充,读取时强制过滤。
+
+## 10. API 设计与序列化 (前端兼容)
+* **大整数处理**:
+ * 所有 `long` 类型 (Snowflake ID) 在 DTO 中**必须序列化为 string**。
+ * 方案:DTO 属性加 `[JsonConverter(typeof(ToStringJsonConverter))]` 或全局配置。
+* **DTO 规范**:
+ * 输入:`XxxRequest`
+ * 输出:`XxxDto`
+ * **禁止** Controller 直接返回 Entity。
+
+## 11. 模块化与复用
+* **核心模块划分**:Identity (身份), Tenancy (租户), Dictionary (字典), Storage (存储)。
+* **公共库 (Shared)**:通用工具类、扩展方法、常量定义必须放在 `Core/Shared` 项目中,避免重复造轮子。
+
+## 12. 测试规范
+* **模式**:Arrange-Act-Assert (AAA)。
+* **工具**:xUnit + Moq + FluentAssertions。
+* **覆盖率**:核心 Domain 逻辑必须 100% 覆盖;Service 层 ≥ 70%。
+
+## 13. Git 工作流
+* **提交格式 (Conventional Commits)**:
+ * `feat`: 新功能
+ * `fix`: 修复 Bug
+ * `refactor`: 重构
+ * `docs`: 文档
+ * `style`: 格式调整
+* **分支规范**:`feature/功能名`,`bugfix/问题描述`。
+
+## 14. 性能优化 (显式指令)
+* **投影查询**:使用 `.Select(x => new Dto { ... })` 只查询需要的字段,减少 I/O。
+* **缓存策略**:Cache-Aside 模式。数据更新后必须立即失效缓存。
+* **批量操作**:
+ * EF Core 10:使用 `ExecuteUpdateAsync` / `ExecuteDeleteAsync`。
+ * Dapper:使用 `ExecuteAsync` 进行批量插入。
+
+## 15. 安全规范
+* **SQL 注入**:已在第 8 条强制参数化。
+* **身份认证**:Admin 端使用 JWT + RBAC;小程序端使用 Session/Token。
+* **密码存储**:必须使用 PBKDF2 或 BCrypt 加盐哈希。
+
+## 16. 绝对禁止事项 (AI 自检清单)
+**生成代码前,请自查是否违反以下红线:**
+1. [ ] **SQL 注入**:是否拼接了 SQL 字符串?
+2. [ ] **架构违规**:是否在 Controller/Domain 中使用了 DbContext?
+3. [ ] **数据泄露**:是否返回了 Entity 或打印了密码?
+4. [ ] **同步阻塞**:是否使用了 `.Result` 或 `.Wait()`?
+5. [ ] **性能陷阱**:是否在循环中查询了数据库 (N+1)?
+6. [ ] **精度丢失**:Long 类型的 ID 是否转为了 String?
+7. [ ] **配置硬编码**:是否直接写死了连接串或密钥?
+
+---
+
+
+# Working agreements
+- 严格遵循上述技术栈和命名规范。
\ No newline at end of file
diff --git a/Document/10_设计期DbContext配置指引.md b/Document/10_设计期DbContext配置指引.md
index 5baba69..2da0fd9 100644
--- a/Document/10_设计期DbContext配置指引.md
+++ b/Document/10_设计期DbContext配置指引.md
@@ -2,6 +2,48 @@
> 目的:在执行 `dotnet ef` 命令时无需硬编码数据库连接,可根据 appsettings 与环境变量自动加载。本文覆盖环境变量设置、配置目录指定等细节。
+## 三库迁移命令 只需更改 SnowflakeIds_App 迁移关键字
+> 先生成迁移,再执行数据库更新。启动项目统一用 AdminApi 确保加载最新配置。
+
+### 生成迁移
+```bash
+# App 主库
+dotnet tool run dotnet-ef migrations add SnowflakeIds_App `
+ --project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
+ --startup-project src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj `
+ --context TakeoutSaaS.Infrastructure.App.Persistence.TakeoutAppDbContext
+
+# Identity 库
+dotnet tool run dotnet-ef migrations add SnowflakeIds_Identity `
+ --project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
+ --startup-project src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj `
+ --context TakeoutSaaS.Infrastructure.Identity.Persistence.IdentityDbContext
+
+# Dictionary 库
+dotnet tool run dotnet-ef migrations add SnowflakeIds_Dictionary `
+ --project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
+ --startup-project src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj `
+ --context TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext
+```
+
+### 更新数据库
+```bash
+dotnet tool run dotnet-ef database update `
+ --project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
+ --startup-project src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj `
+ --context TakeoutSaaS.Infrastructure.App.Persistence.TakeoutAppDbContext
+
+dotnet tool run dotnet-ef database update `
+ --project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
+ --startup-project src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj `
+ --context TakeoutSaaS.Infrastructure.Identity.Persistence.IdentityDbContext
+
+dotnet tool run dotnet-ef database update `
+ --project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
+ --startup-project src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj `
+ --context TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext
+```
+
## 一、设计时工厂读取逻辑概述
设计时工厂(`DesignTimeDbContextFactoryBase`)按下面顺序解析连接串:
1. 若设置了 `TAKEOUTSAAS_APP_CONNECTION` / `TAKEOUTSAAS_IDENTITY_CONNECTION` / `TAKEOUTSAAS_DICTIONARY_CONNECTION` 等环境变量,则优先使用。
diff --git a/Document/11_SystemTodo.md b/Document/11_SystemTodo.md
index d6e5114..f715bbc 100644
--- a/Document/11_SystemTodo.md
+++ b/Document/11_SystemTodo.md
@@ -1,57 +1,57 @@
-# System-Level TODOs
+# TODO Roadmap
-> Infrastructure / platform backlog migrated from the former 11th document. Unless noted explicitly, all checklist items remain pending.
+> 当前列表为原 11 号文档中的待办事项,已迁移到此处并统一以复选框形式标记。若无特殊说明,均尚未完成。
-## 1. 閰嶇疆涓庡熀纭€璁炬柦锛堥珮浼橈級
-- [x] Development/Production 鏁版嵁搴撹繛鎺ヤ笌 Secret 钀藉湴锛圫taging 鏆備笉闇€瑕侊級銆?
-- [x] Redis 鏈嶅姟閮ㄧ讲瀹屾瘯骞惰褰曢厤缃€?
-- [x] RabbitMQ 鏈嶅姟閮ㄧ讲瀹屾瘯骞惰褰曢厤缃€?
-- [x] COS 瀵嗛挜閰嶇疆琛ュ綍瀹屾瘯銆?
-- [ ] OSS 瀵嗛挜閰嶇疆琛ュ綍瀹屾瘯锛堝緟閲囪喘锛夈€?
-- [ ] SMS 骞冲彴瀵嗛挜閰嶇疆琛ュ綍瀹屾瘯锛堝緟閲囪喘锛夈€?
-- [x] WeChat Mini 绋嬪簭瀵嗛挜閰嶇疆琛ュ綍瀹屾瘯锛圓ppID锛歸x30f91e6afe79f405锛孉ppSecret锛?4324a7f604245301066ba7c3add488e锛屽凡鍚屾鍒?admin/mini 閰嶇疆骞剁櫥璁版洿鏂颁汉锛夈€?
-- [x] PostgreSQL 鍩虹瀹炰緥閮ㄧ讲瀹屾瘯骞惰褰曢厤缃€?
-- [x] Postgres/Redis 鎺ュ叆鏂囨。 + IaC/鑴氭湰琛ラ綈锛堣 Document/infra/postgres_redis.md 涓?deploy/postgres|redis锛夈€?
+## 1. 配置与基础设施(高优)
+- [x] Development/Production 数据库连接与 Secret 落地(Staging 暂不需要)。
+- [x] Redis 服务部署完毕并记录配置。
+- [x] RabbitMQ 服务部署完毕并记录配置。
+- [x] COS 密钥配置补录完毕。
+- [ ] OSS 密钥配置补录完毕(已忽略,待采购后再补录)。
+- [ ] SMS 平台密钥配置补录完毕(已忽略,待采购后再补录)。
+- [x] WeChat Mini 程序密钥配置补录完毕(AppID:wx30f91e6afe79f405,AppSecret:64324a7f604245301066ba7c3add488e,已同步到 admin/mini 配置并登记更新人)。
+- [x] PostgreSQL 基础实例部署完毕并记录配置。
+- [x] Postgres/Redis 接入文档 + IaC/脚本补齐(见 Document/infra/postgres_redis.md 与 deploy/postgres|redis)。
- [x] RabbitMQ/Redis/Hangfire storage scripts available (see deploy/postgres and deploy/redis).
-- [ ] admin/mini/user/gateway 缃戝叧鍩熷悕銆佽瘉涔︺€丆ORS 鍒楄〃鏁寸悊瀹屾垚銆?
-- [ ] Hangfire Dashboard 鍚敤骞舵柊澧?Admin 瑙掕壊楠岃瘉/缃戝叧鐧藉悕鍗曘€?
+- [ ] admin/mini/user/gateway 网关域名、证书、CORS 列表整理完成。(忽略,暂时不用完成)
+- [ ] Hangfire Dashboard 启用并新增 Admin 角色验证/网关白名单。(忽略,暂时不用完成)
-## 2. 鏁版嵁涓庤縼绉伙紙楂樹紭锛?
-- [x] App/Identity/Dictionary/Hangfire 鍥涗釜 DbContext 鍧囩敓鎴愬垵濮?Migration 骞舵垚鍔?update database銆?
-- [ ] 鍟嗘埛/闂ㄥ簵/鍟嗗搧/璁㈠崟/鏀粯/閰嶉€佺瓑瀹炰綋涓庝粨鍌ㄥ疄鐜板畬鎴愶紝鎻愪緵 CRUD + 鏌ヨ銆?
-- [ ] 绯荤粺鍙傛暟銆侀粯璁ょ鎴枫€佺鐞嗗憳璐﹀彿銆佸熀纭€瀛楀吀鐨勭瀛愯剼鏈彲閲嶅鎵ц銆?
+## 2. 数据与迁移(高优)
+- [x] App/Identity/Dictionary/Hangfire 四个 DbContext 均生成初始 Migration 并成功 update database。
+- [ ] 商户/门店/商品/订单/支付/配送等实体与仓储实现完成,提供 CRUD + 查询。
+- [ ] 系统参数、默认租户、管理员账号、基础字典的种子脚本可重复执行。
-## 3. 绋冲畾鎬т笌璐ㄩ噺
-- [ ] Dictionary/Identity/Storage/Sms/Messaging/Scheduler 鐨?xUnit+FluentAssertions 鍗曞厓娴嬭瘯妗嗘灦鎼缓銆?
-- [ ] WebApplicationFactory + Testcontainers 鎷夎捣 Postgres/Redis/RabbitMQ/MinIO 鐨勯泦鎴愭祴璇曟ā鏉裤€?
-- [ ] .editorconfig銆?globalconfig銆丷oslyn 鍒嗘瀽鍣ㄩ厤缃粨搴撻€氱敤瑙勫垯骞跺惎鐢?CI 妫€鏌ャ€?
+## 3. 稳定性与质量
+- [ ] Dictionary/Identity/Storage/Sms/Messaging/Scheduler 的 xUnit+FluentAssertions 单元测试框架搭建。
+- [ ] WebApplicationFactory + Testcontainers 拉起 Postgres/Redis/RabbitMQ/MinIO 的集成测试模板。
+- [ ] .editorconfig、.globalconfig、Roslyn 分析器配置仓库通用规则并启用 CI 检查。
-## 4. 瀹夊叏涓庡悎瑙?
-- [ ] RBAC 鏉冮檺銆佺鎴烽殧绂汇€佺敤鎴?鏉冮檺娲炲療 API 瀹屾暣婕旂ず骞跺湪 Swagger 涓彁渚涚ず渚嬨€?
-- [ ] 鐧诲綍/鍒锋柊娴佺▼澧炲姞 IP 鏍¢獙銆佺鎴烽殧绂汇€侀獙璇佺爜/棰戠巼闄愬埗銆?
-- [ ] 鐧诲綍/鏉冮檺/鏁忔劅鎿嶄綔鏃ュ織鍙拷婧紝鎻愪緵鏌ヨ鎺ュ彛鎴?Kibana Saved Search銆?
-- [ ] Secret Store/KeyVault/KMS 绠$悊鏁忔劅閰嶇疆锛岀姝㈠瘑閽ュ啓鍏?Git/鏁版嵁搴撴槑鏂囥€?
+## 4. 安全与合规
+- [ ] RBAC 权限、租户隔离、用户/权限洞察 API 完整演示并在 Swagger 中提供示例。
+- [ ] 登录/刷新流程增加 IP 校验、租户隔离、验证码/频率限制。
+- [ ] 登录/权限/敏感操作日志可追溯,提供查询接口或 Kibana Saved Search。
+- [ ] Secret Store/KeyVault/KMS 管理敏感配置,禁止密钥写入 Git/数据库明文。
-## 5. 瑙傛祴涓庤繍缁?
-- [ ] TraceId 璐€氾紝骞跺湪 Serilog 涓緭鍑?Console/File/ELK 涓夌鐩爣銆?
-- [ ] Prometheus exporter 鏆撮湶鍏抽敭鎸囨爣锛?health 鎺㈤拡涓庡憡璀﹁鍒欏悓姝ユ帹閫併€?
-- [ ] PostgreSQL 鍏ㄩ噺/澧為噺澶囦唤鑴氭湰鍙婁竴娆$湡瀹炴仮澶嶆紨缁冩姤鍛娿€?
+## 5. 观测与运维
+- [ ] TraceId 贯通,并在 Serilog 中输出 Console/File/ELK 三种目标。
+- [ ] Prometheus exporter 暴露关键指标,/health 探针与告警规则同步推送。
+- [ ] PostgreSQL 全量/增量备份脚本及一次真实恢复演练报告。
-## 6. 涓氬姟鑳藉姏琛ュ叏
-- [ ] 鍟嗘埛/闂ㄥ簵/鑿滃搧 API 瀹屾垚骞跺湪 MQ 涓姇閫掍笂鏋?鏀粯鎴愬姛浜嬩欢銆?
-- [ ] 閰嶉€佸鎺?API 鏀寔涓嬪崟/鍙栨秷/鏌ヨ骞跺畬鎴愮鍚嶉獙绛句腑闂翠欢銆?
-- [ ] 灏忕▼搴忕鍟嗗搧娴忚銆佷笅鍗曘€佹敮浠樸€佽瘎浠枫€佸浘鐗囩洿浼犵瓑 API 鍙棴鐜窇閫氥€?
+## 6. 业务能力补全
+- [ ] 商户/门店/菜品 API 完成并在 MQ 中投递上架/支付成功事件。
+- [ ] 配送对接 API 支持下单/取消/查询并完成签名验签中间件。
+- [ ] 小程序端商品浏览、下单、支付、评价、图片直传等 API 可闭环跑通。
-## 7. 鍓嶅悗鍙?UI 瀵规帴
-- [ ] Admin UI 閫氳繃 OpenAPI 鐢熸垚鎴栨墜鍐欑晫闈紝鎺ュ叆 Hangfire Dashboard/MQ 鐩戞帶鍙妯″紡銆?
-- [ ] 灏忕▼搴忕瀹屾垚鐧诲綍銆佽彍鍗曟祻瑙堛€佷笅鍗曘€佹敮浠樸€佺墿娴佽建杩广€佺礌鏉愮洿浼犻棴鐜€?
+## 7. 前后台 UI 对接
+- [ ] Admin UI 通过 OpenAPI 生成或手写界面,接入 Hangfire Dashboard/MQ 监控只读模式。
+- [ ] 小程序端完成登录、菜单浏览、下单、支付、物流轨迹、素材直传闭环。
-## 8. CI/CD 涓庡彂甯?
-- [ ] CI/CD 娴佹按绾胯鐩栨瀯寤恒€佸彂甯冦€侀潤鎬佹壂鎻忋€佹暟鎹簱杩佺Щ銆?
-- [ ] Dev/Staging/Prod 澶氱幆澧冮厤缃煩闃?+ 鍩虹璁炬柦 IaC 鑴氭湰銆?
-- [ ] 鐗堟湰涓庡彂甯冭鏄庢ā鏉挎暣鐞嗗苟鍦ㄤ粨搴撲腑鎻愪緵绀轰緥銆?
+## 8. CI/CD 与发布
+- [ ] CI/CD 流水线覆盖构建、发布、静态扫描、数据库迁移。
+- [ ] Dev/Staging/Prod 多环境配置矩阵 + 基础设施 IaC 脚本。
+- [ ] 版本与发布说明模板整理并在仓库中提供示例。
-## 9. 鏂囨。涓庣煡璇嗗簱
-- [ ] 鎺ュ彛鏂囨。銆侀鍩熸ā鍨嬨€佸叧閿害鏉熶娇鐢?Markdown 鎴?API Portal 瀹屾暣璁板綍銆?
-- [ ] 杩愯鎵嬪唽鍖呭惈閮ㄧ讲姝ラ銆佽祫婧愭嫇鎵戙€佹晠闅滄帓鏌ユ墜鍐屻€?
-- [ ] 瀹夊叏鍚堣妯℃澘瑕嗙洊鏁版嵁鍒嗙骇銆佸瘑閽ョ鐞嗐€佸璁℃祦绋嬪苟褰㈡垚鍙鐢ㄨ〃鏍笺€
+## 9. 文档与知识库
+- [ ] 接口文档、领域模型、关键约束使用 Markdown 或 API Portal 完整记录。
+- [ ] 运行手册包含部署步骤、资源拓扑、故障排查手册。
+- [ ] 安全合规模板覆盖数据分级、密钥管理、审计流程并形成可复用表格。
diff --git a/Document/12_BusinessTodo.md b/Document/12_BusinessTodo.md
index e59a6dc..bd3da81 100644
--- a/Document/12_BusinessTodo.md
+++ b/Document/12_BusinessTodo.md
@@ -1,54 +1,53 @@
-# Business-Level TODOs
+# 里程碑待办追踪
-> Product & business capability roadmap grouped by milestones; each phase only tracks the scoped backlog to enable staged delivery.
+> 按“小程序版模块规划”划分四个里程碑;每个里程碑只含对应范围的任务,便于分阶段推进。
---
-## Phase 1锛堝綋鍓嶉樁娈碉級锛氱鎴?鍟嗗鍏ラ┗銆侀棬搴椾笌鑿滃搧銆佹壂鐮佸爞椋熴€佸熀纭€涓嬪崟鏀粯銆侀璐嚜鎻愩€佺涓夋柟閰嶉€侀鏋?
-- [ ] 绠$悊绔鎴?API锛氭敞鍐屻€佸疄鍚嶈璇併€佸椁愯闃?缁垂/鍗囬檷閰嶃€佸鏍告祦锛孲wagger 鈮? 涓鐐癸紝鍚鏍告棩蹇椼€?
-- [ ] 鍟嗗鍏ラ┗ API锛氳瘉鐓т笂浼犮€佸悎鍚岀鐞嗐€佺被鐩€夋嫨锛岄┍鍔ㄥ緟瀹?瀹℃牳/椹冲洖/閫氳繃鐘舵€佹満锛屾枃浠舵寔涔呭湪 COS銆?
-- [ ] RBAC 妯℃澘锛氬钩鍙扮鐞嗗憳銆佺鎴风鐞嗗憳銆佸簵闀裤€佸簵鍛樺洓瑙掕壊妯℃澘锛汚PI 鍙鍒跺苟鍏佽绉熸埛鑷畾涔夋墿灞曘€?
-- [ ] 閰嶉涓庡椁愶細TenantPackage CRUD銆佽闃?缁垂/閰嶉鏍¢獙锛堥棬搴?璐﹀彿/鐭俊/閰嶉€佸崟閲忥級锛岃秴棰濊繑鍥?409 骞惰褰?TenantQuotaUsage銆?
-- [ ] 绉熸埛杩愯惀闈㈡澘锛氭瑺璐?鍒版湡鍛婅銆佽处鍗曞垪琛ㄣ€佸叕鍛婇€氱煡鎺ュ彛锛屾敮鎸佸凡璇荤姸鎬佸苟鍦?Admin UI 灞曠ず銆?
-- [ ] 闂ㄥ簵绠$悊锛歋tore/StoreBusinessHour/StoreDeliveryZone/StoreHoliday CRUD 瀹屾暣锛屽惈 GeoJSON 閰嶉€佽寖鍥村強鑳藉姏寮€鍏炽€?
-- [ ] 妗岀爜绠$悊锛氭壒閲忕敓鎴愭鐮併€佺粦瀹氬尯鍩?瀹归噺銆佸鍑轰簩缁寸爜 ZIP锛圥OST /api/admin/stores/{id}/tables 鍙笅杞斤級銆?
-- [ ] 鍛樺伐鎺掔彮锛氬垱寤哄憳宸ャ€佺粦瀹氶棬搴楄鑹层€佺淮鎶?StoreEmployeeShift锛屽彲鏌ヨ鏈潵 7 鏃ユ帓鐝€?
-- [ ] 妗岀爜鎵爜鍏ュ彛锛歁ini 绔В鏋愪簩缁寸爜锛孏ET /api/mini/tables/{code}/context 杩斿洖闂ㄥ簵銆佹鍙般€佸叕鍛娿€?
-- [ ] 鑿滃搧寤烘ā锛氬垎绫汇€丼PU銆丼KU銆佽鏍?鍔犳枡缁勩€佷环鏍肩瓥鐣ャ€佸獟璧?CRUD + 涓婁笅鏋舵祦绋嬶紱Mini 绔彲鎷夊彇瀹屾暣 JSON銆?
-- [ ] 搴撳瓨浣撶郴锛歋KU 搴撳瓨銆佹壒娆°€佽皟鏁淬€佸敭缃勭鐞嗭紝鏀寔棰勫敭/妗f湡閿佸畾骞跺湪璁㈠崟涓墸鍑?閲婃斁銆?
-- [ ] 鑷彁妗f湡锛氶棬搴楅厤缃嚜鎻愭椂闂寸獥銆佸閲忋€佹埅鍗曟椂闂达紱Mini 绔嵁姝ら檺鍒朵笅鍗曟椂闂淬€?
-- [ ] 璐墿杞︽湇鍔★細ShoppingCart/CartItem/CartItemAddon API 鏀寔骞跺彂閿併€侀檺璐€佸埜/绉垎棰勬牎楠岋紝淇濊瘉骞跺彂鏃犺剰鏁版嵁銆?
-- [ ] 璁㈠崟涓庢敮浠橈細鍫傞/鑷彁/閰嶉€佷笅鍗曘€佸井淇?鏀粯瀹濇敮浠樸€佷紭鎯犲埜/绉垎鎶垫墸銆佽鍗曠姸鎬佹満涓庨€氱煡閾捐矾榻愬叏銆?
-- [ ] 妗屽彴璐﹀崟锛氬悎鍗?鎷嗗崟銆佺粨璐︺€佺數瀛愬皬绁ㄣ€佹鍙伴噴鏀撅紝瀹屾垚缁撹处鍚庢仮澶?Idle 骞剁敓鎴愮エ鎹?URL銆?
-- [ ] 鑷厤閫侀鏋讹細楠戞墜绠$悊銆佸彇閫佷欢淇℃伅褰曞叆銆佽垂鐢ㄨˉ璐磋褰曪紝Admin 绔彲娲惧崟骞舵洿鏂?DeliveryOrder銆?
-- [ ] 绗笁鏂归厤閫佹娊璞★細缁熶竴涓嬪崟/鍙栨秷/鍔犱环/鏌ヨ鎺ュ彛锛屾敮鎸佽揪杈俱€佺編鍥€侀棯閫佺瓑锛屽惈鍥炶皟楠岀涓庡紓甯歌ˉ鍋块鏋躲€?
-- [ ] 棰勮喘鑷彁鏍搁攢锛氭彁璐х爜鐢熸垚銆佹墜鏈哄彿/浜岀淮鐮佹牳閿€銆佽嚜鎻愭煖/鍓嶅彴娴佺▼锛岃秴鏃惰嚜鍔ㄥ彇娑堟垨閫€娆撅紝璁板綍鎿嶄綔鑰呬笌鏃堕棿銆?
-- [ ] 鎸囨爣涓庢棩蹇楋細Prometheus 杈撳嚭璁㈠崟鍒涘缓銆佹敮浠樻垚鍔熺巼銆侀厤閫佸洖璋冭€楁椂绛夛紝Grafana 鈮? 涓浘琛紱鍏抽敭娴佺▼鏃ュ織璁板綍 TraceId + 涓氬姟 ID銆?
-- [ ] 娴嬭瘯锛歅hase 1 鏍稿績 API 鍏峰 鈮?0 鏉¤嚜鍔ㄥ寲鐢ㄤ緥锛堝崟鍏?+ 闆嗘垚锛夛紝瑕嗙洊绉熸埛鈫掑晢鎴封啋涓嬪崟閾捐矾銆?
-
+## Phase 1(当前阶段):租户/商家入驻、门店与菜品、扫码堂食、基础下单支付、预购自提、第三方配送骨架
+- [ ] 管理端租户 API:注册、实名认证、套餐订阅/续费/升降配、审核流,Swagger ≥6 个端点,含审核日志。
+- [ ] 商家入驻 API:证照上传、合同管理、类目选择,驱动待审/审核/驳回/通过状态机,文件持久在 COS。
+- [ ] RBAC 模板:平台管理员、租户管理员、店长、店员四角色模板;API 可复制并允许租户自定义扩展。
+- [ ] 配额与套餐:TenantPackage CRUD、订阅/续费/配额校验(门店/账号/短信/配送单量),超额返回 409 并记录 TenantQuotaUsage。
+- [ ] 租户运营面板:欠费/到期告警、账单列表、公告通知接口,支持已读状态并在 Admin UI 展示。
+- [ ] 门店管理:Store/StoreBusinessHour/StoreDeliveryZone/StoreHoliday CRUD 完整,含 GeoJSON 配送范围及能力开关。
+- [ ] 桌码管理:批量生成桌码、绑定区域/容量、导出二维码 ZIP(POST /api/admin/stores/{id}/tables 可下载)。
+- [ ] 员工排班:创建员工、绑定门店角色、维护 StoreEmployeeShift,可查询未来 7 日排班。
+- [ ] 桌码扫码入口:Mini 端解析二维码,GET /api/mini/tables/{code}/context 返回门店、桌台、公告。
+- [ ] 菜品建模:分类、SPU、SKU、规格/加料组、价格策略、媒资 CRUD + 上下架流程;Mini 端可拉取完整 JSON。
+- [ ] 库存体系:SKU 库存、批次、调整、售罄管理,支持预售/档期锁定并在订单中扣减/释放。
+- [ ] 自提档期:门店配置自提时间窗、容量、截单时间;Mini 端据此限制下单时间。
+- [ ] 购物车服务:ShoppingCart/CartItem/CartItemAddon API 支持并发锁、限购、券/积分预校验,保证并发无脏数据。
+- [ ] 订单与支付:堂食/自提/配送下单、微信/支付宝支付、优惠券/积分抵扣、订单状态机与通知链路齐全。
+- [ ] 桌台账单:合单/拆单、结账、电子小票、桌台释放,完成结账后恢复 Idle 并生成票据 URL。
+- [ ] 自配送骨架:骑手管理、取送件信息录入、费用补贴记录,Admin 端可派单并更新 DeliveryOrder。
+- [ ] 第三方配送抽象:统一下单/取消/加价/查询接口,支持达达、美团、闪送等,含回调验签与异常补偿骨架。
+- [ ] 预购自提核销:提货码生成、手机号/二维码核销、自提柜/前台流程,超时自动取消或退款,记录操作者与时间。
+- [ ] 指标与日志:Prometheus 输出订单创建、支付成功率、配送回调耗时等,Grafana ≥8 个图表;关键流程日志记录 TraceId + 业务 ID。
+- [ ] 测试:Phase 1 核心 API 具备 ≥30 条自动化用例(单元 + 集成),覆盖租户→商户→下单链路。
---
-## Phase 2锛堜笅涓€闃舵锛夛細鎷煎崟銆佷紭鎯犲埜涓庡熀纭€钀ラ攢銆佷細鍛樼Н鍒?浼氬憳鏃ャ€佸鏈嶈亰澶┿€佸悓鍩庤嚜閰嶉€佽皟搴︺€佹悳绱?
-- [ ] 鎷煎崟寮曟搸锛欸roupOrder/Participant CRUD銆佸彂璧?鍔犲叆/鎴愬洟鏉′欢銆佽嚜鍔ㄨВ鏁d笌閫€娆俱€佸洟鍐呮秷鎭笌鎻愰啋銆?
-- [ ] 浼樻儬鍒镐笌鍩虹钀ラ攢锛氭ā鏉跨鐞嗐€侀鍒搞€佹牳閿€銆佸簱瀛?鏈夋晥鏈?鍙犲姞瑙勫垯锛屽熀纭€鎶藉/绉掓潃/婊″噺娲诲姩銆?
-- [ ] 浼氬憳涓庣Н鍒嗭細浼氬憳妗f銆佺瓑绾?鎴愰暱鍊笺€佷細鍛樻棩閫氱煡锛涚Н鍒嗚幏鍙?娑堣€椼€佹湁鏁堟湡銆侀粦鍚嶅崟銆?
-- [ ] 瀹㈡湇鑱婂ぉ锛氬疄鏃朵細璇濄€佹満鍣ㄤ汉/浜哄伐鍒囨崲銆佹帓闃?杞帴銆佹秷鎭ā鏉裤€佹晱鎰熻瘝瀹℃煡銆佸伐鍗曟祦杞笌璇勪环銆?
-- [ ] 鍚屽煄鑷厤閫佽皟搴︼細楠戞墜鏅鸿兘鎸囨淳銆佽矾绾夸及鏃躲€佹棤鎺ヨЕ閰嶉€併€佽垂鐢ㄨˉ璐寸瓥鐣ャ€佽皟搴︾湅鏉裤€?
-- [ ] 鎼滅储锛氶棬搴?鑿滃搧/娲诲姩/浼樻儬鍒告悳绱紝杩囨护/鎺掑簭銆佺儹闂?鍘嗗彶璁板綍銆佽仈鎯充笌绾犻敊銆?
+## Phase 2(下一阶段):拼单、优惠券与基础营销、会员积分/会员日、客服聊天、同城自配送调度、搜索
+- [ ] 拼单引擎:GroupOrder/Participant CRUD、发起/加入/成团条件、自动解散与退款、团内消息与提醒。
+- [ ] 优惠券与基础营销:模板管理、领券、核销、库存/有效期/叠加规则,基础抽奖/秒杀/满减活动。
+- [ ] 会员与积分:会员档案、等级/成长值、会员日通知;积分获取/消耗、有效期、黑名单。
+- [ ] 客服聊天:实时会话、机器人/人工切换、排队/转接、消息模板、敏感词审查、工单流转与评价。
+- [ ] 同城自配送调度:骑手智能指派、路线估时、无接触配送、费用补贴策略、调度看板。
+- [ ] 搜索:门店/菜品/活动/优惠券搜索,过滤/排序、热门/历史记录、联想与纠错。
---
-## Phase 3锛氬垎閿€杩斿埄銆佺鍒版墦鍗°€侀绾﹂璁€佸湴鍥惧鑸€佺ぞ鍖恒€侀珮闃惰惀閿€銆侀鎺т笌琛ュ伩
-- [ ] 鍒嗛攢杩斿埄锛欰ffiliatePartner/Order/Payout 绠$悊锛屼剑閲戦樁姊€佺粨绠楀懆鏈熴€佺◣鍔′俊鎭€佽繚瑙勫鐞嗐€?
-- [ ] 绛惧埌鎵撳崱锛欳heckInCampaign/Record銆佽繛绛惧鍔便€佽ˉ绛俱€佺Н鍒?鍒?鎴愰暱鍊煎鍔便€佸弽浣滃紛鏈哄埗銆?
-- [ ] 棰勭害棰勮锛氭。鏈?璧勬簮鍗犵敤銆侀绾︿笅鍗?鏀粯銆佹彁閱?鏀规湡/鍙栨秷銆佸埌搴楁牳閿€涓庡饱绾﹁褰曘€?
-- [ ] 鍦板浘瀵艰埅鎵╁睍锛氶檮杩戦棬搴?鎺ㄨ崘銆佽窛绂?璺嚎瑙勫垝銆佽烦杞師鐢熷鑸€佸鑸姹傚煁鐐广€?
-- [ ] 绀惧尯锛氬姩鎬佸彂甯冦€佽瘎璁恒€佺偣璧炪€佽瘽棰?鏍囩銆佸浘鐗?瑙嗛瀹℃牳銆佷妇鎶ヤ笌椋庢帶锛屽簵閾哄彛纰戝睍绀恒€?
-- [ ] 楂橀樁钀ラ攢锛氱鏉€/鎶藉/瑁傚彉銆佽鍙樻捣鎶ャ€佺垎娆炬帹鑽愪綅銆佸娓犻亾鎶曟斁鍒嗘瀽銆?
-- [ ] 椋庢帶涓庡璁★細榛戝悕鍗曘€侀鐜囬檺鍒躲€佸紓甯歌涓虹洃鎺с€佸璁℃棩蹇椼€佽ˉ鍋夸笌鍛婅浣撶郴銆?
+## Phase 3:分销返利、签到打卡、预约预订、地图导航、社区、高阶营销、风控与补偿
+- [ ] 分销返利:AffiliatePartner/Order/Payout 管理,佣金阶梯、结算周期、税务信息、违规处理。
+- [ ] 签到打卡:CheckInCampaign/Record、连签奖励、补签、积分/券/成长值奖励、反作弊机制。
+- [ ] 预约预订:档期/资源占用、预约下单/支付、提醒/改期/取消、到店核销与履约记录。
+- [ ] 地图导航扩展:附近门店/推荐、距离/路线规划、跳转原生导航、导航请求埋点。
+- [ ] 社区:动态发布、评论、点赞、话题/标签、图片/视频审核、举报与风控,店铺口碑展示。
+- [ ] 高阶营销:秒杀/抽奖/裂变、裂变海报、爆款推荐位、多渠道投放分析。
+- [ ] 风控与审计:黑名单、频率限制、异常行为监控、审计日志、补偿与告警体系。
---
-## Phase 4锛氭€ц兘浼樺寲銆佺紦瀛樸€佽繍钀ュぇ鐩樸€佹祴璇曚笌鏂囨。銆佷笂绾夸笌鐩戞帶
-- [ ] 鎬ц兘涓庣紦瀛橈細鐑偣鎺ュ彛缂撳瓨銆佹參鏌ヨ娌荤悊銆佹壒澶勭悊浼樺寲銆佸紓姝ュ寲鏀归€犮€?
-- [ ] 鍙潬鎬э細骞傜瓑涓庨噸璇曠瓥鐣ャ€佷换鍔¤皟搴﹁ˉ鍋裤€侀摼璺拷韪€佸憡璀﹁仈鍔ㄣ€?
-- [ ] 杩愯惀澶х洏锛氫氦鏄?钀ラ攢/灞ョ害/鐢ㄦ埛缁村害鐨勭粏鍒嗘姤琛ㄣ€丟MV/鎴愭湰/姣涘埄鍒嗘瀽銆?
-- [ ] 鏂囨。涓庢祴璇曪細瀹屾暣娴嬭瘯鐭╅樀銆佹€ц兘娴嬭瘯鎶ュ憡銆佷笂绾挎墜鍐屻€佸洖婊氭柟妗堛€?
-- [ ] 鐩戞帶涓庤繍缁达細涓婄嚎鍙戝竷娴佺▼銆佺伆搴?鍥炴粴绛栫暐銆佺郴缁熺ǔ瀹氭€ф寚鏍囥€?4x7 鐩戞帶涓庡憡璀︺€?
+## Phase 4:性能优化、缓存、运营大盘、测试与文档、上线与监控
+- [ ] 性能与缓存:热点接口缓存、慢查询治理、批处理优化、异步化改造。
+- [ ] 可靠性:幂等与重试策略、任务调度补偿、链路追踪、告警联动。
+- [ ] 运营大盘:交易/营销/履约/用户维度的细分报表、GMV/成本/毛利分析。
+- [ ] 文档与测试:完整测试矩阵、性能测试报告、上线手册、回滚方案。
+- [ ] 监控与运维:上线发布流程、灰度/回滚策略、系统稳定性指标、24x7 监控与告警。
\ No newline at end of file
diff --git a/Document/13_AppSeed说明.md b/Document/13_AppSeed说明.md
new file mode 100644
index 0000000..8ab02e7
--- /dev/null
+++ b/Document/13_AppSeed说明.md
@@ -0,0 +1,52 @@
+# App 数据种子使用说明(App:Seed)
+
+> 作用:在启动时自动创建默认租户与基础字典,便于本地/测试环境快速落地必备数据。由 `AppDataSeeder` 执行,支持幂等多次运行。
+
+## 配置入口
+- 文件位置:`appsettings.{Environment}.json`(示例已写入 AdminApi 的 Development 配置)。
+- 配置节:`App:Seed`。
+
+示例(已写入 `src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json`):
+```json
+"App": {
+ "Seed": {
+ "Enabled": true,
+ "DefaultTenant": {
+ "TenantId": 1000000000001,
+ "Code": "demo",
+ "Name": "Demo租户",
+ "ShortName": "Demo",
+ "ContactName": "DemoAdmin",
+ "ContactPhone": "13800000000"
+ },
+ "DictionaryGroups": [
+ {
+ "Code": "order_status",
+ "Name": "订单状态",
+ "Scope": "Business",
+ "Items": [
+ { "Key": "pending", "Value": "待支付", "SortOrder": 10 },
+ { "Key": "paid", "Value": "已支付", "SortOrder": 20 },
+ { "Key": "finished", "Value": "已完成", "SortOrder": 30 }
+ ]
+ }
+ ]
+ }
+}
+```
+
+字段说明:
+- `Enabled`: 是否启用种子。
+- `DefaultTenant`: 默认租户(使用雪花 long ID,0 表示让雪花生成)。
+- `DictionaryGroups`: 基础字典,`Scope` 可选 `System`/`Business`,`Items` 支持重复执行更新。
+
+## 运行方式
+1. 确保 Admin API 已调用 `AddAppInfrastructure`(已在 Program.cs 中注册,会启动 `AppDataSeeder`)。
+2. 修改 `appsettings.{Environment}.json` 的 `App:Seed` 后,启动 Admin API,即会自动执行种子逻辑(幂等)。
+3. 查看日志:`AppSeed` 前缀会输出创建/更新结果。
+
+## 注意事项
+- ID 必须为 long(雪花),不要再使用 Guid/自增。
+- 系统租户使用 `TenantId = 0`;业务租户请填写实际雪花 ID。
+- 字典分组编码需唯一;重复运行会按编码合并更新。
+- 生产环境请按需开启 `Enabled`,避免误写入。
diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/AuthController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/AuthController.cs
index 164d0c3..962ee57 100644
--- a/src/Api/TakeoutSaaS.AdminApi/Controllers/AuthController.cs
+++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/AuthController.cs
@@ -67,7 +67,7 @@ public sealed class AuthController : BaseApiController
public async Task> GetProfile(CancellationToken cancellationToken)
{
var userId = User.GetUserId();
- if (userId == Guid.Empty)
+ if (userId == 0)
{
return ApiResponse.Error(ErrorCodes.Unauthorized, "Token 缺少有效的用户标识");
}
diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryController.cs
index 7232ed2..f63c273 100644
--- a/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryController.cs
+++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/DictionaryController.cs
@@ -55,10 +55,10 @@ public sealed class DictionaryController : BaseApiController
///
/// 更新字典分组。
///
- [HttpPut("{groupId:guid}")]
+ [HttpPut("{groupId:long}")]
[PermissionAuthorize("dictionary:group:update")]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
- public async Task> UpdateGroup(Guid groupId, [FromBody] UpdateDictionaryGroupRequest request, CancellationToken cancellationToken)
+ public async Task> UpdateGroup(long groupId, [FromBody] UpdateDictionaryGroupRequest request, CancellationToken cancellationToken)
{
var group = await _dictionaryAppService.UpdateGroupAsync(groupId, request, cancellationToken);
return ApiResponse.Ok(group);
@@ -67,10 +67,10 @@ public sealed class DictionaryController : BaseApiController
///
/// 删除字典分组。
///
- [HttpDelete("{groupId:guid}")]
+ [HttpDelete("{groupId:long}")]
[PermissionAuthorize("dictionary:group:delete")]
[ProducesResponseType(typeof(ApiResponse
[Required]
- public Guid GroupId { get; set; }
+ [JsonConverter(typeof(SnowflakeIdJsonConverter))]
+ public long GroupId { get; set; }
///
/// 字典项键。
diff --git a/src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryGroupDto.cs b/src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryGroupDto.cs
index 528167f..95a81f0 100644
--- a/src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryGroupDto.cs
+++ b/src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryGroupDto.cs
@@ -1,5 +1,8 @@
using TakeoutSaaS.Domain.Dictionary.Enums;
+using System.Text.Json.Serialization;
+using TakeoutSaaS.Shared.Abstractions.Serialization;
+
namespace TakeoutSaaS.Application.Dictionary.Models;
///
@@ -7,7 +10,8 @@ namespace TakeoutSaaS.Application.Dictionary.Models;
///
public sealed class DictionaryGroupDto
{
- public Guid Id { get; init; }
+ [JsonConverter(typeof(SnowflakeIdJsonConverter))]
+ public long Id { get; init; }
public string Code { get; init; } = string.Empty;
diff --git a/src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryItemDto.cs b/src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryItemDto.cs
index 89faaf7..f154f3e 100644
--- a/src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryItemDto.cs
+++ b/src/Application/TakeoutSaaS.Application/Dictionary/Models/DictionaryItemDto.cs
@@ -1,3 +1,6 @@
+using System.Text.Json.Serialization;
+using TakeoutSaaS.Shared.Abstractions.Serialization;
+
namespace TakeoutSaaS.Application.Dictionary.Models;
///
@@ -5,9 +8,11 @@ namespace TakeoutSaaS.Application.Dictionary.Models;
///
public sealed class DictionaryItemDto
{
- public Guid Id { get; init; }
+ [JsonConverter(typeof(SnowflakeIdJsonConverter))]
+ public long Id { get; init; }
- public Guid GroupId { get; init; }
+ [JsonConverter(typeof(SnowflakeIdJsonConverter))]
+ public long GroupId { get; init; }
public string Key { get; init; } = string.Empty;
diff --git a/src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryAppService.cs b/src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryAppService.cs
index 93d92c2..8ecfc79 100644
--- a/src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryAppService.cs
+++ b/src/Application/TakeoutSaaS.Application/Dictionary/Services/DictionaryAppService.cs
@@ -47,7 +47,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
var group = new DictionaryGroup
{
- Id = Guid.NewGuid(),
+ Id = 0,
TenantId = targetTenant,
Code = normalizedCode,
Name = request.Name.Trim(),
@@ -62,7 +62,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return MapGroup(group, includeItems: false);
}
- public async Task UpdateGroupAsync(Guid groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
+ public async Task UpdateGroupAsync(long groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
{
var group = await RequireGroupAsync(groupId, cancellationToken);
EnsureScopePermission(group.Scope);
@@ -77,7 +77,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return MapGroup(group, includeItems: false);
}
- public async Task DeleteGroupAsync(Guid groupId, CancellationToken cancellationToken = default)
+ public async Task DeleteGroupAsync(long groupId, CancellationToken cancellationToken = default)
{
var group = await RequireGroupAsync(groupId, cancellationToken);
EnsureScopePermission(group.Scope);
@@ -120,7 +120,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
var item = new DictionaryItem
{
- Id = Guid.NewGuid(),
+ Id = 0,
TenantId = group.TenantId,
GroupId = group.Id,
Key = request.Key.Trim(),
@@ -138,7 +138,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return MapItem(item);
}
- public async Task UpdateItemAsync(Guid itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default)
+ public async Task UpdateItemAsync(long itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default)
{
var item = await RequireItemAsync(itemId, cancellationToken);
var group = await RequireGroupAsync(item.GroupId, cancellationToken);
@@ -156,7 +156,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return MapItem(item);
}
- public async Task DeleteItemAsync(Guid itemId, CancellationToken cancellationToken = default)
+ public async Task DeleteItemAsync(long itemId, CancellationToken cancellationToken = default)
{
var item = await RequireItemAsync(itemId, cancellationToken);
var group = await RequireGroupAsync(item.GroupId, cancellationToken);
@@ -186,8 +186,8 @@ public sealed class DictionaryAppService : IDictionaryAppService
foreach (var code in normalizedCodes)
{
- var systemItems = await GetOrLoadCacheAsync(Guid.Empty, code, cancellationToken);
- if (tenantId == Guid.Empty)
+ var systemItems = await GetOrLoadCacheAsync(0, code, cancellationToken);
+ if (tenantId == 0)
{
result[code] = systemItems;
continue;
@@ -200,7 +200,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return result;
}
- private async Task RequireGroupAsync(Guid groupId, CancellationToken cancellationToken)
+ private async Task RequireGroupAsync(long groupId, CancellationToken cancellationToken)
{
var group = await _repository.FindGroupByIdAsync(groupId, cancellationToken);
if (group == null)
@@ -211,7 +211,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
return group;
}
- private async Task RequireItemAsync(Guid itemId, CancellationToken cancellationToken)
+ private async Task RequireItemAsync(long itemId, CancellationToken cancellationToken)
{
var item = await _repository.FindItemByIdAsync(itemId, cancellationToken);
if (item == null)
@@ -222,16 +222,16 @@ public sealed class DictionaryAppService : IDictionaryAppService
return item;
}
- private Guid ResolveTargetTenant(DictionaryScope scope)
+ private long ResolveTargetTenant(DictionaryScope scope)
{
var tenantId = _tenantProvider.GetCurrentTenantId();
if (scope == DictionaryScope.System)
{
EnsurePlatformTenant(tenantId);
- return Guid.Empty;
+ return 0;
}
- if (tenantId == Guid.Empty)
+ if (tenantId == 0)
{
throw new BusinessException(ErrorCodes.BadRequest, "业务参数需指定租户");
}
@@ -241,28 +241,28 @@ public sealed class DictionaryAppService : IDictionaryAppService
private static string NormalizeCode(string code) => code.Trim().ToLowerInvariant();
- private static DictionaryScope ResolveScopeForQuery(DictionaryScope? requestedScope, Guid tenantId)
+ private static DictionaryScope ResolveScopeForQuery(DictionaryScope? requestedScope, long tenantId)
{
if (requestedScope.HasValue)
{
return requestedScope.Value;
}
- return tenantId == Guid.Empty ? DictionaryScope.System : DictionaryScope.Business;
+ return tenantId == 0 ? DictionaryScope.System : DictionaryScope.Business;
}
private void EnsureScopePermission(DictionaryScope scope)
{
var tenantId = _tenantProvider.GetCurrentTenantId();
- if (scope == DictionaryScope.System && tenantId != Guid.Empty)
+ if (scope == DictionaryScope.System && tenantId != 0)
{
throw new BusinessException(ErrorCodes.Forbidden, "仅平台管理员可操作系统字典");
}
}
- private void EnsurePlatformTenant(Guid tenantId)
+ private void EnsurePlatformTenant(long tenantId)
{
- if (tenantId != Guid.Empty)
+ if (tenantId != 0)
{
throw new BusinessException(ErrorCodes.Forbidden, "仅平台管理员可操作系统字典");
}
@@ -279,7 +279,7 @@ public sealed class DictionaryAppService : IDictionaryAppService
// 系统参数更新需要逐租户重新合并,由调用方在下一次请求时重新加载
}
- private async Task> GetOrLoadCacheAsync(Guid tenantId, string code, CancellationToken cancellationToken)
+ private async Task> GetOrLoadCacheAsync(long tenantId, string code, CancellationToken cancellationToken)
{
var cached = await _cache.GetAsync(tenantId, code, cancellationToken);
if (cached != null)
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IAdminAuthService.cs b/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IAdminAuthService.cs
index f60dffb..28510e3 100644
--- a/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IAdminAuthService.cs
+++ b/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IAdminAuthService.cs
@@ -12,5 +12,5 @@ public interface IAdminAuthService
{
Task LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default);
Task RefreshTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default);
- Task GetProfileAsync(Guid userId, CancellationToken cancellationToken = default);
+ Task GetProfileAsync(long userId, CancellationToken cancellationToken = default);
}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IMiniAuthService.cs b/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IMiniAuthService.cs
index 11efdb4..c7a509e 100644
--- a/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IMiniAuthService.cs
+++ b/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IMiniAuthService.cs
@@ -12,5 +12,5 @@ public interface IMiniAuthService
{
Task LoginWithWeChatAsync(WeChatLoginRequest request, CancellationToken cancellationToken = default);
Task RefreshTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default);
- Task GetProfileAsync(Guid userId, CancellationToken cancellationToken = default);
+ Task GetProfileAsync(long userId, CancellationToken cancellationToken = default);
}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IRefreshTokenStore.cs b/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IRefreshTokenStore.cs
index d966ca2..29a1bf6 100644
--- a/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IRefreshTokenStore.cs
+++ b/src/Application/TakeoutSaaS.Application/Identity/Abstractions/IRefreshTokenStore.cs
@@ -10,7 +10,7 @@ namespace TakeoutSaaS.Application.Identity.Abstractions;
///
public interface IRefreshTokenStore
{
- Task IssueAsync(Guid userId, DateTime expiresAt, CancellationToken cancellationToken = default);
+ Task IssueAsync(long userId, DateTime expiresAt, CancellationToken cancellationToken = default);
Task GetAsync(string refreshToken, CancellationToken cancellationToken = default);
Task RevokeAsync(string refreshToken, CancellationToken cancellationToken = default);
}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Contracts/CurrentUserProfile.cs b/src/Application/TakeoutSaaS.Application/Identity/Contracts/CurrentUserProfile.cs
index e93e2bc..922b7d3 100644
--- a/src/Application/TakeoutSaaS.Application/Identity/Contracts/CurrentUserProfile.cs
+++ b/src/Application/TakeoutSaaS.Application/Identity/Contracts/CurrentUserProfile.cs
@@ -8,7 +8,7 @@ public sealed class CurrentUserProfile
///
/// 用户 ID。
///
- public Guid UserId { get; init; }
+ public long UserId { get; init; }
///
/// 登录账号。
@@ -23,12 +23,12 @@ public sealed class CurrentUserProfile
///
/// 所属租户 ID。
///
- public Guid TenantId { get; init; }
+ public long TenantId { get; init; }
///
/// 所属商户 ID(平台管理员为空)。
///
- public Guid? MerchantId { get; init; }
+ public long? MerchantId { get; init; }
///
/// 角色集合。
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Models/RefreshTokenDescriptor.cs b/src/Application/TakeoutSaaS.Application/Identity/Models/RefreshTokenDescriptor.cs
index 68e07e6..8a968bb 100644
--- a/src/Application/TakeoutSaaS.Application/Identity/Models/RefreshTokenDescriptor.cs
+++ b/src/Application/TakeoutSaaS.Application/Identity/Models/RefreshTokenDescriptor.cs
@@ -9,6 +9,6 @@ namespace TakeoutSaaS.Application.Identity.Models;
/// 是否已撤销
public sealed record RefreshTokenDescriptor(
string Token,
- Guid UserId,
+ long UserId,
DateTime ExpiresAt,
bool Revoked);
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs b/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs
index ee0fbeb..42418a4 100644
--- a/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs
+++ b/src/Application/TakeoutSaaS.Application/Identity/Services/AdminAuthService.cs
@@ -77,7 +77,7 @@ public sealed class AdminAuthService(
/// 取消令牌
/// 用户档案
/// 用户不存在时抛出
- public async Task GetProfileAsync(Guid userId, CancellationToken cancellationToken = default)
+ public async Task GetProfileAsync(long userId, CancellationToken cancellationToken = default)
{
var user = await userRepository.FindByIdAsync(userId, cancellationToken)
?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在");
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Services/MiniAuthService.cs b/src/Application/TakeoutSaaS.Application/Identity/Services/MiniAuthService.cs
index 25efbf9..5d83289 100644
--- a/src/Application/TakeoutSaaS.Application/Identity/Services/MiniAuthService.cs
+++ b/src/Application/TakeoutSaaS.Application/Identity/Services/MiniAuthService.cs
@@ -44,7 +44,7 @@ public sealed class MiniAuthService(
// 3. 获取当前租户 ID(多租户支持)
var tenantId = tenantProvider.GetCurrentTenantId();
- if (tenantId == Guid.Empty)
+ if (tenantId == 0)
{
throw new BusinessException(ErrorCodes.BadRequest, "缺少租户标识");
}
@@ -95,7 +95,7 @@ public sealed class MiniAuthService(
/// 取消令牌
/// 用户档案
/// 用户不存在时抛出
- public async Task GetProfileAsync(Guid userId, CancellationToken cancellationToken = default)
+ public async Task GetProfileAsync(long userId, CancellationToken cancellationToken = default)
{
var user = await miniUserRepository.FindByIdAsync(userId, cancellationToken)
?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在");
@@ -113,7 +113,7 @@ public sealed class MiniAuthService(
/// 租户 ID
/// 取消令牌
/// 用户实体和是否为新用户的元组
- private async Task<(MiniUser user, bool isNew)> GetOrBindMiniUserAsync(string openId, string? unionId, string? nickname, string? avatar, Guid tenantId, CancellationToken cancellationToken)
+ private async Task<(MiniUser user, bool isNew)> GetOrBindMiniUserAsync(string openId, string? unionId, string? nickname, string? avatar, long tenantId, CancellationToken cancellationToken)
{
// 检查用户是否已存在
var existing = await miniUserRepository.FindByOpenIdAsync(openId, cancellationToken);
diff --git a/src/Application/TakeoutSaaS.Application/Messaging/Events/OrderCreatedEvent.cs b/src/Application/TakeoutSaaS.Application/Messaging/Events/OrderCreatedEvent.cs
index be61584..2a84f05 100644
--- a/src/Application/TakeoutSaaS.Application/Messaging/Events/OrderCreatedEvent.cs
+++ b/src/Application/TakeoutSaaS.Application/Messaging/Events/OrderCreatedEvent.cs
@@ -8,7 +8,7 @@ public sealed class OrderCreatedEvent
///
/// 订单标识。
///
- public Guid OrderId { get; init; }
+ public long OrderId { get; init; }
///
/// 订单编号。
@@ -23,7 +23,7 @@ public sealed class OrderCreatedEvent
///
/// 所属租户。
///
- public Guid TenantId { get; init; }
+ public long TenantId { get; init; }
///
/// 创建时间(UTC)。
diff --git a/src/Application/TakeoutSaaS.Application/Messaging/Events/PaymentSucceededEvent.cs b/src/Application/TakeoutSaaS.Application/Messaging/Events/PaymentSucceededEvent.cs
index f62a88e..b0094f7 100644
--- a/src/Application/TakeoutSaaS.Application/Messaging/Events/PaymentSucceededEvent.cs
+++ b/src/Application/TakeoutSaaS.Application/Messaging/Events/PaymentSucceededEvent.cs
@@ -8,7 +8,7 @@ public sealed class PaymentSucceededEvent
///
/// 订单标识。
///
- public Guid OrderId { get; init; }
+ public long OrderId { get; init; }
///
/// 支付流水号。
@@ -23,7 +23,7 @@ public sealed class PaymentSucceededEvent
///
/// 所属租户。
///
- public Guid TenantId { get; init; }
+ public long TenantId { get; init; }
///
/// 支付时间(UTC)。
diff --git a/src/Application/TakeoutSaaS.Application/Sms/Services/VerificationCodeService.cs b/src/Application/TakeoutSaaS.Application/Sms/Services/VerificationCodeService.cs
index 042410a..88806c9 100644
--- a/src/Application/TakeoutSaaS.Application/Sms/Services/VerificationCodeService.cs
+++ b/src/Application/TakeoutSaaS.Application/Sms/Services/VerificationCodeService.cs
@@ -44,7 +44,7 @@ public sealed class VerificationCodeService(
var codeOptions = codeOptionsMonitor.CurrentValue;
var templateCode = ResolveTemplate(request.Scene, smsOptions);
var phone = NormalizePhoneNumber(request.PhoneNumber);
- var tenantKey = tenantProvider.GetCurrentTenantId() == Guid.Empty ? "platform" : tenantProvider.GetCurrentTenantId().ToString("N");
+ var tenantKey = tenantProvider.GetCurrentTenantId() == 0 ? "platform" : tenantProvider.GetCurrentTenantId().ToString();
var cacheKey = $"{codeOptions.CachePrefix}:{tenantKey}:{request.Scene}:{phone}";
var cooldownKey = $"{cacheKey}:cooldown";
@@ -90,7 +90,7 @@ public sealed class VerificationCodeService(
var codeOptions = codeOptionsMonitor.CurrentValue;
var phone = NormalizePhoneNumber(request.PhoneNumber);
- var tenantKey = tenantProvider.GetCurrentTenantId() == Guid.Empty ? "platform" : tenantProvider.GetCurrentTenantId().ToString("N");
+ var tenantKey = tenantProvider.GetCurrentTenantId() == 0 ? "platform" : tenantProvider.GetCurrentTenantId().ToString();
var cacheKey = $"{codeOptions.CachePrefix}:{tenantKey}:{request.Scene}:{phone}";
var cachedCode = await cache.GetStringAsync(cacheKey, cancellationToken).ConfigureAwait(false);
diff --git a/src/Application/TakeoutSaaS.Application/Storage/Services/FileStorageService.cs b/src/Application/TakeoutSaaS.Application/Storage/Services/FileStorageService.cs
index 05a02f2..f2105c5 100644
--- a/src/Application/TakeoutSaaS.Application/Storage/Services/FileStorageService.cs
+++ b/src/Application/TakeoutSaaS.Application/Storage/Services/FileStorageService.cs
@@ -212,7 +212,7 @@ public sealed class FileStorageService(
private string BuildObjectKey(UploadFileType type, string extension)
{
var tenantId = tenantProvider.GetCurrentTenantId();
- var tenantSegment = tenantId == Guid.Empty ? "platform" : tenantId.ToString("N");
+ var tenantSegment = tenantId == 0 ? "platform" : tenantId.ToString();
var folder = type.ToFolderName();
var now = DateTime.UtcNow;
var fileName = $"{Guid.NewGuid():N}{extension}";
diff --git a/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj b/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj
index 15da3e6..e320f29 100644
--- a/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj
+++ b/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj
@@ -8,6 +8,7 @@
+
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/AuditableEntityBase.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/AuditableEntityBase.cs
index 3aaedf9..6dbcf9c 100644
--- a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/AuditableEntityBase.cs
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/AuditableEntityBase.cs
@@ -23,15 +23,15 @@ public abstract class AuditableEntityBase : EntityBase, IAuditableEntity
///
/// 创建人用户标识,匿名或系统操作时为 null。
///
- public Guid? CreatedBy { get; set; }
+ public long? CreatedBy { get; set; }
///
/// 最后更新人用户标识,匿名或系统操作时为 null。
///
- public Guid? UpdatedBy { get; set; }
+ public long? UpdatedBy { get; set; }
///
/// 删除人用户标识(软删除),未删除时为 null。
///
- public Guid? DeletedBy { get; set; }
+ public long? DeletedBy { get; set; }
}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/EntityBase.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/EntityBase.cs
index 1cd539e..e1e2aa4 100644
--- a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/EntityBase.cs
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/EntityBase.cs
@@ -8,5 +8,5 @@ public abstract class EntityBase
///
/// 实体唯一标识。
///
- public Guid Id { get; set; }
+ public long Id { get; set; }
}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IAuditableEntity.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IAuditableEntity.cs
index 7168803..844ad54 100644
--- a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IAuditableEntity.cs
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IAuditableEntity.cs
@@ -23,15 +23,15 @@ public interface IAuditableEntity : ISoftDeleteEntity
///
/// 创建人用户标识,匿名或系统操作时为 null。
///
- Guid? CreatedBy { get; set; }
+ long? CreatedBy { get; set; }
///
/// 最后更新人用户标识,匿名或系统操作时为 null。
///
- Guid? UpdatedBy { get; set; }
+ long? UpdatedBy { get; set; }
///
/// 删除人用户标识(软删除),未删除时为 null。
///
- Guid? DeletedBy { get; set; }
+ long? DeletedBy { get; set; }
}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IMultiTenantEntity.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IMultiTenantEntity.cs
index 5ea8f7d..1a0fecd 100644
--- a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IMultiTenantEntity.cs
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IMultiTenantEntity.cs
@@ -8,5 +8,5 @@ public interface IMultiTenantEntity
///
/// 所属租户 ID。
///
- Guid TenantId { get; set; }
+ long TenantId { get; set; }
}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/MultiTenantEntityBase.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/MultiTenantEntityBase.cs
index 59bf1f8..df6417e 100644
--- a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/MultiTenantEntityBase.cs
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/MultiTenantEntityBase.cs
@@ -8,5 +8,5 @@ public abstract class MultiTenantEntityBase : AuditableEntityBase, IMultiTenantE
///
/// 所属租户 ID。
///
- public Guid TenantId { get; set; }
+ public long TenantId { get; set; }
}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IIdGenerator.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IIdGenerator.cs
new file mode 100644
index 0000000..ce8dff4
--- /dev/null
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IIdGenerator.cs
@@ -0,0 +1,13 @@
+namespace TakeoutSaaS.Shared.Abstractions.Ids;
+
+///
+/// 雪花 ID 生成器接口。
+///
+public interface IIdGenerator
+{
+ ///
+ /// 生成下一个唯一长整型 ID。
+ ///
+ /// 雪花 ID。
+ long NextId();
+}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IdGeneratorOptions.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IdGeneratorOptions.cs
new file mode 100644
index 0000000..6d9b40f
--- /dev/null
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Ids/IdGeneratorOptions.cs
@@ -0,0 +1,26 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace TakeoutSaaS.Shared.Abstractions.Ids;
+
+///
+/// 雪花 ID 生成器配置。
+///
+public sealed class IdGeneratorOptions
+{
+ ///
+ /// 配置节名称。
+ ///
+ public const string SectionName = "IdGenerator";
+
+ ///
+ /// 工作节点标识,0-31。
+ ///
+ [Range(0, 31)]
+ public int WorkerId { get; set; }
+
+ ///
+ /// 机房标识,0-31。
+ ///
+ [Range(0, 31)]
+ public int DatacenterId { get; set; }
+}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Results/ApiResponse.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Results/ApiResponse.cs
index 50401a7..b49a215 100644
--- a/src/Core/TakeoutSaaS.Shared.Abstractions/Results/ApiResponse.cs
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Results/ApiResponse.cs
@@ -99,6 +99,65 @@ public sealed record ApiResponse
return TraceContext.TraceId;
}
- return Activity.Current?.Id ?? Guid.NewGuid().ToString("N");
+ if (!string.IsNullOrWhiteSpace(TraceContext.TraceId))
+ {
+ return TraceContext.TraceId;
+ }
+
+ if (Activity.Current?.Id is { } id && !string.IsNullOrWhiteSpace(id))
+ {
+ return id;
+ }
+
+ return IdFallbackGenerator.Instance.NextId().ToString();
+ }
+}
+
+internal sealed class IdFallbackGenerator
+{
+ private static readonly Lazy Lazy = new(() => new IdFallbackGenerator());
+ public static IdFallbackGenerator Instance => Lazy.Value;
+
+ private readonly object _sync = new();
+ private long _lastTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ private long _sequence;
+
+ private IdFallbackGenerator()
+ {
+ }
+
+ public long NextId()
+ {
+ lock (_sync)
+ {
+ var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ if (timestamp == _lastTimestamp)
+ {
+ _sequence = (_sequence + 1) & 4095;
+ if (_sequence == 0)
+ {
+ timestamp = WaitNextMillis(_lastTimestamp);
+ }
+ }
+ else
+ {
+ _sequence = 0;
+ }
+
+ _lastTimestamp = timestamp;
+ return ((timestamp - 1577836800000L) << 22) | _sequence;
+ }
+ }
+
+ private static long WaitNextMillis(long lastTimestamp)
+ {
+ var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ while (timestamp <= lastTimestamp)
+ {
+ Thread.SpinWait(100);
+ timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ }
+
+ return timestamp;
}
}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Security/ICurrentUserAccessor.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Security/ICurrentUserAccessor.cs
index 9b7e7fb..ba0d7e2 100644
--- a/src/Core/TakeoutSaaS.Shared.Abstractions/Security/ICurrentUserAccessor.cs
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Security/ICurrentUserAccessor.cs
@@ -8,7 +8,7 @@ public interface ICurrentUserAccessor
///
/// 当前用户 ID,未登录时为 Guid.Empty。
///
- Guid UserId { get; }
+ long UserId { get; }
///
/// 是否已登录。
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Serialization/SnowflakeIdJsonConverter.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Serialization/SnowflakeIdJsonConverter.cs
new file mode 100644
index 0000000..b7f82a0
--- /dev/null
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Serialization/SnowflakeIdJsonConverter.cs
@@ -0,0 +1,52 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace TakeoutSaaS.Shared.Abstractions.Serialization;
+
+///
+/// 将 long 类型的雪花 ID 以字符串形式序列化/反序列化,避免前端精度丢失。
+///
+public sealed class SnowflakeIdJsonConverter : JsonConverter
+{
+ ///
+ public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return reader.TokenType switch
+ {
+ JsonTokenType.Number => reader.GetInt64(),
+ JsonTokenType.String when long.TryParse(reader.GetString(), out var value) => value,
+ JsonTokenType.Null => 0,
+ _ => throw new JsonException("无法解析雪花 ID")
+ };
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value == 0 ? "0" : value.ToString());
+ }
+}
+
+///
+/// 可空雪花 ID 转换器。
+///
+public sealed class NullableSnowflakeIdJsonConverter : JsonConverter
+{
+ ///
+ public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return reader.TokenType switch
+ {
+ JsonTokenType.Number => reader.GetInt64(),
+ JsonTokenType.String when long.TryParse(reader.GetString(), out var value) => value,
+ JsonTokenType.Null => null,
+ _ => throw new JsonException("无法解析雪花 ID")
+ };
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.HasValue ? value.Value.ToString() : null);
+ }
+}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/ITenantProvider.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/ITenantProvider.cs
index 41b999f..02c818a 100644
--- a/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/ITenantProvider.cs
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/ITenantProvider.cs
@@ -8,5 +8,5 @@ public interface ITenantProvider
///
/// 获取当前租户 ID,未解析时返回 Guid.Empty。
///
- Guid GetCurrentTenantId();
+ long GetCurrentTenantId();
}
diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/TenantContext.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/TenantContext.cs
index a4686a4..a53b38f 100644
--- a/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/TenantContext.cs
+++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/TenantContext.cs
@@ -8,7 +8,7 @@ public sealed class TenantContext
///
/// 未解析到租户时的默认上下文。
///
- public static TenantContext Empty { get; } = new(Guid.Empty, null, "unresolved");
+ public static TenantContext Empty { get; } = new(0, null, "unresolved");
///
/// 初始化租户上下文。
@@ -16,7 +16,7 @@ public sealed class TenantContext
/// 租户 ID
/// 租户编码(可选)
/// 解析来源
- public TenantContext(Guid tenantId, string? tenantCode, string source)
+ public TenantContext(long tenantId, string? tenantCode, string source)
{
TenantId = tenantId;
TenantCode = tenantCode;
@@ -26,7 +26,7 @@ public sealed class TenantContext
///
/// 当前租户 ID,未解析时为 Guid.Empty。
///
- public Guid TenantId { get; }
+ public long TenantId { get; }
///
/// 当前租户编码(例如子域名或业务编码),可为空。
@@ -41,5 +41,5 @@ public sealed class TenantContext
///
/// 是否已成功解析到租户。
///
- public bool IsResolved => TenantId != Guid.Empty;
+ public bool IsResolved => TenantId != 0;
}
diff --git a/src/Core/TakeoutSaaS.Shared.Kernel/Ids/SnowflakeIdGenerator.cs b/src/Core/TakeoutSaaS.Shared.Kernel/Ids/SnowflakeIdGenerator.cs
new file mode 100644
index 0000000..533789d
--- /dev/null
+++ b/src/Core/TakeoutSaaS.Shared.Kernel/Ids/SnowflakeIdGenerator.cs
@@ -0,0 +1,111 @@
+using System.Diagnostics;
+using System.Security.Cryptography;
+using System.Threading;
+using TakeoutSaaS.Shared.Abstractions.Ids;
+
+namespace TakeoutSaaS.Shared.Kernel.Ids;
+
+///
+/// 基于雪花算法的长整型 ID 生成器。
+///
+public sealed class SnowflakeIdGenerator : IIdGenerator
+{
+ private const long Twepoch = 1577836800000L; // 2020-01-01 UTC
+ private const int WorkerIdBits = 5;
+ private const int DatacenterIdBits = 5;
+ private const int SequenceBits = 12;
+
+ private const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits);
+ private const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits);
+
+ private const int WorkerIdShift = SequenceBits;
+ private const int DatacenterIdShift = SequenceBits + WorkerIdBits;
+ private const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;
+ private const long SequenceMask = -1L ^ (-1L << SequenceBits);
+
+ private readonly long _workerId;
+ private readonly long _datacenterId;
+ private long _lastTimestamp = -1L;
+ private long _sequence;
+ private readonly object _syncRoot = new();
+
+ ///
+ /// 初始化生成器。
+ ///
+ /// 工作节点 ID。
+ /// 机房 ID。
+ public SnowflakeIdGenerator(long workerId = 0, long datacenterId = 0)
+ {
+ _workerId = Normalize(workerId, MaxWorkerId, nameof(workerId));
+ _datacenterId = Normalize(datacenterId, MaxDatacenterId, nameof(datacenterId));
+ _sequence = RandomNumberGenerator.GetInt32(0, (int)SequenceMask);
+ }
+
+ ///
+ public long NextId()
+ {
+ lock (_syncRoot)
+ {
+ var timestamp = CurrentTimeMillis();
+
+ if (timestamp < _lastTimestamp)
+ {
+ // 时钟回拨时等待到下一毫秒。
+ var wait = _lastTimestamp - timestamp;
+ Thread.Sleep(TimeSpan.FromMilliseconds(wait));
+ timestamp = CurrentTimeMillis();
+ if (timestamp < _lastTimestamp)
+ {
+ throw new InvalidOperationException($"系统时钟回拨 {_lastTimestamp - timestamp} 毫秒,无法生成 ID。");
+ }
+ }
+
+ if (_lastTimestamp == timestamp)
+ {
+ _sequence = (_sequence + 1) & SequenceMask;
+ if (_sequence == 0)
+ {
+ timestamp = WaitNextMillis(_lastTimestamp);
+ }
+ }
+ else
+ {
+ _sequence = 0;
+ }
+
+ _lastTimestamp = timestamp;
+
+ var id = ((timestamp - Twepoch) << TimestampLeftShift)
+ | (_datacenterId << DatacenterIdShift)
+ | (_workerId << WorkerIdShift)
+ | _sequence;
+
+ Debug.Assert(id > 0);
+ return id;
+ }
+ }
+
+ private static long WaitNextMillis(long lastTimestamp)
+ {
+ var timestamp = CurrentTimeMillis();
+ while (timestamp <= lastTimestamp)
+ {
+ Thread.SpinWait(50);
+ timestamp = CurrentTimeMillis();
+ }
+
+ return timestamp;
+ }
+
+ private static long CurrentTimeMillis() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+
+ private static long Normalize(long value, long max, string name)
+ {
+ if (value < 0 || value > max)
+ {
+ throw new ArgumentOutOfRangeException(name, value, $"取值范围 0~{max}");
+ }
+
+ return value;
+ }
+}
diff --git a/src/Core/TakeoutSaaS.Shared.Web/Middleware/CorrelationIdMiddleware.cs b/src/Core/TakeoutSaaS.Shared.Web/Middleware/CorrelationIdMiddleware.cs
index b3cf763..ddbed3c 100644
--- a/src/Core/TakeoutSaaS.Shared.Web/Middleware/CorrelationIdMiddleware.cs
+++ b/src/Core/TakeoutSaaS.Shared.Web/Middleware/CorrelationIdMiddleware.cs
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Shared.Abstractions.Diagnostics;
+using TakeoutSaaS.Shared.Abstractions.Ids;
namespace TakeoutSaaS.Shared.Web.Middleware;
@@ -17,11 +18,13 @@ public sealed class CorrelationIdMiddleware
private readonly RequestDelegate _next;
private readonly ILogger _logger;
+ private readonly IIdGenerator _idGenerator;
- public CorrelationIdMiddleware(RequestDelegate next, ILogger logger)
+ public CorrelationIdMiddleware(RequestDelegate next, ILogger logger, IIdGenerator idGenerator)
{
_next = next;
_logger = logger;
+ _idGenerator = idGenerator;
}
public async Task InvokeAsync(HttpContext context)
@@ -52,7 +55,7 @@ public sealed class CorrelationIdMiddleware
}
}
- private static string ResolveTraceId(HttpContext context)
+ private string ResolveTraceId(HttpContext context)
{
if (TryGetHeader(context, TraceHeader, out var traceId))
{
@@ -64,7 +67,7 @@ public sealed class CorrelationIdMiddleware
return requestId;
}
- return Guid.NewGuid().ToString("N");
+ return _idGenerator.NextId().ToString();
}
private static bool TryGetHeader(HttpContext context, string headerName, out string value)
diff --git a/src/Core/TakeoutSaaS.Shared.Web/Security/ClaimsPrincipalExtensions.cs b/src/Core/TakeoutSaaS.Shared.Web/Security/ClaimsPrincipalExtensions.cs
index 8502109..05a90b2 100644
--- a/src/Core/TakeoutSaaS.Shared.Web/Security/ClaimsPrincipalExtensions.cs
+++ b/src/Core/TakeoutSaaS.Shared.Web/Security/ClaimsPrincipalExtensions.cs
@@ -9,20 +9,20 @@ namespace TakeoutSaaS.Shared.Web.Security;
public static class ClaimsPrincipalExtensions
{
///
- /// 获取当前用户 Id(不存在时返回 Guid.Empty)
+ /// 获取当前用户 Id(不存在时返回 0)。
///
- public static Guid GetUserId(this ClaimsPrincipal? principal)
+ public static long GetUserId(this ClaimsPrincipal? principal)
{
if (principal == null)
{
- return Guid.Empty;
+ return 0;
}
var identifier = principal.FindFirstValue(ClaimTypes.NameIdentifier)
?? principal.FindFirstValue("sub");
- return Guid.TryParse(identifier, out var userId)
+ return long.TryParse(identifier, out var userId)
? userId
- : Guid.Empty;
+ : 0;
}
}
diff --git a/src/Core/TakeoutSaaS.Shared.Web/Security/HttpContextCurrentUserAccessor.cs b/src/Core/TakeoutSaaS.Shared.Web/Security/HttpContextCurrentUserAccessor.cs
index d73fbbd..6256f05 100644
--- a/src/Core/TakeoutSaaS.Shared.Web/Security/HttpContextCurrentUserAccessor.cs
+++ b/src/Core/TakeoutSaaS.Shared.Web/Security/HttpContextCurrentUserAccessor.cs
@@ -20,23 +20,23 @@ public sealed class HttpContextCurrentUserAccessor : ICurrentUserAccessor
}
///
- public Guid UserId
+ public long UserId
{
get
{
var principal = _httpContextAccessor.HttpContext?.User;
if (principal == null || !principal.Identity?.IsAuthenticated == true)
{
- return Guid.Empty;
+ return 0;
}
var identifier = principal.FindFirstValue(ClaimTypes.NameIdentifier)
?? principal.FindFirstValue("sub");
- return Guid.TryParse(identifier, out var id) ? id : Guid.Empty;
+ return long.TryParse(identifier, out var id) ? id : 0;
}
}
///
- public bool IsAuthenticated => UserId != Guid.Empty;
+ public bool IsAuthenticated => UserId != 0;
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricAlertRule.cs b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricAlertRule.cs
index 694dfca..d5ae3ee 100644
--- a/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricAlertRule.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricAlertRule.cs
@@ -11,7 +11,7 @@ public sealed class MetricAlertRule : MultiTenantEntityBase
///
/// 关联指标。
///
- public Guid MetricDefinitionId { get; set; }
+ public long MetricDefinitionId { get; set; }
///
/// 触发条件 JSON。
diff --git a/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricSnapshot.cs b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricSnapshot.cs
index cb0e726..234b0a0 100644
--- a/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricSnapshot.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Analytics/Entities/MetricSnapshot.cs
@@ -10,7 +10,7 @@ public sealed class MetricSnapshot : MultiTenantEntityBase
///
/// 指标定义 ID。
///
- public Guid MetricDefinitionId { get; set; }
+ public long MetricDefinitionId { get; set; }
///
/// 维度键(JSON)。
diff --git a/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/Coupon.cs b/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/Coupon.cs
index c327f0b..7c375e1 100644
--- a/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/Coupon.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Coupons/Entities/Coupon.cs
@@ -11,7 +11,7 @@ public sealed class Coupon : MultiTenantEntityBase
///
/// 模板标识。
///
- public Guid CouponTemplateId { get; set; }
+ public long CouponTemplateId { get; set; }
///
/// 券码或序列号。
@@ -21,12 +21,12 @@ public sealed class Coupon : MultiTenantEntityBase
///
/// 归属用户。
///
- public Guid UserId { get; set; }
+ public long UserId { get; set; }
///
/// 订单 ID(已使用时记录)。
///
- public Guid? OrderId { get; set; }
+ public long? OrderId { get; set; }
///
/// 状态。
diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatMessage.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatMessage.cs
index 56799b5..ba0d3e6 100644
--- a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatMessage.cs
+++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatMessage.cs
@@ -11,7 +11,7 @@ public sealed class ChatMessage : MultiTenantEntityBase
///
/// 会话标识。
///
- public Guid ChatSessionId { get; set; }
+ public long ChatSessionId { get; set; }
///
/// 发送方类型。
@@ -21,7 +21,7 @@ public sealed class ChatMessage : MultiTenantEntityBase
///
/// 发送方用户 ID。
///
- public Guid? SenderUserId { get; set; }
+ public long? SenderUserId { get; set; }
///
/// 消息内容。
diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatSession.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatSession.cs
index d2528ea..d9ea66d 100644
--- a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatSession.cs
+++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/ChatSession.cs
@@ -16,17 +16,17 @@ public sealed class ChatSession : MultiTenantEntityBase
///
/// 顾客用户 ID。
///
- public Guid CustomerUserId { get; set; }
+ public long CustomerUserId { get; set; }
///
/// 当前客服员工 ID。
///
- public Guid? AgentUserId { get; set; }
+ public long? AgentUserId { get; set; }
///
/// 所属门店(可空为平台)。
///
- public Guid? StoreId { get; set; }
+ public long? StoreId { get; set; }
///
/// 会话状态。
diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/SupportTicket.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/SupportTicket.cs
index 931727d..39c5ae4 100644
--- a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/SupportTicket.cs
+++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/SupportTicket.cs
@@ -16,12 +16,12 @@ public sealed class SupportTicket : MultiTenantEntityBase
///
/// 客户用户 ID。
///
- public Guid CustomerUserId { get; set; }
+ public long CustomerUserId { get; set; }
///
/// 关联订单(如有)。
///
- public Guid? OrderId { get; set; }
+ public long? OrderId { get; set; }
///
/// 工单主题。
@@ -46,7 +46,7 @@ public sealed class SupportTicket : MultiTenantEntityBase
///
/// 指派的客服。
///
- public Guid? AssignedAgentId { get; set; }
+ public long? AssignedAgentId { get; set; }
///
/// 关闭时间。
diff --git a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/TicketComment.cs b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/TicketComment.cs
index f6abba6..fd24969 100644
--- a/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/TicketComment.cs
+++ b/src/Domain/TakeoutSaaS.Domain/CustomerService/Entities/TicketComment.cs
@@ -10,12 +10,12 @@ public sealed class TicketComment : MultiTenantEntityBase
///
/// 工单标识。
///
- public Guid SupportTicketId { get; set; }
+ public long SupportTicketId { get; set; }
///
/// 评论人 ID。
///
- public Guid? AuthorUserId { get; set; }
+ public long? AuthorUserId { get; set; }
///
/// 评论内容。
diff --git a/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryEvent.cs b/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryEvent.cs
index 771f564..a10fd61 100644
--- a/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryEvent.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryEvent.cs
@@ -11,7 +11,7 @@ public sealed class DeliveryEvent : MultiTenantEntityBase
///
/// 配送单标识。
///
- public Guid DeliveryOrderId { get; set; }
+ public long DeliveryOrderId { get; set; }
///
/// 事件类型。
diff --git a/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryOrder.cs b/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryOrder.cs
index 31dbfc1..c547d6b 100644
--- a/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryOrder.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Deliveries/Entities/DeliveryOrder.cs
@@ -8,7 +8,7 @@ namespace TakeoutSaaS.Domain.Deliveries.Entities;
///
public sealed class DeliveryOrder : MultiTenantEntityBase
{
- public Guid OrderId { get; set; }
+ public long OrderId { get; set; }
///
/// 配送服务商。
diff --git a/src/Domain/TakeoutSaaS.Domain/Deliveries/Repositories/IDeliveryRepository.cs b/src/Domain/TakeoutSaaS.Domain/Deliveries/Repositories/IDeliveryRepository.cs
new file mode 100644
index 0000000..010793d
--- /dev/null
+++ b/src/Domain/TakeoutSaaS.Domain/Deliveries/Repositories/IDeliveryRepository.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using TakeoutSaaS.Domain.Deliveries.Entities;
+
+namespace TakeoutSaaS.Domain.Deliveries.Repositories;
+
+///
+/// 配送聚合仓储契约。
+///
+public interface IDeliveryRepository
+{
+ ///
+ /// 依据标识获取配送单。
+ ///
+ Task FindByIdAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 依据订单标识获取配送单。
+ ///
+ Task FindByOrderIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取配送事件轨迹。
+ ///
+ Task> GetEventsAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增配送单。
+ ///
+ Task AddDeliveryOrderAsync(DeliveryOrder deliveryOrder, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增配送事件。
+ ///
+ Task AddEventAsync(DeliveryEvent deliveryEvent, CancellationToken cancellationToken = default);
+
+ ///
+ /// 持久化变更。
+ ///
+ Task SaveChangesAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/Domain/TakeoutSaaS.Domain/Dictionary/Entities/DictionaryItem.cs b/src/Domain/TakeoutSaaS.Domain/Dictionary/Entities/DictionaryItem.cs
index 4d47912..1a38aba 100644
--- a/src/Domain/TakeoutSaaS.Domain/Dictionary/Entities/DictionaryItem.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Dictionary/Entities/DictionaryItem.cs
@@ -10,7 +10,7 @@ public sealed class DictionaryItem : MultiTenantEntityBase
///
/// 关联分组 ID。
///
- public Guid GroupId { get; set; }
+ public long GroupId { get; set; }
///
/// 字典项键。
diff --git a/src/Domain/TakeoutSaaS.Domain/Dictionary/Repositories/IDictionaryRepository.cs b/src/Domain/TakeoutSaaS.Domain/Dictionary/Repositories/IDictionaryRepository.cs
index ee338f2..9a9b427 100644
--- a/src/Domain/TakeoutSaaS.Domain/Dictionary/Repositories/IDictionaryRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Dictionary/Repositories/IDictionaryRepository.cs
@@ -14,7 +14,7 @@ public interface IDictionaryRepository
///
/// 依据 ID 获取分组。
///
- Task FindGroupByIdAsync(Guid id, CancellationToken cancellationToken = default);
+ Task FindGroupByIdAsync(long id, CancellationToken cancellationToken = default);
///
/// 依据编码获取分组。
@@ -39,17 +39,17 @@ public interface IDictionaryRepository
///
/// 依据 ID 获取字典项。
///
- Task FindItemByIdAsync(Guid id, CancellationToken cancellationToken = default);
+ Task FindItemByIdAsync(long id, CancellationToken cancellationToken = default);
///
/// 获取某分组下的所有字典项。
///
- Task> GetItemsByGroupIdAsync(Guid groupId, CancellationToken cancellationToken = default);
+ Task> GetItemsByGroupIdAsync(long groupId, CancellationToken cancellationToken = default);
///
/// 按分组编码集合获取字典项(可包含系统参数)。
///
- Task> GetItemsByCodesAsync(IEnumerable codes, Guid tenantId, bool includeSystem, CancellationToken cancellationToken = default);
+ Task> GetItemsByCodesAsync(IEnumerable codes, long tenantId, bool includeSystem, CancellationToken cancellationToken = default);
///
/// 新增字典项。
diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliateOrder.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliateOrder.cs
index 0b1bb24..856f922 100644
--- a/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliateOrder.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliateOrder.cs
@@ -11,17 +11,17 @@ public sealed class AffiliateOrder : MultiTenantEntityBase
///
/// 推广人标识。
///
- public Guid AffiliatePartnerId { get; set; }
+ public long AffiliatePartnerId { get; set; }
///
/// 关联订单。
///
- public Guid OrderId { get; set; }
+ public long OrderId { get; set; }
///
/// 用户 ID。
///
- public Guid BuyerUserId { get; set; }
+ public long BuyerUserId { get; set; }
///
/// 订单金额。
diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePartner.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePartner.cs
index d9ceaa1..551e39d 100644
--- a/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePartner.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePartner.cs
@@ -11,7 +11,7 @@ public sealed class AffiliatePartner : MultiTenantEntityBase
///
/// 用户 ID(如绑定平台账号)。
///
- public Guid? UserId { get; set; }
+ public long? UserId { get; set; }
///
/// 昵称或渠道名称。
diff --git a/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePayout.cs b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePayout.cs
index 774c4e4..c9955f9 100644
--- a/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePayout.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Distribution/Entities/AffiliatePayout.cs
@@ -11,7 +11,7 @@ public sealed class AffiliatePayout : MultiTenantEntityBase
///
/// 合作伙伴标识。
///
- public Guid AffiliatePartnerId { get; set; }
+ public long AffiliatePartnerId { get; set; }
///
/// 结算周期描述。
diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInRecord.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInRecord.cs
index 9f41172..011d048 100644
--- a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInRecord.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CheckInRecord.cs
@@ -10,12 +10,12 @@ public sealed class CheckInRecord : MultiTenantEntityBase
///
/// 活动标识。
///
- public Guid CheckInCampaignId { get; set; }
+ public long CheckInCampaignId { get; set; }
///
/// 用户标识。
///
- public Guid UserId { get; set; }
+ public long UserId { get; set; }
///
/// 签到日期(本地)。
diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityComment.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityComment.cs
index 3888bfd..f158121 100644
--- a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityComment.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityComment.cs
@@ -10,12 +10,12 @@ public sealed class CommunityComment : MultiTenantEntityBase
///
/// 动态标识。
///
- public Guid PostId { get; set; }
+ public long PostId { get; set; }
///
/// 评论人。
///
- public Guid AuthorUserId { get; set; }
+ public long AuthorUserId { get; set; }
///
/// 评论内容。
@@ -25,7 +25,7 @@ public sealed class CommunityComment : MultiTenantEntityBase
///
/// 父级评论 ID。
///
- public Guid? ParentId { get; set; }
+ public long? ParentId { get; set; }
///
/// 状态。
diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityPost.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityPost.cs
index 73c7e21..6b77d38 100644
--- a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityPost.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityPost.cs
@@ -11,7 +11,7 @@ public sealed class CommunityPost : MultiTenantEntityBase
///
/// 作者用户 ID。
///
- public Guid AuthorUserId { get; set; }
+ public long AuthorUserId { get; set; }
///
/// 标题。
diff --git a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityReaction.cs b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityReaction.cs
index b7d5c19..4d1dbdc 100644
--- a/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityReaction.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Engagement/Entities/CommunityReaction.cs
@@ -11,12 +11,12 @@ public sealed class CommunityReaction : MultiTenantEntityBase
///
/// 动态 ID。
///
- public Guid PostId { get; set; }
+ public long PostId { get; set; }
///
/// 用户 ID。
///
- public Guid UserId { get; set; }
+ public long UserId { get; set; }
///
/// 反应类型。
diff --git a/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupOrder.cs b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupOrder.cs
index e5412de..86d31e3 100644
--- a/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupOrder.cs
+++ b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupOrder.cs
@@ -11,12 +11,12 @@ public sealed class GroupOrder : MultiTenantEntityBase
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 关联商品或套餐。
///
- public Guid ProductId { get; set; }
+ public long ProductId { get; set; }
///
/// 拼单编号。
@@ -26,7 +26,7 @@ public sealed class GroupOrder : MultiTenantEntityBase
///
/// 团长用户 ID。
///
- public Guid LeaderUserId { get; set; }
+ public long LeaderUserId { get; set; }
///
/// 成团需要的人数。
diff --git a/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupParticipant.cs b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupParticipant.cs
index 61e169a..943ab14 100644
--- a/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupParticipant.cs
+++ b/src/Domain/TakeoutSaaS.Domain/GroupBuying/Entities/GroupParticipant.cs
@@ -11,17 +11,17 @@ public sealed class GroupParticipant : MultiTenantEntityBase
///
/// 拼单活动标识。
///
- public Guid GroupOrderId { get; set; }
+ public long GroupOrderId { get; set; }
///
/// 对应订单标识。
///
- public Guid OrderId { get; set; }
+ public long OrderId { get; set; }
///
/// 用户标识。
///
- public Guid UserId { get; set; }
+ public long UserId { get; set; }
///
/// 参与状态。
diff --git a/src/Domain/TakeoutSaaS.Domain/Identity/Entities/IdentityUser.cs b/src/Domain/TakeoutSaaS.Domain/Identity/Entities/IdentityUser.cs
index 6ccb304..6080712 100644
--- a/src/Domain/TakeoutSaaS.Domain/Identity/Entities/IdentityUser.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Identity/Entities/IdentityUser.cs
@@ -25,7 +25,7 @@ public sealed class IdentityUser : MultiTenantEntityBase
///
/// 所属商户(平台管理员为空)。
///
- public Guid? MerchantId { get; set; }
+ public long? MerchantId { get; set; }
///
/// 角色集合。
diff --git a/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IIdentityUserRepository.cs b/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IIdentityUserRepository.cs
index 2d5eae1..cc74343 100644
--- a/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IIdentityUserRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IIdentityUserRepository.cs
@@ -18,5 +18,5 @@ public interface IIdentityUserRepository
///
/// 根据 ID 获取后台用户。
///
- Task FindByIdAsync(Guid userId, CancellationToken cancellationToken = default);
+ Task FindByIdAsync(long userId, CancellationToken cancellationToken = default);
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IMiniUserRepository.cs b/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IMiniUserRepository.cs
index 4688ea6..41594c2 100644
--- a/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IMiniUserRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IMiniUserRepository.cs
@@ -21,7 +21,7 @@ public interface IMiniUserRepository
/// 用户 ID
/// 取消令牌
/// 小程序用户,如果不存在则返回 null
- Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default);
+ Task FindByIdAsync(long id, CancellationToken cancellationToken = default);
///
/// 创建或更新小程序用户(如果 OpenId 已存在则更新,否则创建)。
@@ -33,5 +33,5 @@ public interface IMiniUserRepository
/// 租户 ID
/// 取消令牌
/// 创建或更新后的小程序用户
- Task CreateOrUpdateAsync(string openId, string? unionId, string? nickname, string? avatar, Guid tenantId, CancellationToken cancellationToken = default);
+ Task CreateOrUpdateAsync(string openId, string? unionId, string? nickname, string? avatar, long tenantId, CancellationToken cancellationToken = default);
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryAdjustment.cs b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryAdjustment.cs
index 36b7d59..ef145fd 100644
--- a/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryAdjustment.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryAdjustment.cs
@@ -11,7 +11,7 @@ public sealed class InventoryAdjustment : MultiTenantEntityBase
///
/// 对应的库存记录标识。
///
- public Guid InventoryItemId { get; set; }
+ public long InventoryItemId { get; set; }
///
/// 调整类型。
@@ -31,7 +31,7 @@ public sealed class InventoryAdjustment : MultiTenantEntityBase
///
/// 操作人标识。
///
- public Guid? OperatorId { get; set; }
+ public long? OperatorId { get; set; }
///
/// 发生时间。
diff --git a/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryBatch.cs b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryBatch.cs
index 8a764b3..eee47f4 100644
--- a/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryBatch.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryBatch.cs
@@ -10,12 +10,12 @@ public sealed class InventoryBatch : MultiTenantEntityBase
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// SKU 标识。
///
- public Guid ProductSkuId { get; set; }
+ public long ProductSkuId { get; set; }
///
/// 批次编号。
diff --git a/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryItem.cs b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryItem.cs
index 6432253..6aca234 100644
--- a/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryItem.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Inventory/Entities/InventoryItem.cs
@@ -10,12 +10,12 @@ public sealed class InventoryItem : MultiTenantEntityBase
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// SKU 标识。
///
- public Guid ProductSkuId { get; set; }
+ public long ProductSkuId { get; set; }
///
/// 批次编号,可为空表示混批。
diff --git a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberGrowthLog.cs b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberGrowthLog.cs
index b06f23f..b65003d 100644
--- a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberGrowthLog.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberGrowthLog.cs
@@ -10,7 +10,7 @@ public sealed class MemberGrowthLog : MultiTenantEntityBase
///
/// 会员标识。
///
- public Guid MemberId { get; set; }
+ public long MemberId { get; set; }
///
/// 变动数量。
diff --git a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberPointLedger.cs b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberPointLedger.cs
index 07c4fbe..3e4dd07 100644
--- a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberPointLedger.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberPointLedger.cs
@@ -11,7 +11,7 @@ public sealed class MemberPointLedger : MultiTenantEntityBase
///
/// 会员标识。
///
- public Guid MemberId { get; set; }
+ public long MemberId { get; set; }
///
/// 变动数量,可为负值。
@@ -31,7 +31,7 @@ public sealed class MemberPointLedger : MultiTenantEntityBase
///
/// 来源 ID(订单、活动等)。
///
- public Guid? SourceId { get; set; }
+ public long? SourceId { get; set; }
///
/// 发生时间。
diff --git a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberProfile.cs b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberProfile.cs
index 7378ccd..32df115 100644
--- a/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberProfile.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Membership/Entities/MemberProfile.cs
@@ -11,7 +11,7 @@ public sealed class MemberProfile : MultiTenantEntityBase
///
/// 用户标识。
///
- public Guid UserId { get; set; }
+ public long UserId { get; set; }
///
/// 手机号。
@@ -31,7 +31,7 @@ public sealed class MemberProfile : MultiTenantEntityBase
///
/// 当前会员等级 ID。
///
- public Guid? MemberTierId { get; set; }
+ public long? MemberTierId { get; set; }
///
/// 会员状态。
diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantContract.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantContract.cs
index e8a21bb..cf39793 100644
--- a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantContract.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantContract.cs
@@ -11,7 +11,7 @@ public sealed class MerchantContract : MultiTenantEntityBase
///
/// 所属商户标识。
///
- public Guid MerchantId { get; set; }
+ public long MerchantId { get; set; }
///
/// 合同编号。
diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantDocument.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantDocument.cs
index 572f718..433e2df 100644
--- a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantDocument.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantDocument.cs
@@ -11,7 +11,7 @@ public sealed class MerchantDocument : MultiTenantEntityBase
///
/// 所属商户标识。
///
- public Guid MerchantId { get; set; }
+ public long MerchantId { get; set; }
///
/// 证照类型。
diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantStaff.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantStaff.cs
index 273054a..50026e5 100644
--- a/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantStaff.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Entities/MerchantStaff.cs
@@ -11,12 +11,12 @@ public sealed class MerchantStaff : MultiTenantEntityBase
///
/// 所属商户标识。
///
- public Guid MerchantId { get; set; }
+ public long MerchantId { get; set; }
///
/// 可选的关联门店 ID。
///
- public Guid? StoreId { get; set; }
+ public long? StoreId { get; set; }
///
/// 员工姓名。
@@ -36,7 +36,7 @@ public sealed class MerchantStaff : MultiTenantEntityBase
///
/// 登录账号 ID(指向统一身份体系)。
///
- public Guid? IdentityUserId { get; set; }
+ public long? IdentityUserId { get; set; }
///
/// 员工角色类型。
diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs
new file mode 100644
index 0000000..a735ff7
--- /dev/null
+++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using TakeoutSaaS.Domain.Merchants.Entities;
+using TakeoutSaaS.Domain.Merchants.Enums;
+
+namespace TakeoutSaaS.Domain.Merchants.Repositories;
+
+///
+/// 商户聚合仓储契约,提供基础 CRUD 与查询能力。
+///
+public interface IMerchantRepository
+{
+ ///
+ /// 依据标识获取商户。
+ ///
+ Task FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 按状态筛选商户列表。
+ ///
+ Task> SearchAsync(long tenantId, MerchantStatus? status, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取指定商户的员工列表。
+ ///
+ Task> GetStaffAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取指定商户的合同列表。
+ ///
+ Task> GetContractsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取指定商户的资质文件列表。
+ ///
+ Task> GetDocumentsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增商户主体。
+ ///
+ Task AddMerchantAsync(Merchant merchant, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增商户员工。
+ ///
+ Task AddStaffAsync(MerchantStaff staff, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增商户合同。
+ ///
+ Task AddContractAsync(MerchantContract contract, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增商户资质文件。
+ ///
+ Task AddDocumentAsync(MerchantDocument document, CancellationToken cancellationToken = default);
+
+ ///
+ /// 持久化变更。
+ ///
+ Task SaveChangesAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/MapLocation.cs b/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/MapLocation.cs
index 74d9e14..54524f9 100644
--- a/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/MapLocation.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/MapLocation.cs
@@ -10,7 +10,7 @@ public sealed class MapLocation : MultiTenantEntityBase
///
/// 关联门店 ID,可空表示独立 POI。
///
- public Guid? StoreId { get; set; }
+ public long? StoreId { get; set; }
///
/// 名称。
diff --git a/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/NavigationRequest.cs b/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/NavigationRequest.cs
index 284f1a5..f1bdf4b 100644
--- a/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/NavigationRequest.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Navigation/Entities/NavigationRequest.cs
@@ -11,12 +11,12 @@ public sealed class NavigationRequest : MultiTenantEntityBase
///
/// 用户 ID。
///
- public Guid UserId { get; set; }
+ public long UserId { get; set; }
///
/// 门店 ID。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 来源通道(小程序、H5 等)。
diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItem.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItem.cs
index d19d659..655e308 100644
--- a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItem.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItem.cs
@@ -11,17 +11,17 @@ public sealed class CartItem : MultiTenantEntityBase
///
/// 所属购物车标识。
///
- public Guid ShoppingCartId { get; set; }
+ public long ShoppingCartId { get; set; }
///
/// 商品或 SKU 标识。
///
- public Guid ProductId { get; set; }
+ public long ProductId { get; set; }
///
/// SKU 标识。
///
- public Guid? ProductSkuId { get; set; }
+ public long? ProductSkuId { get; set; }
///
/// 商品名称快照。
diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItemAddon.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItemAddon.cs
index eb99a0d..867c098 100644
--- a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItemAddon.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CartItemAddon.cs
@@ -10,7 +10,7 @@ public sealed class CartItemAddon : MultiTenantEntityBase
///
/// 所属购物车条目。
///
- public Guid CartItemId { get; set; }
+ public long CartItemId { get; set; }
///
/// 选项名称。
@@ -25,5 +25,5 @@ public sealed class CartItemAddon : MultiTenantEntityBase
///
/// 选项 ID(可对应 ProductAddonOption)。
///
- public Guid? OptionId { get; set; }
+ public long? OptionId { get; set; }
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CheckoutSession.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CheckoutSession.cs
index a3beb7a..314aaa2 100644
--- a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CheckoutSession.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/CheckoutSession.cs
@@ -11,12 +11,12 @@ public sealed class CheckoutSession : MultiTenantEntityBase
///
/// 用户标识。
///
- public Guid UserId { get; set; }
+ public long UserId { get; set; }
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 会话 Token。
diff --git a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/ShoppingCart.cs b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/ShoppingCart.cs
index 7928c1e..6c7e6ff 100644
--- a/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/ShoppingCart.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Ordering/Entities/ShoppingCart.cs
@@ -11,12 +11,12 @@ public sealed class ShoppingCart : MultiTenantEntityBase
///
/// 用户标识。
///
- public Guid UserId { get; set; }
+ public long UserId { get; set; }
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 购物车状态,包含正常/锁定。
diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/Order.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/Order.cs
index 9a1c295..398f8f6 100644
--- a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/Order.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/Order.cs
@@ -17,7 +17,7 @@ public sealed class Order : MultiTenantEntityBase
///
/// 门店。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 下单渠道。
@@ -62,7 +62,7 @@ public sealed class Order : MultiTenantEntityBase
///
/// 预约 ID。
///
- public Guid? ReservationId { get; set; }
+ public long? ReservationId { get; set; }
///
/// 商品总额。
diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderItem.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderItem.cs
index 83681ca..63c5a67 100644
--- a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderItem.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderItem.cs
@@ -10,12 +10,12 @@ public sealed class OrderItem : MultiTenantEntityBase
///
/// 订单 ID。
///
- public Guid OrderId { get; set; }
+ public long OrderId { get; set; }
///
/// 商品 ID。
///
- public Guid ProductId { get; set; }
+ public long ProductId { get; set; }
///
/// 商品名称。
diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderStatusHistory.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderStatusHistory.cs
index 26823f2..c9b7a55 100644
--- a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderStatusHistory.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/OrderStatusHistory.cs
@@ -11,7 +11,7 @@ public sealed class OrderStatusHistory : MultiTenantEntityBase
///
/// 订单标识。
///
- public Guid OrderId { get; set; }
+ public long OrderId { get; set; }
///
/// 变更后的状态。
@@ -21,7 +21,7 @@ public sealed class OrderStatusHistory : MultiTenantEntityBase
///
/// 操作人标识(可为空表示系统)。
///
- public Guid? OperatorId { get; set; }
+ public long? OperatorId { get; set; }
///
/// 备注信息。
diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/RefundRequest.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/RefundRequest.cs
index d18d4ab..6b333e9 100644
--- a/src/Domain/TakeoutSaaS.Domain/Orders/Entities/RefundRequest.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Orders/Entities/RefundRequest.cs
@@ -11,7 +11,7 @@ public sealed class RefundRequest : MultiTenantEntityBase
///
/// 关联订单标识。
///
- public Guid OrderId { get; set; }
+ public long OrderId { get; set; }
///
/// 退款单号。
diff --git a/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs b/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs
new file mode 100644
index 0000000..baba30b
--- /dev/null
+++ b/src/Domain/TakeoutSaaS.Domain/Orders/Repositories/IOrderRepository.cs
@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using TakeoutSaaS.Domain.Orders.Entities;
+using TakeoutSaaS.Domain.Orders.Enums;
+using TakeoutSaaS.Domain.Payments.Enums;
+
+namespace TakeoutSaaS.Domain.Orders.Repositories;
+
+///
+/// 订单聚合仓储契约。
+///
+public interface IOrderRepository
+{
+ ///
+ /// 依据标识获取订单。
+ ///
+ Task FindByIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 依据订单号获取订单。
+ ///
+ Task FindByOrderNoAsync(string orderNo, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 按状态筛选订单列表。
+ ///
+ Task> SearchAsync(long tenantId, OrderStatus? status, PaymentStatus? paymentStatus, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取订单明细行。
+ ///
+ Task> GetItemsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取订单状态流转记录。
+ ///
+ Task> GetStatusHistoryAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取订单退款申请。
+ ///
+ Task> GetRefundsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增订单。
+ ///
+ Task AddOrderAsync(Order order, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增订单明细。
+ ///
+ Task AddItemsAsync(IEnumerable items, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增订单状态记录。
+ ///
+ Task AddStatusHistoryAsync(OrderStatusHistory history, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增退款申请。
+ ///
+ Task AddRefundAsync(RefundRequest refund, CancellationToken cancellationToken = default);
+
+ ///
+ /// 持久化变更。
+ ///
+ Task SaveChangesAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRecord.cs b/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRecord.cs
index 2d0923f..7ba0380 100644
--- a/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRecord.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRecord.cs
@@ -11,7 +11,7 @@ public sealed class PaymentRecord : MultiTenantEntityBase
///
/// 关联订单。
///
- public Guid OrderId { get; set; }
+ public long OrderId { get; set; }
///
/// 支付方式。
diff --git a/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRefundRecord.cs b/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRefundRecord.cs
index 9e91973..6fa657b 100644
--- a/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRefundRecord.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Payments/Entities/PaymentRefundRecord.cs
@@ -11,12 +11,12 @@ public sealed class PaymentRefundRecord : MultiTenantEntityBase
///
/// 原支付记录标识。
///
- public Guid PaymentRecordId { get; set; }
+ public long PaymentRecordId { get; set; }
///
/// 关联订单标识。
///
- public Guid OrderId { get; set; }
+ public long OrderId { get; set; }
///
/// 退款金额。
diff --git a/src/Domain/TakeoutSaaS.Domain/Payments/Repositories/IPaymentRepository.cs b/src/Domain/TakeoutSaaS.Domain/Payments/Repositories/IPaymentRepository.cs
new file mode 100644
index 0000000..c217ce2
--- /dev/null
+++ b/src/Domain/TakeoutSaaS.Domain/Payments/Repositories/IPaymentRepository.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using TakeoutSaaS.Domain.Payments.Entities;
+
+namespace TakeoutSaaS.Domain.Payments.Repositories;
+
+///
+/// 支付记录仓储契约。
+///
+public interface IPaymentRepository
+{
+ ///
+ /// 依据标识获取支付记录。
+ ///
+ Task FindByIdAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 依据订单标识获取支付记录。
+ ///
+ Task FindByOrderIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取支付对应的退款记录。
+ ///
+ Task> GetRefundsAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增支付记录。
+ ///
+ Task AddPaymentAsync(PaymentRecord payment, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增退款记录。
+ ///
+ Task AddRefundAsync(PaymentRefundRecord refund, CancellationToken cancellationToken = default);
+
+ ///
+ /// 持久化变更。
+ ///
+ Task SaveChangesAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/Product.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/Product.cs
index c9617e2..953a322 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Entities/Product.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/Product.cs
@@ -11,12 +11,12 @@ public sealed class Product : MultiTenantEntityBase
///
/// 所属门店。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 所属分类。
///
- public Guid CategoryId { get; set; }
+ public long CategoryId { get; set; }
///
/// 商品编码。
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonGroup.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonGroup.cs
index dfd8dd5..605005a 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonGroup.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonGroup.cs
@@ -11,7 +11,7 @@ public sealed class ProductAddonGroup : MultiTenantEntityBase
///
/// 所属商品。
///
- public Guid ProductId { get; set; }
+ public long ProductId { get; set; }
///
/// 分组名称。
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonOption.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonOption.cs
index 3d27185..3ce3047 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonOption.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAddonOption.cs
@@ -10,7 +10,7 @@ public sealed class ProductAddonOption : MultiTenantEntityBase
///
/// 所属加料分组。
///
- public Guid AddonGroupId { get; set; }
+ public long AddonGroupId { get; set; }
///
/// 选项名称。
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeGroup.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeGroup.cs
index 62a9af1..f9b3269 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeGroup.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeGroup.cs
@@ -11,7 +11,12 @@ public sealed class ProductAttributeGroup : MultiTenantEntityBase
///
/// 关联门店,可为空表示所有门店共享。
///
- public Guid? StoreId { get; set; }
+ public long? StoreId { get; set; }
+
+ ///
+ /// 所属商品标识。
+ ///
+ public long ProductId { get; set; }
///
/// 分组名称,例如“辣度”“份量”。
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeOption.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeOption.cs
index 80332d3..1ae3c73 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeOption.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductAttributeOption.cs
@@ -10,7 +10,7 @@ public sealed class ProductAttributeOption : MultiTenantEntityBase
///
/// 所属规格组。
///
- public Guid AttributeGroupId { get; set; }
+ public long AttributeGroupId { get; set; }
///
/// 选项名称。
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductCategory.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductCategory.cs
index 4d055a2..ab2afb2 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductCategory.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductCategory.cs
@@ -10,7 +10,7 @@ public sealed class ProductCategory : MultiTenantEntityBase
///
/// 所属门店。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 分类名称。
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductMediaAsset.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductMediaAsset.cs
index c07ef44..a963b26 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductMediaAsset.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductMediaAsset.cs
@@ -11,7 +11,7 @@ public sealed class ProductMediaAsset : MultiTenantEntityBase
///
/// 商品标识。
///
- public Guid ProductId { get; set; }
+ public long ProductId { get; set; }
///
/// 媒体类型。
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductPricingRule.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductPricingRule.cs
index bace92a..a92c0fe 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductPricingRule.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductPricingRule.cs
@@ -11,7 +11,7 @@ public sealed class ProductPricingRule : MultiTenantEntityBase
///
/// 所属商品。
///
- public Guid ProductId { get; set; }
+ public long ProductId { get; set; }
///
/// 策略类型。
@@ -42,4 +42,9 @@ public sealed class ProductPricingRule : MultiTenantEntityBase
/// 生效星期(JSON 数组)。
///
public string? WeekdaysJson { get; set; }
+
+ ///
+ /// 排序值。
+ ///
+ public int SortOrder { get; set; } = 100;
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductSku.cs b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductSku.cs
index 1c1e6ce..f20fc17 100644
--- a/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductSku.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Entities/ProductSku.cs
@@ -10,7 +10,7 @@ public sealed class ProductSku : MultiTenantEntityBase
///
/// 所属商品标识。
///
- public Guid ProductId { get; set; }
+ public long ProductId { get; set; }
///
/// SKU 编码。
@@ -46,4 +46,9 @@ public sealed class ProductSku : MultiTenantEntityBase
/// 规格属性 JSON(记录选项 ID)。
///
public string AttributesJson { get; set; } = string.Empty;
+
+ ///
+ /// 排序值。
+ ///
+ public int SortOrder { get; set; } = 100;
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs b/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs
new file mode 100644
index 0000000..093ee81
--- /dev/null
+++ b/src/Domain/TakeoutSaaS.Domain/Products/Repositories/IProductRepository.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using TakeoutSaaS.Domain.Products.Entities;
+using TakeoutSaaS.Domain.Products.Enums;
+
+namespace TakeoutSaaS.Domain.Products.Repositories;
+
+///
+/// 商品聚合仓储契约。
+///
+public interface IProductRepository
+{
+ ///
+ /// 依据标识获取商品。
+ ///
+ Task FindByIdAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 按分类与状态筛选商品列表。
+ ///
+ Task> SearchAsync(long tenantId, long? categoryId, ProductStatus? status, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取租户下的商品分类。
+ ///
+ Task> GetCategoriesAsync(long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取商品 SKU。
+ ///
+ Task> GetSkusAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取商品加料组与选项。
+ ///
+ Task> GetAddonGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取商品加料选项。
+ ///
+ Task> GetAddonOptionsAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取商品规格组与选项。
+ ///
+ Task> GetAttributeGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取商品规格选项。
+ ///
+ Task> GetAttributeOptionsAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取商品媒资。
+ ///
+ Task> GetMediaAssetsAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取商品定价规则。
+ ///
+ Task> GetPricingRulesAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增分类。
+ ///
+ Task AddCategoryAsync(ProductCategory category, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增商品。
+ ///
+ Task AddProductAsync(Product product, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增 SKU。
+ ///
+ Task AddSkusAsync(IEnumerable skus, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增加料组与选项。
+ ///
+ Task AddAddonGroupsAsync(IEnumerable groups, IEnumerable options, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增规格组与选项。
+ ///
+ Task AddAttributeGroupsAsync(IEnumerable groups, IEnumerable options, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增媒资。
+ ///
+ Task AddMediaAssetsAsync(IEnumerable assets, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增定价规则。
+ ///
+ Task AddPricingRulesAsync(IEnumerable rules, CancellationToken cancellationToken = default);
+
+ ///
+ /// 持久化变更。
+ ///
+ Task SaveChangesAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/Domain/TakeoutSaaS.Domain/Queues/Entities/QueueTicket.cs b/src/Domain/TakeoutSaaS.Domain/Queues/Entities/QueueTicket.cs
index f09c94e..e235909 100644
--- a/src/Domain/TakeoutSaaS.Domain/Queues/Entities/QueueTicket.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Queues/Entities/QueueTicket.cs
@@ -8,7 +8,7 @@ namespace TakeoutSaaS.Domain.Queues.Entities;
///
public sealed class QueueTicket : MultiTenantEntityBase
{
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 排队编号。
diff --git a/src/Domain/TakeoutSaaS.Domain/Reservations/Entities/Reservation.cs b/src/Domain/TakeoutSaaS.Domain/Reservations/Entities/Reservation.cs
index 3214ee3..1e846e8 100644
--- a/src/Domain/TakeoutSaaS.Domain/Reservations/Entities/Reservation.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Reservations/Entities/Reservation.cs
@@ -11,7 +11,7 @@ public sealed class Reservation : MultiTenantEntityBase
///
/// 门店。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 预约号。
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs
index 5c82a91..f3c8e44 100644
--- a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/Store.cs
@@ -11,7 +11,7 @@ public sealed class Store : MultiTenantEntityBase
///
/// 所属商户标识。
///
- public Guid MerchantId { get; set; }
+ public long MerchantId { get; set; }
///
/// 门店编码,便于扫码及外部对接。
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreBusinessHour.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreBusinessHour.cs
index a85b0d8..083a46f 100644
--- a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreBusinessHour.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreBusinessHour.cs
@@ -11,7 +11,7 @@ public sealed class StoreBusinessHour : MultiTenantEntityBase
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 星期几,0 表示周日。
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreDeliveryZone.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreDeliveryZone.cs
index e067e1f..9e72e51 100644
--- a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreDeliveryZone.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreDeliveryZone.cs
@@ -10,7 +10,7 @@ public sealed class StoreDeliveryZone : MultiTenantEntityBase
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 区域名称。
@@ -36,4 +36,9 @@ public sealed class StoreDeliveryZone : MultiTenantEntityBase
/// 预计送达分钟。
///
public int? EstimatedMinutes { get; set; }
+
+ ///
+ /// 排序值。
+ ///
+ public int SortOrder { get; set; } = 100;
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreEmployeeShift.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreEmployeeShift.cs
index a1bc39a..a1f0b39 100644
--- a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreEmployeeShift.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreEmployeeShift.cs
@@ -11,12 +11,12 @@ public sealed class StoreEmployeeShift : MultiTenantEntityBase
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 员工标识。
///
- public Guid StaffId { get; set; }
+ public long StaffId { get; set; }
///
/// 班次日期。
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs
index c464849..e10edce 100644
--- a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreHoliday.cs
@@ -10,7 +10,7 @@ public sealed class StoreHoliday : MultiTenantEntityBase
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 日期。
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTable.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTable.cs
index 1b55549..97dc86b 100644
--- a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTable.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTable.cs
@@ -11,12 +11,12 @@ public sealed class StoreTable : MultiTenantEntityBase
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 所在区域 ID。
///
- public Guid? AreaId { get; set; }
+ public long? AreaId { get; set; }
///
/// 桌码。
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTableArea.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTableArea.cs
index 6255266..0fb6fb6 100644
--- a/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTableArea.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Entities/StoreTableArea.cs
@@ -10,7 +10,7 @@ public sealed class StoreTableArea : MultiTenantEntityBase
///
/// 门店标识。
///
- public Guid StoreId { get; set; }
+ public long StoreId { get; set; }
///
/// 区域名称。
@@ -21,4 +21,9 @@ public sealed class StoreTableArea : MultiTenantEntityBase
/// 区域描述。
///
public string? Description { get; set; }
+
+ ///
+ /// 排序值。
+ ///
+ public int SortOrder { get; set; } = 100;
}
diff --git a/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs b/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs
new file mode 100644
index 0000000..dccde79
--- /dev/null
+++ b/src/Domain/TakeoutSaaS.Domain/Stores/Repositories/IStoreRepository.cs
@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using TakeoutSaaS.Domain.Stores.Entities;
+using TakeoutSaaS.Domain.Stores.Enums;
+
+namespace TakeoutSaaS.Domain.Stores.Repositories;
+
+///
+/// 门店聚合仓储契约。
+///
+public interface IStoreRepository
+{
+ ///
+ /// 依据标识获取门店。
+ ///
+ Task FindByIdAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 按租户筛选门店列表。
+ ///
+ Task> SearchAsync(long tenantId, StoreStatus? status, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取门店营业时段。
+ ///
+ Task> GetBusinessHoursAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取门店配送区域配置。
+ ///
+ Task> GetDeliveryZonesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取门店节假日配置。
+ ///
+ Task> GetHolidaysAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取门店桌台区域。
+ ///
+ Task> GetTableAreasAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取门店桌台列表。
+ ///
+ Task> GetTablesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取门店员工排班。
+ ///
+ Task> GetShiftsAsync(long storeId, long tenantId, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增门店。
+ ///
+ Task AddStoreAsync(Store store, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增营业时段。
+ ///
+ Task AddBusinessHoursAsync(IEnumerable hours, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增配送区域。
+ ///
+ Task AddDeliveryZonesAsync(IEnumerable zones, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增节假日配置。
+ ///
+ Task AddHolidaysAsync(IEnumerable holidays, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增桌台区域。
+ ///
+ Task AddTableAreasAsync(IEnumerable areas, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增桌台。
+ ///
+ Task AddTablesAsync(IEnumerable tables, CancellationToken cancellationToken = default);
+
+ ///
+ /// 新增排班。
+ ///
+ Task AddShiftsAsync(IEnumerable shifts, CancellationToken cancellationToken = default);
+
+ ///
+ /// 持久化变更。
+ ///
+ Task SaveChangesAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs
index fd65958..b2823e3 100644
--- a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/Tenant.cs
@@ -86,7 +86,7 @@ public sealed class Tenant : AuditableEntityBase
///
/// 系统内对应的租户所有者账号 ID。
///
- public Guid? PrimaryOwnerUserId { get; set; }
+ public long? PrimaryOwnerUserId { get; set; }
///
/// 租户当前状态,涵盖审核、启用、停用等场景。
diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantSubscription.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantSubscription.cs
index a54fce2..3090be2 100644
--- a/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantSubscription.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Entities/TenantSubscription.cs
@@ -11,7 +11,7 @@ public sealed class TenantSubscription : MultiTenantEntityBase
///
/// 当前订阅关联的套餐标识。
///
- public Guid TenantPackageId { get; set; }
+ public long TenantPackageId { get; set; }
///
/// 订阅生效时间(UTC)。
@@ -41,7 +41,7 @@ public sealed class TenantSubscription : MultiTenantEntityBase
///
/// 若已排期升降配,对应的新套餐 ID。
///
- public Guid? ScheduledPackageId { get; set; }
+ public long? ScheduledPackageId { get; set; }
///
/// 运营备注信息。
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs
new file mode 100644
index 0000000..02df728
--- /dev/null
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs
@@ -0,0 +1,48 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using TakeoutSaaS.Domain.Deliveries.Repositories;
+using TakeoutSaaS.Domain.Merchants.Repositories;
+using TakeoutSaaS.Domain.Orders.Repositories;
+using TakeoutSaaS.Domain.Payments.Repositories;
+using TakeoutSaaS.Domain.Products.Repositories;
+using TakeoutSaaS.Domain.Stores.Repositories;
+using TakeoutSaaS.Infrastructure.App.Options;
+using TakeoutSaaS.Infrastructure.App.Persistence;
+using TakeoutSaaS.Infrastructure.App.Repositories;
+using TakeoutSaaS.Infrastructure.Common.Extensions;
+using TakeoutSaaS.Shared.Abstractions.Constants;
+
+namespace TakeoutSaaS.Infrastructure.App.Extensions;
+
+///
+/// 业务主库基础设施注册扩展。
+///
+public static class AppServiceCollectionExtensions
+{
+ ///
+ /// 注册业务主库 DbContext 与仓储。
+ ///
+ /// 服务集合。
+ /// 配置源。
+ /// 服务集合。
+ public static IServiceCollection AddAppInfrastructure(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddDatabaseInfrastructure(configuration);
+ services.AddPostgresDbContext(DatabaseConstants.AppDataSource);
+
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+
+ services.AddOptions()
+ .Bind(configuration.GetSection(AppSeedOptions.SectionName))
+ .ValidateDataAnnotations();
+
+ services.AddHostedService();
+
+ return services;
+ }
+}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.Designer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.Designer.cs
deleted file mode 100644
index 56454dc..0000000
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201044927_InitialApp.Designer.cs
+++ /dev/null
@@ -1,949 +0,0 @@
-//
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-using TakeoutSaaS.Infrastructure.App.Persistence;
-
-#nullable disable
-
-namespace TakeoutSaaS.Infrastructure.App.Migrations
-{
- [DbContext(typeof(TakeoutAppDbContext))]
- [Migration("20251201044927_InitialApp")]
- partial class InitialApp
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "10.0.0")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("TakeoutSaaS.Domain.Deliveries.Entities.DeliveryOrder", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid");
-
- b.Property("CourierName")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("CourierPhone")
- .HasMaxLength(32)
- .HasColumnType("character varying(32)");
-
- b.Property("CreatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("CreatedBy")
- .HasColumnType("uuid");
-
- b.Property("DeletedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("DeletedBy")
- .HasColumnType("uuid");
-
- b.Property("DeliveredAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("DeliveryFee")
- .HasPrecision(18, 2)
- .HasColumnType("numeric(18,2)");
-
- b.Property("DispatchedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("FailureReason")
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("OrderId")
- .HasColumnType("uuid");
-
- b.Property("PickedUpAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Provider")
- .HasColumnType("integer");
-
- b.Property("ProviderOrderId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("Status")
- .HasColumnType("integer");
-
- b.Property("TenantId")
- .HasColumnType("uuid");
-
- b.Property("UpdatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("UpdatedBy")
- .HasColumnType("uuid");
-
- b.HasKey("Id");
-
- b.HasIndex("TenantId", "OrderId")
- .IsUnique();
-
- b.ToTable("delivery_orders", (string)null);
- });
-
- modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.Merchant", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid");
-
- b.Property("Address")
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("BrandAlias")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("BrandName")
- .IsRequired()
- .HasMaxLength(128)
- .HasColumnType("character varying(128)");
-
- b.Property("BusinessLicenseNumber")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("City")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("ContactEmail")
- .HasMaxLength(128)
- .HasColumnType("character varying(128)");
-
- b.Property("ContactPhone")
- .IsRequired()
- .HasMaxLength(32)
- .HasColumnType("character varying(32)");
-
- b.Property("CreatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("CreatedBy")
- .HasColumnType("uuid");
-
- b.Property("DeletedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("DeletedBy")
- .HasColumnType("uuid");
-
- b.Property("District")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("LegalPerson")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("OnboardedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Province")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("ReviewRemarks")
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.Property("Status")
- .HasColumnType("integer");
-
- b.Property("TenantId")
- .HasColumnType("uuid");
-
- b.Property("UpdatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("UpdatedBy")
- .HasColumnType("uuid");
-
- b.HasKey("Id");
-
- b.HasIndex("TenantId");
-
- b.ToTable("merchants", (string)null);
- });
-
- modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.Order", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid");
-
- b.Property("CancelReason")
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("CancelledAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Channel")
- .HasColumnType("integer");
-
- b.Property("CreatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("CreatedBy")
- .HasColumnType("uuid");
-
- b.Property("CustomerName")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("CustomerPhone")
- .HasMaxLength(32)
- .HasColumnType("character varying(32)");
-
- b.Property("DeletedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("DeletedBy")
- .HasColumnType("uuid");
-
- b.Property("DeliveryType")
- .HasColumnType("integer");
-
- b.Property("DiscountAmount")
- .HasPrecision(18, 2)
- .HasColumnType("numeric(18,2)");
-
- b.Property("FinishedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("ItemsAmount")
- .HasPrecision(18, 2)
- .HasColumnType("numeric(18,2)");
-
- b.Property("OrderNo")
- .IsRequired()
- .HasMaxLength(32)
- .HasColumnType("character varying(32)");
-
- b.Property("PaidAmount")
- .HasPrecision(18, 2)
- .HasColumnType("numeric(18,2)");
-
- b.Property("PaidAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("PayableAmount")
- .HasPrecision(18, 2)
- .HasColumnType("numeric(18,2)");
-
- b.Property("PaymentStatus")
- .HasColumnType("integer");
-
- b.Property("QueueNumber")
- .HasMaxLength(32)
- .HasColumnType("character varying(32)");
-
- b.Property("Remark")
- .HasMaxLength(512)
- .HasColumnType("character varying(512)");
-
- b.Property("ReservationId")
- .HasColumnType("uuid");
-
- b.Property("Status")
- .HasColumnType("integer");
-
- b.Property("StoreId")
- .HasColumnType("uuid");
-
- b.Property("TableNo")
- .HasMaxLength(32)
- .HasColumnType("character varying(32)");
-
- b.Property("TenantId")
- .HasColumnType("uuid");
-
- b.Property("UpdatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("UpdatedBy")
- .HasColumnType("uuid");
-
- b.HasKey("Id");
-
- b.HasIndex("TenantId", "OrderNo")
- .IsUnique();
-
- b.HasIndex("TenantId", "StoreId", "Status");
-
- b.ToTable("orders", (string)null);
- });
-
- modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid");
-
- b.Property("AttributesJson")
- .HasColumnType("text");
-
- b.Property("CreatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("CreatedBy")
- .HasColumnType("uuid");
-
- b.Property("DeletedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("DeletedBy")
- .HasColumnType("uuid");
-
- b.Property("DiscountAmount")
- .HasPrecision(18, 2)
- .HasColumnType("numeric(18,2)");
-
- b.Property("OrderId")
- .HasColumnType("uuid");
-
- b.Property("ProductId")
- .HasColumnType("uuid");
-
- b.Property("ProductName")
- .IsRequired()
- .HasMaxLength(128)
- .HasColumnType("character varying(128)");
-
- b.Property("Quantity")
- .HasColumnType("integer");
-
- b.Property("SkuName")
- .HasMaxLength(128)
- .HasColumnType("character varying(128)");
-
- b.Property("SubTotal")
- .HasPrecision(18, 2)
- .HasColumnType("numeric(18,2)");
-
- b.Property("TenantId")
- .HasColumnType("uuid");
-
- b.Property("Unit")
- .HasMaxLength(16)
- .HasColumnType("character varying(16)");
-
- b.Property("UnitPrice")
- .HasPrecision(18, 2)
- .HasColumnType("numeric(18,2)");
-
- b.Property("UpdatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("UpdatedBy")
- .HasColumnType("uuid");
-
- b.HasKey("Id");
-
- b.HasIndex("OrderId");
-
- b.HasIndex("TenantId", "OrderId");
-
- b.ToTable("order_items", (string)null);
- });
-
- modelBuilder.Entity("TakeoutSaaS.Domain.Payments.Entities.PaymentRecord", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid");
-
- b.Property("Amount")
- .HasPrecision(18, 2)
- .HasColumnType("numeric(18,2)");
-
- b.Property("ChannelTransactionId")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("CreatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("CreatedBy")
- .HasColumnType("uuid");
-
- b.Property("DeletedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("DeletedBy")
- .HasColumnType("uuid");
-
- b.Property("Method")
- .HasColumnType("integer");
-
- b.Property("OrderId")
- .HasColumnType("uuid");
-
- b.Property("PaidAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Payload")
- .HasColumnType("text");
-
- b.Property("Remark")
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("Status")
- .HasColumnType("integer");
-
- b.Property("TenantId")
- .HasColumnType("uuid");
-
- b.Property("TradeNo")
- .HasMaxLength(64)
- .HasColumnType("character varying(64)");
-
- b.Property("UpdatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("UpdatedBy")
- .HasColumnType("uuid");
-
- b.HasKey("Id");
-
- b.HasIndex("TenantId", "OrderId");
-
- b.ToTable("payment_records", (string)null);
- });
-
- modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.Product", b =>
- {
- b.Property