chore: 初始化项目基础文件
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.vs/
|
||||
bin/
|
||||
obj/
|
||||
**/bin/
|
||||
**/obj/
|
||||
188
0_Document/01_项目概述.md
Normal file
188
0_Document/01_项目概述.md
Normal file
@@ -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个月)
|
||||
- 测试与修复
|
||||
- 文档完善
|
||||
- 部署上线
|
||||
- 运维监控
|
||||
|
||||
253
0_Document/02_技术架构.md
Normal file
253
0_Document/02_技术架构.md
Normal file
@@ -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<Order> GetOrderWithDetailsAsync(Guid orderId)
|
||||
{
|
||||
return await _dbContext.Orders
|
||||
.Include(o => o.OrderItems)
|
||||
.Include(o => o.Customer)
|
||||
.FirstOrDefaultAsync(o => o.Id == orderId);
|
||||
}
|
||||
|
||||
// Dapper - 高性能统计查询
|
||||
public async Task<OrderStatistics> 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<OrderStatistics>(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跨域配置
|
||||
|
||||
641
0_Document/03_数据库设计.md
Normal file
641
0_Document/03_数据库设计.md
Normal file
@@ -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文件纳入版本控制
|
||||
- 生产环境变更需要审核
|
||||
|
||||
93
0_Document/04A_管理后台API.md
Normal file
93
0_Document/04A_管理后台API.md
Normal file
@@ -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
|
||||
|
||||
108
0_Document/04B_小程序API.md
Normal file
108
0_Document/04B_小程序API.md
Normal file
@@ -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
|
||||
|
||||
885
0_Document/04_API接口设计.md
Normal file
885
0_Document/04_API接口设计.md
Normal file
@@ -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: <binary>
|
||||
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`:支付成功通知
|
||||
|
||||
976
0_Document/05_部署运维.md
Normal file
976
0_Document/05_部署运维.md
Normal file
@@ -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<GzipCompressionProvider>();
|
||||
options.Providers.Add<BrotliCompressionProvider>();
|
||||
});
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
395
0_Document/06_开发规范.md
Normal file
395
0_Document/06_开发规范.md
Normal file
@@ -0,0 +1,395 @@
|
||||
# 外卖SaaS系统 - 开发规范
|
||||
|
||||
## 1. 代码规范
|
||||
|
||||
### 1.1 命名规范
|
||||
|
||||
#### C#命名规范
|
||||
```csharp
|
||||
// 类名:PascalCase
|
||||
public class OrderService { }
|
||||
|
||||
// 接口:I + PascalCase
|
||||
public interface IOrderRepository { }
|
||||
|
||||
// 方法:PascalCase
|
||||
public async Task<Order> 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
|
||||
/// <summary>
|
||||
/// 订单服务接口
|
||||
/// </summary>
|
||||
public interface IOrderService
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建订单
|
||||
/// </summary>
|
||||
/// <param name="request">订单创建请求</param>
|
||||
/// <returns>订单信息</returns>
|
||||
/// <exception cref="BusinessException">业务异常</exception>
|
||||
Task<OrderDto> CreateOrderAsync(CreateOrderRequest request);
|
||||
}
|
||||
|
||||
// 复杂业务逻辑添加注释
|
||||
public async Task<decimal> 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<ExceptionHandlingMiddleware> _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<Order> 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>(<scope>): <subject>
|
||||
|
||||
# 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<OrderService> _logger;
|
||||
private readonly IOptions<OrderSettings> _settings;
|
||||
|
||||
public async Task<OrderDto> 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<OrderDto>(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记录版本变更
|
||||
321
0_Document/07_系统架构图.md
Normal file
321
0_Document/07_系统架构图.md
Normal file
@@ -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 │
|
||||
│ - 可视化仪表板 │
|
||||
│ - 告警配置 │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
1589
0_Document/08_AI编程规范.md
Normal file
1589
0_Document/08_AI编程规范.md
Normal file
File diff suppressed because it is too large
Load Diff
79
0_Document/09_AI精简开发规范.md
Normal file
79
0_Document/09_AI精简开发规范.md
Normal file
@@ -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<T>;分页返回使用 PagedResult<T>
|
||||
- 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编程助手生成的代码必须符合本规范,并默认使用这些约束。
|
||||
195
0_Document/README.md
Normal file
195
0_Document/README.md
Normal file
@@ -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
|
||||
10
Directory.Build.props
Normal file
10
Directory.Build.props
Normal file
@@ -0,0 +1,10 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
171
README.md
171
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
|
||||
|
||||
## 协作者
|
||||
> 高效的协作会激发无尽的创造力,将他们的名字记录在这里吧
|
||||
|
||||
感谢所有为本项目做出贡献的开发者!
|
||||
|
||||
---
|
||||
|
||||
⭐ 如果这个项目对你有帮助,请给我们一个星标!
|
||||
|
||||
255
TakeoutSaaS.sln
Normal file
255
TakeoutSaaS.sln
Normal file
@@ -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
|
||||
1
src/Api/TakeoutSaaS.AdminApi/Controllers/.gitkeep
Normal file
1
src/Api/TakeoutSaaS.AdminApi/Controllers/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
12
src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json
Normal file
12
src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"TakeoutSaaS.AdminApi": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:2676;http://localhost:2680"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj
Normal file
21
src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj" />
|
||||
<ProjectReference Include="..\..\Application\TakeoutSaaS.Application\TakeoutSaaS.Application.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\TakeoutSaaS.Infrastructure\TakeoutSaaS.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Modules\TakeoutSaaS.Module.Identity\TakeoutSaaS.Module.Identity.csproj" />
|
||||
<ProjectReference Include="..\..\Modules\TakeoutSaaS.Module.Authorization\TakeoutSaaS.Module.Authorization.csproj" />
|
||||
<ProjectReference Include="..\..\Modules\TakeoutSaaS.Module.Tenancy\TakeoutSaaS.Module.Tenancy.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
1
src/Api/TakeoutSaaS.MiniApi/Controllers/.gitkeep
Normal file
1
src/Api/TakeoutSaaS.MiniApi/Controllers/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
12
src/Api/TakeoutSaaS.MiniApi/Properties/launchSettings.json
Normal file
12
src/Api/TakeoutSaaS.MiniApi/Properties/launchSettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"TakeoutSaaS.MiniApi": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:2678;http://localhost:2681"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Api/TakeoutSaaS.MiniApi/TakeoutSaaS.MiniApi.csproj
Normal file
21
src/Api/TakeoutSaaS.MiniApi/TakeoutSaaS.MiniApi.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj" />
|
||||
<ProjectReference Include="..\..\Application\TakeoutSaaS.Application\TakeoutSaaS.Application.csproj" />
|
||||
<ProjectReference Include="..\..\Modules\TakeoutSaaS.Module.Identity\TakeoutSaaS.Module.Identity.csproj" />
|
||||
<ProjectReference Include="..\..\Modules\TakeoutSaaS.Module.Tenancy\TakeoutSaaS.Module.Tenancy.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\TakeoutSaaS.Infrastructure\TakeoutSaaS.Infrastructure.csproj" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
12
src/Api/TakeoutSaaS.UserApi/Properties/launchSettings.json
Normal file
12
src/Api/TakeoutSaaS.UserApi/Properties/launchSettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"TakeoutSaaS.UserApi": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:2679;http://localhost:2682"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/Api/TakeoutSaaS.UserApi/TakeoutSaaS.UserApi.csproj
Normal file
17
src/Api/TakeoutSaaS.UserApi/TakeoutSaaS.UserApi.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj" />
|
||||
<ProjectReference Include="..\..\Application\TakeoutSaaS.Application\TakeoutSaaS.Application.csproj" />
|
||||
<ProjectReference Include="..\..\Modules\TakeoutSaaS.Module.Tenancy\TakeoutSaaS.Module.Tenancy.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\Domain\TakeoutSaaS.Domain\TakeoutSaaS.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// 统一错误码常量。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 审计字段接口
|
||||
/// </summary>
|
||||
public interface IAuditableEntity
|
||||
{
|
||||
DateTime CreatedAt { get; set; }
|
||||
DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// 业务异常(用于可预期的业务校验错误)。
|
||||
/// </summary>
|
||||
public class BusinessException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 业务错误码。
|
||||
/// </summary>
|
||||
public int ErrorCode { get; }
|
||||
|
||||
public BusinessException(int errorCode, string message) : base(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// 验证异常(用于聚合验证错误信息)。
|
||||
/// </summary>
|
||||
public class ValidationException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 字段/属性的错误集合。
|
||||
/// </summary>
|
||||
public IDictionary<string, string[]> Errors { get; }
|
||||
|
||||
public ValidationException(IDictionary<string, string[]> errors)
|
||||
: base("一个或多个验证错误")
|
||||
{
|
||||
Errors = errors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
public interface ITenantProvider
|
||||
{
|
||||
Guid GetCurrentTenantId();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
15
src/Core/TakeoutSaaS.Shared.Web/Api/BaseApiController.cs
Normal file
15
src/Core/TakeoutSaaS.Shared.Web/Api/BaseApiController.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace TakeoutSaaS.Shared.Web.Api;
|
||||
|
||||
/// <summary>
|
||||
/// API 基类控制器:
|
||||
/// - 统一应用 [ApiController] 和默认响应类型
|
||||
/// - 作为所有 API 控制器的基类,便于复用过滤器/中间件特性
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Produces("application/json")]
|
||||
public abstract class BaseApiController : ControllerBase
|
||||
{
|
||||
}
|
||||
|
||||
11
src/Domain/TakeoutSaaS.Domain/TakeoutSaaS.Domain.csproj
Normal file
11
src/Domain/TakeoutSaaS.Domain/TakeoutSaaS.Domain.csproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
53
src/Gateway/TakeoutSaaS.ApiGateway/Program.cs
Normal file
53
src/Gateway/TakeoutSaaS.ApiGateway/Program.cs
Normal file
@@ -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();
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"TakeoutSaaS.ApiGateway": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:2677;http://localhost:2683"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0-rc.2" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Domain\TakeoutSaaS.Domain\TakeoutSaaS.Domain.csproj" />
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace TakeoutSaaS.Module.Identity.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// 微信登录服务抽象(code2Session)
|
||||
/// </summary>
|
||||
public interface IWeChatAuthService
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用小程序登录 code 换取 openid/unionid/session_key
|
||||
/// </summary>
|
||||
Task<WeChatSessionInfo> Code2SessionAsync(string code, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信会话信息
|
||||
/// </summary>
|
||||
public sealed class WeChatSessionInfo
|
||||
{
|
||||
public string OpenId { get; init; } = string.Empty;
|
||||
public string? UnionId { get; init; }
|
||||
public string SessionKey { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.6.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
39
src/Modules/TakeoutSaaS.Module.Tenancy/TenantProvider.cs
Normal file
39
src/Modules/TakeoutSaaS.Module.Tenancy/TenantProvider.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Module.Tenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 默认租户提供者:优先从Header: X-Tenant-Id,其次从Token Claim: tenant_id
|
||||
/// </summary>
|
||||
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; // 未识别到则返回空(上层可按需处理)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user