diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3857e65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs/ +bin/ +obj/ +**/bin/ +**/obj/ diff --git a/0_Document/01_项目概述.md b/0_Document/01_项目概述.md new file mode 100644 index 0000000..3d8920c --- /dev/null +++ b/0_Document/01_项目概述.md @@ -0,0 +1,188 @@ +# 外卖SaaS系统 - 项目概述 + +## 1. 项目简介 + +### 1.1 项目背景 +外卖SaaS系统是一个面向餐饮企业的多租户外卖管理平台,旨在为中小型餐饮企业提供完整的外卖业务解决方案。系统支持商家入驻、菜品管理、订单处理、配送管理等核心功能。 + +### 1.2 项目目标 +- 提供稳定、高效的外卖业务管理平台 +- 支持多租户架构,实现数据隔离和资源共享 +- 提供完善的商家管理和运营工具 +- 支持灵活的配送模式(自配送、第三方配送) +- 提供实时数据分析和报表功能 + +### 1.3 核心价值 +- **降低成本**:SaaS模式降低企业IT投入成本 +- **快速上线**:开箱即用,快速开展外卖业务 +- **灵活扩展**:支持业务增长和功能定制 +- **数据驱动**:提供数据分析,辅助经营决策 + +## 2. 业务模块 + +### 2.1 租户管理模块 +- 租户注册与认证 +- 租户信息管理 +- 套餐订阅管理 +- 权限与配额管理 + +### 2.2 商家管理模块 +- 商家入驻审核 +- 商家信息管理 +- 门店管理(支持多门店) +- 营业时间设置 +- 配送范围设置 + +### 2.3 菜品管理模块 +- 菜品分类管理 +- 菜品信息管理(名称、价格、图片、描述) +- 菜品规格管理(大份、小份等) +- 菜品库存管理 +- 菜品上下架管理 + +### 2.4 订单管理模块 +- 订单创建与支付 +- 订单状态流转(待支付、待接单、制作中、配送中、已完成、已取消) +- 订单查询与筛选 +- 订单退款处理 +- 订单统计分析 + +### 2.5 配送管理模块 +- 配送员管理 +- 配送任务分配 +- 配送路线规划 +- 配送状态跟踪 +- 配送费用计算 + +### 2.6 用户管理模块 +- 用户注册与登录 +- 用户信息管理 +- 收货地址管理 +- 用户订单历史 +- 用户评价管理 + +### 2.7 支付管理模块 +- 多支付方式支持(微信、支付宝、余额) +- 支付回调处理 +- 退款处理 +- 账单管理 + +### 2.8 营销管理模块 +- 优惠券管理 +- 满减活动 +- 会员积分 +- 推广活动 + +### 2.9 数据分析模块 +- 销售数据统计 +- 订单趋势分析 +- 用户行为分析 +- 商家经营报表 +- 平台运营大盘 + +### 2.10 系统管理模块 +- 系统配置管理 +- 日志管理 +- 权限管理 +- 消息通知管理 + +## 3. 用户角色 + +### 3.1 平台管理员(Web管理端) +- 管理所有租户和商家 +- 系统配置和维护 +- 数据监控和分析 +- 审核商家入驻 +- 平台运营管理 + +### 3.2 租户管理员(Web管理端) +- 管理租户下的所有商家 +- 查看租户数据报表 +- 管理租户套餐和权限 +- 租户配置管理 + +### 3.3 商家管理员(Web管理端) +- 管理门店信息 +- 管理菜品和订单 +- 查看经营数据 +- 管理配送(自配送或第三方配送对接) +- 营销活动管理 + +### 3.4 商家员工(Web管理端) +- 处理订单(接单/出餐/发货) +- 更新菜品状态 +- 订单打印与出餐看板 + +### 3.5 普通用户/消费者(小程序端 + Web用户端) +- 浏览商家和菜品 +- 下单和支付 +- 查看订单状态 +- 评价和反馈 +- 收货地址管理 +- 优惠券领取和使用 + +## 4. 系统特性 + +### 4.1 多租户架构 +- 数据隔离:每个租户数据完全隔离 +- 资源共享:共享基础设施,降低成本 +- 灵活配置:支持租户级别的个性化配置 + +### 4.2 高可用性 +- 服务高可用:支持集群部署 +- 数据高可用:数据库主从复制 +- 故障自动恢复 + +### 4.3 高性能 +- 缓存策略:Redis缓存热点数据 +- 数据库优化:索引优化、查询优化 +- 异步处理:消息队列处理耗时任务 + +### 4.4 安全性 +- 身份认证:JWT Token认证 +- 权限控制:基于角色的访问控制(RBAC) +- 数据加密:敏感数据加密存储 +- 接口防护:限流、防重放攻击 + +### 4.5 可扩展性 +- 微服务架构:支持服务独立扩展 +- 插件化设计:支持功能模块插拔 +- API开放:提供开放API接口 + +## 5. 技术选型 + +- **后端框架**:.NET 10 +- **ORM框架**:Entity Framework Core 10 + Dapper +- **数据库**:PostgreSQL 16+ +- **缓存**:Redis 7.0+ +- **消息队列**:RabbitMQ 3.12+ +- **API文档**:Swagger/OpenAPI +- **日志**:Serilog +- **认证授权**:JWT + OAuth2.0 + +## 6. 项目里程碑 + +### Phase 1:基础功能(1-2个月) +- 租户管理 +- 商家管理 +- 菜品管理 +- 订单管理(基础流程) + +### Phase 2:核心功能(2-3个月) +- 配送管理 +- 支付集成 +- 用户管理 +- 基础营销功能 + +### Phase 3:高级功能(3-4个月) +- 数据分析 +- 高级营销 +- 系统优化 +- 性能调优 + +### Phase 4:完善与上线(1个月) +- 测试与修复 +- 文档完善 +- 部署上线 +- 运维监控 + diff --git a/0_Document/02_技术架构.md b/0_Document/02_技术架构.md new file mode 100644 index 0000000..e7d1861 --- /dev/null +++ b/0_Document/02_技术架构.md @@ -0,0 +1,253 @@ +# 外卖SaaS系统 - 技术架构 + +## 1. 技术栈 + +### 1.1 后端技术栈 +- **.NET 10**:最新的.NET平台,提供高性能和现代化开发体验 +- **ASP.NET Core Web API**:构建RESTful API服务 +- **Entity Framework Core 10**:最新ORM框架,用于复杂查询和实体管理 +- **Dapper 2.1+**:轻量级ORM,用于高性能查询和批量操作 +- **PostgreSQL 16+**:主数据库,支持JSON、全文搜索等高级特性 +- **Redis 7.0+**:缓存和会话存储 +- **RabbitMQ 3.12+**:消息队列,处理异步任务 + +### 1.2 开发工具和框架 +- **AutoMapper**:对象映射 +- **FluentValidation**:数据验证 +- **Serilog**:结构化日志 +- **MediatR**:CQRS和中介者模式实现 +- **Hangfire**:后台任务调度 +- **Polly**:弹性和瞬态故障处理 +- **Swagger/Swashbuckle**:API文档生成 + +### 1.3 认证授权 +- **JWT (JSON Web Token)**:无状态身份认证 +- **IdentityServer/Duende IdentityServer**:OAuth2.0和OpenID Connect +- **ASP.NET Core Identity**:用户身份管理 + +### 1.4 测试框架 +- **xUnit**:单元测试框架 +- **Moq**:Mock框架 +- **FluentAssertions**:断言库 +- **Testcontainers**:集成测试容器化 + +### 1.5 DevOps工具 +- **Docker**:容器化部署 +- **Docker Compose**:本地开发环境 +- **GitHub Actions/GitLab CI**:CI/CD流水线 +- **Nginx**:反向代理和负载均衡 + +## 2. 系统架构 + +### 2.1 整体架构 +``` +┌─────────────────────────────────────────────────────────────┐ +│ 客户端层 │ +│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ +│ │ Web管理端 │ │ Web用户端 │ │ 小程序端(用户) │ │ +│ └──────────┘ └──────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ API网关层 │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Nginx / API Gateway (路由、限流、认证、日志) │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 应用服务层 │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │租户服务 │ │商家服务 │ │订单服务 │ │配送服务 │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │用户服务 │ │支付服务 │ │营销服务 │ │通知服务 │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 基础设施层 │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │PostgreSQL │ │ Redis │ │ RabbitMQ │ │ MinIO │ │ +│ │ (主库) │ │ (缓存) │ │ (消息队列)│ │(对象存储) │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.2 分层架构 + +#### 2.2.1 表现层 (Presentation Layer) +- **TakeoutSaaS.AdminApi**:管理后台 Web API 项目(/api/admin/v1) + - Controllers:后台管理API控制器 + - Filters:过滤器(异常处理、日志、验证) + - Middleware:中间件(认证、租户识别、RBAC) + - Models:请求/响应DTO +- **TakeoutSaaS.MiniApi**:小程序/用户端 Web API 项目(/api/mini/v1) + - Controllers:用户端API控制器 + - Filters:过滤器(异常处理、限流、签名校验) + - Middleware:中间件(小程序登录态、租户识别、CORS) + - Models:请求/响应DTO + +#### 2.2.2 应用层 (Application Layer) +- **TakeoutSaaS.Application**:应用逻辑 + - Services:应用服务 + - DTOs:数据传输对象 + - Interfaces:服务接口 + - Validators:FluentValidation验证器 + - Mappings:AutoMapper配置 + - Commands/Queries:CQRS命令和查询 + +#### 2.2.3 领域层 (Domain Layer) +- **TakeoutSaaS.Domain**:领域模型 + - Entities:实体类 + - ValueObjects:值对象 + - Enums:枚举 + - Events:领域事件 + - Interfaces:仓储接口 + - Specifications:规约模式 + +#### 2.2.4 基础设施层 (Infrastructure Layer) +- **TakeoutSaaS.Infrastructure**:基础设施实现 + - Data:数据访问 + - EFCore:EF Core DbContext和配置 + - Dapper:Dapper查询实现 + - Repositories:仓储实现 + - Migrations:数据库迁移 + - Cache:Redis缓存实现 + - MessageQueue:RabbitMQ实现 + - ExternalServices:第三方服务集成 + +#### 2.2.5 共享层 (Shared Layer) +- **TakeoutSaaS.Shared**:共享组件 + - Constants:常量定义 + - Exceptions:自定义异常 + - Extensions:扩展方法 + - Helpers:辅助类 + - Results:统一返回结果 + +## 3. 核心设计模式 + +### 3.1 多租户模式 +- **数据隔离策略**:每个租户独立Schema +- **租户识别**:通过HTTP Header或JWT Token识别租户 +- **动态切换**:运行时动态切换数据库连接 + +### 3.2 CQRS模式 +- **命令(Command)**:处理写操作,修改数据 +- **查询(Query)**:处理读操作,不修改数据 +- **分离优势**:读写分离,优化性能 + +### 3.3 仓储模式 +- **抽象数据访问**:统一数据访问接口 +- **EF Core仓储**:复杂查询和事务处理 +- **Dapper仓储**:高性能查询和批量操作 + +### 3.4 工作单元模式 +- **事务管理**:统一管理数据库事务 +- **批量提交**:减少数据库往返次数 + +### 3.5 领域驱动设计(DDD) +- **聚合根**:定义实体边界 +- **值对象**:不可变对象 +- **领域事件**:解耦业务逻辑 + +## 4. 数据访问策略 + +### 4.1 EF Core使用场景 +- 复杂的实体关系查询 +- 需要变更跟踪的操作 +- 事务性操作 +- 数据库迁移管理 + +### 4.2 Dapper使用场景 +- 高性能查询(大数据量) +- 复杂SQL查询 +- 批量插入/更新 +- 报表统计查询 +- 存储过程调用 + +### 4.3 混合使用策略 +```csharp +// EF Core - 复杂查询和实体管理 +public async Task GetOrderWithDetailsAsync(Guid orderId) +{ + return await _dbContext.Orders + .Include(o => o.OrderItems) + .Include(o => o.Customer) + .FirstOrDefaultAsync(o => o.Id == orderId); +} + +// Dapper - 高性能统计查询 +public async Task GetOrderStatisticsAsync(DateTime startDate, DateTime endDate) +{ + var sql = @" + SELECT + COUNT(*) as TotalOrders, + SUM(total_amount) as TotalAmount, + AVG(total_amount) as AvgAmount + FROM orders + WHERE created_at BETWEEN @StartDate AND @EndDate"; + + return await _connection.QueryFirstOrDefaultAsync(sql, + new { StartDate = startDate, EndDate = endDate }); +} +``` + +## 5. 缓存策略 + +### 5.1 缓存层次 +- **L1缓存**:内存缓存(IMemoryCache)- 进程内缓存 +- **L2缓存**:Redis缓存 - 分布式缓存 + +### 5.2 缓存场景 +- 商家信息缓存(30分钟) +- 菜品信息缓存(15分钟) +- 用户会话缓存(2小时) +- 配置信息缓存(1小时) +- 热点数据缓存(动态过期) + +### 5.3 缓存更新策略 +- **Cache-Aside**:旁路缓存,先查缓存,未命中查数据库 +- **Write-Through**:写入时同步更新缓存 +- **Write-Behind**:异步更新缓存 + +## 6. 消息队列应用 + +### 6.1 异步任务 +- 订单状态变更通知 +- 短信/邮件发送 +- 数据统计计算 +- 日志持久化 + +### 6.2 事件驱动 +- 订单创建事件 +- 支付成功事件 +- 配送状态变更事件 + +## 7. 安全设计 + +### 7.1 认证机制 +- JWT Token认证 +- Refresh Token刷新 +- Token过期管理 + +### 7.2 授权机制 +- 基于角色的访问控制(RBAC) +- 基于策略的授权 +- 资源级权限控制 + +### 7.3 数据安全 +- 敏感数据加密(密码、支付信息) +- HTTPS传输加密 +- SQL注入防护 +- XSS防护 + +### 7.4 接口安全 +- 请求签名验证 +- 接口限流(Rate Limiting) +- 防重放攻击 +- CORS跨域配置 + diff --git a/0_Document/03_数据库设计.md b/0_Document/03_数据库设计.md new file mode 100644 index 0000000..93074df --- /dev/null +++ b/0_Document/03_数据库设计.md @@ -0,0 +1,641 @@ +# 外卖SaaS系统 - 数据库设计 + +## 1. 数据库设计原则 + +### 1.1 命名规范 +- **表名**:小写字母,下划线分隔,复数形式(如:`orders`, `order_items`) +- **字段名**:小写字母,下划线分隔(如:`created_at`, `total_amount`) +- **主键**:统一使用 `id`,类型为 UUID +- **外键**:`表名_id`(如:`order_id`, `merchant_id`) +- **索引**:`idx_表名_字段名`(如:`idx_orders_merchant_id`) + +### 1.2 通用字段 +所有表都包含以下字段: +- `id`:UUID,主键 +- `created_at`:TIMESTAMP,创建时间 +- `updated_at`:TIMESTAMP,更新时间 +- `deleted_at`:TIMESTAMP,软删除时间(可选) +- `tenant_id`:UUID,租户ID(多租户隔离) + +### 1.3 数据类型规范 +- **金额**:DECIMAL(18,2) +- **时间**:TIMESTAMP WITH TIME ZONE +- **布尔**:BOOLEAN +- **枚举**:VARCHAR 或 INTEGER +- **JSON数据**:JSONB + +## 2. 核心表结构 + +### 2.1 租户管理 + +#### tenants(租户表) +```sql +CREATE TABLE tenants ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(100) NOT NULL, + code VARCHAR(50) UNIQUE NOT NULL, + contact_name VARCHAR(50), + contact_phone VARCHAR(20), + contact_email VARCHAR(100), + status INTEGER NOT NULL DEFAULT 1, -- 1:正常 2:冻结 3:过期 + subscription_plan VARCHAR(50), -- 订阅套餐 + subscription_start_date TIMESTAMP WITH TIME ZONE, + subscription_end_date TIMESTAMP WITH TIME ZONE, + max_merchants INTEGER DEFAULT 10, -- 最大商家数 + max_orders_per_day INTEGER DEFAULT 1000, -- 每日订单限额 + settings JSONB, -- 租户配置 + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_tenants_code ON tenants(code); +CREATE INDEX idx_tenants_status ON tenants(status); +``` + +### 2.2 商家管理 + +#### merchants(商家表) +```sql +CREATE TABLE merchants ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + name VARCHAR(100) NOT NULL, + logo_url VARCHAR(500), + description TEXT, + contact_phone VARCHAR(20), + contact_person VARCHAR(50), + business_license VARCHAR(100), -- 营业执照号 + status INTEGER NOT NULL DEFAULT 1, -- 1:正常 2:休息 3:停业 + rating DECIMAL(3,2) DEFAULT 0, -- 评分 + total_sales INTEGER DEFAULT 0, -- 总销量 + settings JSONB, -- 商家配置 + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_merchants_tenant_id ON merchants(tenant_id); +CREATE INDEX idx_merchants_status ON merchants(status); +``` + +#### merchant_stores(门店表) +```sql +CREATE TABLE merchant_stores ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + merchant_id UUID NOT NULL REFERENCES merchants(id), + name VARCHAR(100) NOT NULL, + address VARCHAR(500) NOT NULL, + latitude DECIMAL(10,7), -- 纬度 + longitude DECIMAL(10,7), -- 经度 + phone VARCHAR(20), + business_hours JSONB, -- 营业时间 {"monday": {"open": "09:00", "close": "22:00"}} + delivery_range INTEGER DEFAULT 3000, -- 配送范围(米) + min_order_amount DECIMAL(18,2) DEFAULT 0, -- 起送价 + delivery_fee DECIMAL(18,2) DEFAULT 0, -- 配送费 + status INTEGER NOT NULL DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_merchant_stores_merchant_id ON merchant_stores(merchant_id); +CREATE INDEX idx_merchant_stores_location ON merchant_stores USING GIST(point(longitude, latitude)); +``` + +### 2.3 菜品管理 + +#### categories(菜品分类表) +```sql +CREATE TABLE categories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + merchant_id UUID NOT NULL REFERENCES merchants(id), + name VARCHAR(50) NOT NULL, + sort_order INTEGER DEFAULT 0, + status INTEGER NOT NULL DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_categories_merchant_id ON categories(merchant_id); +``` + +#### dishes(菜品表) +```sql +CREATE TABLE dishes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + merchant_id UUID NOT NULL REFERENCES merchants(id), + category_id UUID REFERENCES categories(id), + name VARCHAR(100) NOT NULL, + description TEXT, + image_url VARCHAR(500), + price DECIMAL(18,2) NOT NULL, + original_price DECIMAL(18,2), -- 原价 + unit VARCHAR(20) DEFAULT '份', -- 单位 + stock INTEGER, -- 库存(NULL表示不限) + sales_count INTEGER DEFAULT 0, -- 销量 + rating DECIMAL(3,2) DEFAULT 0, -- 评分 + sort_order INTEGER DEFAULT 0, + status INTEGER NOT NULL DEFAULT 1, -- 1:上架 2:下架 + tags JSONB, -- 标签 ["热销", "新品"] + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_dishes_merchant_id ON dishes(merchant_id); +CREATE INDEX idx_dishes_category_id ON dishes(category_id); +CREATE INDEX idx_dishes_status ON dishes(status); +``` + +#### dish_specs(菜品规格表) +```sql +CREATE TABLE dish_specs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + dish_id UUID NOT NULL REFERENCES dishes(id), + name VARCHAR(50) NOT NULL, -- 规格名称(如:大份、小份) + price DECIMAL(18,2) NOT NULL, + stock INTEGER, + status INTEGER NOT NULL DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_dish_specs_dish_id ON dish_specs(dish_id); +``` + +### 2.4 用户管理 + +#### users(用户表) +```sql +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + phone VARCHAR(20) UNIQUE NOT NULL, + nickname VARCHAR(50), + avatar_url VARCHAR(500), + gender INTEGER, -- 0:未知 1:男 2:女 + birthday DATE, + balance DECIMAL(18,2) DEFAULT 0, -- 余额 + points INTEGER DEFAULT 0, -- 积分 + status INTEGER NOT NULL DEFAULT 1, + last_login_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_users_phone ON users(phone); +``` + +#### user_addresses(用户地址表) +```sql +CREATE TABLE user_addresses ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id), + contact_name VARCHAR(50) NOT NULL, + contact_phone VARCHAR(20) NOT NULL, + province VARCHAR(50), + city VARCHAR(50), + district VARCHAR(50), + address VARCHAR(500) NOT NULL, + house_number VARCHAR(50), -- 门牌号 + latitude DECIMAL(10,7), + longitude DECIMAL(10,7), + is_default BOOLEAN DEFAULT FALSE, + label VARCHAR(20), -- 标签:家、公司等 + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_user_addresses_user_id ON user_addresses(user_id); +``` + +### 2.5 订单管理 + +#### orders(订单表) +```sql +CREATE TABLE orders ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + order_no VARCHAR(50) UNIQUE NOT NULL, -- 订单号 + merchant_id UUID NOT NULL REFERENCES merchants(id), + store_id UUID NOT NULL REFERENCES merchant_stores(id), + user_id UUID NOT NULL REFERENCES users(id), + + -- 收货信息 + delivery_address VARCHAR(500) NOT NULL, + delivery_latitude DECIMAL(10,7), + delivery_longitude DECIMAL(10,7), + contact_name VARCHAR(50) NOT NULL, + contact_phone VARCHAR(20) NOT NULL, + + -- 金额信息 + dish_amount DECIMAL(18,2) NOT NULL, -- 菜品金额 + delivery_fee DECIMAL(18,2) DEFAULT 0, -- 配送费 + package_fee DECIMAL(18,2) DEFAULT 0, -- 打包费 + discount_amount DECIMAL(18,2) DEFAULT 0, -- 优惠金额 + total_amount DECIMAL(18,2) NOT NULL, -- 总金额 + actual_amount DECIMAL(18,2) NOT NULL, -- 实付金额 + + -- 订单状态 + status INTEGER NOT NULL DEFAULT 1, -- 1:待支付 2:待接单 3:制作中 4:待配送 5:配送中 6:已完成 7:已取消 + payment_status INTEGER DEFAULT 0, -- 0:未支付 1:已支付 2:已退款 + payment_method VARCHAR(20), -- 支付方式 + payment_time TIMESTAMP WITH TIME ZONE, + + -- 时间信息 + estimated_delivery_time TIMESTAMP WITH TIME ZONE, -- 预计送达时间 + accepted_at TIMESTAMP WITH TIME ZONE, -- 接单时间 + cooking_at TIMESTAMP WITH TIME ZONE, -- 开始制作时间 + delivered_at TIMESTAMP WITH TIME ZONE, -- 送达时间 + completed_at TIMESTAMP WITH TIME ZONE, -- 完成时间 + cancelled_at TIMESTAMP WITH TIME ZONE, -- 取消时间 + + remark TEXT, -- 备注 + cancel_reason TEXT, -- 取消原因 + + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_orders_tenant_id ON orders(tenant_id); +CREATE INDEX idx_orders_order_no ON orders(order_no); +CREATE INDEX idx_orders_merchant_id ON orders(merchant_id); +CREATE INDEX idx_orders_user_id ON orders(user_id); +CREATE INDEX idx_orders_status ON orders(status); +CREATE INDEX idx_orders_created_at ON orders(created_at); +``` + +#### order_items(订单明细表) +```sql +CREATE TABLE order_items ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + order_id UUID NOT NULL REFERENCES orders(id), + dish_id UUID NOT NULL REFERENCES dishes(id), + dish_name VARCHAR(100) NOT NULL, -- 冗余字段,防止菜品被删除 + dish_image_url VARCHAR(500), + spec_id UUID REFERENCES dish_specs(id), + spec_name VARCHAR(50), + price DECIMAL(18,2) NOT NULL, -- 单价 + quantity INTEGER NOT NULL, -- 数量 + amount DECIMAL(18,2) NOT NULL, -- 小计 + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_order_items_order_id ON order_items(order_id); +CREATE INDEX idx_order_items_dish_id ON order_items(dish_id); +``` + +### 2.6 配送管理 + +#### delivery_drivers(配送员表) +```sql +CREATE TABLE delivery_drivers ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + merchant_id UUID REFERENCES merchants(id), -- NULL表示平台配送员 + name VARCHAR(50) NOT NULL, + phone VARCHAR(20) UNIQUE NOT NULL, + id_card VARCHAR(18), -- 身份证号 + vehicle_type VARCHAR(20), -- 车辆类型:电动车、摩托车 + vehicle_number VARCHAR(20), -- 车牌号 + status INTEGER NOT NULL DEFAULT 1, -- 1:空闲 2:配送中 3:休息 4:离线 + current_latitude DECIMAL(10,7), -- 当前位置 + current_longitude DECIMAL(10,7), + rating DECIMAL(3,2) DEFAULT 0, + total_deliveries INTEGER DEFAULT 0, -- 总配送单数 + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_delivery_drivers_merchant_id ON delivery_drivers(merchant_id); +CREATE INDEX idx_delivery_drivers_status ON delivery_drivers(status); +``` + +#### delivery_tasks(配送任务表) +```sql +CREATE TABLE delivery_tasks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + order_id UUID NOT NULL REFERENCES orders(id), + driver_id UUID REFERENCES delivery_drivers(id), + pickup_address VARCHAR(500) NOT NULL, -- 取餐地址 + pickup_latitude DECIMAL(10,7), + pickup_longitude DECIMAL(10,7), + delivery_address VARCHAR(500) NOT NULL, -- 送餐地址 + delivery_latitude DECIMAL(10,7), + delivery_longitude DECIMAL(10,7), + distance INTEGER, -- 配送距离(米) + estimated_time INTEGER, -- 预计时长(分钟) + status INTEGER NOT NULL DEFAULT 1, -- 1:待分配 2:待取餐 3:配送中 4:已送达 5:异常 + assigned_at TIMESTAMP WITH TIME ZONE, -- 分配时间 + picked_at TIMESTAMP WITH TIME ZONE, -- 取餐时间 + delivered_at TIMESTAMP WITH TIME ZONE, -- 送达时间 + delivery_fee DECIMAL(18,2) DEFAULT 0, -- 配送费 + remark TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_delivery_tasks_order_id ON delivery_tasks(order_id); +CREATE INDEX idx_delivery_tasks_driver_id ON delivery_tasks(driver_id); +CREATE INDEX idx_delivery_tasks_status ON delivery_tasks(status); +``` + +### 2.7 支付管理 + +#### payments(支付记录表) +```sql +CREATE TABLE payments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + order_id UUID NOT NULL REFERENCES orders(id), + user_id UUID NOT NULL REFERENCES users(id), + payment_no VARCHAR(50) UNIQUE NOT NULL, -- 支付单号 + payment_method VARCHAR(20) NOT NULL, -- 支付方式:wechat、alipay、balance + amount DECIMAL(18,2) NOT NULL, + status INTEGER NOT NULL DEFAULT 0, -- 0:待支付 1:支付中 2:成功 3:失败 4:已退款 + third_party_no VARCHAR(100), -- 第三方支付单号 + paid_at TIMESTAMP WITH TIME ZONE, + callback_data JSONB, -- 回调数据 + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_payments_order_id ON payments(order_id); +CREATE INDEX idx_payments_payment_no ON payments(payment_no); +CREATE INDEX idx_payments_user_id ON payments(user_id); +``` + +#### refunds(退款记录表) +```sql +CREATE TABLE refunds ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + order_id UUID NOT NULL REFERENCES orders(id), + payment_id UUID NOT NULL REFERENCES payments(id), + refund_no VARCHAR(50) UNIQUE NOT NULL, + amount DECIMAL(18,2) NOT NULL, + reason TEXT, + status INTEGER NOT NULL DEFAULT 0, -- 0:待审核 1:退款中 2:成功 3:失败 + third_party_no VARCHAR(100), + refunded_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_refunds_order_id ON refunds(order_id); +CREATE INDEX idx_refunds_payment_id ON refunds(payment_id); +``` + +### 2.8 营销管理 + +#### coupons(优惠券表) +```sql +CREATE TABLE coupons ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + merchant_id UUID REFERENCES merchants(id), -- NULL表示平台券 + name VARCHAR(100) NOT NULL, + type INTEGER NOT NULL, -- 1:满减券 2:折扣券 3:代金券 + discount_type INTEGER NOT NULL, -- 1:固定金额 2:百分比 + discount_value DECIMAL(18,2) NOT NULL, -- 优惠值 + min_order_amount DECIMAL(18,2) DEFAULT 0, -- 最低消费 + max_discount_amount DECIMAL(18,2), -- 最大优惠金额(折扣券用) + total_quantity INTEGER NOT NULL, -- 总数量 + received_quantity INTEGER DEFAULT 0, -- 已领取数量 + used_quantity INTEGER DEFAULT 0, -- 已使用数量 + valid_start_time TIMESTAMP WITH TIME ZONE NOT NULL, + valid_end_time TIMESTAMP WITH TIME ZONE NOT NULL, + status INTEGER NOT NULL DEFAULT 1, -- 1:正常 2:停用 + description TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_coupons_merchant_id ON coupons(merchant_id); +CREATE INDEX idx_coupons_status ON coupons(status); +``` + +#### user_coupons(用户优惠券表) +```sql +CREATE TABLE user_coupons ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id), + coupon_id UUID NOT NULL REFERENCES coupons(id), + status INTEGER NOT NULL DEFAULT 1, -- 1:未使用 2:已使用 3:已过期 + used_order_id UUID REFERENCES orders(id), + received_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + used_at TIMESTAMP WITH TIME ZONE, + expired_at TIMESTAMP WITH TIME ZONE NOT NULL +); + +CREATE INDEX idx_user_coupons_user_id ON user_coupons(user_id); +CREATE INDEX idx_user_coupons_coupon_id ON user_coupons(coupon_id); +CREATE INDEX idx_user_coupons_status ON user_coupons(status); +``` + +### 2.9 评价管理 + +#### reviews(评价表) +```sql +CREATE TABLE reviews ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id), + order_id UUID NOT NULL REFERENCES orders(id), + user_id UUID NOT NULL REFERENCES users(id), + merchant_id UUID NOT NULL REFERENCES merchants(id), + rating INTEGER NOT NULL, -- 评分 1-5 + taste_rating INTEGER, -- 口味评分 + package_rating INTEGER, -- 包装评分 + delivery_rating INTEGER, -- 配送评分 + content TEXT, + images JSONB, -- 评价图片 + is_anonymous BOOLEAN DEFAULT FALSE, + reply_content TEXT, -- 商家回复 + reply_at TIMESTAMP WITH TIME ZONE, + status INTEGER NOT NULL DEFAULT 1, -- 1:正常 2:隐藏 + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_reviews_order_id ON reviews(order_id); +CREATE INDEX idx_reviews_user_id ON reviews(user_id); +CREATE INDEX idx_reviews_merchant_id ON reviews(merchant_id); +``` + +### 2.10 系统管理 + +#### system_users(系统用户表) +```sql +CREATE TABLE system_users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID REFERENCES tenants(id), -- NULL表示平台管理员 + merchant_id UUID REFERENCES merchants(id), -- NULL表示租户管理员 + username VARCHAR(50) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + real_name VARCHAR(50), + phone VARCHAR(20), + email VARCHAR(100), + role_id UUID REFERENCES roles(id), + status INTEGER NOT NULL DEFAULT 1, + last_login_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_system_users_username ON system_users(username); +CREATE INDEX idx_system_users_tenant_id ON system_users(tenant_id); +``` + +#### roles(角色表) +```sql +CREATE TABLE roles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID REFERENCES tenants(id), + name VARCHAR(50) NOT NULL, + code VARCHAR(50) NOT NULL, + description TEXT, + permissions JSONB, -- 权限列表 + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP WITH TIME ZONE +); + +CREATE INDEX idx_roles_tenant_id ON roles(tenant_id); +``` + +#### operation_logs(操作日志表) +```sql +CREATE TABLE operation_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID REFERENCES tenants(id), + user_id UUID, + user_type VARCHAR(20), -- system_user, merchant_user, customer + module VARCHAR(50), -- 模块 + action VARCHAR(50), -- 操作 + description TEXT, + ip_address VARCHAR(50), + user_agent TEXT, + request_data JSONB, + response_data JSONB, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_operation_logs_tenant_id ON operation_logs(tenant_id); +CREATE INDEX idx_operation_logs_user_id ON operation_logs(user_id); +CREATE INDEX idx_operation_logs_created_at ON operation_logs(created_at); +``` + +## 3. 数据库索引策略 + +### 3.1 主键索引 +- 所有表使用UUID作为主键,自动创建主键索引 + +### 3.2 外键索引 +- 所有外键字段创建索引,提升关联查询性能 + +### 3.3 业务索引 +- 订单号、支付单号等唯一业务字段创建唯一索引 +- 状态字段创建普通索引 +- 时间字段(created_at)创建索引,支持时间范围查询 + +### 3.4 复合索引 +```sql +-- 订单查询常用复合索引 +CREATE INDEX idx_orders_merchant_status_created ON orders(merchant_id, status, created_at DESC); + +-- 用户订单查询 +CREATE INDEX idx_orders_user_status_created ON orders(user_id, status, created_at DESC); +``` + +### 3.5 地理位置索引 +```sql +-- 使用PostGIS扩展支持地理位置查询 +CREATE EXTENSION IF NOT EXISTS postgis; + +-- 门店位置索引 +CREATE INDEX idx_merchant_stores_location ON merchant_stores + USING GIST(ST_MakePoint(longitude, latitude)); +``` + +## 4. 数据库优化 + +### 4.1 分区策略 +```sql +-- 订单表按月分区 +CREATE TABLE orders_2024_01 PARTITION OF orders + FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'); + +CREATE TABLE orders_2024_02 PARTITION OF orders + FOR VALUES FROM ('2024-02-01') TO ('2024-03-01'); +``` + +### 4.2 物化视图 +```sql +-- 商家统计物化视图 +CREATE MATERIALIZED VIEW merchant_statistics AS +SELECT + m.id as merchant_id, + m.name, + COUNT(DISTINCT o.id) as total_orders, + SUM(o.actual_amount) as total_revenue, + AVG(r.rating) as avg_rating +FROM merchants m +LEFT JOIN orders o ON m.id = o.merchant_id AND o.status = 6 +LEFT JOIN reviews r ON m.id = r.merchant_id +GROUP BY m.id, m.name; + +CREATE UNIQUE INDEX ON merchant_statistics(merchant_id); +``` + +### 4.3 查询优化建议 +- 避免SELECT *,只查询需要的字段 +- 使用EXPLAIN分析查询计划 +- 合理使用JOIN,避免过多关联 +- 大数据量查询使用分页 +- 使用prepared statement防止SQL注入 + +## 5. 数据备份策略 + +### 5.1 备份方案 +- **全量备份**:每天凌晨2点执行 +- **增量备份**:每4小时执行一次 +- **WAL归档**:实时归档,支持PITR + +### 5.2 备份脚本示例 +```bash +#!/bin/bash +# 全量备份 +pg_dump -h localhost -U postgres -d takeout_saas -F c -f /backup/full_$(date +%Y%m%d).dump + +# 保留最近30天的备份 +find /backup -name "full_*.dump" -mtime +30 -delete +``` + +## 6. 数据迁移 + +### 6.1 EF Core Migrations +```bash +# 添加迁移 +dotnet ef migrations add InitialCreate --project TakeoutSaaS.Infrastructure + +# 更新数据库 +dotnet ef database update --project TakeoutSaaS.Infrastructure +``` + +### 6.2 版本控制 +- 所有数据库变更通过Migration管理 +- Migration文件纳入版本控制 +- 生产环境变更需要审核 + diff --git a/0_Document/04A_管理后台API.md b/0_Document/04A_管理后台API.md new file mode 100644 index 0000000..9a2d7cf --- /dev/null +++ b/0_Document/04A_管理后台API.md @@ -0,0 +1,93 @@ +# 管理后台 API 设计(Admin API) + +- 项目:TakeoutSaaS.AdminApi +- 版本前缀:/api/admin/v1 +- 认证:JWT + RBAC(平台、租户、商家角色) +- 租户识别:X-Tenant-Id 头或 Token Claim + +## 1. 通用规范 +- Content-Type: application/json +- 成功响应 +{ + "success": true, + "code": 200, + "message": "OK", + "data": {} +} +- 失败响应 +{ + "success": false, + "code": 422, + "message": "业务异常" +} + +## 2. 认证与权限 +- POST /api/admin/v1/auth/login +- POST /api/admin/v1/auth/refresh +- GET /api/admin/v1/auth/profile +- 角色:PlatformAdmin、TenantAdmin、MerchantAdmin、Staff + +## 3. 租户与商家管理 +- 租户 + - GET /api/admin/v1/tenants + - POST /api/admin/v1/tenants + - PUT /api/admin/v1/tenants/{id} + - PATCH/api/admin/v1/tenants/{id}/status +- 商家 + - GET /api/admin/v1/merchants + - POST /api/admin/v1/merchants + - GET /api/admin/v1/merchants/{id} + - PUT /api/admin/v1/merchants/{id} + - DELETE /api/admin/v1/merchants/{id} +- 门店 + - GET /api/admin/v1/stores + - POST /api/admin/v1/stores + +## 4. 菜品管理 +- 分类 + - GET /api/admin/v1/categories + - POST /api/admin/v1/categories + - PUT /api/admin/v1/categories/{id} + - DELETE /api/admin/v1/categories/{id} +- 菜品 + - GET /api/admin/v1/dishes + - POST /api/admin/v1/dishes + - GET /api/admin/v1/dishes/{id} + - PUT /api/admin/v1/dishes/{id} + - PATCH/api/admin/v1/dishes/batch-status + +## 5. 订单与售后 +- 订单 + - GET /api/admin/v1/orders + - GET /api/admin/v1/orders/{id} + - POST /api/admin/v1/orders/{id}/accept + - POST /api/admin/v1/orders/{id}/cook + - POST /api/admin/v1/orders/{id}/deliver + - POST /api/admin/v1/orders/{id}/complete + - POST /api/admin/v1/orders/{id}/cancel +- 售后 + - GET /api/admin/v1/refunds + - POST /api/admin/v1/refunds/{id}/approve + - POST /api/admin/v1/refunds/{id}/reject + +## 6. 营销与用户运营 +- 优惠券 + - GET /api/admin/v1/coupons + - POST /api/admin/v1/coupons + - PUT /api/admin/v1/coupons/{id} + - PATCH/api/admin/v1/coupons/{id}/status +- 评价 + - GET /api/admin/v1/reviews + - POST /api/admin/v1/reviews/{id}/reply + +## 7. 统计报表 +- GET /api/admin/v1/statistics/merchant/overview?merchantId= +- GET /api/admin/v1/statistics/platform/overview + +## 8. 文件上传 +- POST /api/admin/v1/files/upload (multipart/form-data) + +## 9. WebSocket(可选) +- ws://{host}/ws/admin?token=xxx +- 主题:order.new、order.status、refund.updated + diff --git a/0_Document/04B_小程序API.md b/0_Document/04B_小程序API.md new file mode 100644 index 0000000..6a1e340 --- /dev/null +++ b/0_Document/04B_小程序API.md @@ -0,0 +1,108 @@ +# 小程序/用户端 API 设计(Mini API) + +- 项目:TakeoutSaaS.MiniApi +- 版本前缀:/api/mini/v1 +- 认证:JWT(小程序登录态)/ 第三方登录(微信/支付宝) +- 租户识别:X-Tenant-Id 头或域名/小程序场景参数 + +## 1. 通用规范 +- Content-Type: application/json +- 成功响应 +{ + "success": true, + "code": 200, + "message": "OK", + "data": {} +} + +## 2. 认证登录 +- 微信登录 + - POST /api/mini/v1/auth/wechat/login + - { code, encryptedData?, iv? } +- 刷新Token + - POST /api/mini/v1/auth/refresh +- 获取用户信息 + - GET /api/mini/v1/me + +## 3. 商家与门店 +- 获取推荐商家 + - GET /api/mini/v1/merchants/recommend?lat=&lng=&pageIndex=&pageSize= +- 商家详情(含门店与公告) + - GET /api/mini/v1/merchants/{id} +- 门店列表(按距离) + - GET /api/mini/v1/merchants/{id}/stores?lat=&lng= + +## 4. 菜品与分类 +- 分类列表 + - GET /api/mini/v1/categories?merchantId= +- 菜品列表 + - GET /api/mini/v1/dishes?merchantId=&categoryId=&keyword=&sort= +- 菜品详情 + - GET /api/mini/v1/dishes/{id} + +## 5. 购物车 +- 获取购物车 + - GET /api/mini/v1/cart?merchantId= +- 同步购物车(幂等) + - PUT /api/mini/v1/cart + - { merchantId, items:[{dishId,specId?,quantity}] } +- 清空购物车 + - DELETE /api/mini/v1/cart?merchantId= + +## 6. 地址簿 +- 地址列表 + - GET /api/mini/v1/addresses +- 新增地址 + - POST /api/mini/v1/addresses +- 更新地址 + - PUT /api/mini/v1/addresses/{id} +- 删除地址 + - DELETE /api/mini/v1/addresses/{id} +- 设为默认地址 + - POST /api/mini/v1/addresses/{id}/default + +## 7. 订单 +- 创建订单(下单) + - POST /api/mini/v1/orders + - { merchantId, storeId, items:[{dishId,specId?,quantity}], addressId, remark?, couponId? } +- 订单列表 + - GET /api/mini/v1/orders?status=&pageIndex=&pageSize= +- 订单详情 + - GET /api/mini/v1/orders/{id} +- 取消订单 + - POST /api/mini/v1/orders/{id}/cancel { reason } +- 再来一单 + - POST /api/mini/v1/orders/{id}/reorder + +## 8. 支付 +- 预下单(获取支付参数) + - POST /api/mini/v1/payments + - { orderId, method: wechat|alipay } +- 查询支付状态 + - GET /api/mini/v1/payments/{paymentNo} +- 第三方回调(回调专用) + - POST /api/mini/v1/payments/callback/wechat + - POST /api/mini/v1/payments/callback/alipay + +## 9. 优惠券 +- 可领取优惠券列表 + - GET /api/mini/v1/coupons/available?merchantId= +- 领取优惠券 + - POST /api/mini/v1/coupons/{id}/receive +- 我的优惠券 + - GET /api/mini/v1/user-coupons?status= + +## 10. 评价 +- 发表评价 + - POST /api/mini/v1/reviews { orderId, rating, content?, images?[] } +- 商家评价列表 + - GET /api/mini/v1/reviews?merchantId=&rating=&page= + +## 11. 文件上传 +- 上传评价图片/头像 + - POST /api/mini/v1/files/upload (multipart/form-data) + +## 12. WebSocket(可选) +- ws://{host}/ws/mini?token=xxx +- 主题:order.status, payment.success + diff --git a/0_Document/04_API接口设计.md b/0_Document/04_API接口设计.md new file mode 100644 index 0000000..1f3735d --- /dev/null +++ b/0_Document/04_API接口设计.md @@ -0,0 +1,885 @@ +# 外卖SaaS系统 - API接口设计 + +## 1. API设计规范 + +### 1.1 RESTful规范 +- 使用标准HTTP方法:GET、POST、PUT、DELETE、PATCH +- URL使用名词复数形式,如:`/api/orders` +- 使用HTTP状态码表示请求结果 +- 版本控制:`/api/v1/orders` + +### 1.2 请求规范 +- **Content-Type**:`application/json` +- **认证方式**:Bearer Token (JWT) +- **租户识别**:通过Header `X-Tenant-Id` 或从Token中解析 + +### 1.3 响应规范 +```json +{ + "success": true, + "code": 200, + "message": "操作成功", + "data": {}, + "timestamp": "2024-01-01T12:00:00Z" +} +``` + +### 1.4 错误响应 +```json +{ + "success": false, + "code": 400, + "message": "参数错误", + "errors": [ + { + "field": "phone", + "message": "手机号格式不正确" + } + ], + "timestamp": "2024-01-01T12:00:00Z" +} +``` + +### 1.5 HTTP状态码 +- **200 OK**:请求成功 +- **201 Created**:创建成功 +- **204 No Content**:删除成功 +- **400 Bad Request**:参数错误 +- **401 Unauthorized**:未认证 +- **403 Forbidden**:无权限 +- **404 Not Found**:资源不存在 +- **409 Conflict**:资源冲突 +- **422 Unprocessable Entity**:业务逻辑错误 +- **500 Internal Server Error**:服务器错误 + +### 1.6 分页规范 +```json +// 请求参数 +{ + "pageIndex": 1, + "pageSize": 20, + "sortBy": "createdAt", + "sortOrder": "desc" +} + +// 响应格式 +{ + "success": true, + "data": { + "items": [], + "totalCount": 100, + "pageIndex": 1, + "pageSize": 20, + "totalPages": 5 + } +} +``` + +## 2. 认证授权接口 + +### 2.1 用户登录 +```http +POST /api/v1/auth/login +Content-Type: application/json + +{ + "phone": "13800138000", + "password": "password123", + "loginType": "customer" // customer, merchant, system +} + +Response: +{ + "success": true, + "data": { + "accessToken": "eyJhbGciOiJIUzI1NiIs...", + "refreshToken": "eyJhbGciOiJIUzI1NiIs...", + "expiresIn": 7200, + "tokenType": "Bearer", + "userInfo": { + "id": "uuid", + "phone": "13800138000", + "nickname": "张三", + "avatar": "https://..." + } + } +} +``` + +### 2.2 刷新Token +```http +POST /api/v1/auth/refresh +Content-Type: application/json + +{ + "refreshToken": "eyJhbGciOiJIUzI1NiIs..." +} + +Response: +{ + "success": true, + "data": { + "accessToken": "eyJhbGciOiJIUzI1NiIs...", + "expiresIn": 7200 + } +} +``` + +### 2.3 用户注册 +```http +POST /api/v1/auth/register +Content-Type: application/json + +{ + "phone": "13800138000", + "password": "password123", + "verificationCode": "123456", + "nickname": "张三" +} +``` + +### 2.4 发送验证码 +```http +POST /api/v1/auth/send-code +Content-Type: application/json + +{ + "phone": "13800138000", + "type": "register" // register, login, reset_password +} +``` + +## 3. 商家管理接口 + +### 3.1 获取商家列表 +```http +GET /api/v1/merchants?pageIndex=1&pageSize=20&keyword=&status=1 +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "items": [ + { + "id": "uuid", + "name": "美味餐厅", + "logo": "https://...", + "rating": 4.5, + "totalSales": 1000, + "status": 1, + "createdAt": "2024-01-01T12:00:00Z" + } + ], + "totalCount": 50, + "pageIndex": 1, + "pageSize": 20 + } +} +``` + +### 3.2 获取商家详情 +```http +GET /api/v1/merchants/{id} +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "id": "uuid", + "name": "美味餐厅", + "logo": "https://...", + "description": "专注美食20年", + "contactPhone": "400-123-4567", + "rating": 4.5, + "totalSales": 1000, + "status": 1, + "stores": [ + { + "id": "uuid", + "name": "总店", + "address": "北京市朝阳区...", + "phone": "010-12345678" + } + ] + } +} +``` + +### 3.3 创建商家 +```http +POST /api/v1/merchants +Authorization: Bearer {token} +Content-Type: application/json + +{ + "name": "美味餐厅", + "logo": "https://...", + "description": "专注美食20年", + "contactPhone": "400-123-4567", + "contactPerson": "张三", + "businessLicense": "91110000..." +} +``` + +### 3.4 更新商家信息 +```http +PUT /api/v1/merchants/{id} +Authorization: Bearer {token} +Content-Type: application/json + +{ + "name": "美味餐厅", + "logo": "https://...", + "description": "专注美食20年" +} +``` + +### 3.5 删除商家 +```http +DELETE /api/v1/merchants/{id} +Authorization: Bearer {token} +``` + +## 4. 菜品管理接口 + +### 4.1 获取菜品列表 +```http +GET /api/v1/dishes?merchantId={merchantId}&categoryId={categoryId}&keyword=&status=1&pageIndex=1&pageSize=20 +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "items": [ + { + "id": "uuid", + "name": "宫保鸡丁", + "description": "经典川菜", + "image": "https://...", + "price": 38.00, + "originalPrice": 48.00, + "salesCount": 500, + "rating": 4.8, + "status": 1, + "tags": ["热销", "招牌菜"] + } + ], + "totalCount": 100 + } +} +``` + +### 4.2 获取菜品详情 +```http +GET /api/v1/dishes/{id} +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "id": "uuid", + "name": "宫保鸡丁", + "description": "经典川菜,选用优质鸡肉...", + "image": "https://...", + "price": 38.00, + "originalPrice": 48.00, + "unit": "份", + "stock": 100, + "salesCount": 500, + "rating": 4.8, + "status": 1, + "tags": ["热销", "招牌菜"], + "specs": [ + { + "id": "uuid", + "name": "大份", + "price": 48.00, + "stock": 50 + }, + { + "id": "uuid", + "name": "小份", + "price": 28.00, + "stock": 50 + } + ] + } +} +``` + +### 4.3 创建菜品 +```http +POST /api/v1/dishes +Authorization: Bearer {token} +Content-Type: application/json + +{ + "merchantId": "uuid", + "categoryId": "uuid", + "name": "宫保鸡丁", + "description": "经典川菜", + "image": "https://...", + "price": 38.00, + "originalPrice": 48.00, + "unit": "份", + "stock": 100, + "tags": ["热销", "招牌菜"], + "specs": [ + { + "name": "大份", + "price": 48.00, + "stock": 50 + } + ] +} +``` + +### 4.4 更新菜品 +```http +PUT /api/v1/dishes/{id} +Authorization: Bearer {token} +Content-Type: application/json + +{ + "name": "宫保鸡丁", + "price": 38.00, + "stock": 100, + "status": 1 +} +``` + +### 4.5 批量上下架 +```http +PATCH /api/v1/dishes/batch-status +Authorization: Bearer {token} +Content-Type: application/json + +{ + "dishIds": ["uuid1", "uuid2"], + "status": 1 // 1:上架 2:下架 +} +``` + +## 5. 订单管理接口 + +### 5.1 创建订单 +```http +POST /api/v1/orders +Authorization: Bearer {token} +Content-Type: application/json + +{ + "merchantId": "uuid", + "storeId": "uuid", + "items": [ + { + "dishId": "uuid", + "specId": "uuid", + "quantity": 2, + "price": 38.00 + } + ], + "deliveryAddress": { + "contactName": "张三", + "contactPhone": "13800138000", + "address": "北京市朝阳区...", + "latitude": 39.9042, + "longitude": 116.4074 + }, + "remark": "少辣", + "couponId": "uuid" +} + +Response: +{ + "success": true, + "data": { + "orderId": "uuid", + "orderNo": "202401010001", + "totalAmount": 76.00, + "deliveryFee": 5.00, + "discountAmount": 10.00, + "actualAmount": 71.00, + "paymentInfo": { + "paymentNo": "PAY202401010001", + "qrCode": "https://..." // 支付二维码 + } + } +} +``` + +### 5.2 获取订单列表 +```http +GET /api/v1/orders?status=&startDate=&endDate=&pageIndex=1&pageSize=20 +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "items": [ + { + "id": "uuid", + "orderNo": "202401010001", + "merchantName": "美味餐厅", + "totalAmount": 76.00, + "actualAmount": 71.00, + "status": 2, + "statusText": "待接单", + "createdAt": "2024-01-01T12:00:00Z", + "items": [ + { + "dishName": "宫保鸡丁", + "specName": "大份", + "quantity": 2, + "price": 38.00 + } + ] + } + ], + "totalCount": 50 + } +} +``` + +### 5.3 获取订单详情 +```http +GET /api/v1/orders/{id} +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "id": "uuid", + "orderNo": "202401010001", + "merchant": { + "id": "uuid", + "name": "美味餐厅", + "phone": "400-123-4567" + }, + "items": [ + { + "dishName": "宫保鸡丁", + "dishImage": "https://...", + "specName": "大份", + "quantity": 2, + "price": 38.00, + "amount": 76.00 + } + ], + "deliveryAddress": { + "contactName": "张三", + "contactPhone": "13800138000", + "address": "北京市朝阳区..." + }, + "dishAmount": 76.00, + "deliveryFee": 5.00, + "packageFee": 2.00, + "discountAmount": 10.00, + "totalAmount": 83.00, + "actualAmount": 73.00, + "status": 2, + "statusText": "待接单", + "paymentStatus": 1, + "paymentMethod": "wechat", + "estimatedDeliveryTime": "2024-01-01T13:00:00Z", + "createdAt": "2024-01-01T12:00:00Z", + "paidAt": "2024-01-01T12:05:00Z", + "remark": "少辣", + "timeline": [ + { + "status": "created", + "statusText": "订单创建", + "time": "2024-01-01T12:00:00Z" + }, + { + "status": "paid", + "statusText": "支付成功", + "time": "2024-01-01T12:05:00Z" + } + ] + } +} +``` + +### 5.4 商家接单 +```http +POST /api/v1/orders/{id}/accept +Authorization: Bearer {token} +Content-Type: application/json + +{ + "estimatedTime": 30 // 预计制作时长(分钟) +} +``` + +### 5.5 开始制作 +```http +POST /api/v1/orders/{id}/cooking +Authorization: Bearer {token} +``` + +### 5.6 订单完成 +```http +POST /api/v1/orders/{id}/complete +Authorization: Bearer {token} +``` + +### 5.7 取消订单 +```http +POST /api/v1/orders/{id}/cancel +Authorization: Bearer {token} +Content-Type: application/json + +{ + "reason": "用户取消", + "cancelBy": "customer" // customer, merchant, system +} +``` + +## 6. 支付接口 + +### 6.1 创建支付 +```http +POST /api/v1/payments +Authorization: Bearer {token} +Content-Type: application/json + +{ + "orderId": "uuid", + "paymentMethod": "wechat", // wechat, alipay, balance + "amount": 71.00 +} + +Response: +{ + "success": true, + "data": { + "paymentNo": "PAY202401010001", + "qrCode": "https://...", // 支付二维码 + "deepLink": "weixin://..." // 唤起支付的深度链接 + } +} +``` + +### 6.2 查询支付状态 +```http +GET /api/v1/payments/{paymentNo} +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "paymentNo": "PAY202401010001", + "status": 2, // 0:待支付 1:支付中 2:成功 3:失败 + "amount": 71.00, + "paidAt": "2024-01-01T12:05:00Z" + } +} +``` + +### 6.3 支付回调(第三方调用) +```http +POST /api/v1/payments/callback/wechat +Content-Type: application/json + +{ + "out_trade_no": "PAY202401010001", + "transaction_id": "4200001234567890", + "total_fee": 7100, + "result_code": "SUCCESS" +} +``` + +### 6.4 申请退款 +```http +POST /api/v1/refunds +Authorization: Bearer {token} +Content-Type: application/json + +{ + "orderId": "uuid", + "amount": 71.00, + "reason": "不想要了" +} +``` + +## 7. 配送管理接口 + +### 7.1 获取配送任务列表 +```http +GET /api/v1/delivery-tasks?status=&driverId=&pageIndex=1&pageSize=20 +Authorization: Bearer {token} +``` + +### 7.2 分配配送员 +```http +POST /api/v1/delivery-tasks/{id}/assign +Authorization: Bearer {token} +Content-Type: application/json + +{ + "driverId": "uuid" +} +``` + +### 7.3 配送员接单 +```http +POST /api/v1/delivery-tasks/{id}/accept +Authorization: Bearer {token} +``` + +### 7.4 确认取餐 +```http +POST /api/v1/delivery-tasks/{id}/pickup +Authorization: Bearer {token} +``` + +### 7.5 确认送达 +```http +POST /api/v1/delivery-tasks/{id}/deliver +Authorization: Bearer {token} +Content-Type: application/json + +{ + "deliveryCode": "123456" // 取餐码 +} +``` + +### 7.6 更新配送员位置 +```http +POST /api/v1/delivery-drivers/location +Authorization: Bearer {token} +Content-Type: application/json + +{ + "latitude": 39.9042, + "longitude": 116.4074 +} +``` + +## 8. 营销管理接口 + +### 8.1 获取优惠券列表 +```http +GET /api/v1/coupons?merchantId=&status=1&pageIndex=1&pageSize=20 +Authorization: Bearer {token} +``` + +### 8.2 领取优惠券 +```http +POST /api/v1/coupons/{id}/receive +Authorization: Bearer {token} +``` + +### 8.3 获取用户优惠券 +```http +GET /api/v1/user-coupons?status=1&pageIndex=1&pageSize=20 +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "items": [ + { + "id": "uuid", + "couponName": "满50减10", + "discountValue": 10.00, + "minOrderAmount": 50.00, + "status": 1, + "expiredAt": "2024-12-31T23:59:59Z" + } + ] + } +} +``` + +### 8.4 获取可用优惠券 +```http +GET /api/v1/user-coupons/available?merchantId={merchantId}&amount={amount} +Authorization: Bearer {token} +``` + +## 9. 评价管理接口 + +### 9.1 创建评价 +```http +POST /api/v1/reviews +Authorization: Bearer {token} +Content-Type: application/json + +{ + "orderId": "uuid", + "rating": 5, + "tasteRating": 5, + "packageRating": 5, + "deliveryRating": 5, + "content": "非常好吃", + "images": ["https://...", "https://..."], + "isAnonymous": false +} +``` + +### 9.2 获取商家评价列表 +```http +GET /api/v1/reviews?merchantId={merchantId}&rating=&pageIndex=1&pageSize=20 + +Response: +{ + "success": true, + "data": { + "items": [ + { + "id": "uuid", + "userName": "张三", + "userAvatar": "https://...", + "rating": 5, + "content": "非常好吃", + "images": ["https://..."], + "createdAt": "2024-01-01T12:00:00Z", + "replyContent": "感谢支持", + "replyAt": "2024-01-01T13:00:00Z" + } + ], + "totalCount": 100, + "statistics": { + "avgRating": 4.8, + "totalReviews": 100, + "rating5Count": 80, + "rating4Count": 15, + "rating3Count": 3, + "rating2Count": 1, + "rating1Count": 1 + } + } +} +``` + +### 9.3 商家回复评价 +```http +POST /api/v1/reviews/{id}/reply +Authorization: Bearer {token} +Content-Type: application/json + +{ + "replyContent": "感谢您的支持" +} +``` + +## 10. 数据统计接口 + +### 10.1 商家数据概览 +```http +GET /api/v1/statistics/merchant/overview?merchantId={merchantId}&startDate=&endDate= +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "totalOrders": 1000, + "totalRevenue": 50000.00, + "avgOrderAmount": 50.00, + "completionRate": 0.95, + "todayOrders": 50, + "todayRevenue": 2500.00, + "orderTrend": [ + { + "date": "2024-01-01", + "orders": 50, + "revenue": 2500.00 + } + ], + "topDishes": [ + { + "dishId": "uuid", + "dishName": "宫保鸡丁", + "salesCount": 200, + "revenue": 7600.00 + } + ] + } +} +``` + +### 10.2 平台数据大盘 +```http +GET /api/v1/statistics/platform/dashboard?startDate=&endDate= +Authorization: Bearer {token} + +Response: +{ + "success": true, + "data": { + "totalMerchants": 100, + "totalUsers": 10000, + "totalOrders": 50000, + "totalRevenue": 2500000.00, + "activeMerchants": 80, + "activeUsers": 5000, + "todayOrders": 500, + "todayRevenue": 25000.00 + } +} +``` + +## 11. 文件上传接口 + +### 11.1 上传图片 +```http +POST /api/v1/files/upload +Authorization: Bearer {token} +Content-Type: multipart/form-data + +file: +type: dish_image // dish_image, merchant_logo, user_avatar, review_image + +Response: +{ + "success": true, + "data": { + "url": "https://cdn.example.com/images/xxx.jpg", + "fileName": "xxx.jpg", + "fileSize": 102400 + } +} +``` + +## 12. WebSocket实时通知 + +### 12.1 连接WebSocket +```javascript +// 连接地址 +ws://api.example.com/ws?token={jwt_token} + +// 订阅主题 +{ + "action": "subscribe", + "topics": ["order.new", "order.status", "delivery.location"] +} + +// 接收消息 +{ + "topic": "order.new", + "data": { + "orderId": "uuid", + "orderNo": "202401010001", + "merchantId": "uuid" + }, + "timestamp": "2024-01-01T12:00:00Z" +} +``` + +### 12.2 消息主题 +- `order.new`:新订单通知 +- `order.status`:订单状态变更 +- `delivery.location`:配送员位置更新 +- `payment.success`:支付成功通知 + diff --git a/0_Document/05_部署运维.md b/0_Document/05_部署运维.md new file mode 100644 index 0000000..f593ee9 --- /dev/null +++ b/0_Document/05_部署运维.md @@ -0,0 +1,976 @@ +# 外卖SaaS系统 - 部署运维 + +## 1. 环境要求 + +### 1.1 开发环境 +- **.NET SDK**:10.0 或更高版本 +- **IDE**:Visual Studio 2022 / JetBrains Rider / VS Code +- **数据库**:PostgreSQL 16+ +- **缓存**:Redis 7.0+ +- **消息队列**:RabbitMQ 3.12+ +- **Git**:版本控制 +- **Docker Desktop**:容器化开发(可选) + +### 1.2 生产环境 +- **操作系统**:Linux (Ubuntu 22.04 LTS / CentOS 8+) +- **运行时**:.NET Runtime 10.0 +- **Web服务器**:Nginx 1.24+ +- **数据库**:PostgreSQL 16+ (主从复制) +- **缓存**:Redis 7.0+ (哨兵模式) +- **消息队列**:RabbitMQ 3.12+ (集群模式) +- **对象存储**:MinIO / 阿里云OSS / 腾讯云COS +- **监控**:Prometheus + Grafana +- **日志**:ELK Stack (Elasticsearch + Logstash + Kibana) + +### 1.3 硬件要求(生产环境) +- **应用服务器**:4核8GB内存(最低),推荐8核16GB +- **数据库服务器**:8核16GB内存,SSD存储 +- **Redis服务器**:4核8GB内存 +- **负载均衡器**:2核4GB内存 + +## 2. 本地开发环境搭建 + +### 2.1 安装.NET SDK +```bash +# Windows +# 从官网下载安装:https://dotnet.microsoft.com/download + +# Linux (Ubuntu) +wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb +sudo dpkg -i packages-microsoft-prod.deb +sudo apt-get update +sudo apt-get install -y dotnet-sdk-10.0 + +# 验证安装 +dotnet --version +``` + +### 2.2 安装PostgreSQL +```bash +# Ubuntu +sudo apt-get update +sudo apt-get install -y postgresql-16 postgresql-contrib-16 + +# 启动服务 +sudo systemctl start postgresql +sudo systemctl enable postgresql + +# 创建数据库 +sudo -u postgres psql +CREATE DATABASE takeout_saas; +CREATE USER takeout_user WITH PASSWORD 'your_password'; +GRANT ALL PRIVILEGES ON DATABASE takeout_saas TO takeout_user; +\q +``` + +### 2.3 安装Redis +```bash +# Ubuntu +sudo apt-get install -y redis-server + +# 启动服务 +sudo systemctl start redis-server +sudo systemctl enable redis-server + +# 测试连接 +redis-cli ping +``` + +### 2.4 安装RabbitMQ +```bash +# Ubuntu +sudo apt-get install -y rabbitmq-server + +# 启动服务 +sudo systemctl start rabbitmq-server +sudo systemctl enable rabbitmq-server + +# 启用管理插件 +sudo rabbitmq-plugins enable rabbitmq_management + +# 创建用户 +sudo rabbitmqctl add_user admin password +sudo rabbitmqctl set_user_tags admin administrator +sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*" + +# 访问管理界面:http://localhost:15672 +``` + +### 2.5 使用Docker Compose(推荐) +```yaml +# docker-compose.yml +version: '3.8' + +services: + postgres: + image: postgres:16 + container_name: takeout_postgres + environment: + POSTGRES_DB: takeout_saas + POSTGRES_USER: takeout_user + POSTGRES_PASSWORD: your_password + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + redis: + image: redis:7-alpine + container_name: takeout_redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + + rabbitmq: + image: rabbitmq:3.12-management + container_name: takeout_rabbitmq + environment: + RABBITMQ_DEFAULT_USER: admin + RABBITMQ_DEFAULT_PASS: password + ports: + - "5672:5672" + - "15672:15672" + volumes: + - rabbitmq_data:/var/lib/rabbitmq + + minio: + image: minio/minio:latest + container_name: takeout_minio + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: admin + MINIO_ROOT_PASSWORD: password123 + ports: + - "9000:9000" + - "9001:9001" + volumes: + - minio_data:/data + +volumes: + postgres_data: + redis_data: + rabbitmq_data: + minio_data: +``` + +```bash +# 启动所有服务 +docker-compose up -d + +# 查看服务状态 +docker-compose ps + +# 停止服务 +docker-compose down +``` + +### 2.6 配置项目 +```bash +# 克隆项目 +git clone https://github.com/your-org/takeout-saas.git +cd takeout-saas + +# 还原依赖 +dotnet restore + +# 配置appsettings.Development.json +{ + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=takeout_saas;Username=takeout_user;Password=your_password" + }, + "Redis": { + "Configuration": "localhost:6379" + }, + "RabbitMQ": { + "Host": "localhost", + "Port": 5672, + "Username": "admin", + "Password": "password" + } +} + +# 执行数据库迁移 +cd src/TakeoutSaaS.Api +dotnet ef database update + +# 运行项目 +dotnet run +``` + +## 3. Docker部署 + +### 3.1 创建Dockerfile +```dockerfile +# Dockerfile +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src +COPY ["src/TakeoutSaaS.Api/TakeoutSaaS.Api.csproj", "src/TakeoutSaaS.Api/"] +COPY ["src/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj", "src/TakeoutSaaS.Application/"] +COPY ["src/TakeoutSaaS.Domain/TakeoutSaaS.Domain.csproj", "src/TakeoutSaaS.Domain/"] +COPY ["src/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj", "src/TakeoutSaaS.Infrastructure/"] +COPY ["src/TakeoutSaaS.Shared/TakeoutSaaS.Shared.csproj", "src/TakeoutSaaS.Shared/"] +RUN dotnet restore "src/TakeoutSaaS.Api/TakeoutSaaS.Api.csproj" +COPY . . +WORKDIR "/src/src/TakeoutSaaS.Api" +RUN dotnet build "TakeoutSaaS.Api.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "TakeoutSaaS.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "TakeoutSaaS.Api.dll"] +``` + +### 3.2 构建镜像 +```bash +# 构建镜像 +docker build -t takeout-saas-api:latest . + +# 查看镜像 +docker images | grep takeout-saas + +# 运行容器 +docker run -d \ + --name takeout-api \ + -p 8080:80 \ + -e ASPNETCORE_ENVIRONMENT=Production \ + -e ConnectionStrings__DefaultConnection="Host=postgres;Port=5432;Database=takeout_saas;Username=takeout_user;Password=your_password" \ + takeout-saas-api:latest +``` + +### 3.3 生产环境Docker Compose +```yaml +# docker-compose.prod.yml +version: '3.8' + +services: + api: + image: takeout-saas-api:latest + container_name: takeout_api + restart: always + environment: + ASPNETCORE_ENVIRONMENT: Production + ConnectionStrings__DefaultConnection: "Host=postgres;Port=5432;Database=takeout_saas;Username=takeout_user;Password=${DB_PASSWORD}" + Redis__Configuration: "redis:6379" + RabbitMQ__Host: "rabbitmq" + ports: + - "8080:80" + depends_on: + - postgres + - redis + - rabbitmq + networks: + - takeout_network + + nginx: + image: nginx:latest + container_name: takeout_nginx + restart: always + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/ssl:/etc/nginx/ssl + depends_on: + - api + networks: + - takeout_network + +networks: + takeout_network: + driver: bridge +``` + +## 4. Nginx配置 + +### 4.1 基础配置 +```nginx +# /etc/nginx/nginx.conf +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip压缩 + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml text/javascript + application/json application/javascript application/xml+rss + application/rss+xml font/truetype font/opentype + application/vnd.ms-fontobject image/svg+xml; + + # 限流配置 + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s; + limit_conn_zone $binary_remote_addr zone=conn_limit:10m; + + include /etc/nginx/conf.d/*.conf; +} +``` + +### 4.2 API服务配置 +```nginx +# /etc/nginx/conf.d/api.conf +upstream api_backend { + least_conn; + server api1:80 weight=1 max_fails=3 fail_timeout=30s; + server api2:80 weight=1 max_fails=3 fail_timeout=30s; + keepalive 32; +} + +server { + listen 80; + server_name api.example.com; + + # 重定向到HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name api.example.com; + + # SSL证书配置 + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # 安全头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # 客户端请求体大小限制 + client_max_body_size 10M; + + # API接口 + location /api/ { + # 限流 + limit_req zone=api_limit burst=20 nodelay; + limit_conn conn_limit 10; + + proxy_pass http://api_backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 超时设置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + # 缓冲设置 + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; + } + + # WebSocket + location /ws { + proxy_pass http://api_backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # WebSocket超时 + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + } + + # 健康检查 + location /health { + proxy_pass http://api_backend; + access_log off; + } + + # 静态文件缓存 + location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { + proxy_pass http://api_backend; + expires 30d; + add_header Cache-Control "public, immutable"; + } +} +``` + +## 5. 数据库部署 + +### 5.1 PostgreSQL主从复制 +```bash +# 主库配置 (postgresql.conf) +listen_addresses = '*' +wal_level = replica +max_wal_senders = 10 +wal_keep_size = 64MB +hot_standby = on + +# 主库配置 (pg_hba.conf) +host replication replicator 192.168.1.0/24 md5 + +# 创建复制用户 +CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'repl_password'; + +# 从库配置 +# 1. 停止从库 +sudo systemctl stop postgresql + +# 2. 清空从库数据目录 +rm -rf /var/lib/postgresql/16/main/* + +# 3. 从主库复制数据 +pg_basebackup -h master_ip -D /var/lib/postgresql/16/main -U replicator -P -v -R -X stream -C -S replica1 + +# 4. 启动从库 +sudo systemctl start postgresql + +# 5. 验证复制状态 +# 主库执行 +SELECT * FROM pg_stat_replication; +``` + +### 5.2 数据库备份脚本 +```bash +#!/bin/bash +# backup_db.sh + +BACKUP_DIR="/backup/postgres" +DATE=$(date +%Y%m%d_%H%M%S) +DB_NAME="takeout_saas" +DB_USER="takeout_user" +RETENTION_DAYS=30 + +# 创建备份目录 +mkdir -p $BACKUP_DIR + +# 全量备份 +pg_dump -h localhost -U $DB_USER -d $DB_NAME -F c -f $BACKUP_DIR/full_$DATE.dump + +# 压缩备份 +gzip $BACKUP_DIR/full_$DATE.dump + +# 删除过期备份 +find $BACKUP_DIR -name "full_*.dump.gz" -mtime +$RETENTION_DAYS -delete + +# 上传到对象存储(可选) +# aws s3 cp $BACKUP_DIR/full_$DATE.dump.gz s3://your-bucket/backups/ + +echo "Backup completed: full_$DATE.dump.gz" +``` + +### 5.3 定时备份(Crontab) +```bash +# 编辑crontab +crontab -e + +# 每天凌晨2点执行备份 +0 2 * * * /path/to/backup_db.sh >> /var/log/backup.log 2>&1 +``` + +## 6. Redis部署 + +### 6.1 Redis哨兵模式 +```bash +# redis.conf (主节点) +bind 0.0.0.0 +port 6379 +requirepass your_password +masterauth your_password + +# sentinel.conf +port 26379 +sentinel monitor mymaster 192.168.1.100 6379 2 +sentinel auth-pass mymaster your_password +sentinel down-after-milliseconds mymaster 5000 +sentinel parallel-syncs mymaster 1 +sentinel failover-timeout mymaster 10000 +``` + +### 6.2 Redis持久化配置 +```bash +# redis.conf +# RDB持久化 +save 900 1 +save 300 10 +save 60 10000 +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb + +# AOF持久化 +appendonly yes +appendfilename "appendonly.aof" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb +``` + +## 7. CI/CD配置 + +### 7.1 GitHub Actions +```yaml +# .github/workflows/deploy.yml +name: Deploy to Production + +on: + push: + branches: [ main ] + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '10.0.x' + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --configuration Release --no-restore + + - name: Test + run: dotnet test --no-build --verbosity normal + + - name: Publish + run: dotnet publish src/TakeoutSaaS.Api/TakeoutSaaS.Api.csproj -c Release -o ./publish + + - name: Build Docker image + run: | + docker build -t takeout-saas-api:${{ github.sha }} . + docker tag takeout-saas-api:${{ github.sha }} takeout-saas-api:latest + + - name: Push to Registry + run: | + echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + docker push takeout-saas-api:${{ github.sha }} + docker push takeout-saas-api:latest + + - name: Deploy to Server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + cd /opt/takeout-saas + docker-compose pull + docker-compose up -d + docker system prune -f +``` + +### 7.2 GitLab CI +```yaml +# .gitlab-ci.yml +stages: + - build + - test + - deploy + +variables: + DOCKER_IMAGE: registry.example.com/takeout-saas-api + +build: + stage: build + image: mcr.microsoft.com/dotnet/sdk:10.0 + script: + - dotnet restore + - dotnet build --configuration Release + artifacts: + paths: + - src/*/bin/Release/ + +test: + stage: test + image: mcr.microsoft.com/dotnet/sdk:10.0 + script: + - dotnet test --configuration Release + +deploy: + stage: deploy + image: docker:latest + services: + - docker:dind + script: + - docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHA . + - docker tag $DOCKER_IMAGE:$CI_COMMIT_SHA $DOCKER_IMAGE:latest + - docker push $DOCKER_IMAGE:$CI_COMMIT_SHA + - docker push $DOCKER_IMAGE:latest + only: + - main +``` + +## 8. 监控告警 + +### 8.1 Prometheus配置 +```yaml +# prometheus.yml +global: + scrape_interval: 15s + evaluation_interval: 15s + +scrape_configs: + - job_name: 'takeout-api' + static_configs: + - targets: ['api:80'] + metrics_path: '/metrics' + + - job_name: 'postgres' + static_configs: + - targets: ['postgres-exporter:9187'] + + - job_name: 'redis' + static_configs: + - targets: ['redis-exporter:9121'] + + - job_name: 'node' + static_configs: + - targets: ['node-exporter:9100'] +``` + +### 8.2 应用监控指标 +```csharp +// Program.cs - 添加Prometheus监控 +builder.Services.AddPrometheusMetrics(); + +app.UseMetricServer(); // /metrics端点 +app.UseHttpMetrics(); // HTTP请求指标 + +// 自定义指标 +public class MetricsService +{ + private static readonly Counter OrderCreatedCounter = Metrics + .CreateCounter("orders_created_total", "Total orders created"); + + private static readonly Histogram OrderProcessingDuration = Metrics + .CreateHistogram("order_processing_duration_seconds", "Order processing duration"); + + public void RecordOrderCreated() + { + OrderCreatedCounter.Inc(); + } + + public IDisposable MeasureOrderProcessing() + { + return OrderProcessingDuration.NewTimer(); + } +} +``` + +### 8.3 Grafana仪表板 +```json +{ + "dashboard": { + "title": "外卖SaaS系统监控", + "panels": [ + { + "title": "API请求速率", + "targets": [ + { + "expr": "rate(http_requests_total[5m])" + } + ] + }, + { + "title": "订单创建数", + "targets": [ + { + "expr": "increase(orders_created_total[1h])" + } + ] + }, + { + "title": "数据库连接数", + "targets": [ + { + "expr": "pg_stat_activity_count" + } + ] + } + ] + } +} +``` + +### 8.4 告警规则 +```yaml +# alert.rules.yml +groups: + - name: takeout_alerts + interval: 30s + rules: + - alert: HighErrorRate + expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05 + for: 5m + labels: + severity: critical + annotations: + summary: "高错误率告警" + description: "API错误率超过5%" + + - alert: DatabaseDown + expr: up{job="postgres"} == 0 + for: 1m + labels: + severity: critical + annotations: + summary: "数据库宕机" + description: "PostgreSQL数据库不可用" + + - alert: HighMemoryUsage + expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes > 0.9 + for: 5m + labels: + severity: warning + annotations: + summary: "内存使用率过高" + description: "内存使用率超过90%" +``` + +## 9. 日志管理 + +### 9.1 Serilog配置 +```json +{ + "Serilog": { + "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Elasticsearch"], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "logs/log-.txt", + "rollingInterval": "Day", + "retainedFileCountLimit": 30 + } + }, + { + "Name": "Elasticsearch", + "Args": { + "nodeUris": "http://elasticsearch:9200", + "indexFormat": "takeout-logs-{0:yyyy.MM.dd}", + "autoRegisterTemplate": true + } + } + ], + "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"] + } +} +``` + +### 9.2 ELK Stack部署 +```yaml +# docker-compose.elk.yml +version: '3.8' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0 + environment: + - discovery.type=single-node + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ports: + - "9200:9200" + volumes: + - es_data:/usr/share/elasticsearch/data + + logstash: + image: docker.elastic.co/logstash/logstash:8.11.0 + volumes: + - ./logstash/pipeline:/usr/share/logstash/pipeline + ports: + - "5044:5044" + depends_on: + - elasticsearch + + kibana: + image: docker.elastic.co/kibana/kibana:8.11.0 + ports: + - "5601:5601" + environment: + ELASTICSEARCH_HOSTS: http://elasticsearch:9200 + depends_on: + - elasticsearch + +volumes: + es_data: +``` + +## 10. 安全加固 + +### 10.1 防火墙配置 +```bash +# UFW防火墙 +sudo ufw enable +sudo ufw allow 22/tcp # SSH +sudo ufw allow 80/tcp # HTTP +sudo ufw allow 443/tcp # HTTPS +sudo ufw deny 5432/tcp # 禁止外部访问数据库 +sudo ufw deny 6379/tcp # 禁止外部访问Redis +``` + +### 10.2 SSL证书(Let's Encrypt) +```bash +# 安装Certbot +sudo apt-get install certbot python3-certbot-nginx + +# 获取证书 +sudo certbot --nginx -d api.example.com + +# 自动续期 +sudo certbot renew --dry-run + +# 添加定时任务 +0 3 * * * certbot renew --quiet +``` + +### 10.3 应用安全配置 +```csharp +// Program.cs +builder.Services.AddHsts(options => +{ + options.MaxAge = TimeSpan.FromDays(365); + options.IncludeSubDomains = true; + options.Preload = true; +}); + +builder.Services.AddHttpsRedirection(options => +{ + options.RedirectStatusCode = StatusCodes.Status308PermanentRedirect; + options.HttpsPort = 443; +}); + +// 添加安全头 +app.Use(async (context, next) => +{ + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Add("X-Frame-Options", "DENY"); + context.Response.Headers.Add("X-XSS-Protection", "1; mode=block"); + context.Response.Headers.Add("Referrer-Policy", "no-referrer"); + await next(); +}); +``` + +## 11. 性能优化 + +### 11.1 数据库连接池 +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=takeout_saas;Username=user;Password=pass;Pooling=true;MinPoolSize=5;MaxPoolSize=100;ConnectionLifetime=300" + } +} +``` + +### 11.2 Redis连接池 +```csharp +services.AddStackExchangeRedisCache(options => +{ + options.Configuration = configuration["Redis:Configuration"]; + options.InstanceName = "TakeoutSaaS:"; +}); +``` + +### 11.3 响应压缩 +```csharp +builder.Services.AddResponseCompression(options => +{ + options.EnableForHttps = true; + options.Providers.Add(); + options.Providers.Add(); +}); +``` + +## 12. 故障恢复 + +### 12.1 数据库恢复 +```bash +# 从备份恢复 +pg_restore -h localhost -U takeout_user -d takeout_saas -v /backup/full_20240101.dump + +# PITR恢复到指定时间点 +# 1. 停止数据库 +sudo systemctl stop postgresql + +# 2. 恢复基础备份 +rm -rf /var/lib/postgresql/16/main/* +tar -xzf /backup/base_backup.tar.gz -C /var/lib/postgresql/16/main/ + +# 3. 配置recovery.conf +restore_command = 'cp /backup/wal_archive/%f %p' +recovery_target_time = '2024-01-01 12:00:00' + +# 4. 启动数据库 +sudo systemctl start postgresql +``` + +### 12.2 应用回滚 +```bash +# Docker回滚到上一个版本 +docker-compose down +docker-compose up -d --force-recreate --no-deps api + +# 或使用特定版本 +docker pull takeout-saas-api:previous-version +docker-compose up -d +``` + diff --git a/0_Document/06_开发规范.md b/0_Document/06_开发规范.md new file mode 100644 index 0000000..f558dfb --- /dev/null +++ b/0_Document/06_开发规范.md @@ -0,0 +1,395 @@ +# 外卖SaaS系统 - 开发规范 + +## 1. 代码规范 + +### 1.1 命名规范 + +#### C#命名规范 +```csharp +// 类名:PascalCase +public class OrderService { } + +// 接口:I + PascalCase +public interface IOrderRepository { } + +// 方法:PascalCase +public async Task CreateOrderAsync() { } + +// 私有字段:_camelCase +private readonly IOrderRepository _orderRepository; + +// 公共属性:PascalCase +public string OrderNo { get; set; } + +// 局部变量:camelCase +var orderTotal = 100.00m; + +// 常量:PascalCase +public const int MaxOrderItems = 50; + +// 枚举:PascalCase +public enum OrderStatus +{ + Pending = 1, + Confirmed = 2, + Completed = 3 +} +``` + +#### 数据库命名规范 +```sql +-- 表名:小写,下划线分隔,复数 +orders +order_items +merchant_stores + +-- 字段名:小写,下划线分隔 +order_no +created_at +total_amount + +-- 索引:idx_表名_字段名 +idx_orders_merchant_id +idx_orders_created_at + +-- 外键:fk_表名_引用表名 +fk_orders_merchants +``` + +### 1.2 代码组织 + +#### 项目结构 +``` +TakeoutSaaS/ +├── src/ +│ ├── TakeoutSaaS.Api/ # Web API层 +│ │ ├── Controllers/ # 控制器 +│ │ ├── Filters/ # 过滤器 +│ │ ├── Middleware/ # 中间件 +│ │ ├── Models/ # DTO模型 +│ │ └── Program.cs +│ ├── TakeoutSaaS.Application/ # 应用层 +│ │ ├── Services/ # 应用服务 +│ │ ├── DTOs/ # 数据传输对象 +│ │ ├── Interfaces/ # 服务接口 +│ │ ├── Validators/ # 验证器 +│ │ ├── Mappings/ # 对象映射 +│ │ └── Commands/ # CQRS命令 +│ │ └── Queries/ # CQRS查询 +│ ├── TakeoutSaaS.Domain/ # 领域层 +│ │ ├── Entities/ # 实体 +│ │ ├── ValueObjects/ # 值对象 +│ │ ├── Enums/ # 枚举 +│ │ ├── Events/ # 领域事件 +│ │ └── Interfaces/ # 仓储接口 +│ ├── TakeoutSaaS.Infrastructure/ # 基础设施层 +│ │ ├── Data/ # 数据访问 +│ │ │ ├── EFCore/ # EF Core实现 +│ │ │ ├── Dapper/ # Dapper实现 +│ │ │ └── Repositories/ # 仓储实现 +│ │ ├── Cache/ # 缓存实现 +│ │ ├── MessageQueue/ # 消息队列 +│ │ └── ExternalServices/ # 外部服务 +│ └── TakeoutSaaS.Shared/ # 共享层 +│ ├── Constants/ # 常量 +│ ├── Exceptions/ # 异常 +│ ├── Extensions/ # 扩展方法 +│ └── Results/ # 统一返回结果 +├── tests/ +│ ├── TakeoutSaaS.UnitTests/ # 单元测试 +│ ├── TakeoutSaaS.IntegrationTests/ # 集成测试 +│ └── TakeoutSaaS.PerformanceTests/ # 性能测试 +└── docs/ # 文档 +``` + +### 1.3 代码注释 + +```csharp +/// +/// 订单服务接口 +/// +public interface IOrderService +{ + /// + /// 创建订单 + /// + /// 订单创建请求 + /// 订单信息 + /// 业务异常 + Task CreateOrderAsync(CreateOrderRequest request); +} + +// 复杂业务逻辑添加注释 +public async Task CalculateOrderAmount(Order order) +{ + // 1. 计算菜品总金额 + var dishAmount = order.Items.Sum(x => x.Price * x.Quantity); + + // 2. 计算配送费(距离 > 3km,每公里加收2元) + var deliveryFee = CalculateDeliveryFee(order.Distance); + + // 3. 应用优惠券折扣 + var discount = await ApplyCouponDiscountAsync(order.CouponId, dishAmount); + + // 4. 计算最终金额 + return dishAmount + deliveryFee - discount; +} +``` + +### 1.4 异常处理 + +```csharp +// 自定义业务异常 +public class BusinessException : Exception +{ + public int ErrorCode { get; } + + public BusinessException(int errorCode, string message) + : base(message) + { + ErrorCode = errorCode; + } +} + +// 全局异常处理中间件 +public class ExceptionHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "业务异常:{Message}", ex.Message); + await HandleBusinessExceptionAsync(context, ex); + } + catch (Exception ex) + { + _logger.LogError(ex, "系统异常:{Message}", ex.Message); + await HandleSystemExceptionAsync(context, ex); + } + } + + private static Task HandleBusinessExceptionAsync(HttpContext context, BusinessException ex) + { + context.Response.StatusCode = StatusCodes.Status422UnprocessableEntity; + return context.Response.WriteAsJsonAsync(new + { + success = false, + code = ex.ErrorCode, + message = ex.Message + }); + } +} + +// 使用示例 +public async Task GetOrderAsync(Guid orderId) +{ + var order = await _orderRepository.GetByIdAsync(orderId); + if (order == null) + { + throw new BusinessException(404, "订单不存在"); + } + return order; +} +``` + +## 2. Git工作流 + +### 2.1 分支管理 + +``` +main # 主分支,生产环境代码 +├── develop # 开发分支 +│ ├── feature/order-management # 功能分支 +│ ├── feature/payment-integration # 功能分支 +│ └── bugfix/order-calculation # 修复分支 +└── hotfix/critical-bug # 紧急修复分支 +``` + +### 2.2 分支命名规范 + +- **功能分支**:`feature/功能名称`(如:`feature/order-management`) +- **修复分支**:`bugfix/问题描述`(如:`bugfix/order-calculation`) +- **紧急修复**:`hotfix/问题描述`(如:`hotfix/payment-error`) +- **发布分支**:`release/版本号`(如:`release/v1.0.0`) + +### 2.3 提交信息规范 + +```bash +# 格式:(): + +# type类型: +# feat: 新功能 +# fix: 修复bug +# docs: 文档更新 +# style: 代码格式调整 +# refactor: 重构 +# perf: 性能优化 +# test: 测试相关 +# chore: 构建/工具相关 + +# 示例 +git commit -m "feat(order): 添加订单创建功能" +git commit -m "fix(payment): 修复支付回调处理错误" +git commit -m "docs(api): 更新API文档" +git commit -m "refactor(service): 重构订单服务" +``` + +### 2.4 工作流程 + +```bash +# 1. 从develop创建功能分支 +git checkout develop +git pull origin develop +git checkout -b feature/order-management + +# 2. 开发并提交 +git add . +git commit -m "feat(order): 添加订单创建功能" + +# 3. 推送到远程 +git push origin feature/order-management + +# 4. 创建Pull Request到develop分支 + +# 5. 代码审查通过后合并 + +# 6. 删除功能分支 +git branch -d feature/order-management +git push origin --delete feature/order-management +``` + +## 3. 代码审查 + +### 3.1 审查清单 + +- [ ] 代码符合命名规范 +- [ ] 代码逻辑清晰,易于理解 +- [ ] 适当的注释和文档 +- [ ] 异常处理完善 +- [ ] 单元测试覆盖 +- [ ] 性能考虑(N+1查询、大数据量处理) +- [ ] 安全性考虑(SQL注入、XSS、权限校验) +- [ ] 日志记录完善 +- [ ] 无硬编码配置 +- [ ] 符合SOLID原则 + +### 3.2 审查重点 + +```csharp +// ❌ 不好的实践 +public class OrderService +{ + public Order CreateOrder(CreateOrderRequest request) + { + // 直接在服务层操作DbContext + var order = new Order(); + _dbContext.Orders.Add(order); + _dbContext.SaveChanges(); + + // 硬编码配置 + var deliveryFee = 5.0m; + + // 没有异常处理 + // 没有日志记录 + + return order; + } +} + +// ✅ 好的实践 +public class OrderService : IOrderService +{ + private readonly IOrderRepository _orderRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + private readonly IOptions _settings; + + public async Task CreateOrderAsync(CreateOrderRequest request) + { + try + { + // 参数验证 + if (request == null) + throw new ArgumentNullException(nameof(request)); + + _logger.LogInformation("创建订单:{@Request}", request); + + // 业务逻辑 + var order = new Order + { + // ... 初始化订单 + DeliveryFee = _settings.Value.DefaultDeliveryFee + }; + + // 使用仓储 + await _orderRepository.AddAsync(order); + await _unitOfWork.SaveChangesAsync(); + + _logger.LogInformation("订单创建成功:{OrderId}", order.Id); + + return _mapper.Map(order); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建订单失败:{@Request}", request); + throw; + } + } +} +``` + + + +## 4. 单元测试规范 + +### 4.1 测试命名 +- 命名格式:`MethodName_Scenario_ExpectedResult` +- 测试覆盖率要求:核心业务逻辑 >= 80% + +### 4.2 测试示例 + +```csharp +[Fact] +public async Task CreateOrder_ValidRequest_ReturnsOrderDto() +{ + // Arrange + var request = new CreateOrderRequest { /* ... */ }; + + // Act + var result = await _orderService.CreateOrderAsync(request); + + // Assert + result.Should().NotBeNull(); + result.OrderNo.Should().NotBeNullOrEmpty(); +} +``` + +## 5. 性能优化规范 + +### 5.1 数据库查询优化 +- 避免N+1查询,使用Include预加载 +- 大数据量查询使用Dapper +- 合理使用索引 + +### 5.2 缓存策略 +- 商家信息:30分钟 +- 菜品信息:15分钟 +- 配置信息:1小时 +- 用户会话:2小时 + +## 6. 文档要求 + +### 6.1 代码文档 +- 所有公共API必须有XML文档注释 +- 复杂业务逻辑添加详细注释 +- README.md说明项目结构和运行方式 + +### 6.2 变更日志 +维护CHANGELOG.md记录版本变更 \ No newline at end of file diff --git a/0_Document/07_系统架构图.md b/0_Document/07_系统架构图.md new file mode 100644 index 0000000..3a5a350 --- /dev/null +++ b/0_Document/07_系统架构图.md @@ -0,0 +1,321 @@ +# 外卖SaaS系统 - 系统架构图 + +## 1. 整体架构图 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 客户端层 │ +│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ +│ │ Web管理端 │ │ Web用户端 │ │ 小程序端(用户) │ │ +│ │ (React/Vue) │ │ (React/Vue) │ │ (微信/支付宝) │ │ +│ └──────────────┘ └──────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ API网关层 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ Nginx / API Gateway │ │ +│ │ - 路由转发 │ │ +│ │ - 负载均衡 │ │ +│ │ - 限流熔断 │ │ +│ │ - SSL终止 │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ 应用服务层 │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 租户服务 │ │ 商家服务 │ │ 菜品服务 │ │ +│ │ - 租户管理 │ │ - 商家管理 │ │ - 菜品管理 │ │ +│ │ - 权限管理 │ │ - 门店管理 │ │ - 分类管理 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 订单服务 │ │ 配送服务 │ │ 用户服务 │ │ +│ │ - 订单管理 │ │ - 配送员管理 │ │ - 用户管理 │ │ +│ │ - 订单流转 │ │ - 任务分配 │ │ - 地址管理 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 支付服务 │ │ 营销服务 │ │ 通知服务 │ │ +│ │ - 支付处理 │ │ - 优惠券 │ │ - 短信通知 │ │ +│ │ - 退款处理 │ │ - 活动管理 │ │ - 推送通知 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ 基础设施层 │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ PostgreSQL │ │ Redis │ │ RabbitMQ │ │ +│ │ - 主数据库 │ │ - 缓存 │ │ - 消息队列 │ │ +│ │ - 主从复制 │ │ - 会话存储 │ │ - 异步任务 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ MinIO/OSS │ │ Elasticsearch│ │ Prometheus │ │ +│ │ - 对象存储 │ │ - 日志存储 │ │ - 监控告警 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## 2. 应用分层架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Presentation Layer │ +│ (表现层) │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ TakeoutSaaS.Api │ │ +│ │ - Controllers (控制器) │ │ +│ │ - Filters (过滤器) │ │ +│ │ - Middleware (中间件) │ │ +│ │ - Models (DTO模型) │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Application Layer │ +│ (应用层) │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ TakeoutSaaS.Application │ │ +│ │ - Services (应用服务) │ │ +│ │ - DTOs (数据传输对象) │ │ +│ │ - Interfaces (服务接口) │ │ +│ │ - Validators (验证器) │ │ +│ │ - Mappings (对象映射) │ │ +│ │ - Commands/Queries (CQRS) │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Domain Layer │ +│ (领域层) │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ TakeoutSaaS.Domain │ │ +│ │ - Entities (实体) │ │ +│ │ - ValueObjects (值对象) │ │ +│ │ - Enums (枚举) │ │ +│ │ - Events (领域事件) │ │ +│ │ - Interfaces (仓储接口) │ │ +│ │ - Specifications (规约) │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Infrastructure Layer │ +│ (基础设施层) │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ TakeoutSaaS.Infrastructure │ │ +│ │ - Data (数据访问) │ │ +│ │ - EFCore (EF Core实现) │ │ +│ │ - Dapper (Dapper实现) │ │ +│ │ - Repositories (仓储实现) │ │ +│ │ - Cache (缓存实现) │ │ +│ │ - MessageQueue (消息队列) │ │ +│ │ - ExternalServices (外部服务) │ │ +│ └──────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 3. 订单处理流程图 + +``` +用户下单 → 创建订单 → 支付 → 商家接单 → 制作 → 配送 → 完成 + │ │ │ │ │ │ │ + │ │ │ │ │ │ └─→ 订单完成 + │ │ │ │ │ └─→ 配送中 + │ │ │ │ └─→ 制作中 + │ │ │ └─→ 待制作 + │ │ └─→ 待接单 + │ └─→ 待支付 + └─→ 订单创建 + +取消流程: +用户取消 ──→ 退款处理 ──→ 订单取消 +商家拒单 ──→ 退款处理 ──→ 订单取消 +超时未支付 ──→ 自动取消 +``` + +## 4. 数据流转图 + +``` +┌──────────┐ +│ 客户端 │ +└────┬─────┘ + │ HTTP Request + ▼ +┌──────────────────┐ +│ API Gateway │ +│ (Nginx) │ +└────┬─────────────┘ + │ 路由转发 + ▼ +┌──────────────────┐ +│ Web API │ +│ - 认证授权 │ +│ - 参数验证 │ +└────┬─────────────┘ + │ 调用服务 + ▼ +┌──────────────────┐ +│ Application │ +│ Service │ +│ - 业务逻辑 │ +└────┬─────────────┘ + │ 数据访问 + ▼ +┌──────────────────┐ ┌──────────┐ +│ Repository │────→│ Cache │ +│ - EF Core │ │ (Redis) │ +│ - Dapper │ └──────────┘ +└────┬─────────────┘ + │ SQL查询 + ▼ +┌──────────────────┐ +│ PostgreSQL │ +│ Database │ +└──────────────────┘ +``` + +## 5. 多租户数据隔离架构 + +``` +┌─────────────────────────────────────────────────┐ +│ 租户识别中间件 │ +│ - 从JWT Token解析租户ID │ +│ - 从HTTP Header获取租户ID │ +└─────────────────────┬───────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ 租户上下文 │ +│ - 当前租户ID │ +│ - 租户配置信息 │ +└─────────────────────┬───────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ 数据访问层 │ +│ - 自动添加租户ID过滤 │ +│ - 全局查询过滤器 │ +└─────────────────────┬───────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ 数据库 │ +│ 租户A数据 │ 租户B数据 │ 租户C数据 │ +│ (tenant_id = A) (tenant_id = B) (tenant_id = C)│ +└─────────────────────────────────────────────────┘ +``` + +## 6. 缓存架构 + +``` +┌──────────────┐ +│ Application │ +└──────┬───────┘ + │ + ▼ +┌──────────────────────────────────┐ +│ Cache Aside Pattern │ +│ 1. 查询缓存 │ +│ 2. 缓存未命中,查询数据库 │ +│ 3. 写入缓存 │ +└──────┬───────────────────────────┘ + │ + ├─→ L1 Cache (Memory Cache) + │ - 进程内缓存 + │ - 热点数据 + │ + └─→ L2 Cache (Redis) + - 分布式缓存 + - 会话数据 + - 共享数据 +``` + +## 7. 消息队列架构 + +``` +┌──────────────┐ +│ Producer │ +│ (订单服务) │ +└──────┬───────┘ + │ 发布事件 + ▼ +┌──────────────────┐ +│ RabbitMQ │ +│ Exchange │ +└──────┬───────────┘ + │ + ├─→ Queue: order.created + │ └─→ Consumer: 通知服务 + │ + ├─→ Queue: order.paid + │ └─→ Consumer: 库存服务 + │ + └─→ Queue: order.completed + └─→ Consumer: 统计服务 +``` + +## 8. 部署架构 + +``` +┌─────────────────────────────────────────────────┐ +│ 负载均衡器 (Nginx) │ +└─────────────┬───────────────────────────────────┘ + │ + ┌───────┴───────┐ + │ │ + ▼ ▼ +┌──────────┐ ┌──────────┐ +│ API 1 │ │ API 2 │ +│ (容器) │ │ (容器) │ +└────┬─────┘ └────┬─────┘ + │ │ + └───────┬───────┘ + │ + ┌───────┴────────┬──────────────┐ + │ │ │ + ▼ ▼ ▼ +┌──────────┐ ┌──────────┐ ┌──────────┐ +│PostgreSQL│ │ Redis │ │ RabbitMQ │ +│ 主从 │ │ 哨兵 │ │ 集群 │ +└──────────┘ └──────────┘ └──────────┘ +``` + +## 9. 监控架构 + +``` +┌──────────────────────────────────────────┐ +│ 应用程序 │ +│ - 业务指标 │ +│ - 性能指标 │ +│ - 日志输出 │ +└─────────┬────────────────────────────────┘ + │ + ┌─────┴─────┬──────────┐ + │ │ │ + ▼ ▼ ▼ +┌────────┐ ┌────────┐ ┌────────┐ +│Metrics │ │ Logs │ │Traces │ +│ │ │ │ │ │ +└───┬────┘ └───┬────┘ └───┬────┘ + │ │ │ + ▼ ▼ ▼ +┌──────────────────────────────┐ +│ Prometheus │ +│ Elasticsearch │ +│ Jaeger │ +└─────────┬────────────────────┘ + │ + ▼ +┌──────────────────────────────┐ +│ Grafana │ +│ Kibana │ +│ - 可视化仪表板 │ +│ - 告警配置 │ +└──────────────────────────────┘ +``` diff --git a/0_Document/08_AI编程规范.md b/0_Document/08_AI编程规范.md new file mode 100644 index 0000000..c893af3 --- /dev/null +++ b/0_Document/08_AI编程规范.md @@ -0,0 +1,1589 @@ +# 外卖SaaS系统 - AI编程规范汇总 + +> 本文档专门为AI编程助手准备,汇总了所有编码规范和规则,确保代码质量和一致性。 + +## 1. 技术栈要求 + +### 1.1 核心技术 +- **.NET 10** + **ASP.NET Core Web API** +- **Entity Framework Core 10**(复杂查询和实体管理) +- **Dapper 2.1+**(高性能查询和批量操作) +- **PostgreSQL 16+**(主数据库) +- **Redis 7.0+**(缓存和会话) +- **RabbitMQ 3.12+**(消息队列) + +### 1.2 必用框架和库 +- **AutoMapper**:对象映射 +- **FluentValidation**:数据验证 +- **Serilog**:结构化日志 +- **MediatR**:CQRS和中介者模式 +- **Hangfire**:后台任务调度 +- **Polly**:弹性和瞬态故障处理 +- **Swagger/Swashbuckle**:API文档 + +### 1.3 测试框架 +- **xUnit**:单元测试 +- **Moq**:Mock框架 +- **FluentAssertions**:断言库 + +## 2. 命名规范(严格遵守) + +### 2.1 C#命名规范 +```csharp +// ✅ 类名:PascalCase +public class OrderService { } + +// ✅ 接口:I + PascalCase +public interface IOrderRepository { } + +// ✅ 方法:PascalCase,异步方法以Async结尾 +public async Task CreateOrderAsync() { } + +// ✅ 私有字段:_camelCase(下划线前缀) +private readonly IOrderRepository _orderRepository; + +// ✅ 公共属性:PascalCase +public string OrderNo { get; set; } + +// ✅ 局部变量:camelCase +var orderTotal = 100.00m; + +// ✅ 常量:PascalCase +public const int MaxOrderItems = 50; + +// ✅ 枚举:PascalCase,枚举值也是PascalCase +public enum OrderStatus +{ + Pending = 1, + Confirmed = 2, + Completed = 3 +} +``` + +### 2.2 数据库命名规范 +```sql +-- ✅ 表名:小写,下划线分隔,复数形式 +orders +order_items +merchant_stores + +-- ✅ 字段名:小写,下划线分隔 +order_no +created_at +total_amount + +-- ✅ 索引:idx_表名_字段名 +idx_orders_merchant_id +idx_orders_created_at + +-- ✅ 外键:fk_表名_引用表名 +fk_orders_merchants +``` + +## 3. 项目结构规范 + +### 3.1 分层架构(DDD + Clean Architecture) +``` +TakeoutSaaS/ +├── src/ +│ ├── Api/ # API层(表现层) +│ │ ├── Controllers/ # 控制器 +│ │ ├── Filters/ # 过滤器 +│ │ ├── Middleware/ # 中间件 +│ │ └── Models/ # DTO模型 +│ ├── Application/ # 应用层 +│ │ ├── Services/ # 应用服务 +│ │ ├── DTOs/ # 数据传输对象 +│ │ ├── Interfaces/ # 服务接口 +│ │ ├── Validators/ # FluentValidation验证器 +│ │ ├── Mappings/ # AutoMapper配置 +│ │ └── Commands/Queries/ # CQRS命令和查询 +│ ├── Domain/ # 领域层(核心业务) +│ │ ├── Entities/ # 实体 +│ │ ├── ValueObjects/ # 值对象 +│ │ ├── Enums/ # 枚举 +│ │ ├── Events/ # 领域事件 +│ │ └── Interfaces/ # 仓储接口 +│ ├── Infrastructure/ # 基础设施层 +│ │ ├── Data/ # 数据访问 +│ │ │ ├── EFCore/ # EF Core实现 +│ │ │ ├── Dapper/ # Dapper实现 +│ │ │ └── Repositories/ # 仓储实现 +│ │ ├── Cache/ # 缓存实现 +│ │ ├── MessageQueue/ # 消息队列 +│ │ └── ExternalServices/ # 外部服务 +│ ├── Core/ # 核心共享层 +│ │ ├── Constants/ # 常量 +│ │ ├── Exceptions/ # 异常 +│ │ ├── Extensions/ # 扩展方法 +│ │ └── Results/ # 统一返回结果 +│ └── Modules/ # 模块化(可选) +└── tests/ + ├── UnitTests/ # 单元测试 + ├── IntegrationTests/ # 集成测试 + └── PerformanceTests/ # 性能测试 +``` + +### 3.2 文件组织规则 +- 每个文件只包含一个公共类/接口 +- 文件名与类名保持一致 +- 相关的类放在同一个文件夹 +- 使用命名空间反映文件夹结构 + +## 4. 代码注释规范 + +### 4.1 XML文档注释(必须) +```csharp +/// +/// 订单服务接口 +/// +public interface IOrderService +{ + /// + /// 创建订单 + /// + /// 订单创建请求 + /// 订单信息 + /// 业务异常 + Task CreateOrderAsync(CreateOrderRequest request); +} +``` + +### 4.2 业务逻辑注释 +```csharp +// ✅ 复杂业务逻辑必须添加注释 +public async Task CalculateOrderAmount(Order order) +{ + // 1. 计算菜品总金额 + var dishAmount = order.Items.Sum(x => x.Price * x.Quantity); + + // 2. 计算配送费(距离 > 3km,每公里加收2元) + var deliveryFee = CalculateDeliveryFee(order.Distance); + + // 3. 应用优惠券折扣 + var discount = await ApplyCouponDiscountAsync(order.CouponId, dishAmount); + + // 4. 计算最终金额 + return dishAmount + deliveryFee - discount; +} +``` + +## 5. 异常处理规范 + +### 5.1 自定义异常 +```csharp +// ✅ 业务异常 +public class BusinessException : Exception +{ + public int ErrorCode { get; } + + public BusinessException(int errorCode, string message) + : base(message) + { + ErrorCode = errorCode; + } +} + +// ✅ 验证异常 +public class ValidationException : Exception +{ + public IDictionary Errors { get; } + + public ValidationException(IDictionary errors) + : base("一个或多个验证错误") + { + Errors = errors; + } +} +``` + +### 5.2 全局异常处理中间件(必须实现) +```csharp +public class ExceptionHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "业务异常:{Message}", ex.Message); + await HandleBusinessExceptionAsync(context, ex); + } + catch (ValidationException ex) + { + _logger.LogWarning(ex, "验证异常:{Errors}", ex.Errors); + await HandleValidationExceptionAsync(context, ex); + } + catch (Exception ex) + { + _logger.LogError(ex, "系统异常:{Message}", ex.Message); + await HandleSystemExceptionAsync(context, ex); + } + } + + private static Task HandleBusinessExceptionAsync(HttpContext context, BusinessException ex) + { + context.Response.StatusCode = StatusCodes.Status422UnprocessableEntity; + return context.Response.WriteAsJsonAsync(new + { + success = false, + code = ex.ErrorCode, + message = ex.Message + }); + } +} +``` + +### 5.3 异常使用示例 +```csharp +// ✅ 正确的异常抛出 +public async Task GetOrderAsync(Guid orderId) +{ + var order = await _orderRepository.GetByIdAsync(orderId); + if (order == null) + { + throw new BusinessException(404, "订单不存在"); + } + return order; +} + +// ❌ 错误:不要吞掉异常 +try +{ + // ... +} +catch (Exception) +{ + // 什么都不做 - 这是错误的! +} +``` + +## 6. 服务层编码规范 + +### 6.1 服务类结构(标准模板) +```csharp +// ✅ 好的服务实现 +public class OrderService : IOrderService +{ + private readonly IOrderRepository _orderRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + private readonly IMapper _mapper; + private readonly IOptions _settings; + + public OrderService( + IOrderRepository orderRepository, + IUnitOfWork unitOfWork, + ILogger logger, + IMapper mapper, + IOptions settings) + { + _orderRepository = orderRepository; + _unitOfWork = unitOfWork; + _logger = logger; + _mapper = mapper; + _settings = settings; + } + + public async Task CreateOrderAsync(CreateOrderRequest request) + { + try + { + // 1. 参数验证(FluentValidation会自动验证,这里是额外检查) + if (request == null) + throw new ArgumentNullException(nameof(request)); + + // 2. 记录日志 + _logger.LogInformation("创建订单:{@Request}", request); + + // 3. 业务逻辑 + var order = new Order + { + OrderNo = GenerateOrderNo(), + TotalAmount = request.TotalAmount, + DeliveryFee = _settings.Value.DefaultDeliveryFee, + CreatedAt = DateTime.UtcNow + }; + + // 4. 数据持久化 + await _orderRepository.AddAsync(order); + await _unitOfWork.SaveChangesAsync(); + + // 5. 记录成功日志 + _logger.LogInformation("订单创建成功:{OrderId}", order.Id); + + // 6. 返回DTO + return _mapper.Map(order); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建订单失败:{@Request}", request); + throw; + } + } +} + +// ❌ 错误的服务实现 +public class BadOrderService +{ + // ❌ 直接注入DbContext而不是仓储 + private readonly AppDbContext _dbContext; + + // ❌ 没有日志 + // ❌ 硬编码配置 + public Order CreateOrder(CreateOrderRequest request) + { + var order = new Order(); + order.DeliveryFee = 5.0m; // ❌ 硬编码 + _dbContext.Orders.Add(order); + _dbContext.SaveChanges(); // ❌ 同步方法 + return order; // ❌ 返回实体而不是DTO + } +} +``` + +### 6.2 依赖注入规则 +```csharp +// ✅ 使用构造函数注入 +public class OrderService +{ + private readonly IOrderRepository _orderRepository; + + public OrderService(IOrderRepository orderRepository) + { + _orderRepository = orderRepository; + } +} + +// ❌ 不要使用属性注入 +public class BadOrderService +{ + [Inject] + public IOrderRepository OrderRepository { get; set; } +} + +// ❌ 不要使用服务定位器模式 +public class BadOrderService +{ + public void DoSomething() + { + var repository = ServiceLocator.GetService(); + } +} +``` + +## 7. 数据访问规范 + +### 7.1 仓储模式(必须使用) +```csharp +// ✅ 仓储接口 +public interface IOrderRepository +{ + Task GetByIdAsync(Guid id); + Task> GetAllAsync(); + Task AddAsync(Order order); + Task UpdateAsync(Order order); + Task DeleteAsync(Guid id); + Task ExistsAsync(Guid id); +} + +// ✅ EF Core仓储实现 +public class OrderRepository : IOrderRepository +{ + private readonly AppDbContext _context; + + public OrderRepository(AppDbContext context) + { + _context = context; + } + + public async Task GetByIdAsync(Guid id) + { + return await _context.Orders + .Include(o => o.OrderItems) + .Include(o => o.Customer) + .FirstOrDefaultAsync(o => o.Id == id); + } + + public async Task AddAsync(Order order) + { + await _context.Orders.AddAsync(order); + return order; + } +} +``` + +### 7.2 EF Core vs Dapper 使用场景 +```csharp +// ✅ EF Core - 复杂查询和实体管理 +public async Task GetOrderWithDetailsAsync(Guid orderId) +{ + return await _dbContext.Orders + .Include(o => o.OrderItems) + .ThenInclude(oi => oi.Dish) + .Include(o => o.Customer) + .Include(o => o.Merchant) + .FirstOrDefaultAsync(o => o.Id == orderId); +} + +// ✅ Dapper - 高性能统计查询 +public async Task GetOrderStatisticsAsync(DateTime startDate, DateTime endDate) +{ + var sql = @" + SELECT + COUNT(*) as TotalOrders, + SUM(total_amount) as TotalAmount, + AVG(total_amount) as AvgAmount, + MAX(total_amount) as MaxAmount, + MIN(total_amount) as MinAmount + FROM orders + WHERE created_at BETWEEN @StartDate AND @EndDate + AND status = @Status"; + + return await _connection.QueryFirstOrDefaultAsync(sql, + new { StartDate = startDate, EndDate = endDate, Status = OrderStatus.Completed }); +} + +// ✅ Dapper - 批量插入 +public async Task BulkInsertOrdersAsync(IEnumerable orders) +{ + var sql = @" + INSERT INTO orders (id, order_no, merchant_id, total_amount, created_at) + VALUES (@Id, @OrderNo, @MerchantId, @TotalAmount, @CreatedAt)"; + + return await _connection.ExecuteAsync(sql, orders); +} +``` + +### 7.3 工作单元模式(必须使用) +```csharp +// ✅ 工作单元接口 +public interface IUnitOfWork : IDisposable +{ + Task SaveChangesAsync(CancellationToken cancellationToken = default); + Task BeginTransactionAsync(); + Task CommitTransactionAsync(); + Task RollbackTransactionAsync(); +} + +// ✅ 使用工作单元 +public async Task CreateOrderWithItemsAsync(CreateOrderRequest request) +{ + await _unitOfWork.BeginTransactionAsync(); + + try + { + // 1. 创建订单 + var order = new Order { /* ... */ }; + await _orderRepository.AddAsync(order); + + // 2. 创建订单项 + foreach (var item in request.Items) + { + var orderItem = new OrderItem { /* ... */ }; + await _orderItemRepository.AddAsync(orderItem); + } + + // 3. 提交事务 + await _unitOfWork.SaveChangesAsync(); + await _unitOfWork.CommitTransactionAsync(); + + return _mapper.Map(order); + } + catch + { + await _unitOfWork.RollbackTransactionAsync(); + throw; + } +} +``` + +## 8. CQRS模式规范 + +### 8.1 命令(Command)- 写操作 +```csharp +// ✅ 命令定义 +public class CreateOrderCommand : IRequest +{ + public Guid MerchantId { get; set; } + public Guid CustomerId { get; set; } + public List Items { get; set; } + public decimal TotalAmount { get; set; } +} + +// ✅ 命令处理器 +public class CreateOrderCommandHandler : IRequestHandler +{ + private readonly IOrderRepository _orderRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken) + { + _logger.LogInformation("处理创建订单命令:{@Command}", request); + + var order = new Order + { + MerchantId = request.MerchantId, + CustomerId = request.CustomerId, + TotalAmount = request.TotalAmount + }; + + await _orderRepository.AddAsync(order); + await _unitOfWork.SaveChangesAsync(cancellationToken); + + return _mapper.Map(order); + } +} +``` + +### 8.2 查询(Query)- 读操作 +```csharp +// ✅ 查询定义 +public class GetOrderByIdQuery : IRequest +{ + public Guid OrderId { get; set; } +} + +// ✅ 查询处理器 +public class GetOrderByIdQueryHandler : IRequestHandler +{ + private readonly IOrderRepository _orderRepository; + private readonly IMapper _mapper; + + public async Task Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) + { + var order = await _orderRepository.GetByIdAsync(request.OrderId); + + if (order == null) + throw new BusinessException(404, "订单不存在"); + + return _mapper.Map(order); + } +} +``` + +## 9. 验证规范(FluentValidation) + +### 9.1 验证器定义 +```csharp +// ✅ 使用FluentValidation +public class CreateOrderRequestValidator : AbstractValidator +{ + public CreateOrderRequestValidator() + { + RuleFor(x => x.MerchantId) + .NotEmpty().WithMessage("商家ID不能为空"); + + RuleFor(x => x.CustomerId) + .NotEmpty().WithMessage("客户ID不能为空"); + + RuleFor(x => x.Items) + .NotEmpty().WithMessage("订单项不能为空") + .Must(items => items.Count <= 50).WithMessage("订单项不能超过50个"); + + RuleFor(x => x.TotalAmount) + .GreaterThan(0).WithMessage("订单金额必须大于0"); + + RuleFor(x => x.DeliveryAddress) + .NotEmpty().WithMessage("配送地址不能为空") + .MaximumLength(200).WithMessage("配送地址不能超过200个字符"); + } +} +``` + +### 9.2 验证器注册 +```csharp +// ✅ 在Program.cs中注册 +builder.Services.AddValidatorsFromAssemblyContaining(); +builder.Services.AddFluentValidationAutoValidation(); +``` + +## 10. 缓存策略规范 + + +### 10.1 缓存时间策略 +```csharp +// ✅ 缓存时间常量 +public static class CacheTimeouts +{ + public static readonly TimeSpan MerchantInfo = TimeSpan.FromMinutes(30); + public static readonly TimeSpan DishInfo = TimeSpan.FromMinutes(15); + public static readonly TimeSpan UserSession = TimeSpan.FromHours(2); + public static readonly TimeSpan ConfigInfo = TimeSpan.FromHours(1); + public static readonly TimeSpan HotData = TimeSpan.FromMinutes(5); +} +``` + +### 10.2 缓存使用示例 +```csharp +// ✅ 使用分布式缓存(Redis) +public class MerchantService : IMerchantService +{ + private readonly IMerchantRepository _merchantRepository; + private readonly IDistributedCache _cache; + private readonly ILogger _logger; + + public async Task GetMerchantAsync(Guid merchantId) + { + var cacheKey = $"merchant:{merchantId}"; + + // 1. 尝试从缓存获取 + var cachedData = await _cache.GetStringAsync(cacheKey); + if (!string.IsNullOrEmpty(cachedData)) + { + _logger.LogDebug("从缓存获取商家信息:{MerchantId}", merchantId); + return JsonSerializer.Deserialize(cachedData); + } + + // 2. 缓存未命中,从数据库查询 + var merchant = await _merchantRepository.GetByIdAsync(merchantId); + if (merchant == null) + throw new BusinessException(404, "商家不存在"); + + var dto = _mapper.Map(merchant); + + // 3. 写入缓存 + var cacheOptions = new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = CacheTimeouts.MerchantInfo + }; + await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(dto), cacheOptions); + + _logger.LogDebug("商家信息已缓存:{MerchantId}", merchantId); + return dto; + } + + public async Task UpdateMerchantAsync(Guid merchantId, UpdateMerchantRequest request) + { + // 更新数据库 + var merchant = await _merchantRepository.GetByIdAsync(merchantId); + // ... 更新逻辑 + await _unitOfWork.SaveChangesAsync(); + + // ✅ 更新后清除缓存 + var cacheKey = $"merchant:{merchantId}"; + await _cache.RemoveAsync(cacheKey); + _logger.LogDebug("已清除商家缓存:{MerchantId}", merchantId); + } +} +``` + +## 11. 日志规范(Serilog) + +### 11.1 日志级别使用 +```csharp +// ✅ 正确的日志级别使用 +public class OrderService +{ + private readonly ILogger _logger; + + public async Task CreateOrderAsync(CreateOrderRequest request) + { + // Trace: 非常详细的调试信息(生产环境不记录) + _logger.LogTrace("进入CreateOrderAsync方法"); + + // Debug: 调试信息(生产环境不记录) + _logger.LogDebug("订单请求参数:{@Request}", request); + + // Information: 一般信息(重要业务流程) + _logger.LogInformation("开始创建订单,商家ID:{MerchantId},客户ID:{CustomerId}", + request.MerchantId, request.CustomerId); + + try + { + // ... 业务逻辑 + + // Information: 成功完成 + _logger.LogInformation("订单创建成功:{OrderId},订单号:{OrderNo}", + order.Id, order.OrderNo); + + return dto; + } + catch (BusinessException ex) + { + // Warning: 业务异常(预期内的错误) + _logger.LogWarning(ex, "创建订单失败(业务异常):{Message}", ex.Message); + throw; + } + catch (Exception ex) + { + // Error: 系统异常(非预期错误) + _logger.LogError(ex, "创建订单失败(系统异常):{@Request}", request); + throw; + } + } +} +``` + +### 11.2 结构化日志 +```csharp +// ✅ 使用结构化日志(推荐) +_logger.LogInformation("用户 {UserId} 创建了订单 {OrderId},金额 {Amount}", + userId, orderId, amount); + +// ✅ 记录对象(使用@符号) +_logger.LogInformation("订单详情:{@Order}", order); + +// ❌ 不要使用字符串拼接 +_logger.LogInformation("用户 " + userId + " 创建了订单 " + orderId); +``` + +### 11.3 敏感信息处理 +```csharp +// ✅ 不要记录敏感信息 +public class PaymentService +{ + public async Task ProcessPaymentAsync(PaymentRequest request) + { + // ❌ 错误:记录了密码、支付密码等敏感信息 + _logger.LogInformation("支付请求:{@Request}", request); + + // ✅ 正确:只记录非敏感信息 + _logger.LogInformation("处理支付,订单ID:{OrderId},金额:{Amount}", + request.OrderId, request.Amount); + } +} +``` + +## 12. API控制器规范 + +### 12.1 控制器结构(标准模板) +```csharp +/// +/// 订单管理API +/// +[ApiController] +[Route("api/[controller]")] +[Authorize] +public class OrdersController : ControllerBase +{ + private readonly IMediator _mediator; + private readonly ILogger _logger; + + public OrdersController(IMediator mediator, ILogger logger) + { + _mediator = mediator; + _logger = logger; + } + + /// + /// 创建订单 + /// + /// 订单创建请求 + /// 订单信息 + /// 创建成功 + /// 请求参数错误 + /// 业务验证失败 + [HttpPost] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] + public async Task CreateOrder([FromBody] CreateOrderRequest request) + { + _logger.LogInformation("API调用:创建订单"); + + var command = new CreateOrderCommand + { + MerchantId = request.MerchantId, + CustomerId = request.CustomerId, + Items = request.Items, + TotalAmount = request.TotalAmount + }; + + var result = await _mediator.Send(command); + + return Ok(ApiResponse.SuccessResult(result)); + } + + /// + /// 获取订单详情 + /// + /// 订单ID + /// 订单详情 + [HttpGet("{id}")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] + public async Task GetOrder(Guid id) + { + var query = new GetOrderByIdQuery { OrderId = id }; + var result = await _mediator.Send(query); + return Ok(ApiResponse.SuccessResult(result)); + } + + /// + /// 获取订单列表 + /// + /// 查询参数 + /// 订单列表 + [HttpGet] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task GetOrders([FromQuery] GetOrdersRequest request) + { + var query = new GetOrdersQuery + { + PageIndex = request.PageIndex, + PageSize = request.PageSize, + Status = request.Status + }; + + var result = await _mediator.Send(query); + return Ok(ApiResponse>.SuccessResult(result)); + } +} +``` + +### 12.2 统一返回结果(ApiResponse) +```csharp +// ✅ 统一返回结果(泛型) +public class ApiResponse +{ + public bool Success { get; set; } + public int Code { get; set; } = 200; + public string? Message { get; set; } + public T? Data { get; set; } + public DateTime Timestamp { get; set; } = DateTime.UtcNow; + + // 工厂方法:成功 + public static ApiResponse SuccessResult(T data, string? message = "操作成功") => new() + { + Success = true, + Code = 200, + Message = message, + Data = data + }; + + // 工厂方法:失败 + public static ApiResponse Failure(int code, string message) => new() + { + Success = false, + Code = code, + Message = message + }; +} + +// ✅ 分页结果 +public class PagedResult +{ + public List Items { get; set; } + public int TotalCount { get; set; } + public int PageIndex { get; set; } + public int PageSize { get; set; } + public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize); + public bool HasPreviousPage => PageIndex > 1; + public bool HasNextPage => PageIndex < TotalPages; +} +``` + +### 12.3 非泛型便捷封装(推荐在仅返回消息时使用) +```csharp +public static class ApiResponse +{ + // ✅ 仅返回成功/消息(无数据载荷) + public static ApiResponse Success(string? message = "操作成功") + => new ApiResponse { Success = true, Code = 200, Message = message, Data = null }; + + // ✅ 错误(配合统一错误码) + public static ApiResponse Failure(int code, string message) + => new ApiResponse { Success = false, Code = code, Message = message, Data = null }; +} +``` + +### 12.4 统一错误码(ErrorCodes) +```csharp +public static class ErrorCodes +{ + public const int BadRequest = 400; + public const int Unauthorized = 401; + public const int Forbidden = 403; + public const int NotFound = 404; + public const int Conflict = 409; + public const int ValidationFailed = 422; // 业务/验证不通过 + public const int InternalServerError = 500; + + // 业务自定义区间(10000+) + public const int BusinessError = 10001; +} +``` + +### 12.5 错误响应的ProblemDetails映射(全局异常处理中间件) +```csharp +public class BusinessException : Exception +{ + public int ErrorCode { get; } + public BusinessException(int errorCode, string message) : base(message) => ErrorCode = errorCode; +} + +public class ValidationException : Exception +{ + public IDictionary Errors { get; } + public ValidationException(IDictionary errors) : base("一个或多个验证错误") => Errors = errors; +} + +public class ExceptionHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger) + { _next = next; _logger = logger; } + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (BusinessException ex) + { + await WriteProblemDetailsAsync(context, StatusCodes.Status422UnprocessableEntity, "业务异常", ex.Message, ex.ErrorCode); + } + catch (ValidationException ex) + { + await WriteProblemDetailsAsync(context, StatusCodes.Status422UnprocessableEntity, "验证异常", ex.Message, ErrorCodes.ValidationFailed, ex.Errors); + } + catch (Exception ex) + { + _logger.LogError(ex, "系统异常"); + await WriteProblemDetailsAsync(context, StatusCodes.Status500InternalServerError, "系统异常", "服务器发生错误,请稍后重试", ErrorCodes.InternalServerError); + } + } + + private static Task WriteProblemDetailsAsync(HttpContext context, int status, string title, string detail, int code, object? errors = null) + { + var problem = new ProblemDetails + { + Status = status, + Title = title, + Detail = detail, + Instance = context.Request.Path, + Type = $"https://httpstatuses.com/{status}" + }; + problem.Extensions["code"] = code; + if (errors != null) problem.Extensions["errors"] = errors; + + context.Response.StatusCode = status; + context.Response.ContentType = "application/problem+json"; + return context.Response.WriteAsJsonAsync(problem); + } +} + +// Program.cs 中注册 +app.UseMiddleware(); +``` + + +## 13. 实体和DTO规范 + +### 13.1 实体类(Domain Entity) +```csharp +// ✅ 领域实体 +public class Order : BaseEntity +{ + public Guid Id { get; private set; } + public string OrderNo { get; private set; } + public Guid MerchantId { get; private set; } + public Guid CustomerId { get; private set; } + public decimal TotalAmount { get; private set; } + public OrderStatus Status { get; private set; } + public DateTime CreatedAt { get; private set; } + public DateTime? UpdatedAt { get; private set; } + + // 导航属性 + public virtual Merchant Merchant { get; private set; } + public virtual Customer Customer { get; private set; } + public virtual ICollection OrderItems { get; private set; } + + // 私有构造函数(用于EF Core) + private Order() { } + + // 工厂方法 + public static Order Create(Guid merchantId, Guid customerId, decimal totalAmount) + { + return new Order + { + Id = Guid.NewGuid(), + OrderNo = GenerateOrderNo(), + MerchantId = merchantId, + CustomerId = customerId, + TotalAmount = totalAmount, + Status = OrderStatus.Pending, + CreatedAt = DateTime.UtcNow + }; + } + + // 业务方法 + public void Confirm() + { + if (Status != OrderStatus.Pending) + throw new BusinessException(400, "只有待确认的订单才能确认"); + + Status = OrderStatus.Confirmed; + UpdatedAt = DateTime.UtcNow; + } + + public void Cancel(string reason) + { + if (Status == OrderStatus.Completed) + throw new BusinessException(400, "已完成的订单不能取消"); + + Status = OrderStatus.Cancelled; + UpdatedAt = DateTime.UtcNow; + } + + private static string GenerateOrderNo() + { + return $"ORD{DateTime.UtcNow:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}"; + } +} +``` + +### 13.2 DTO(数据传输对象) +```csharp +// ✅ 请求DTO +public class CreateOrderRequest +{ + public Guid MerchantId { get; set; } + public Guid CustomerId { get; set; } + public List Items { get; set; } + public decimal TotalAmount { get; set; } + public string DeliveryAddress { get; set; } + public string ContactPhone { get; set; } + public string Remark { get; set; } +} + +// ✅ 响应DTO +public class OrderDto +{ + public Guid Id { get; set; } + public string OrderNo { get; set; } + public Guid MerchantId { get; set; } + public string MerchantName { get; set; } + public Guid CustomerId { get; set; } + public string CustomerName { get; set; } + public decimal TotalAmount { get; set; } + public OrderStatus Status { get; set; } + public string StatusText { get; set; } + public List Items { get; set; } + public DateTime CreatedAt { get; set; } +} +``` + +### 13.3 AutoMapper配置 +```csharp +// ✅ AutoMapper Profile +public class OrderMappingProfile : Profile +{ + public OrderMappingProfile() + { + CreateMap() + .ForMember(dest => dest.MerchantName, opt => opt.MapFrom(src => src.Merchant.Name)) + .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.Name)) + .ForMember(dest => dest.StatusText, opt => opt.MapFrom(src => src.Status.ToString())); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.OrderNo, opt => opt.Ignore()) + .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()); + } +} +``` + + +## 14. Git工作流规范 + +### 14.1 分支管理 +``` +main # 主分支,生产环境代码 +├── develop # 开发分支 +│ ├── feature/order-management # 功能分支 +│ ├── feature/payment-integration # 功能分支 +│ └── bugfix/order-calculation # 修复分支 +└── hotfix/critical-bug # 紧急修复分支 +``` + +### 14.2 分支命名规范 +- **功能分支**:`feature/功能名称`(如:`feature/order-management`) +- **修复分支**:`bugfix/问题描述`(如:`bugfix/order-calculation`) +- **紧急修复**:`hotfix/问题描述`(如:`hotfix/payment-error`) +- **发布分支**:`release/版本号`(如:`release/v1.0.0`) + +### 14.3 提交信息规范(严格遵守) +```bash +# 格式:(): + +# type类型(必须使用以下之一): +# feat: 新功能 +# fix: 修复bug +# docs: 文档更新 +# style: 代码格式调整(不影响代码运行) +# refactor: 重构(既不是新功能也不是修复bug) +# perf: 性能优化 +# test: 测试相关 +# chore: 构建/工具相关 + +# 示例(必须遵循): +git commit -m "feat(order): 添加订单创建功能" +git commit -m "fix(payment): 修复支付回调处理错误" +git commit -m "docs(api): 更新API文档" +git commit -m "refactor(service): 重构订单服务" +git commit -m "perf(query): 优化订单查询性能" +``` + +## 15. 单元测试规范 + +### 15.1 测试命名规范 +```csharp +// ✅ 测试命名格式:MethodName_Scenario_ExpectedResult +[Fact] +public async Task CreateOrder_ValidRequest_ReturnsOrderDto() +{ + // Arrange(准备) + var request = new CreateOrderRequest + { + MerchantId = Guid.NewGuid(), + CustomerId = Guid.NewGuid(), + Items = new List + { + new OrderItemDto { DishId = Guid.NewGuid(), Quantity = 2, Price = 50.00m } + }, + TotalAmount = 100.00m + }; + + // Act(执行) + var result = await _orderService.CreateOrderAsync(request); + + // Assert(断言) + result.Should().NotBeNull(); + result.OrderNo.Should().NotBeNullOrEmpty(); + result.TotalAmount.Should().Be(100.00m); +} + +[Fact] +public async Task CreateOrder_InvalidMerchantId_ThrowsBusinessException() +{ + // Arrange + var request = new CreateOrderRequest + { + MerchantId = Guid.Empty, // 无效的商家ID + CustomerId = Guid.NewGuid(), + TotalAmount = 100.00m + }; + + // Act & Assert + await Assert.ThrowsAsync( + async () => await _orderService.CreateOrderAsync(request)); +} +``` + +### 15.2 Mock使用 +```csharp +// ✅ 使用Moq进行Mock +public class OrderServiceTests +{ + private readonly Mock _orderRepositoryMock; + private readonly Mock _unitOfWorkMock; + private readonly Mock> _loggerMock; + private readonly Mock _mapperMock; + private readonly OrderService _orderService; + + public OrderServiceTests() + { + _orderRepositoryMock = new Mock(); + _unitOfWorkMock = new Mock(); + _loggerMock = new Mock>(); + _mapperMock = new Mock(); + + _orderService = new OrderService( + _orderRepositoryMock.Object, + _unitOfWorkMock.Object, + _loggerMock.Object, + _mapperMock.Object, + Options.Create(new OrderSettings()) + ); + } + + [Fact] + public async Task GetOrder_ExistingId_ReturnsOrder() + { + // Arrange + var orderId = Guid.NewGuid(); + var order = new Order { Id = orderId, OrderNo = "ORD001" }; + var orderDto = new OrderDto { Id = orderId, OrderNo = "ORD001" }; + + _orderRepositoryMock + .Setup(x => x.GetByIdAsync(orderId)) + .ReturnsAsync(order); + + _mapperMock + .Setup(x => x.Map(order)) + .Returns(orderDto); + + // Act + var result = await _orderService.GetOrderAsync(orderId); + + // Assert + result.Should().NotBeNull(); + result.Id.Should().Be(orderId); + _orderRepositoryMock.Verify(x => x.GetByIdAsync(orderId), Times.Once); + } +} +``` + +### 15.3 测试覆盖率要求 +- **核心业务逻辑**:>= 80% +- **服务层**:>= 70% +- **仓储层**:>= 60% +- **控制器层**:>= 50% + +## 16. 性能优化规范 + +### 16.1 数据库查询优化 +```csharp +// ❌ 错误:N+1查询问题 +public async Task> GetOrdersAsync() +{ + var orders = await _context.Orders.ToListAsync(); + + foreach (var order in orders) + { + // 每次循环都会查询数据库! + order.Customer = await _context.Customers.FindAsync(order.CustomerId); + order.Merchant = await _context.Merchants.FindAsync(order.MerchantId); + } + + return _mapper.Map>(orders); +} + +// ✅ 正确:使用Include预加载 +public async Task> GetOrdersAsync() +{ + var orders = await _context.Orders + .Include(o => o.Customer) + .Include(o => o.Merchant) + .Include(o => o.OrderItems) + .ThenInclude(oi => oi.Dish) + .ToListAsync(); + + return _mapper.Map>(orders); +} + +// ✅ 更好:大数据量使用Dapper +public async Task> GetOrdersAsync() +{ + var sql = @" + SELECT + o.id, o.order_no, o.total_amount, + c.id as customer_id, c.name as customer_name, + m.id as merchant_id, m.name as merchant_name + FROM orders o + INNER JOIN customers c ON o.customer_id = c.id + INNER JOIN merchants m ON o.merchant_id = m.id + WHERE o.status = @Status + ORDER BY o.created_at DESC + LIMIT @Limit OFFSET @Offset"; + + return await _connection.QueryAsync(sql, new { Status = 1, Limit = 100, Offset = 0 }); +} +``` + +### 16.2 异步编程规范 +```csharp +// ✅ 正确:使用async/await +public async Task CreateOrderAsync(CreateOrderRequest request) +{ + var order = new Order { /* ... */ }; + await _orderRepository.AddAsync(order); + await _unitOfWork.SaveChangesAsync(); + return _mapper.Map(order); +} + +// ❌ 错误:不要使用.Result或.Wait() +public OrderDto CreateOrder(CreateOrderRequest request) +{ + var order = new Order { /* ... */ }; + _orderRepository.AddAsync(order).Wait(); // 可能导致死锁! + _unitOfWork.SaveChangesAsync().Result; // 可能导致死锁! + return _mapper.Map(order); +} + +// ❌ 错误:不要混用同步和异步 +public async Task CreateOrderAsync(CreateOrderRequest request) +{ + var order = new Order { /* ... */ }; + _orderRepository.AddAsync(order).Wait(); // 错误! + await _unitOfWork.SaveChangesAsync(); + return _mapper.Map(order); +} +``` + +### 16.3 批量操作优化 +```csharp +// ❌ 错误:逐条插入 +public async Task ImportOrdersAsync(List orders) +{ + foreach (var order in orders) + { + await _context.Orders.AddAsync(order); + await _context.SaveChangesAsync(); // 每次都保存,性能差! + } +} + +// ✅ 正确:批量插入 +public async Task ImportOrdersAsync(List orders) +{ + await _context.Orders.AddRangeAsync(orders); + await _context.SaveChangesAsync(); // 一次性保存 +} + +// ✅ 更好:使用Dapper批量插入(大数据量) +public async Task ImportOrdersAsync(List orders) +{ + var sql = @" + INSERT INTO orders (id, order_no, merchant_id, total_amount, created_at) + VALUES (@Id, @OrderNo, @MerchantId, @TotalAmount, @CreatedAt)"; + + await _connection.ExecuteAsync(sql, orders); +} +``` + +## 17. 安全规范 + +### 17.1 SQL注入防护 +```csharp +// ❌ 错误:字符串拼接SQL(SQL注入风险) +public async Task GetOrderByNoAsync(string orderNo) +{ + var sql = $"SELECT * FROM orders WHERE order_no = '{orderNo}'"; // 危险! + return await _connection.QueryFirstOrDefaultAsync(sql); +} + +// ✅ 正确:使用参数化查询 +public async Task GetOrderByNoAsync(string orderNo) +{ + var sql = "SELECT * FROM orders WHERE order_no = @OrderNo"; + return await _connection.QueryFirstOrDefaultAsync(sql, new { OrderNo = orderNo }); +} +``` + +### 17.2 敏感数据加密 +```csharp +// ✅ 密码加密存储 +public class UserService +{ + private readonly IPasswordHasher _passwordHasher; + + public async Task CreateUserAsync(string username, string password) + { + var user = new User { Username = username }; + + // ✅ 使用密码哈希 + user.PasswordHash = _passwordHasher.HashPassword(user, password); + + await _userRepository.AddAsync(user); + await _unitOfWork.SaveChangesAsync(); + + return user; + } + + public async Task ValidatePasswordAsync(User user, string password) + { + var result = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password); + return result == PasswordVerificationResult.Success; + } +} +``` + +### 17.3 授权验证 +```csharp +// ✅ 使用授权特性 +[Authorize(Roles = "Admin")] +public class AdminController : ControllerBase +{ + [HttpGet("users")] + public async Task GetUsers() + { + // 只有Admin角色可以访问 + } +} + +// ✅ 基于策略的授权 +[Authorize(Policy = "MerchantOwner")] +public class MerchantController : ControllerBase +{ + [HttpPut("{id}")] + public async Task UpdateMerchant(Guid id, UpdateMerchantRequest request) + { + // 只有商家所有者可以更新 + } +} + +// ✅ 在服务层也要验证权限 +public async Task UpdateMerchantAsync(Guid merchantId, Guid userId, UpdateMerchantRequest request) +{ + var merchant = await _merchantRepository.GetByIdAsync(merchantId); + + // 验证用户是否有权限 + if (merchant.OwnerId != userId) + { + throw new BusinessException(403, "无权限操作"); + } + + // ... 更新逻辑 +} +``` + +## 18. 代码审查清单 + +### 18.1 必查项目 +- [ ] 代码符合命名规范(PascalCase、camelCase、_camelCase) +- [ ] 所有公共API有XML文档注释 +- [ ] 复杂业务逻辑有注释说明 +- [ ] 异常处理完善(try-catch、自定义异常) +- [ ] 使用异步方法(async/await) +- [ ] 使用依赖注入(构造函数注入) +- [ ] 使用仓储模式(不直接操作DbContext) +- [ ] 使用工作单元模式(事务管理) +- [ ] 日志记录完善(Information、Warning、Error) +- [ ] 参数验证(FluentValidation) +- [ ] 返回DTO而不是实体 +- [ ] 无硬编码配置(使用IOptions) +- [ ] 无SQL注入风险(参数化查询) +- [ ] 敏感数据加密 +- [ ] 权限验证完善 + +### 18.2 性能检查 +- [ ] 避免N+1查询(使用Include) +- [ ] 大数据量使用Dapper +- [ ] 合理使用缓存 +- [ ] 批量操作使用批量方法 +- [ ] 异步方法不使用.Result或.Wait() + +### 18.3 安全检查 +- [ ] 无SQL注入风险 +- [ ] 密码已加密 +- [ ] 授权验证完善 +- [ ] 敏感信息不记录日志 +- [ ] HTTPS传输 + +## 19. 禁止事项(严格禁止) + +### 19.1 绝对禁止 +```csharp +// ❌ 禁止:直接在控制器或服务中使用DbContext +public class OrderController +{ + private readonly AppDbContext _context; // 禁止! +} + +// ❌ 禁止:硬编码配置 +var deliveryFee = 5.0m; // 禁止!应该从配置读取 + +// ❌ 禁止:返回实体类 +public Order GetOrder(Guid id) // 禁止!应该返回DTO +{ + return _context.Orders.Find(id); +} + +// ❌ 禁止:字符串拼接SQL +var sql = $"SELECT * FROM orders WHERE id = '{id}'"; // 禁止!SQL注入风险 + +// ❌ 禁止:吞掉异常 +try +{ + // ... +} +catch (Exception) +{ + // 什么都不做 - 禁止! +} + +// ❌ 禁止:使用.Result或.Wait() +var result = _service.GetOrderAsync(id).Result; // 禁止!可能死锁 + +// ❌ 禁止:记录敏感信息 +_logger.LogInformation("用户密码:{Password}", password); // 禁止! + +// ❌ 禁止:不使用异步方法 +public Order CreateOrder(CreateOrderRequest request) // 禁止!应该使用async +{ + _context.Orders.Add(order); + _context.SaveChanges(); // 应该使用SaveChangesAsync +} +``` + +## 20. 最佳实践总结 + +### 20.1 核心原则 +1. **SOLID原则**:单一职责、开闭原则、里氏替换、接口隔离、依赖倒置 +2. **DRY原则**:不要重复自己(Don't Repeat Yourself) +3. **KISS原则**:保持简单(Keep It Simple, Stupid) +4. **YAGNI原则**:你不会需要它(You Aren't Gonna Need It) + +### 20.2 编码习惯 +- ✅ 使用有意义的变量名 +- ✅ 方法保持简短(不超过50行) +- ✅ 类保持单一职责 +- ✅ 优先使用组合而不是继承 +- ✅ 编写可测试的代码 +- ✅ 先写测试再写代码(TDD) +- ✅ 持续重构,保持代码整洁 + +### 20.3 团队协作 +- ✅ 遵循统一的代码规范 +- ✅ 代码审查必须通过 +- ✅ 提交前运行测试 +- ✅ 提交信息清晰明确 +- ✅ 及时更新文档 +- ✅ 主动分享知识 + +--- + +## 附录:快速参考 + +### A. 常用命名模式 +- 类:`OrderService`、`MerchantRepository` +- 接口:`IOrderService`、`IMerchantRepository` +- 方法:`CreateOrderAsync`、`GetOrderByIdAsync` +- 字段:`_orderRepository`、`_logger` +- 属性:`OrderNo`、`TotalAmount` +- 变量:`orderTotal`、`merchantId` + +### B. 常用文件夹结构 +``` +Controllers/ +Services/ +Repositories/ +DTOs/ +Entities/ +Validators/ +Mappings/ +Exceptions/ +Constants/ +Extensions/ +``` + +### C. 必须使用的NuGet包 +- Microsoft.EntityFrameworkCore +- Dapper +- AutoMapper.Extensions.Microsoft.DependencyInjection +- FluentValidation.AspNetCore +- Serilog.AspNetCore +- MediatR +- Swashbuckle.AspNetCore +- xUnit +- Moq +- FluentAssertions + +--- + +**文档版本**:v1.0 +**最后更新**:2025-11-22 +**适用项目**:外卖SaaS系统 +**目标读者**:AI编程助手、开发人员 diff --git a/0_Document/09_AI精简开发规范.md b/0_Document/09_AI精简开发规范.md new file mode 100644 index 0000000..7447db8 --- /dev/null +++ b/0_Document/09_AI精简开发规范.md @@ -0,0 +1,79 @@ +# 编程规范_FOR_AI(TakeoutSaaS) + +说明:本规范为AI编程助手与开发者共同遵循的统一编码规范,结合 0_Document 下文档约定(特别是 06_开发规范.md、02_技术架构.md、12.* 规范)执行。超出本文件内容的详细条目请以文档中心为准。 + +## 1. 技术栈 +- .NET 10 + ASP.NET Core Web API +- EF Core 10(复杂关系/事务)+ Dapper(统计/批量)+ PostgreSQL 16+ +- Redis、RabbitMQ、Swagger、MediatR、Serilog、FluentValidation、AutoMapper、Hangfire、Polly + +## 2. 命名与风格 +- 类/方法/属性:PascalCase;接口:I前缀;私有字段:_camelCase;变量:camelCase;常量:PascalCase +- 每文件仅1个公共类型,文件名与类型名一致 +- 命名空间与目录结构一致 + +## 3. 分层与结构 +- 物理结构:Api(AdminApi/MiniApi/UserApi)+ Application + Domain + Infrastructure + Core(Shared.*) + Modules + Gateway +- 不允许在Controller/Service中直接操作DbContext,必须通过仓储/应用服务 +- 返回DTO,禁止直接返回实体 + +## 4. 注释与文档 +- 所有公共API、接口、复杂逻辑必须有XML注释 +- 控制器、服务方法提供简要说明与异常声明 + +## 5. 异常与错误码 +- 使用 BusinessException(含ErrorCode)/ ValidationException;禁止吞异常 +- 全局异常中间件输出 ProblemDetails(扩展code与errors) +- 错误码:400/401/403/404/409/422/500 + 业务10001+ + +## 6. 异步与日志 +- 全面使用 async/await,禁止 .Result/.Wait() +- 使用 Serilog 记录结构化日志,避免记录敏感数据 + +## 7. 依赖注入 +- 统一使用构造函数注入,禁止服务定位器 +- 业务逻辑在应用层,仓储在基础设施层 + +## 8. 数据访问 +- EF Core 10 负责关系/事务/迁移;Dapper 负责统计和大批量 +- 使用工作单元与仓储模式;避免N+1;只读查询使用AsNoTracking +- 参数化查询,禁止字符串拼接SQL + +## 9. 多租户 +- 通过 Header:X-Tenant-Id 或 Token Claim: tenant_id 解析租户 +- EF Core 全局过滤(tenant_id);写入数据时自动填充租户 + +## 10. 安全 +- HTTPS、Security Headers、CORS按端配置 +- 授权:AdminApi 使用JWT+RBAC;MiniApi 小程序登录态+JWT +- 严禁日志打印密码/支付信息等敏感数据 + +## 11. API 设计 +- RESTful,统一 /api/{area}/v{version} +- 统一返回:ApiResponse;分页返回使用 PagedResult +- Swagger 按版本与端分组,开启鉴权按钮 + +## 12. 模块化 +- 独立模块抽象:Identity、Authorization、Tenancy、Dictionary、Storage、Sms、Messaging、Scheduler、Delivery +- 公共横切能力抽到 Shared.* 复用 + +## 13. 测试 +- xUnit + Moq + FluentAssertions;命名:Method_Scenario_Expected +- 核心业务覆盖率≥80% + +## 14. Git 提交 +- 使用 Conventional Commits:feat/fix/docs/style/refactor/perf/test/chore + +## 15. 性能 +- 投影查询、编译查询、批量操作(ExecuteUpdate/ExecuteDelete) +- 缓存优先:Cache-Aside;更新后清缓存 + +## 16. 禁止事项 +- 直接使用DbContext(绕过仓储/工作单元) +- 硬编码配置(使用IOptions) +- 返回实体类 +- SQL拼接注入风险 +- 吞异常或静默失败 +- 同步阻塞异步 + +以上规范将随着文档中心的演进不断完善;AI编程助手生成的代码必须符合本规范,并默认使用这些约束。 diff --git a/0_Document/README.md b/0_Document/README.md new file mode 100644 index 0000000..aa08578 --- /dev/null +++ b/0_Document/README.md @@ -0,0 +1,195 @@ +# 外卖SaaS系统 - 文档中心 + +欢迎查阅外卖SaaS系统的完整文档。本文档中心包含了项目的所有技术文档和开发指南。 + +## 📚 文档目录 + +### 1. [项目概述](01_项目概述.md) +- 项目简介与背景 +- 核心业务模块介绍 +- 用户角色说明 +- 系统特性 +- 技术选型 +- 项目里程碑 + +**适合人群**:项目经理、产品经理、新加入的开发人员 + +--- + +### 2. [技术架构](02_技术架构.md) +- 技术栈详解 +- 系统架构设计 +- 分层架构说明 +- 核心设计模式 +- 数据访问策略(EF Core + Dapper) +- 缓存策略 +- 消息队列应用 +- 安全设计 + +**适合人群**:架构师、技术负责人、高级开发人员 + +--- + +### 3. [数据库设计](03_数据库设计.md) +- 数据库设计原则 +- 命名规范 +- 核心表结构 + - 租户管理 + - 商家管理 + - 菜品管理 + - 订单管理 + - 配送管理 + - 支付管理 + - 营销管理 + - 系统管理 +- 索引策略 +- 数据库优化 +- 备份策略 + +**适合人群**:数据库管理员、后端开发人员 + +--- + +### 4A. [管理后台 API 设计](04A_管理后台API.md) +- 角色与权限(平台/租户/商家) +- 租户与商家管理 +- 菜品与分类管理 +- 订单流转与售后 +- 优惠券与评价管理 +- 统计报表与文件上传 + +### 4B. [小程序/用户端 API 设计](04B_小程序API.md) +- 小程序登录与用户信息 +- 商家与门店浏览 +- 菜品与分类列表 +- 购物车同步 +- 订单创建/查询/取消 +- 支付对接(微信/支付宝) +- 优惠券领取与使用、评价发布 + +**适合人群**:前端开发人员(小程序/Web用户端)、后端开发人员、接口对接人员 + +--- + +### 5. [部署运维](05_部署运维.md) +- 环境要求 +- 本地开发环境搭建 +- Docker部署 +- Nginx配置 +- 数据库部署(主从复制) +- Redis部署(哨兵模式) +- CI/CD配置 +- 监控告警(Prometheus + Grafana) +- 日志管理(ELK Stack) +- 安全加固 +- 性能优化 +- 故障恢复 + +**适合人群**:运维工程师、DevOps工程师、系统管理员 + +--- + +### 6. [开发规范](06_开发规范.md) +- 代码规范 + - 命名规范 + - 代码组织 + - 代码注释 + - 异常处理 +- Git工作流 + - 分支管理 + - 提交信息规范 +- 代码审查标准 +- 单元测试规范 +- 性能优化规范 +- 安全规范 +- 日志规范 +- 配置管理 +- API设计规范 + +**适合人群**:所有开发人员 + +--- + +### 7. [系统架构图](07_系统架构图.md) +- 整体架构图 +- 应用分层架构 +- 订单处理流程图 +- 数据流转图 +- 多租户数据隔离架构 +- 缓存架构 +- 消息队列架构 +- 部署架构 +- 监控架构 + +**适合人群**:架构师、技术负责人、所有开发人员 + +--- + +## 🚀 快速导航 + +### 我是新人,从哪里开始? +1. 先阅读 [项目概述](01_项目概述.md) 了解项目背景和业务 +2. 查看 [系统架构图](07_系统架构图.md) 理解系统整体架构 +3. 阅读 [开发规范](06_开发规范.md) 了解开发要求 +4. 参考 [部署运维](05_部署运维.md) 搭建本地开发环境 + +### 我要开发新功能 +1. 查看 [数据库设计](03_数据库设计.md) 了解数据模型 +2. 参考 [API接口设计](04_API接口设计.md) 设计接口 +3. 遵循 [开发规范](06_开发规范.md) 编写代码 +4. 参考 [技术架构](02_技术架构.md) 选择合适的技术方案 + +### 我要部署系统 +1. 阅读 [部署运维](05_部署运维.md) 了解部署流程 +2. 参考 [系统架构图](07_系统架构图.md) 理解部署架构 +3. 按照文档配置监控和日志系统 + +### 我要对接API +1. 查看 [API接口设计](04_API接口设计.md) 了解接口规范 +2. 参考接口文档进行开发和测试 + +--- + +## 📖 文档更新记录 + +### v1.0.0 (2024-01-01) +- ✅ 完成项目概述文档 +- ✅ 完成技术架构文档 +- ✅ 完成数据库设计文档 +- ✅ 完成API接口设计文档 +- ✅ 完成部署运维文档 +- ✅ 完成开发规范文档 +- ✅ 完成系统架构图文档 + +--- + +## 💡 文档贡献 + +如果您发现文档有任何问题或需要改进的地方,欢迎: +1. 提交 Issue 反馈问题 +2. 提交 Pull Request 改进文档 +3. 联系项目负责人 + +--- + +## 📞 联系方式 + +- 项目地址:https://github.com/your-org/takeout-saas +- 问题反馈:https://github.com/your-org/takeout-saas/issues +- 邮箱:dev@example.com + +--- + +## 📝 文档规范 + +本文档使用 Markdown 格式编写,遵循以下规范: +- 使用清晰的标题层级 +- 代码示例使用语法高亮 +- 重要内容使用加粗或引用 +- 保持文档简洁易读 +- 及时更新文档内容 + +--- + +**最后更新时间**:2024-01-01 +**文档版本**:v1.0.0 diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..59c4f40 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,10 @@ + + + net10.0 + enable + enable + latest + false + + + diff --git a/README.md b/README.md index 4bd4606..d82e93a 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,173 @@ -## 项目名称 -> 请介绍一下你的项目吧 +# 外卖SaaS系统 (TakeoutSaaS) +## 项目简介 +外卖SaaS系统是一个基于.NET 10的多租户外卖管理平台,为中小型餐饮企业提供完整的外卖业务解决方案。系统采用现代化的技术栈,支持商家管理、菜品管理、订单处理、配送管理、支付集成等核心功能。 + +### 核心特性 + +- 🏢 **多租户架构**:支持多租户数据隔离,SaaS模式运营 +- 🍔 **商家管理**:完善的商家入驻、门店管理、菜品管理功能 +- 📦 **订单管理**:订单全生命周期管理,实时状态跟踪 +🚚 配送管理:配送任务、路线规划、第三方配送对接 +- 💰 **支付集成**:支持微信、支付宝等多种支付方式 +- 🎁 **营销功能**:优惠券、满减活动、会员积分 +- 📊 **数据分析**:实时数据统计、经营报表、趋势分析 +- 🔒 **安全可靠**:JWT认证、权限控制、数据加密 + +## 技术栈 + +### 后端技术 +- **.NET 10**:最新的.NET平台 +- **ASP.NET Core Web API**:RESTful API服务 +- **Entity Framework Core 10**:最新ORM框架 +- **Dapper 2.1+**:高性能数据访问 +- **PostgreSQL 16+**:主数据库 +- **Redis 7.0+**:分布式缓存 +- **RabbitMQ 3.12+**:消息队列 + +### 开发框架 +- **AutoMapper**:对象映射 +- **FluentValidation**:数据验证 +- **Serilog**:结构化日志 +- **MediatR**:CQRS模式 +- **Hangfire**:后台任务 +- **Swagger**:API文档 ## 运行条件 -> 列出运行该项目所必须的条件和相关依赖 -* 条件一 -* 条件二 -* 条件三 +### 开发环境要求 +* .NET SDK 10.0 或更高版本 +* PostgreSQL 16+ +* Redis 7.0+ +* RabbitMQ 3.12+(可选) +* Docker Desktop(推荐,用于容器化开发) +### 推荐IDE +* Visual Studio 2022 +* JetBrains Rider +* Visual Studio Code -## 运行说明 -> 说明如何运行和使用你的项目,建议给出具体的步骤说明 -* 操作一 -* 操作二 -* 操作三 +## 快速开始 +### 1. 克隆项目 +```bash +git clone https://github.com/your-org/takeout-saas.git +cd takeout-saas +``` +### 2. 使用Docker Compose启动依赖服务(推荐) +```bash +# 启动PostgreSQL、Redis、RabbitMQ等服务 +docker-compose up -d + +# 查看服务状态 +docker-compose ps +``` + +### 3. 配置数据库连接 +编辑 `src/TakeoutSaaS.Api/appsettings.Development.json` + +### 4. 执行数据库迁移 +```bash +cd src/TakeoutSaaS.Api +dotnet ef database update +``` + +### 5. 运行项目 +```bash +dotnet run +``` + +访问 API 文档: +- 管理后台 AdminApi Swagger:http://localhost:5001/swagger +- 小程序/用户端 MiniApi Swagger:http://localhost:5002/swagger + +## 项目结构 + +``` +TakeoutSaaS/ +├── 0_Document/ # 项目文档 +│ ├── 01_项目概述.md +│ ├── 02_技术架构.md +│ ├── 03_数据库设计.md +│ ├── 04A_管理后台API.md +│ ├── 04B_小程序API.md +│ ├── 05_部署运维.md +│ └── 06_开发规范.md +├── src/ +│ ├── TakeoutSaaS.AdminApi/ # 管理后台 Web API +│ ├── TakeoutSaaS.MiniApi/ # 小程序/用户端 Web API +│ ├── TakeoutSaaS.Application/ # 应用层 +│ ├── TakeoutSaaS.Domain/ # 领域层 +│ ├── TakeoutSaaS.Infrastructure/ # 基础设施层 +│ └── TakeoutSaaS.Shared/ # 共享层 +├── tests/ +│ ├── TakeoutSaaS.UnitTests/ # 单元测试 +│ └── TakeoutSaaS.IntegrationTests/ # 集成测试 +├── docker-compose.yml # Docker编排文件 +└── README.md +``` ## 测试说明 -> 如果有测试相关内容需要说明,请填写在这里 +### 运行单元测试 +```bash +dotnet test tests/TakeoutSaaS.UnitTests +``` +### 运行集成测试 +```bash +dotnet test tests/TakeoutSaaS.IntegrationTests +``` -## 技术架构 -> 使用的技术框架或系统架构图等相关说明,请填写在这里 +## 部署说明 +### Docker部署 +```bash +# 构建镜像 +docker build -t takeout-saas-api:latest . + +# 运行容器 +docker run -d -p 8080:80 --name takeout-api takeout-saas-api:latest +``` + +详细部署文档请参考:[部署运维文档](0_Document/05_部署运维.md) + +## 文档 + +- [项目概述](0_Document/01_项目概述.md) - 系统介绍和业务说明 +- [技术架构](0_Document/02_技术架构.md) - 技术栈和架构设计 +- [数据库设计](0_Document/03_数据库设计.md) - 数据模型和表结构 +- [API接口设计](0_Document/04_API接口设计.md) - RESTful API规范 +- [部署运维](0_Document/05_部署运维.md) - 部署和运维指南 +- [开发规范](0_Document/06_开发规范.md) - 代码规范和最佳实践 + +## 开发规范 + +请遵循项目的[开发规范](0_Document/06_开发规范.md) + +## 贡献指南 + +1. Fork 本仓库 +2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) +3. 提交更改 (`git commit -m 'feat: Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 创建 Pull Request + +## 许可证 + +本项目采用 MIT 许可证 + +## 联系方式 + +- 项目地址:https://github.com/your-org/takeout-saas +- 问题反馈:https://github.com/your-org/takeout-saas/issues ## 协作者 -> 高效的协作会激发无尽的创造力,将他们的名字记录在这里吧 + +感谢所有为本项目做出贡献的开发者! + +--- + +⭐ 如果这个项目对你有帮助,请给我们一个星标! diff --git a/TakeoutSaaS.sln b/TakeoutSaaS.sln new file mode 100644 index 0000000..86c3d6d --- /dev/null +++ b/TakeoutSaaS.sln @@ -0,0 +1,255 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Api", "Api", "{81034408-37C8-1011-444E-4C15C2FADA8E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.AdminApi", "src\Api\TakeoutSaaS.AdminApi\TakeoutSaaS.AdminApi.csproj", "{0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{8D626EA8-CB54-BC41-363A-217881BEBA6E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Shared.Web", "src\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj", "{022FCF39-EC48-46EA-AC08-FA2EAD1548B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Shared.Abstractions", "src\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj", "{0DA03B31-E718-4424-A1F0-9989E79FFE81}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{22BAF98C-8415-17C4-B26A-D537657BC863}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Application", "src\Application\TakeoutSaaS.Application\TakeoutSaaS.Application.csproj", "{27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{8B290487-4C16-E85E-E807-F579CBE9FC4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Domain", "src\Domain\TakeoutSaaS.Domain\TakeoutSaaS.Domain.csproj", "{464913F5-70F2-4661-B3AF-B1C87FFFA4EC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{9048EB7F-3875-A59E-E36B-5BD4C6F2A282}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Infrastructure", "src\Infrastructure\TakeoutSaaS.Infrastructure\TakeoutSaaS.Infrastructure.csproj", "{80B45C7D-9423-400A-8279-40D95BFEBC9D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Module.Identity", "src\Modules\TakeoutSaaS.Module.Identity\TakeoutSaaS.Module.Identity.csproj", "{582EDD19-3C2F-4693-9595-CC367318CD19}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Module.Authorization", "src\Modules\TakeoutSaaS.Module.Authorization\TakeoutSaaS.Module.Authorization.csproj", "{6CB8487D-5C74-487C-9D84-E57838BDA015}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Module.Tenancy", "src\Modules\TakeoutSaaS.Module.Tenancy\TakeoutSaaS.Module.Tenancy.csproj", "{5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.MiniApi", "src\Api\TakeoutSaaS.MiniApi\TakeoutSaaS.MiniApi.csproj", "{12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.UserApi", "src\Api\TakeoutSaaS.UserApi\TakeoutSaaS.UserApi.csproj", "{1C0BCC51-AF18-44F3-A1E6-A693F74276B5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateway", "Gateway", "{6306A8FB-679E-111F-6585-8F70E0EE6013}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.ApiGateway", "src\Gateway\TakeoutSaaS.ApiGateway\TakeoutSaaS.ApiGateway.csproj", "{A2620200-D487-49A7-ABAF-9B84951F81DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Shared.Kernel", "src\Core\TakeoutSaaS.Shared.Kernel\TakeoutSaaS.Shared.Kernel.csproj", "{BBC99B58-ECA8-42C3-9070-9AA058D778D3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TakeoutSaaS.Module.Storage", "src\Modules\TakeoutSaaS.Module.Storage\TakeoutSaaS.Module.Storage.csproj", "{05058F44-6FB7-43AF-8648-8BF538E283EF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|x64.ActiveCfg = Debug|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|x64.Build.0 = Debug|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|x86.ActiveCfg = Debug|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Debug|x86.Build.0 = Debug|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|Any CPU.Build.0 = Release|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|x64.ActiveCfg = Release|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|x64.Build.0 = Release|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|x86.ActiveCfg = Release|Any CPU + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954}.Release|x86.Build.0 = Release|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Debug|x64.ActiveCfg = Debug|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Debug|x64.Build.0 = Debug|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Debug|x86.ActiveCfg = Debug|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Debug|x86.Build.0 = Debug|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Release|Any CPU.Build.0 = Release|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Release|x64.ActiveCfg = Release|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Release|x64.Build.0 = Release|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Release|x86.ActiveCfg = Release|Any CPU + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7}.Release|x86.Build.0 = Release|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Debug|x64.ActiveCfg = Debug|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Debug|x64.Build.0 = Debug|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Debug|x86.ActiveCfg = Debug|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Debug|x86.Build.0 = Debug|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Release|Any CPU.Build.0 = Release|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Release|x64.ActiveCfg = Release|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Release|x64.Build.0 = Release|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Release|x86.ActiveCfg = Release|Any CPU + {0DA03B31-E718-4424-A1F0-9989E79FFE81}.Release|x86.Build.0 = Release|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Debug|x64.ActiveCfg = Debug|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Debug|x64.Build.0 = Debug|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Debug|x86.ActiveCfg = Debug|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Debug|x86.Build.0 = Debug|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Release|Any CPU.Build.0 = Release|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Release|x64.ActiveCfg = Release|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Release|x64.Build.0 = Release|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Release|x86.ActiveCfg = Release|Any CPU + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00}.Release|x86.Build.0 = Release|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Debug|x64.ActiveCfg = Debug|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Debug|x64.Build.0 = Debug|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Debug|x86.ActiveCfg = Debug|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Debug|x86.Build.0 = Debug|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Release|Any CPU.Build.0 = Release|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Release|x64.ActiveCfg = Release|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Release|x64.Build.0 = Release|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Release|x86.ActiveCfg = Release|Any CPU + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC}.Release|x86.Build.0 = Release|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Debug|x64.ActiveCfg = Debug|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Debug|x64.Build.0 = Debug|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Debug|x86.ActiveCfg = Debug|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Debug|x86.Build.0 = Debug|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Release|Any CPU.Build.0 = Release|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Release|x64.ActiveCfg = Release|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Release|x64.Build.0 = Release|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Release|x86.ActiveCfg = Release|Any CPU + {80B45C7D-9423-400A-8279-40D95BFEBC9D}.Release|x86.Build.0 = Release|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Debug|x64.ActiveCfg = Debug|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Debug|x64.Build.0 = Debug|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Debug|x86.ActiveCfg = Debug|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Debug|x86.Build.0 = Debug|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Release|Any CPU.Build.0 = Release|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Release|x64.ActiveCfg = Release|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Release|x64.Build.0 = Release|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Release|x86.ActiveCfg = Release|Any CPU + {582EDD19-3C2F-4693-9595-CC367318CD19}.Release|x86.Build.0 = Release|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Debug|x64.ActiveCfg = Debug|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Debug|x64.Build.0 = Debug|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Debug|x86.ActiveCfg = Debug|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Debug|x86.Build.0 = Debug|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Release|Any CPU.Build.0 = Release|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Release|x64.ActiveCfg = Release|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Release|x64.Build.0 = Release|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Release|x86.ActiveCfg = Release|Any CPU + {6CB8487D-5C74-487C-9D84-E57838BDA015}.Release|x86.Build.0 = Release|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Debug|x64.ActiveCfg = Debug|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Debug|x64.Build.0 = Debug|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Debug|x86.ActiveCfg = Debug|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Debug|x86.Build.0 = Debug|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Release|Any CPU.Build.0 = Release|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Release|x64.ActiveCfg = Release|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Release|x64.Build.0 = Release|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Release|x86.ActiveCfg = Release|Any CPU + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38}.Release|x86.Build.0 = Release|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Debug|x64.ActiveCfg = Debug|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Debug|x64.Build.0 = Debug|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Debug|x86.ActiveCfg = Debug|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Debug|x86.Build.0 = Debug|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Release|Any CPU.Build.0 = Release|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Release|x64.ActiveCfg = Release|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Release|x64.Build.0 = Release|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Release|x86.ActiveCfg = Release|Any CPU + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D}.Release|x86.Build.0 = Release|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Debug|x64.ActiveCfg = Debug|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Debug|x64.Build.0 = Debug|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Debug|x86.ActiveCfg = Debug|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Debug|x86.Build.0 = Debug|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Release|Any CPU.Build.0 = Release|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Release|x64.ActiveCfg = Release|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Release|x64.Build.0 = Release|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Release|x86.ActiveCfg = Release|Any CPU + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5}.Release|x86.Build.0 = Release|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|x64.Build.0 = Debug|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Debug|x86.Build.0 = Debug|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|Any CPU.Build.0 = Release|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|x64.ActiveCfg = Release|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|x64.Build.0 = Release|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|x86.ActiveCfg = Release|Any CPU + {A2620200-D487-49A7-ABAF-9B84951F81DD}.Release|x86.Build.0 = Release|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Debug|x64.ActiveCfg = Debug|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Debug|x64.Build.0 = Debug|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Debug|x86.ActiveCfg = Debug|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Debug|x86.Build.0 = Debug|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Release|Any CPU.Build.0 = Release|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Release|x64.ActiveCfg = Release|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Release|x64.Build.0 = Release|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Release|x86.ActiveCfg = Release|Any CPU + {BBC99B58-ECA8-42C3-9070-9AA058D778D3}.Release|x86.Build.0 = Release|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Debug|x64.Build.0 = Debug|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Debug|x86.Build.0 = Debug|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Release|Any CPU.Build.0 = Release|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Release|x64.ActiveCfg = Release|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Release|x64.Build.0 = Release|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Release|x86.ActiveCfg = Release|Any CPU + {05058F44-6FB7-43AF-8648-8BF538E283EF}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {81034408-37C8-1011-444E-4C15C2FADA8E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {0F32CC9C-E8B2-4854-BBF0-D8D2DDFFA954} = {81034408-37C8-1011-444E-4C15C2FADA8E} + {8D626EA8-CB54-BC41-363A-217881BEBA6E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {022FCF39-EC48-46EA-AC08-FA2EAD1548B7} = {8D626EA8-CB54-BC41-363A-217881BEBA6E} + {0DA03B31-E718-4424-A1F0-9989E79FFE81} = {8D626EA8-CB54-BC41-363A-217881BEBA6E} + {22BAF98C-8415-17C4-B26A-D537657BC863} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {27FA49F3-FC1A-44F7-B2A9-3833AC3A2E00} = {22BAF98C-8415-17C4-B26A-D537657BC863} + {8B290487-4C16-E85E-E807-F579CBE9FC4D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {464913F5-70F2-4661-B3AF-B1C87FFFA4EC} = {8B290487-4C16-E85E-E807-F579CBE9FC4D} + {9048EB7F-3875-A59E-E36B-5BD4C6F2A282} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {80B45C7D-9423-400A-8279-40D95BFEBC9D} = {9048EB7F-3875-A59E-E36B-5BD4C6F2A282} + {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {582EDD19-3C2F-4693-9595-CC367318CD19} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} + {6CB8487D-5C74-487C-9D84-E57838BDA015} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} + {5B1DAF2B-C36C-4CB1-9452-81D5D6F79D38} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} + {12ECF33A-D5E3-4F8B-A9D9-60F7F55B869D} = {81034408-37C8-1011-444E-4C15C2FADA8E} + {1C0BCC51-AF18-44F3-A1E6-A693F74276B5} = {81034408-37C8-1011-444E-4C15C2FADA8E} + {6306A8FB-679E-111F-6585-8F70E0EE6013} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {A2620200-D487-49A7-ABAF-9B84951F81DD} = {6306A8FB-679E-111F-6585-8F70E0EE6013} + {BBC99B58-ECA8-42C3-9070-9AA058D778D3} = {8D626EA8-CB54-BC41-363A-217881BEBA6E} + {05058F44-6FB7-43AF-8648-8BF538E283EF} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} + EndGlobalSection +EndGlobal diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/.gitkeep b/src/Api/TakeoutSaaS.AdminApi/Controllers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/.gitkeep @@ -0,0 +1 @@ + diff --git a/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json b/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json new file mode 100644 index 0000000..efda216 --- /dev/null +++ b/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "TakeoutSaaS.AdminApi": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:2676;http://localhost:2680" + } + } +} \ No newline at end of file diff --git a/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj b/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj new file mode 100644 index 0000000..9a71725 --- /dev/null +++ b/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj @@ -0,0 +1,21 @@ + + + net10.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/src/Api/TakeoutSaaS.MiniApi/Controllers/.gitkeep b/src/Api/TakeoutSaaS.MiniApi/Controllers/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Api/TakeoutSaaS.MiniApi/Controllers/.gitkeep @@ -0,0 +1 @@ + diff --git a/src/Api/TakeoutSaaS.MiniApi/Properties/launchSettings.json b/src/Api/TakeoutSaaS.MiniApi/Properties/launchSettings.json new file mode 100644 index 0000000..c959907 --- /dev/null +++ b/src/Api/TakeoutSaaS.MiniApi/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "TakeoutSaaS.MiniApi": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:2678;http://localhost:2681" + } + } +} \ No newline at end of file diff --git a/src/Api/TakeoutSaaS.MiniApi/TakeoutSaaS.MiniApi.csproj b/src/Api/TakeoutSaaS.MiniApi/TakeoutSaaS.MiniApi.csproj new file mode 100644 index 0000000..6be5000 --- /dev/null +++ b/src/Api/TakeoutSaaS.MiniApi/TakeoutSaaS.MiniApi.csproj @@ -0,0 +1,21 @@ + + + net10.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/src/Api/TakeoutSaaS.UserApi/Properties/launchSettings.json b/src/Api/TakeoutSaaS.UserApi/Properties/launchSettings.json new file mode 100644 index 0000000..8439ade --- /dev/null +++ b/src/Api/TakeoutSaaS.UserApi/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "TakeoutSaaS.UserApi": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:2679;http://localhost:2682" + } + } +} \ No newline at end of file diff --git a/src/Api/TakeoutSaaS.UserApi/TakeoutSaaS.UserApi.csproj b/src/Api/TakeoutSaaS.UserApi/TakeoutSaaS.UserApi.csproj new file mode 100644 index 0000000..ca338f6 --- /dev/null +++ b/src/Api/TakeoutSaaS.UserApi/TakeoutSaaS.UserApi.csproj @@ -0,0 +1,17 @@ + + + net10.0 + enable + enable + true + + + + + + + + + + + diff --git a/src/Application/TakeoutSaaS.Application/Services/.gitkeep b/src/Application/TakeoutSaaS.Application/Services/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Services/.gitkeep @@ -0,0 +1 @@ + diff --git a/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj b/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj new file mode 100644 index 0000000..88dea96 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj @@ -0,0 +1,12 @@ + + + net10.0 + enable + enable + + + + + + + diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Constants/ErrorCodes.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Constants/ErrorCodes.cs new file mode 100644 index 0000000..2d7ed97 --- /dev/null +++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Constants/ErrorCodes.cs @@ -0,0 +1,19 @@ +namespace TakeoutSaaS.Shared.Abstractions.Constants; + +/// +/// 统一错误码常量。 +/// +public static class ErrorCodes +{ + public const int BadRequest = 400; + public const int Unauthorized = 401; + public const int Forbidden = 403; + public const int NotFound = 404; + public const int Conflict = 409; + public const int ValidationFailed = 422; + public const int InternalServerError = 500; + + // 业务自定义区间(10000+) + public const int BusinessError = 10001; +} + diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IAuditableEntity.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IAuditableEntity.cs new file mode 100644 index 0000000..fe612a2 --- /dev/null +++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Entities/IAuditableEntity.cs @@ -0,0 +1,11 @@ +namespace TakeoutSaaS.Shared.Abstractions.Entities; + +/// +/// 审计字段接口 +/// +public interface IAuditableEntity +{ + DateTime CreatedAt { get; set; } + DateTime? UpdatedAt { get; set; } +} + diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Exceptions/BusinessException.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Exceptions/BusinessException.cs new file mode 100644 index 0000000..60d793a --- /dev/null +++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Exceptions/BusinessException.cs @@ -0,0 +1,20 @@ +using System; + +namespace TakeoutSaaS.Shared.Abstractions.Exceptions; + +/// +/// 业务异常(用于可预期的业务校验错误)。 +/// +public class BusinessException : Exception +{ + /// + /// 业务错误码。 + /// + public int ErrorCode { get; } + + public BusinessException(int errorCode, string message) : base(message) + { + ErrorCode = errorCode; + } +} + diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Exceptions/ValidationException.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Exceptions/ValidationException.cs new file mode 100644 index 0000000..a4c48d0 --- /dev/null +++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Exceptions/ValidationException.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace TakeoutSaaS.Shared.Abstractions.Exceptions; + +/// +/// 验证异常(用于聚合验证错误信息)。 +/// +public class ValidationException : Exception +{ + /// + /// 字段/属性的错误集合。 + /// + public IDictionary Errors { get; } + + public ValidationException(IDictionary errors) + : base("一个或多个验证错误") + { + Errors = errors; + } +} + diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/TakeoutSaaS.Shared.Abstractions.csproj b/src/Core/TakeoutSaaS.Shared.Abstractions/TakeoutSaaS.Shared.Abstractions.csproj new file mode 100644 index 0000000..05f0387 --- /dev/null +++ b/src/Core/TakeoutSaaS.Shared.Abstractions/TakeoutSaaS.Shared.Abstractions.csproj @@ -0,0 +1,8 @@ + + + net10.0 + enable + enable + + + diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/ITenantProvider.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/ITenantProvider.cs new file mode 100644 index 0000000..4af8592 --- /dev/null +++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Tenancy/ITenantProvider.cs @@ -0,0 +1,7 @@ +namespace TakeoutSaaS.Shared.Abstractions.Tenancy; + +public interface ITenantProvider +{ + Guid GetCurrentTenantId(); +} + diff --git a/src/Core/TakeoutSaaS.Shared.Kernel/TakeoutSaaS.Shared.Kernel.csproj b/src/Core/TakeoutSaaS.Shared.Kernel/TakeoutSaaS.Shared.Kernel.csproj new file mode 100644 index 0000000..c52f050 --- /dev/null +++ b/src/Core/TakeoutSaaS.Shared.Kernel/TakeoutSaaS.Shared.Kernel.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Core/TakeoutSaaS.Shared.Web/Api/BaseApiController.cs b/src/Core/TakeoutSaaS.Shared.Web/Api/BaseApiController.cs new file mode 100644 index 0000000..af1240e --- /dev/null +++ b/src/Core/TakeoutSaaS.Shared.Web/Api/BaseApiController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace TakeoutSaaS.Shared.Web.Api; + +/// +/// API 基类控制器: +/// - 统一应用 [ApiController] 和默认响应类型 +/// - 作为所有 API 控制器的基类,便于复用过滤器/中间件特性 +/// +[ApiController] +[Produces("application/json")] +public abstract class BaseApiController : ControllerBase +{ +} + diff --git a/src/Domain/TakeoutSaaS.Domain/TakeoutSaaS.Domain.csproj b/src/Domain/TakeoutSaaS.Domain/TakeoutSaaS.Domain.csproj new file mode 100644 index 0000000..b407eac --- /dev/null +++ b/src/Domain/TakeoutSaaS.Domain/TakeoutSaaS.Domain.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/Program.cs b/src/Gateway/TakeoutSaaS.ApiGateway/Program.cs new file mode 100644 index 0000000..3ec985c --- /dev/null +++ b/src/Gateway/TakeoutSaaS.ApiGateway/Program.cs @@ -0,0 +1,53 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddReverseProxy() + .LoadFromMemory(new() + { + Clusters = + { + ["admin"] = new() + { + Destinations = { ["d1"] = new() { Address = "http://localhost:5001/" } } + }, + ["mini"] = new() + { + Destinations = { ["d1"] = new() { Address = "http://localhost:5002/" } } + }, + ["user"] = new() + { + Destinations = { ["d1"] = new() { Address = "http://localhost:5003/" } } + } + }, + Routes = + { + new() + { + RouteId = "admin-route", + ClusterId = "admin", + Match = new() { Path = "/api/admin/{**catch-all}" } + }, + new() + { + RouteId = "mini-route", + ClusterId = "mini", + Match = new() { Path = "/api/mini/{**catch-all}" } + }, + new() + { + RouteId = "user-route", + ClusterId = "user", + Match = new() { Path = "/api/user/{**catch-all}" } + } + } + }); + +var app = builder.Build(); + +app.MapReverseProxy(); + +app.Run(); + diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/Properties/launchSettings.json b/src/Gateway/TakeoutSaaS.ApiGateway/Properties/launchSettings.json new file mode 100644 index 0000000..3956499 --- /dev/null +++ b/src/Gateway/TakeoutSaaS.ApiGateway/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "TakeoutSaaS.ApiGateway": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:2677;http://localhost:2683" + } + } +} \ No newline at end of file diff --git a/src/Gateway/TakeoutSaaS.ApiGateway/TakeoutSaaS.ApiGateway.csproj b/src/Gateway/TakeoutSaaS.ApiGateway/TakeoutSaaS.ApiGateway.csproj new file mode 100644 index 0000000..fda4d4d --- /dev/null +++ b/src/Gateway/TakeoutSaaS.ApiGateway/TakeoutSaaS.ApiGateway.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj b/src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj new file mode 100644 index 0000000..10cd70f --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj @@ -0,0 +1,17 @@ + + + net10.0 + enable + enable + + + + + + + + + + + + diff --git a/src/Modules/TakeoutSaaS.Module.Authorization/TakeoutSaaS.Module.Authorization.csproj b/src/Modules/TakeoutSaaS.Module.Authorization/TakeoutSaaS.Module.Authorization.csproj new file mode 100644 index 0000000..b407eac --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Authorization/TakeoutSaaS.Module.Authorization.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Modules/TakeoutSaaS.Module.Delivery/TakeoutSaaS.Module.Delivery.csproj b/src/Modules/TakeoutSaaS.Module.Delivery/TakeoutSaaS.Module.Delivery.csproj new file mode 100644 index 0000000..b407eac --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Delivery/TakeoutSaaS.Module.Delivery.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Modules/TakeoutSaaS.Module.Dictionary/TakeoutSaaS.Module.Dictionary.csproj b/src/Modules/TakeoutSaaS.Module.Dictionary/TakeoutSaaS.Module.Dictionary.csproj new file mode 100644 index 0000000..b407eac --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Dictionary/TakeoutSaaS.Module.Dictionary.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Modules/TakeoutSaaS.Module.Identity/Abstractions/IWeChatAuthService.cs b/src/Modules/TakeoutSaaS.Module.Identity/Abstractions/IWeChatAuthService.cs new file mode 100644 index 0000000..f14abfe --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Identity/Abstractions/IWeChatAuthService.cs @@ -0,0 +1,23 @@ +namespace TakeoutSaaS.Module.Identity.Abstractions; + +/// +/// 微信登录服务抽象(code2Session) +/// +public interface IWeChatAuthService +{ + /// + /// 使用小程序登录 code 换取 openid/unionid/session_key + /// + Task Code2SessionAsync(string code, CancellationToken cancellationToken = default); +} + +/// +/// 微信会话信息 +/// +public sealed class WeChatSessionInfo +{ + public string OpenId { get; init; } = string.Empty; + public string? UnionId { get; init; } + public string SessionKey { get; init; } = string.Empty; +} + diff --git a/src/Modules/TakeoutSaaS.Module.Identity/TakeoutSaaS.Module.Identity.csproj b/src/Modules/TakeoutSaaS.Module.Identity/TakeoutSaaS.Module.Identity.csproj new file mode 100644 index 0000000..b407eac --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Identity/TakeoutSaaS.Module.Identity.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Modules/TakeoutSaaS.Module.Messaging/TakeoutSaaS.Module.Messaging.csproj b/src/Modules/TakeoutSaaS.Module.Messaging/TakeoutSaaS.Module.Messaging.csproj new file mode 100644 index 0000000..4e9d749 --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Messaging/TakeoutSaaS.Module.Messaging.csproj @@ -0,0 +1,14 @@ + + + net10.0 + enable + enable + + + + + + + + + diff --git a/src/Modules/TakeoutSaaS.Module.Scheduler/TakeoutSaaS.Module.Scheduler.csproj b/src/Modules/TakeoutSaaS.Module.Scheduler/TakeoutSaaS.Module.Scheduler.csproj new file mode 100644 index 0000000..b407eac --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Scheduler/TakeoutSaaS.Module.Scheduler.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Modules/TakeoutSaaS.Module.Sms/TakeoutSaaS.Module.Sms.csproj b/src/Modules/TakeoutSaaS.Module.Sms/TakeoutSaaS.Module.Sms.csproj new file mode 100644 index 0000000..b407eac --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Sms/TakeoutSaaS.Module.Sms.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Modules/TakeoutSaaS.Module.Storage/TakeoutSaaS.Module.Storage.csproj b/src/Modules/TakeoutSaaS.Module.Storage/TakeoutSaaS.Module.Storage.csproj new file mode 100644 index 0000000..b407eac --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Storage/TakeoutSaaS.Module.Storage.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + + + + + + diff --git a/src/Modules/TakeoutSaaS.Module.Tenancy/TenantProvider.cs b/src/Modules/TakeoutSaaS.Module.Tenancy/TenantProvider.cs new file mode 100644 index 0000000..41e74d1 --- /dev/null +++ b/src/Modules/TakeoutSaaS.Module.Tenancy/TenantProvider.cs @@ -0,0 +1,39 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Http; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Module.Tenancy; + +/// +/// 默认租户提供者:优先从Header: X-Tenant-Id,其次从Token Claim: tenant_id +/// +public sealed class TenantProvider : ITenantProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public TenantProvider(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public Guid GetCurrentTenantId() + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) return Guid.Empty; + + // 1. Header 优先 + if (httpContext.Request.Headers.TryGetValue("X-Tenant-Id", out var values)) + { + if (Guid.TryParse(values.FirstOrDefault(), out var headerTenant)) + return headerTenant; + } + + // 2. Token Claim + var claim = httpContext.User?.FindFirst("tenant_id"); + if (claim != null && Guid.TryParse(claim.Value, out var claimTenant)) + return claimTenant; + + return Guid.Empty; // 未识别到则返回空(上层可按需处理) + } +} +