feat: 补充数据库脚本和配置
This commit is contained in:
@@ -1,62 +1,57 @@
|
||||
# TODO Roadmap
|
||||
# TODO Roadmap
|
||||
|
||||
说明:本清单覆盖当前阶段的骨架搭建与核心基础能力(不含部署与CI/CD,留到项目跑通后再做)。
|
||||
> 当前列表为原 11 号文档中的待办事项,已迁移到此处并统一以复选框形式标记。若无特殊说明,均尚未完成。
|
||||
|
||||
## A. 基础骨架与规范
|
||||
- [x] 统一返回结果/异常处理中间件(Shared.Web)
|
||||
- [x] 模型验证、验证失败统一输出(Shared.Web)
|
||||
- [x] 统一日志(Serilog)与请求日志/TraceId(Shared.Web)
|
||||
- [x] API 版本化与分组(AdminApi、MiniApi、UserApi)
|
||||
- [x] Swagger 定制(鉴权按钮、分组说明、示例)
|
||||
- [x] 安全中间件:Security Headers、CORS 策略(按端区分)
|
||||
## 1. 配置与基础设施(高优)
|
||||
- [x] Development/Production 数据库连接与 Secret 落地(Staging 暂不需要)。
|
||||
- [x] Redis 服务部署完毕并记录配置。
|
||||
- [x] RabbitMQ 服务部署完毕并记录配置。
|
||||
- [x] COS 密钥配置补录完毕。
|
||||
- [ ] OSS 密钥配置补录完毕(待采购)。
|
||||
- [ ] SMS 平台密钥配置补录完毕(待采购)。
|
||||
- [x] WeChat Mini 程序密钥配置补录完毕(AppID:wx30f91e6afe79f405,AppSecret:64324a7f604245301066ba7c3add488e,已同步到 admin/mini 配置并登记更新人)。
|
||||
- [x] PostgreSQL 基础实例部署完毕并记录配置。
|
||||
- [x] Postgres/Redis 接入文档 + IaC/脚本补齐(见 Document/infra/postgres_redis.md 与 deploy/postgres|redis)。
|
||||
- [x] RabbitMQ/Redis/Hangfire storage scripts available (see deploy/postgres and deploy/redis).
|
||||
- [ ] admin/mini/user/gateway 网关域名、证书、CORS 列表整理完成。
|
||||
- [ ] Hangfire Dashboard 启用并新增 Admin 角色验证/网关白名单。
|
||||
|
||||
## B. 认证与权限
|
||||
- [x] JWT 颁发与刷新(AdminApi、MiniApi)
|
||||
- [x] RBAC 权限模型(角色/权限/策略)与特性授权(AdminApi)
|
||||
- [x] 小程序登录(微信 code2Session)并绑定用户账户(MiniApi)
|
||||
- [x] 登录防刷限流(MiniApi)
|
||||
## 2. 数据与迁移(高优)
|
||||
- [ ] App/Identity/Dictionary/Hangfire 四个 DbContext 均生成初始 Migration 并成功 update database。
|
||||
- [ ] 商户/门店/商品/订单/支付/配送等实体与仓储实现完成,提供 CRUD + 查询。
|
||||
- [ ] 系统参数、默认租户、管理员账号、基础字典的种子脚本可重复执行。
|
||||
|
||||
## C. 多租户与参数字典
|
||||
- [x] 多租户中间件:从 Header/域名解析租户(Shared.Web + Tenancy)
|
||||
- [x] EF Core 全局查询过滤(tenant_id)
|
||||
- [x] 参数字典模块(系统参数/业务参数)CRUD 与缓存(Dictionary 模块)
|
||||
## 3. 稳定性与质量
|
||||
- [ ] Dictionary/Identity/Storage/Sms/Messaging/Scheduler 的 xUnit+FluentAssertions 单元测试框架搭建。
|
||||
- [ ] WebApplicationFactory + Testcontainers 拉起 Postgres/Redis/RabbitMQ/MinIO 的集成测试模板。
|
||||
- [ ] .editorconfig、.globalconfig、Roslyn 分析器配置仓库通用规则并启用 CI 检查。
|
||||
|
||||
## D. 数据访问与多数据源
|
||||
- [x] EF Core 10 基础上下文、实体基类、审计字段
|
||||
- [x] 读写分离/多数据源配置(主写、从读)
|
||||
- [x] Dapper 基础设施封装(统计/报表类查询)
|
||||
## 4. 安全与合规
|
||||
- [ ] RBAC 权限、租户隔离、用户/权限洞察 API 完整演示并在 Swagger 中提供示例。
|
||||
- [ ] 登录/刷新流程增加 IP 校验、租户隔离、验证码/频率限制。
|
||||
- [ ] 登录/权限/敏感操作日志可追溯,提供查询接口或 Kibana Saved Search。
|
||||
- [ ] Secret Store/KeyVault/KMS 管理敏感配置,禁止密钥写入 Git/数据库明文。
|
||||
|
||||
## E. 文件与存储
|
||||
- [x] 存储模块抽象(腾讯云COS/七牛云/阿里云OSS)
|
||||
- [x] 上传接口(AdminApi、MiniApi)与签名直传预留
|
||||
- [x] 图片/文件访问安全策略(防盗链、过期签名)
|
||||
## 5. 观测与运维
|
||||
- [ ] TraceId 贯通,并在 Serilog 中输出 Console/File/ELK 三种目标。
|
||||
- [ ] Prometheus exporter 暴露关键指标,/health 探针与告警规则同步推送。
|
||||
- [ ] PostgreSQL 全量/增量备份脚本及一次真实恢复演练报告。
|
||||
|
||||
## F. 短信与消息队列
|
||||
- [x] 短信模块(阿里云/腾讯云 适配占位)与验证码发送
|
||||
- [x] MQ 模块(RabbitMQ)Publisher/Subscriber 抽象
|
||||
- [x] 业务事件定义(订单创建/支付成功等)与事件发布入口
|
||||
## 6. 业务能力补全
|
||||
- [ ] 商户/门店/菜品 API 完成并在 MQ 中投递上架/支付成功事件。
|
||||
- [ ] 配送对接 API 支持下单/取消/查询并完成签名验签中间件。
|
||||
- [ ] 小程序端商品浏览、下单、支付、评价、图片直传等 API 可闭环跑通。
|
||||
|
||||
## G. 调度与定时任务
|
||||
- [x] 调度模块(Quartz/Hangfire 二选一,默认 Hangfire)
|
||||
- [x] 基础任务:订单超时取消、优惠券过期处理、日志清理
|
||||
- [x] 调度面板(后续 AdminUI 对接)
|
||||
## 7. 前后台 UI 对接
|
||||
- [ ] Admin UI 通过 OpenAPI 生成或手写界面,接入 Hangfire Dashboard/MQ 监控只读模式。
|
||||
- [ ] 小程序端完成登录、菜单浏览、下单、支付、物流轨迹、素材直传闭环。
|
||||
|
||||
## H. 第三方配送对接(仅第三方)
|
||||
- [ ] 配送适配抽象(达达/闪送/顺丰同城等)
|
||||
- [ ] 统一下单/取消/查询接口与回调验签
|
||||
- [ ] AdminApi 后台运力单查询与补单
|
||||
## 8. CI/CD 与发布
|
||||
- [ ] CI/CD 流水线覆盖构建、发布、静态扫描、数据库迁移。
|
||||
- [ ] Dev/Staging/Prod 多环境配置矩阵 + 基础设施 IaC 脚本。
|
||||
- [ ] 版本与发布说明模板整理并在仓库中提供示例。
|
||||
|
||||
## I. 网关与横切能力
|
||||
- [x] YARP 路由拆分(/api/admin、/api/mini、/api/user)
|
||||
- [x] 网关级限流与请求日志
|
||||
- [x] 透传鉴权/租户标识与统一错误页
|
||||
|
||||
## J. 测试与质量
|
||||
- [ ] 单元测试工程骨架(xUnit + FluentAssertions)
|
||||
- [ ] 集成测试基座(WebApplicationFactory、测试容器)
|
||||
- [ ] 静态分析与风格规范(.editorconfig)
|
||||
|
||||
## K. 文档与规范落地
|
||||
- [ ] 在文档中补充:仅第三方配送的接口与回调规范
|
||||
- [ ] MiniApi 认证流程图(微信登录)与错误码
|
||||
- [ ] 模块间调用关系图与依赖边界
|
||||
## 9. 文档与知识库
|
||||
- [ ] 接口文档、领域模型、关键约束使用 Markdown 或 API Portal 完整记录。
|
||||
- [ ] 运行手册包含部署步骤、资源拓扑、故障排查手册。
|
||||
- [ ] 安全合规模板覆盖数据分级、密钥管理、审计流程并形成可复用表格。
|
||||
|
||||
@@ -1,49 +1,53 @@
|
||||
# 下一步 TODO(骨架完成后)
|
||||
# 里程碑待办追踪
|
||||
|
||||
说明:当前骨架已覆盖认证、权限、多租户、存储、短信、MQ、调度、网关等基础能力。下面的清单用于进入“可运行/可上线”的补全与质量阶段,可按优先级推进。
|
||||
> 按“小程序版模块规划”划分四个里程碑;每个里程碑只含对应范围的任务,便于分阶段推进。
|
||||
|
||||
## 1. 配置与基础设施落地(高优)
|
||||
- 补充真实配置:数据库/Redis/RabbitMQ/对象存储/SMS/WeChat Mini/身份密钥,并分环境管理(Development/Staging/Production)。
|
||||
- 准备基础设施:PostgreSQL 主从、Redis(哨兵/集群)、RabbitMQ、COS/OSS、Hangfire 存储库;完善 docker-compose 与部署说明。
|
||||
- 网关与服务域名规划:为 admin/mini/user/gateway 配置实际域名、TLS 证书与 CORS 列表。
|
||||
- Hangfire Dashboard 鉴权:开启并加上 Admin 角色校验或网关白名单。
|
||||
---
|
||||
## Phase 1(当前阶段):租户/商家入驻、门店与菜品、扫码堂食、基础下单支付、预购自提、第三方配送骨架
|
||||
- [ ] 管理端租户 API:注册、实名认证、套餐订阅/续费/升降配、审核流,Swagger ≥6 个端点,含审核日志。
|
||||
- [ ] 商家入驻 API:证照上传、合同管理、类目选择,驱动待审/审核/驳回/通过状态机,文件持久在 COS。
|
||||
- [ ] RBAC 模板:平台管理员、租户管理员、店长、店员四角色模板;API 可复制并允许租户自定义扩展。
|
||||
- [ ] 配额与套餐:TenantPackage CRUD、订阅/续费/配额校验(门店/账号/短信/配送单量),超额返回 409 并记录 TenantQuotaUsage。
|
||||
- [ ] 租户运营面板:欠费/到期告警、账单列表、公告通知接口,支持已读状态并在 Admin UI 展示。
|
||||
- [ ] 门店管理:Store/StoreBusinessHour/StoreDeliveryZone/StoreHoliday CRUD 完整,含 GeoJSON 配送范围及能力开关。
|
||||
- [ ] 桌码管理:批量生成桌码、绑定区域/容量、导出二维码 ZIP(POST /api/admin/stores/{id}/tables 可下载)。
|
||||
- [ ] 员工排班:创建员工、绑定门店角色、维护 StoreEmployeeShift,可查询未来 7 日排班。
|
||||
- [ ] 桌码扫码入口:Mini 端解析二维码,GET /api/mini/tables/{code}/context 返回门店、桌台、公告。
|
||||
- [ ] 菜品建模:分类、SPU、SKU、规格/加料组、价格策略、媒资 CRUD + 上下架流程;Mini 端可拉取完整 JSON。
|
||||
- [ ] 库存体系:SKU 库存、批次、调整、售罄管理,支持预售/档期锁定并在订单中扣减/释放。
|
||||
- [ ] 自提档期:门店配置自提时间窗、容量、截单时间;Mini 端据此限制下单时间。
|
||||
- [ ] 购物车服务:ShoppingCart/CartItem/CartItemAddon API 支持并发锁、限购、券/积分预校验,保证并发无脏数据。
|
||||
- [ ] 订单与支付:堂食/自提/配送下单、微信/支付宝支付、优惠券/积分抵扣、订单状态机与通知链路齐全。
|
||||
- [ ] 桌台账单:合单/拆单、结账、电子小票、桌台释放,完成结账后恢复 Idle 并生成票据 URL。
|
||||
- [ ] 自配送骨架:骑手管理、取送件信息录入、费用补贴记录,Admin 端可派单并更新 DeliveryOrder。
|
||||
- [ ] 第三方配送抽象:统一下单/取消/加价/查询接口,支持达达、美团、闪送等,含回调验签与异常补偿骨架。
|
||||
- [ ] 预购自提核销:提货码生成、手机号/二维码核销、自提柜/前台流程,超时自动取消或退款,记录操作者与时间。
|
||||
- [ ] 指标与日志:Prometheus 输出订单创建、支付成功率、配送回调耗时等,Grafana ≥8 个图表;关键流程日志记录 TraceId + 业务 ID。
|
||||
- [ ] 测试:Phase 1 核心 API 具备 ≥30 条自动化用例(单元 + 集成),覆盖租户→商户→下单链路。
|
||||
|
||||
## 2. 数据与迁移(高优)
|
||||
- 建立 EF Core Migration 基线并生成数据库(App/Identity/Dictionary/Hangfire)。
|
||||
- 设计并落地核心业务表(商户/门店/商品/订单/支付/配送等),补齐 Domain 与 Infrastructure 仓储。
|
||||
- 数据初始化/种子:系统参数、默认租户、管理员、基础字典。
|
||||
---
|
||||
## Phase 2(下一阶段):拼单、优惠券与基础营销、会员积分/会员日、客服聊天、同城自配送调度、搜索
|
||||
- [ ] 拼单引擎:GroupOrder/Participant CRUD、发起/加入/成团条件、自动解散与退款、团内消息与提醒。
|
||||
- [ ] 优惠券与基础营销:模板管理、领券、核销、库存/有效期/叠加规则,基础抽奖/秒杀/满减活动。
|
||||
- [ ] 会员与积分:会员档案、等级/成长值、会员日通知;积分获取/消耗、有效期、黑名单。
|
||||
- [ ] 客服聊天:实时会话、机器人/人工切换、排队/转接、消息模板、敏感词审查、工单流转与评价。
|
||||
- [ ] 同城自配送调度:骑手智能指派、路线估时、无接触配送、费用补贴策略、调度看板。
|
||||
- [ ] 搜索:门店/菜品/活动/优惠券搜索,过滤/排序、热门/历史记录、联想与纠错。
|
||||
|
||||
## 3. 质量与测试(高优)
|
||||
- 单元测试骨架:xUnit + FluentAssertions(Dictionary、Identity、Storage、Sms、Messaging、Scheduler)。
|
||||
- 集成测试基座:WebApplicationFactory + Testcontainers(Postgres/Redis/RabbitMQ/MinIO 可选)。
|
||||
- 静态分析:添加 .editorconfig/.globalconfig,启用可空警告、风格规则,接入 Roslyn 分析器。
|
||||
---
|
||||
## Phase 3:分销返利、签到打卡、预约预订、地图导航、社区、高阶营销、风控与补偿
|
||||
- [ ] 分销返利:AffiliatePartner/Order/Payout 管理,佣金阶梯、结算周期、税务信息、违规处理。
|
||||
- [ ] 签到打卡:CheckInCampaign/Record、连签奖励、补签、积分/券/成长值奖励、反作弊机制。
|
||||
- [ ] 预约预订:档期/资源占用、预约下单/支付、提醒/改期/取消、到店核销与履约记录。
|
||||
- [ ] 地图导航扩展:附近门店/推荐、距离/路线规划、跳转原生导航、导航请求埋点。
|
||||
- [ ] 社区:动态发布、评论、点赞、话题/标签、图片/视频审核、举报与风控,店铺口碑展示。
|
||||
- [ ] 高阶营销:秒杀/抽奖/裂变、裂变海报、爆款推荐位、多渠道投放分析。
|
||||
- [ ] 风控与审计:黑名单、频率限制、异常行为监控、审计日志、补偿与告警体系。
|
||||
|
||||
## 4. 安全与合规
|
||||
- 完善鉴权:网关透传与后端校验的租户/用户/权限;Swagger 鉴权示例。
|
||||
- 输入校验与防刷:全局限流策略(按 IP/租户),登录与验证码防刷策略参数化。
|
||||
- 日志与审计:敏感字段脱敏,登录/权限/管理操作审计日志模型与落库。
|
||||
- 配置机密:使用 Secret Store/环境变量/KMS 管理密钥,禁止明文提交。
|
||||
|
||||
## 5. 可观测性与运维
|
||||
- 日志链路:统一 TraceId 透传(网关→服务),配置 Serilog 输出(Console/File/ELK)与留存策略。
|
||||
- 指标/监控:Prometheus exporter、健康检查探针(/health)、告警规则草案。
|
||||
- 备份恢复:PostgreSQL 全量/增量备份脚本,恢复演练记录。
|
||||
|
||||
## 6. 业务功能补全
|
||||
- 订单/商品/商户等领域建模与应用服务接口实现,结合 MQ 事件发布(订单创建、支付成功等)。
|
||||
- 配送对接抽象实现(达达/闪送/顺丰同城)占位,提供下单/取消/查询接口与回调验签。
|
||||
- 小程序端接口补齐:商品浏览、下单、支付、评价、上传图片直传联调。
|
||||
|
||||
## 7. 前台/后台 UI 对接
|
||||
- Admin UI:接入 Swagger 导出的 OpenAPI,生成或手写管理端界面;接入 Hangfire Dashboard/MQ 监控只读访问。
|
||||
- MiniApp:小程序登录流程与错误码文档完善,联调上传、下单、支付链路。
|
||||
|
||||
## 8. CI/CD 与发布
|
||||
- 建立流水线:构建/测试/扫描(SAST)、镜像推送、数据库迁移步骤。
|
||||
- 多环境部署策略:Dev/Staging/Prod 配置隔离,蓝绿或滚动发布方案草拟。
|
||||
- 版本与变更管理:约定版本号/发布说明模板。
|
||||
|
||||
## 9. 文档补全
|
||||
- 更新接口文档(新增业务 API、错误码、回调规范)、模块依赖关系图。
|
||||
- 运维手册:启动参数、环境变量列表、端口/域名映射、常见故障排查。
|
||||
- 安全与合规清单:数据分类分级、审计、留存周期。
|
||||
---
|
||||
## Phase 4:性能优化、缓存、运营大盘、测试与文档、上线与监控
|
||||
- [ ] 性能与缓存:热点接口缓存、慢查询治理、批处理优化、异步化改造。
|
||||
- [ ] 可靠性:幂等与重试策略、任务调度补偿、链路追踪、告警联动。
|
||||
- [ ] 运营大盘:交易/营销/履约/用户维度的细分报表、GMV/成本/毛利分析。
|
||||
- [ ] 文档与测试:完整测试矩阵、性能测试报告、上线手册、回滚方案。
|
||||
- [ ] 监控与运维:上线发布流程、灰度/回滚策略、系统稳定性指标、24x7 监控与告警。
|
||||
|
||||
89
Document/16_设计时DbContext配置指引.md
Normal file
89
Document/16_设计时DbContext配置指引.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# 设计时 DbContext 配置指引
|
||||
|
||||
> 目的:在执行 `dotnet ef` 命令时无需硬编码数据库连接,可根据 appsettings 与环境变量自动加载。本文覆盖环境变量设置、配置目录指定等细节。
|
||||
|
||||
## 一、设计时工厂读取逻辑概述
|
||||
设计时工厂(`DesignTimeDbContextFactoryBase<T>`)按下面顺序解析连接串:
|
||||
1. 若设置了 `TAKEOUTSAAS_APP_CONNECTION` / `TAKEOUTSAAS_IDENTITY_CONNECTION` / `TAKEOUTSAAS_DICTIONARY_CONNECTION` 等环境变量,则优先使用。
|
||||
2. 否则查找配置文件:
|
||||
- 从当前目录开始向上找到含 `TakeoutSaaS.sln` 的仓库根。
|
||||
- 依次检查 `src/Api/TakeoutSaaS.AdminApi`、`src/Api/TakeoutSaaS.UserApi`、`src/Api/TakeoutSaaS.MiniApi` 等目录,如果存在 `appsettings.json` 或 `appsettings.{Environment}.json` 则加载。
|
||||
- 若未找到,可通过环境变量 `TAKEOUTSAAS_APPSETTINGS_DIR` 指定包含 appsettings 文件的目录。
|
||||
|
||||
配置结构示例(出现在 AdminApi/MiniApi/UserApi 的 appsettings):
|
||||
```json
|
||||
"Database": {
|
||||
"DataSources": {
|
||||
"AppDatabase": {
|
||||
"Write": "Host=120.53...;Database=takeout_app_db;Username=...;Password=...",
|
||||
"Reads": [
|
||||
"Host=120.53...;Database=takeout_app_db;Username=...;Password=..."
|
||||
]
|
||||
},
|
||||
"IdentityDatabase": {
|
||||
"Write": "...",
|
||||
"Reads": [ "..." ]
|
||||
},
|
||||
"DictionaryDatabase": {
|
||||
"Write": "...",
|
||||
"Reads": [ "..." ]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
设计时工厂会根据数据源名称(`DatabaseConstants.AppDataSource` 等)读取 `Write` 连接串,实现与运行时一致。
|
||||
|
||||
## 二、环境变量配置
|
||||
### 1. Windows PowerShell
|
||||
```powershell
|
||||
# 指向包含 appsettings.json 的目录
|
||||
$env:TAKEOUTSAAS_APPSETTINGS_DIR = \"D:\\HAZCode\\TakeOut\\src\\Api\\TakeoutSaaS.AdminApi\"
|
||||
|
||||
#(可选)覆盖 AppDatabase 连接串
|
||||
$env:TAKEOUTSAAS_APP_CONNECTION = \"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=***\"
|
||||
|
||||
#(可选)覆盖 IdentityDatabase 连接串
|
||||
$env:TAKEOUTSAAS_IDENTITY_CONNECTION = \"Host=...;Database=takeout_identity_db;Username=...;Password=...\"
|
||||
|
||||
#<23><><EFBFBD><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD> DictionaryDatabase <20><><EFBFBD>Ӵ<EFBFBD>
|
||||
$env:TAKEOUTSAAS_DICTIONARY_CONNECTION = "Host=...;Database=takeout_dictionary_db;Username=...;Password=..."
|
||||
```
|
||||
|
||||
### 2. Linux / macOS
|
||||
```bash
|
||||
export TAKEOUTSAAS_APPSETTINGS_DIR=/home/user/TakeOut/src/Api/TakeoutSaaS.AdminApi
|
||||
export TAKEOUTSAAS_APP_CONNECTION=\"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=***\"
|
||||
export TAKEOUTSAAS_IDENTITY_CONNECTION=\"Host=...;Database=takeout_identity_db;Username=...;Password=...\"
|
||||
export TAKEOUTSAAS_DICTIONARY_CONNECTION="Host=...;Database=takeout_dictionary_db;Username=...;Password=..."
|
||||
```
|
||||
|
||||
> 注意:若设置了 `TAKEOUTSAAS_APP_CONNECTION`,则无需在 appsettings 中提供 `Write` 连接串,反之亦然。不要将明文密码写入代码仓库,建议使用 Secret Manager 或部署环境的安全存储。
|
||||
|
||||
## 三、执行脚本示例
|
||||
完成上述环境变量配置后即可执行:
|
||||
```powershell
|
||||
# TakeoutAppDbContext(业务库)
|
||||
dotnet tool run dotnet-ef database update `
|
||||
--project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
|
||||
--startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
|
||||
--context TakeoutSaaS.Infrastructure.App.Persistence.TakeoutAppDbContext
|
||||
|
||||
# IdentityDbContext(身份库)
|
||||
dotnet tool run dotnet-ef database update `
|
||||
--project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
|
||||
--startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
|
||||
--context TakeoutSaaS.Infrastructure.Identity.Persistence.IdentityDbContext
|
||||
|
||||
# DictionaryDbContext(字典库)
|
||||
dotnet tool run dotnet-ef database update `
|
||||
--project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
|
||||
--startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
|
||||
--context TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext
|
||||
```
|
||||
|
||||
若需迁移 Identity/Dictionary 等上下文,替换 `--context` 参数为对应类型即可。
|
||||
|
||||
## 四、常见问题
|
||||
1. **未找到 appsettings**:确保 `TAKEOUTSAAS_APPSETTINGS_DIR` 指向存在 `appsettings.json` 的目录,或将命令在 API 项目目录中执行。
|
||||
2. **密码错误**:确认远程 PostgreSQL 用户/密码是否与 appsettings 或环境变量一致,避免在 CLI 中使用默认的账号。
|
||||
3. **多环境配置**:`ASPNETCORE_ENVIRONMENT` 变量可控制加载 `appsettings.{Environment}.json`;默认是 Development。
|
||||
101
Document/infra/postgres_redis.md
Normal file
101
Document/infra/postgres_redis.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# PostgreSQL 与 Redis 接入手册
|
||||
|
||||
> 本文档补齐 `Document/10_TODO.md` 中“Postgres/Redis 接入文档与 IaC/脚本”的要求,统一描述连接信息、账号权限、运维流程,以及可复用的部署脚本位置。
|
||||
|
||||
## 1. 运行环境总览
|
||||
|
||||
| 组件 | 地址/端口 | 主要数据库/实例 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| PostgreSQL | `120.53.222.17:5432` | `takeout_app_db`、`takeout_identity_db`、`takeout_dictionary_db`、`takeout_hangfire_db` | 线上实例,所有业务上下文共用。 |
|
||||
| Redis | `49.232.6.45:6379` | 单节点 | 业务缓存/登录限流/刷新令牌存储。 |
|
||||
|
||||
> 注意:所有业务账号都只具备既有库的读写权限,无 `CREATEDB`。若需新库,需使用平台管理员账号(`postgres`)或联系 DBA。
|
||||
|
||||
## 2. 账号与库映射
|
||||
|
||||
| 数据库 | 角色 | 密码 | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| `takeout_app_db` | `app_user` | `AppUser112233` | 业务域 (`TakeoutAppDbContext`) |
|
||||
| `takeout_identity_db` | `identity_user` | `IdentityUser112233` | 身份域 (`IdentityDbContext`) |
|
||||
| `takeout_dictionary_db` | `dictionary_user` | `DictionaryUser112233` | 字典域 (`DictionaryDbContext`) |
|
||||
| `takeout_hangfire_db` | `hangfire_user` | `HangFire112233` | 后台调度/Hangfire |
|
||||
|
||||
Redis 密码:`MsuMshk112233`,见 `appsettings.*.json -> Redis`。
|
||||
|
||||
## 3. 环境变量/配置注入
|
||||
|
||||
### PowerShell
|
||||
|
||||
```powershell
|
||||
$env:TAKEOUTSAAS_APPSETTINGS_DIR = "D:\HAZCode\TakeOut\src\Api\TakeoutSaaS.AdminApi"
|
||||
$env:TAKEOUTSAAS_APP_CONNECTION = "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true"
|
||||
$env:TAKEOUTSAAS_IDENTITY_CONNECTION = "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true"
|
||||
$env:TAKEOUTSAAS_DICTIONARY_CONNECTION = "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true"
|
||||
```
|
||||
|
||||
### Bash
|
||||
|
||||
```bash
|
||||
export TAKEOUTSAAS_APPSETTINGS_DIR=/home/user/TakeOut/src/Api/TakeoutSaaS.AdminApi
|
||||
export TAKEOUTSAAS_APP_CONNECTION="Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true"
|
||||
export TAKEOUTSAAS_IDENTITY_CONNECTION="Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true"
|
||||
export TAKEOUTSAAS_DICTIONARY_CONNECTION="Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true"
|
||||
```
|
||||
|
||||
Redis 连接字符串直接写入 `appsettings.*.json` 即可,如:
|
||||
|
||||
```jsonc
|
||||
"Redis": "49.232.6.45:6379,password=MsuMshk112233,abortConnect=false"
|
||||
```
|
||||
|
||||
## 4. 运维指南
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
1. **只读账号验证**
|
||||
```powershell
|
||||
psql "host=120.53.222.17 port=5432 dbname=takeout_app_db user=app_user password=AppUser112233"
|
||||
```
|
||||
2. **备份**
|
||||
```bash
|
||||
pg_dump -h 120.53.222.17 -p 5432 -U postgres -F c -d takeout_app_db -f backup/takeout_app_db_$(date +%Y%m%d).dump
|
||||
pg_dumpall -h 120.53.222.17 -p 5432 -U postgres > backup/all_$(date +%Y%m%d).sql
|
||||
```
|
||||
3. **恢复**
|
||||
```bash
|
||||
pg_restore -h 120.53.222.17 -p 5432 -U postgres -d takeout_app_db backup/takeout_app_db_xxx.dump
|
||||
psql -h 120.53.222.17 -p 5432 -U postgres -f backup/all_yyyymmdd.sql
|
||||
```
|
||||
4. **账号/权限策略**
|
||||
- `app_user` / `identity_user` / `dictionary_user` 拥有 `CONNECT`、`TEMP`、Schema `public` 的 CRUD 权限。
|
||||
- `hangfire_user` 仅能访问 `takeout_hangfire_db`,不可访问业务库。
|
||||
- 创建新表/列时,通过 EF Migration 自动添加 COMMENT。
|
||||
|
||||
### Redis
|
||||
|
||||
1. **连接验证**
|
||||
```bash
|
||||
redis-cli -h 49.232.6.45 -p 6379 -a MsuMshk112233 ping
|
||||
```
|
||||
2. **备份**
|
||||
```bash
|
||||
redis-cli -h 49.232.6.45 -p 6379 -a MsuMshk112233 save # 触发 RDB
|
||||
redis-cli -h 49.232.6.45 -p 6379 -a MsuMshk112233 bgsave # 后台
|
||||
```
|
||||
RDB/AOF 文件在服务器 `redis.conf` 定义的目录(默认 `/var/lib/redis`)。
|
||||
3. **常见运维项**
|
||||
- `CONFIG GET dir` / `CONFIG GET dbfilename` 可查看持久化路径。
|
||||
- `INFO memory` 监控内存;开启 `maxmemory` + `allkeys-lru` 保护。
|
||||
|
||||
## 5. IaC / 脚本
|
||||
|
||||
| 文件 | 说明 |
|
||||
| --- | --- |
|
||||
| `deploy/postgres/create_databases.sql` | 基于 `postgres` 管理员执行,创建四个业务库及角色、授予权限、补 COMMENT。 |
|
||||
| `deploy/postgres/bootstrap.ps1` | PowerShell 包装脚本,调用 `psql` 执行上面的 SQL(默认读取 `postgres` 管理员账号)。 |
|
||||
| `deploy/postgres/README.md` | 介绍如何在本地/测试环境执行 bootstrap 并校验连接。 |
|
||||
| `deploy/redis/docker-compose.yml` | 可复用的 Redis 部署(Redis 7 + AOF),便于本地或测试环境一键拉起。 |
|
||||
| `deploy/redis/redis.conf` | compose/裸机均可共用的配置(`requirepass`、持久化等已写好)。 |
|
||||
| `deploy/redis/README.md` | 说明如何使用 compose 或将 `redis.conf` 部署到现有实例。 |
|
||||
|
||||
> 线上目前为裸机安装(非容器),如需创建新环境/快速恢复,可直接运行上述脚本达到同样配置;即使在现有机器上,也可把 SQL/配置当作“最终规范”确保环境一致性。
|
||||
47
deploy/postgres/README.md
Normal file
47
deploy/postgres/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# PostgreSQL 部署脚本
|
||||
|
||||
本目录提供在测试/预发布环境快速拉起 PostgreSQL 的脚本,复用线上同名数据库与账号,方便迁移/恢复。
|
||||
|
||||
## 目录结构
|
||||
|
||||
- `create_databases.sql`:创建四个业务库与对应角色(可多次执行,存在则跳过)。
|
||||
- `bootstrap.ps1`:PowerShell 包装脚本,调用 `psql` 执行 SQL。
|
||||
|
||||
## 前置条件
|
||||
|
||||
1. 已安装 PostgreSQL 12+,并能以管理员身份访问(默认使用 `postgres`)。
|
||||
2. 本地已配置 `psql` 可执行命令。
|
||||
|
||||
## 使用方法
|
||||
|
||||
```powershell
|
||||
cd deploy/postgres
|
||||
.\bootstrap.ps1 `
|
||||
-Host 120.53.222.17 `
|
||||
-Port 5432 `
|
||||
-AdminUser postgres `
|
||||
-AdminPassword "超级管理员密码"
|
||||
```
|
||||
|
||||
脚本会:
|
||||
|
||||
1. 创建/更新以下角色与库:
|
||||
- `app_user` / `takeout_app_db`
|
||||
- `identity_user` / `takeout_identity_db`
|
||||
- `dictionary_user` / `takeout_dictionary_db`
|
||||
- `hangfire_user` / `takeout_hangfire_db`
|
||||
2. 为库设置 COMMENT,授予 Schema `public` 的 CRUD 权限。
|
||||
3. 输出执行日志,失败时终止。
|
||||
|
||||
## 自定义
|
||||
|
||||
- 如需修改密码或新增库,编辑 `create_databases.sql` 后重新运行脚本。
|
||||
- 若在本地拉起测试库,可把 `Host` 指向 `localhost`,其余参数保持一致。
|
||||
|
||||
## 常见问题
|
||||
|
||||
| 问题 | 处理方式 |
|
||||
| --- | --- |
|
||||
| `psql : command not found` | 确认 PostgreSQL bin 目录已加入 PATH。 |
|
||||
| `permission denied to create database` | 改用具有 `CREATEDB` 权限的管理员执行脚本。 |
|
||||
| 需要删除库 | 先 `DROP DATABASE xxx`,再运行脚本重新创建。 |
|
||||
37
deploy/postgres/bootstrap.ps1
Normal file
37
deploy/postgres/bootstrap.ps1
Normal file
@@ -0,0 +1,37 @@
|
||||
param(
|
||||
[string]$Host = "120.53.222.17",
|
||||
[int]$Port = 5432,
|
||||
[string]$AdminUser = "postgres",
|
||||
[string]$AdminPassword = ""
|
||||
)
|
||||
|
||||
if (-not (Get-Command psql -ErrorAction SilentlyContinue)) {
|
||||
throw "psql command not found. Add PostgreSQL bin directory to PATH."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($AdminPassword)) {
|
||||
Write-Warning "AdminPassword not provided. You will be prompted by psql."
|
||||
}
|
||||
|
||||
$sqlPath = Join-Path $PSScriptRoot "create_databases.sql"
|
||||
if (-not (Test-Path $sqlPath)) {
|
||||
throw "Cannot find create_databases.sql under $PSScriptRoot."
|
||||
}
|
||||
|
||||
$env:PGPASSWORD = $AdminPassword
|
||||
|
||||
$arguments = @(
|
||||
"-h", $Host,
|
||||
"-p", $Port,
|
||||
"-U", $AdminUser,
|
||||
"-f", $sqlPath
|
||||
)
|
||||
|
||||
Write-Host "Executing create_databases.sql on $Host:$Port as $AdminUser ..."
|
||||
& psql @arguments
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "psql returned non-zero exit code ($LASTEXITCODE)."
|
||||
}
|
||||
|
||||
Write-Host "PostgreSQL databases and roles ensured successfully."
|
||||
83
deploy/postgres/create_databases.sql
Normal file
83
deploy/postgres/create_databases.sql
Normal file
@@ -0,0 +1,83 @@
|
||||
-- Reusable provisioning script for Takeout SaaS PostgreSQL databases.
|
||||
-- Execute with a superuser (e.g. postgres). Safe to re-run.
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN
|
||||
CREATE ROLE app_user LOGIN PASSWORD 'AppUser112233';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'identity_user') THEN
|
||||
CREATE ROLE identity_user LOGIN PASSWORD 'IdentityUser112233';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dictionary_user') THEN
|
||||
CREATE ROLE dictionary_user LOGIN PASSWORD 'DictionaryUser112233';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'hangfire_user') THEN
|
||||
CREATE ROLE hangfire_user LOGIN PASSWORD 'HangFire112233';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'takeout_app_db') THEN
|
||||
CREATE DATABASE takeout_app_db OWNER app_user ENCODING 'UTF8';
|
||||
END IF;
|
||||
END $$;
|
||||
COMMENT ON DATABASE takeout_app_db IS 'Takeout SaaS 业务域数据库';
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'takeout_identity_db') THEN
|
||||
CREATE DATABASE takeout_identity_db OWNER identity_user ENCODING 'UTF8';
|
||||
END IF;
|
||||
END $$;
|
||||
COMMENT ON DATABASE takeout_identity_db IS 'Takeout SaaS 身份域数据库';
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'takeout_dictionary_db') THEN
|
||||
CREATE DATABASE takeout_dictionary_db OWNER dictionary_user ENCODING 'UTF8';
|
||||
END IF;
|
||||
END $$;
|
||||
COMMENT ON DATABASE takeout_dictionary_db IS 'Takeout SaaS 字典域数据库';
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'takeout_hangfire_db') THEN
|
||||
CREATE DATABASE takeout_hangfire_db OWNER hangfire_user ENCODING 'UTF8';
|
||||
END IF;
|
||||
END $$;
|
||||
COMMENT ON DATABASE takeout_hangfire_db IS 'Takeout SaaS 调度/Hangfire 数据库';
|
||||
|
||||
-- Ensure privileges and default schema permissions
|
||||
\connect takeout_app_db
|
||||
GRANT CONNECT, TEMP ON DATABASE takeout_app_db TO app_user;
|
||||
GRANT USAGE ON SCHEMA public TO app_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
|
||||
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO app_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO app_user;
|
||||
|
||||
\connect takeout_identity_db
|
||||
GRANT CONNECT, TEMP ON DATABASE takeout_identity_db TO identity_user;
|
||||
GRANT USAGE ON SCHEMA public TO identity_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO identity_user;
|
||||
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO identity_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO identity_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO identity_user;
|
||||
|
||||
\connect takeout_dictionary_db
|
||||
GRANT CONNECT, TEMP ON DATABASE takeout_dictionary_db TO dictionary_user;
|
||||
GRANT USAGE ON SCHEMA public TO dictionary_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO dictionary_user;
|
||||
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO dictionary_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO dictionary_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO dictionary_user;
|
||||
|
||||
\connect takeout_hangfire_db
|
||||
GRANT CONNECT, TEMP ON DATABASE takeout_hangfire_db TO hangfire_user;
|
||||
GRANT USAGE ON SCHEMA public TO hangfire_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO hangfire_user;
|
||||
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO hangfire_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO hangfire_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO hangfire_user;
|
||||
34
deploy/redis/README.md
Normal file
34
deploy/redis/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Redis 部署脚本
|
||||
|
||||
本目录提供可复用的 Redis 配置,既可在本地通过 Docker Compose 启动,也可将 `redis.conf` 拷贝到现有服务器,确保与线上一致。
|
||||
|
||||
## 1. 部署步骤 (裸机)\n\n1. 将 \\
|
||||
edis.conf\\ 拷贝到服务器(例如 /etc/redis/redis.conf)。\n2. 根据需要修改数据目录(\\dir\\)和绑定地址。\n3. 使用系统服务或 \\
|
||||
edis-server redis.conf\\ 启动。\n4. 确认开放端口 6379,保证通过 \\
|
||||
edis-cli -h <host> -a <pwd> ping\\ 可访问。\n\n## 2. 配置说明\n\n- \\
|
||||
equirepass\\ 已设置为 MsuMshk112233。\n- 启用 appendonly(AOF),并每秒 fsync。\n- \\maxmemory-policy\\ 为 allkeys-lru,适合缓存场景。\n- \\protected-mode no\\ 允许远程连接,需结合安全组或防火墙限制来源 IP。\n\n## 3. 常用命令使用 `redis.conf`
|
||||
|
||||
1. 把 `redis.conf` 拷贝到服务器 `/etc/redis/redis.conf`(或自定义目录)。
|
||||
2. 修改 `dir` 指向实际数据目录。
|
||||
3. 使用系统服务或 `redis-server redis.conf` 启动。
|
||||
|
||||
关键配置已包含:
|
||||
|
||||
- `requirepass`(密码)
|
||||
- `protected-mode no`(允许远程连接)
|
||||
- `appendonly yes` + `appendfsync everysec`
|
||||
- `maxmemory-policy allkeys-lru`
|
||||
|
||||
## 3. 常用命令
|
||||
|
||||
在应用或 CLI 中使用:
|
||||
|
||||
```bash
|
||||
redis-cli -h 49.232.6.45 -p 6379 -a MsuMshk112233 ping
|
||||
```
|
||||
|
||||
`appsettings.*.json` 的格式:`"Redis": "49.232.6.45:6379,password=MsuMshk112233,abortConnect=false"`
|
||||
|
||||
## 4. 备份
|
||||
|
||||
- RDB 文件:`dump.rdb`
|
||||
25
deploy/redis/redis.conf
Normal file
25
deploy/redis/redis.conf
Normal file
@@ -0,0 +1,25 @@
|
||||
bind 0.0.0.0
|
||||
port 6379
|
||||
protected-mode no
|
||||
|
||||
requirepass MsuMshk112233
|
||||
|
||||
timeout 0
|
||||
tcp-keepalive 300
|
||||
|
||||
daemonize no
|
||||
|
||||
loglevel notice
|
||||
databases 16
|
||||
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
|
||||
dir /data
|
||||
|
||||
maxmemory-policy allkeys-lru
|
||||
@@ -2,18 +2,27 @@
|
||||
"Database": {
|
||||
"DataSources": {
|
||||
"AppDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"IdentityDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"DictionaryDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
@@ -134,7 +143,7 @@
|
||||
"PrefetchCount": 20
|
||||
},
|
||||
"Scheduler": {
|
||||
"ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_saas_hangfire;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_hangfire_db;Username=hangfire_user;Password=HangFire112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"WorkerCount": 5,
|
||||
"DashboardEnabled": false,
|
||||
"DashboardPath": "/hangfire"
|
||||
|
||||
@@ -2,18 +2,27 @@
|
||||
"Database": {
|
||||
"DataSources": {
|
||||
"AppDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"IdentityDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"DictionaryDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
@@ -134,7 +143,7 @@
|
||||
"PrefetchCount": 20
|
||||
},
|
||||
"Scheduler": {
|
||||
"ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_saas_hangfire;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"ConnectionString": "Host=120.53.222.17;Port=5432;Database=takeout_hangfire_db;Username=hangfire_user;Password=HangFire112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"WorkerCount": 5,
|
||||
"DashboardEnabled": false,
|
||||
"DashboardPath": "/hangfire"
|
||||
|
||||
@@ -2,18 +2,27 @@
|
||||
"Database": {
|
||||
"DataSources": {
|
||||
"AppDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"IdentityDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"DictionaryDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
|
||||
@@ -2,18 +2,27 @@
|
||||
"Database": {
|
||||
"DataSources": {
|
||||
"AppDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"IdentityDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"DictionaryDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
|
||||
@@ -2,18 +2,27 @@
|
||||
"Database": {
|
||||
"DataSources": {
|
||||
"AppDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"IdentityDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"DictionaryDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
|
||||
@@ -2,18 +2,27 @@
|
||||
"Database": {
|
||||
"DataSources": {
|
||||
"AppDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_app;Username=app_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_app_db;Username=app_user;Password=AppUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"IdentityDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_saas_identity;Username=identity_user;Password=MsuMshk112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_identity_db;Username=identity_user;Password=IdentityUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelaySeconds": 5
|
||||
},
|
||||
"DictionaryDatabase": {
|
||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
||||
"Reads": [
|
||||
"Host=120.53.222.17;Port=5432;Database=takeout_dictionary_db;Username=dictionary_user;Password=DictionaryUser112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
||||
],
|
||||
"CommandTimeoutSeconds": 30,
|
||||
"MaxRetryCount": 3,
|
||||
|
||||
@@ -14,4 +14,9 @@ public static class DatabaseConstants
|
||||
/// 身份认证库(IdentityDatabase)。
|
||||
/// </summary>
|
||||
public const string IdentityDataSource = "IdentityDatabase";
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD>⣨DictionaryDatabase<73><65><EFBFBD><EFBFBD>
|
||||
/// </summary>
|
||||
public const string DictionaryDataSource = "DictionaryDatabase";
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
|
||||
|
||||
4330
src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201055852_ExpandDomainSchema.Designer.cs
generated
Normal file
4330
src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201055852_ExpandDomainSchema.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
5641
src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201094254_AddEntityComments.Designer.cs
generated
Normal file
5641
src/Infrastructure/TakeoutSaaS.Infrastructure/App/Migrations/20251201094254_AddEntityComments.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,17 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using TakeoutSaaS.Domain.Analytics.Entities;
|
||||
using TakeoutSaaS.Domain.Coupons.Entities;
|
||||
using TakeoutSaaS.Domain.CustomerService.Entities;
|
||||
using TakeoutSaaS.Domain.Deliveries.Entities;
|
||||
using TakeoutSaaS.Domain.Distribution.Entities;
|
||||
using TakeoutSaaS.Domain.Engagement.Entities;
|
||||
using TakeoutSaaS.Domain.GroupBuying.Entities;
|
||||
using TakeoutSaaS.Domain.Inventory.Entities;
|
||||
using TakeoutSaaS.Domain.Membership.Entities;
|
||||
using TakeoutSaaS.Domain.Merchants.Entities;
|
||||
using TakeoutSaaS.Domain.Navigation.Entities;
|
||||
using TakeoutSaaS.Domain.Ordering.Entities;
|
||||
using TakeoutSaaS.Domain.Orders.Entities;
|
||||
using TakeoutSaaS.Domain.Payments.Entities;
|
||||
using TakeoutSaaS.Domain.Products.Entities;
|
||||
@@ -25,16 +35,91 @@ public sealed class TakeoutAppDbContext(
|
||||
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor)
|
||||
{
|
||||
public DbSet<Tenant> Tenants => Set<Tenant>();
|
||||
public DbSet<TenantPackage> TenantPackages => Set<TenantPackage>();
|
||||
public DbSet<TenantSubscription> TenantSubscriptions => Set<TenantSubscription>();
|
||||
public DbSet<TenantQuotaUsage> TenantQuotaUsages => Set<TenantQuotaUsage>();
|
||||
public DbSet<TenantBillingStatement> TenantBillingStatements => Set<TenantBillingStatement>();
|
||||
public DbSet<TenantNotification> TenantNotifications => Set<TenantNotification>();
|
||||
|
||||
public DbSet<Merchant> Merchants => Set<Merchant>();
|
||||
public DbSet<MerchantDocument> MerchantDocuments => Set<MerchantDocument>();
|
||||
public DbSet<MerchantContract> MerchantContracts => Set<MerchantContract>();
|
||||
public DbSet<MerchantStaff> MerchantStaff => Set<MerchantStaff>();
|
||||
|
||||
public DbSet<Store> Stores => Set<Store>();
|
||||
public DbSet<StoreBusinessHour> StoreBusinessHours => Set<StoreBusinessHour>();
|
||||
public DbSet<StoreHoliday> StoreHolidays => Set<StoreHoliday>();
|
||||
public DbSet<StoreDeliveryZone> StoreDeliveryZones => Set<StoreDeliveryZone>();
|
||||
public DbSet<StoreTableArea> StoreTableAreas => Set<StoreTableArea>();
|
||||
public DbSet<StoreTable> StoreTables => Set<StoreTable>();
|
||||
public DbSet<StoreEmployeeShift> StoreEmployeeShifts => Set<StoreEmployeeShift>();
|
||||
|
||||
public DbSet<ProductCategory> ProductCategories => Set<ProductCategory>();
|
||||
public DbSet<Product> Products => Set<Product>();
|
||||
public DbSet<ProductAttributeGroup> ProductAttributeGroups => Set<ProductAttributeGroup>();
|
||||
public DbSet<ProductAttributeOption> ProductAttributeOptions => Set<ProductAttributeOption>();
|
||||
public DbSet<ProductSku> ProductSkus => Set<ProductSku>();
|
||||
public DbSet<ProductAddonGroup> ProductAddonGroups => Set<ProductAddonGroup>();
|
||||
public DbSet<ProductAddonOption> ProductAddonOptions => Set<ProductAddonOption>();
|
||||
public DbSet<ProductPricingRule> ProductPricingRules => Set<ProductPricingRule>();
|
||||
public DbSet<ProductMediaAsset> ProductMediaAssets => Set<ProductMediaAsset>();
|
||||
|
||||
public DbSet<InventoryItem> InventoryItems => Set<InventoryItem>();
|
||||
public DbSet<InventoryAdjustment> InventoryAdjustments => Set<InventoryAdjustment>();
|
||||
public DbSet<InventoryBatch> InventoryBatches => Set<InventoryBatch>();
|
||||
|
||||
public DbSet<ShoppingCart> ShoppingCarts => Set<ShoppingCart>();
|
||||
public DbSet<CartItem> CartItems => Set<CartItem>();
|
||||
public DbSet<CartItemAddon> CartItemAddons => Set<CartItemAddon>();
|
||||
public DbSet<CheckoutSession> CheckoutSessions => Set<CheckoutSession>();
|
||||
|
||||
public DbSet<Order> Orders => Set<Order>();
|
||||
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
|
||||
public DbSet<OrderStatusHistory> OrderStatusHistories => Set<OrderStatusHistory>();
|
||||
public DbSet<RefundRequest> RefundRequests => Set<RefundRequest>();
|
||||
|
||||
public DbSet<PaymentRecord> PaymentRecords => Set<PaymentRecord>();
|
||||
public DbSet<PaymentRefundRecord> PaymentRefundRecords => Set<PaymentRefundRecord>();
|
||||
|
||||
public DbSet<Reservation> Reservations => Set<Reservation>();
|
||||
public DbSet<QueueTicket> QueueTickets => Set<QueueTicket>();
|
||||
|
||||
public DbSet<DeliveryOrder> DeliveryOrders => Set<DeliveryOrder>();
|
||||
public DbSet<DeliveryEvent> DeliveryEvents => Set<DeliveryEvent>();
|
||||
|
||||
public DbSet<GroupOrder> GroupOrders => Set<GroupOrder>();
|
||||
public DbSet<GroupParticipant> GroupParticipants => Set<GroupParticipant>();
|
||||
|
||||
public DbSet<CouponTemplate> CouponTemplates => Set<CouponTemplate>();
|
||||
public DbSet<Coupon> Coupons => Set<Coupon>();
|
||||
public DbSet<PromotionCampaign> PromotionCampaigns => Set<PromotionCampaign>();
|
||||
|
||||
public DbSet<MemberProfile> MemberProfiles => Set<MemberProfile>();
|
||||
public DbSet<MemberTier> MemberTiers => Set<MemberTier>();
|
||||
public DbSet<MemberPointLedger> MemberPointLedgers => Set<MemberPointLedger>();
|
||||
public DbSet<MemberGrowthLog> MemberGrowthLogs => Set<MemberGrowthLog>();
|
||||
|
||||
public DbSet<ChatSession> ChatSessions => Set<ChatSession>();
|
||||
public DbSet<ChatMessage> ChatMessages => Set<ChatMessage>();
|
||||
public DbSet<SupportTicket> SupportTickets => Set<SupportTicket>();
|
||||
public DbSet<TicketComment> TicketComments => Set<TicketComment>();
|
||||
|
||||
public DbSet<AffiliatePartner> AffiliatePartners => Set<AffiliatePartner>();
|
||||
public DbSet<AffiliateOrder> AffiliateOrders => Set<AffiliateOrder>();
|
||||
public DbSet<AffiliatePayout> AffiliatePayouts => Set<AffiliatePayout>();
|
||||
|
||||
public DbSet<CheckInCampaign> CheckInCampaigns => Set<CheckInCampaign>();
|
||||
public DbSet<CheckInRecord> CheckInRecords => Set<CheckInRecord>();
|
||||
public DbSet<CommunityPost> CommunityPosts => Set<CommunityPost>();
|
||||
public DbSet<CommunityComment> CommunityComments => Set<CommunityComment>();
|
||||
public DbSet<CommunityReaction> CommunityReactions => Set<CommunityReaction>();
|
||||
|
||||
public DbSet<MapLocation> MapLocations => Set<MapLocation>();
|
||||
public DbSet<NavigationRequest> NavigationRequests => Set<NavigationRequest>();
|
||||
|
||||
public DbSet<MetricDefinition> MetricDefinitions => Set<MetricDefinition>();
|
||||
public DbSet<MetricSnapshot> MetricSnapshots => Set<MetricSnapshot>();
|
||||
public DbSet<MetricAlertRule> MetricAlertRules => Set<MetricAlertRule>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -43,14 +128,72 @@ public sealed class TakeoutAppDbContext(
|
||||
ConfigureTenant(modelBuilder.Entity<Tenant>());
|
||||
ConfigureMerchant(modelBuilder.Entity<Merchant>());
|
||||
ConfigureStore(modelBuilder.Entity<Store>());
|
||||
ConfigureTenantPackage(modelBuilder.Entity<TenantPackage>());
|
||||
ConfigureTenantSubscription(modelBuilder.Entity<TenantSubscription>());
|
||||
ConfigureTenantQuotaUsage(modelBuilder.Entity<TenantQuotaUsage>());
|
||||
ConfigureTenantBilling(modelBuilder.Entity<TenantBillingStatement>());
|
||||
ConfigureTenantNotification(modelBuilder.Entity<TenantNotification>());
|
||||
ConfigureMerchantDocument(modelBuilder.Entity<MerchantDocument>());
|
||||
ConfigureMerchantContract(modelBuilder.Entity<MerchantContract>());
|
||||
ConfigureMerchantStaff(modelBuilder.Entity<MerchantStaff>());
|
||||
ConfigureStoreBusinessHour(modelBuilder.Entity<StoreBusinessHour>());
|
||||
ConfigureStoreHoliday(modelBuilder.Entity<StoreHoliday>());
|
||||
ConfigureStoreDeliveryZone(modelBuilder.Entity<StoreDeliveryZone>());
|
||||
ConfigureStoreTableArea(modelBuilder.Entity<StoreTableArea>());
|
||||
ConfigureStoreTable(modelBuilder.Entity<StoreTable>());
|
||||
ConfigureStoreEmployeeShift(modelBuilder.Entity<StoreEmployeeShift>());
|
||||
ConfigureProductCategory(modelBuilder.Entity<ProductCategory>());
|
||||
ConfigureProduct(modelBuilder.Entity<Product>());
|
||||
ConfigureProductAttributeGroup(modelBuilder.Entity<ProductAttributeGroup>());
|
||||
ConfigureProductAttributeOption(modelBuilder.Entity<ProductAttributeOption>());
|
||||
ConfigureProductSku(modelBuilder.Entity<ProductSku>());
|
||||
ConfigureProductAddonGroup(modelBuilder.Entity<ProductAddonGroup>());
|
||||
ConfigureProductAddonOption(modelBuilder.Entity<ProductAddonOption>());
|
||||
ConfigureProductPricingRule(modelBuilder.Entity<ProductPricingRule>());
|
||||
ConfigureProductMediaAsset(modelBuilder.Entity<ProductMediaAsset>());
|
||||
ConfigureInventoryItem(modelBuilder.Entity<InventoryItem>());
|
||||
ConfigureInventoryAdjustment(modelBuilder.Entity<InventoryAdjustment>());
|
||||
ConfigureInventoryBatch(modelBuilder.Entity<InventoryBatch>());
|
||||
ConfigureShoppingCart(modelBuilder.Entity<ShoppingCart>());
|
||||
ConfigureCartItem(modelBuilder.Entity<CartItem>());
|
||||
ConfigureCartItemAddon(modelBuilder.Entity<CartItemAddon>());
|
||||
ConfigureCheckoutSession(modelBuilder.Entity<CheckoutSession>());
|
||||
ConfigureOrder(modelBuilder.Entity<Order>());
|
||||
ConfigureOrderItem(modelBuilder.Entity<OrderItem>());
|
||||
ConfigureOrderStatusHistory(modelBuilder.Entity<OrderStatusHistory>());
|
||||
ConfigureRefundRequest(modelBuilder.Entity<RefundRequest>());
|
||||
ConfigurePaymentRecord(modelBuilder.Entity<PaymentRecord>());
|
||||
ConfigurePaymentRefundRecord(modelBuilder.Entity<PaymentRefundRecord>());
|
||||
ConfigureReservation(modelBuilder.Entity<Reservation>());
|
||||
ConfigureQueueTicket(modelBuilder.Entity<QueueTicket>());
|
||||
ConfigureDelivery(modelBuilder.Entity<DeliveryOrder>());
|
||||
ConfigureDeliveryEvent(modelBuilder.Entity<DeliveryEvent>());
|
||||
ConfigureGroupOrder(modelBuilder.Entity<GroupOrder>());
|
||||
ConfigureGroupParticipant(modelBuilder.Entity<GroupParticipant>());
|
||||
ConfigureCouponTemplate(modelBuilder.Entity<CouponTemplate>());
|
||||
ConfigureCoupon(modelBuilder.Entity<Coupon>());
|
||||
ConfigurePromotionCampaign(modelBuilder.Entity<PromotionCampaign>());
|
||||
ConfigureMemberProfile(modelBuilder.Entity<MemberProfile>());
|
||||
ConfigureMemberTier(modelBuilder.Entity<MemberTier>());
|
||||
ConfigureMemberPointLedger(modelBuilder.Entity<MemberPointLedger>());
|
||||
ConfigureMemberGrowthLog(modelBuilder.Entity<MemberGrowthLog>());
|
||||
ConfigureChatSession(modelBuilder.Entity<ChatSession>());
|
||||
ConfigureChatMessage(modelBuilder.Entity<ChatMessage>());
|
||||
ConfigureSupportTicket(modelBuilder.Entity<SupportTicket>());
|
||||
ConfigureTicketComment(modelBuilder.Entity<TicketComment>());
|
||||
ConfigureAffiliatePartner(modelBuilder.Entity<AffiliatePartner>());
|
||||
ConfigureAffiliateOrder(modelBuilder.Entity<AffiliateOrder>());
|
||||
ConfigureAffiliatePayout(modelBuilder.Entity<AffiliatePayout>());
|
||||
ConfigureCheckInCampaign(modelBuilder.Entity<CheckInCampaign>());
|
||||
ConfigureCheckInRecord(modelBuilder.Entity<CheckInRecord>());
|
||||
ConfigureCommunityPost(modelBuilder.Entity<CommunityPost>());
|
||||
ConfigureCommunityComment(modelBuilder.Entity<CommunityComment>());
|
||||
ConfigureCommunityReaction(modelBuilder.Entity<CommunityReaction>());
|
||||
ConfigureMapLocation(modelBuilder.Entity<MapLocation>());
|
||||
ConfigureNavigationRequest(modelBuilder.Entity<NavigationRequest>());
|
||||
ConfigureMetricDefinition(modelBuilder.Entity<MetricDefinition>());
|
||||
ConfigureMetricSnapshot(modelBuilder.Entity<MetricSnapshot>());
|
||||
ConfigureMetricAlertRule(modelBuilder.Entity<MetricAlertRule>());
|
||||
|
||||
ApplyTenantQueryFilters(modelBuilder);
|
||||
}
|
||||
@@ -218,4 +361,632 @@ public sealed class TakeoutAppDbContext(
|
||||
builder.Property(x => x.FailureReason).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.OrderId }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureTenantPackage(EntityTypeBuilder<TenantPackage> builder)
|
||||
{
|
||||
builder.ToTable("tenant_packages");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Name).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Description).HasMaxLength(512);
|
||||
builder.Property(x => x.FeaturePoliciesJson).HasColumnType("text");
|
||||
}
|
||||
|
||||
private static void ConfigureTenantSubscription(EntityTypeBuilder<TenantSubscription> builder)
|
||||
{
|
||||
builder.ToTable("tenant_subscriptions");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.TenantPackageId).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.TenantPackageId });
|
||||
}
|
||||
|
||||
private static void ConfigureTenantQuotaUsage(EntityTypeBuilder<TenantQuotaUsage> builder)
|
||||
{
|
||||
builder.ToTable("tenant_quota_usages");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.QuotaType).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.QuotaType }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureTenantBilling(EntityTypeBuilder<TenantBillingStatement> builder)
|
||||
{
|
||||
builder.ToTable("tenant_billing_statements");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StatementNo).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.AmountDue).HasPrecision(18, 2);
|
||||
builder.Property(x => x.AmountPaid).HasPrecision(18, 2);
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.LineItemsJson).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.StatementNo }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureTenantNotification(EntityTypeBuilder<TenantNotification> builder)
|
||||
{
|
||||
builder.ToTable("tenant_notifications");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Title).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Message).HasMaxLength(1024).IsRequired();
|
||||
builder.Property(x => x.Channel).HasConversion<int>();
|
||||
builder.Property(x => x.Severity).HasConversion<int>();
|
||||
builder.Property(x => x.MetadataJson).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.Channel, x.SentAt });
|
||||
}
|
||||
|
||||
private static void ConfigureMerchantDocument(EntityTypeBuilder<MerchantDocument> builder)
|
||||
{
|
||||
builder.ToTable("merchant_documents");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.MerchantId).IsRequired();
|
||||
builder.Property(x => x.DocumentType).HasConversion<int>();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.FileUrl).HasMaxLength(512).IsRequired();
|
||||
builder.Property(x => x.DocumentNumber).HasMaxLength(64);
|
||||
builder.HasIndex(x => new { x.TenantId, x.MerchantId, x.DocumentType });
|
||||
}
|
||||
|
||||
private static void ConfigureMerchantContract(EntityTypeBuilder<MerchantContract> builder)
|
||||
{
|
||||
builder.ToTable("merchant_contracts");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.MerchantId).IsRequired();
|
||||
builder.Property(x => x.ContractNumber).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.FileUrl).HasMaxLength(512).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.TerminationReason).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.MerchantId, x.ContractNumber }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureMerchantStaff(EntityTypeBuilder<MerchantStaff> builder)
|
||||
{
|
||||
builder.ToTable("merchant_staff");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.MerchantId).IsRequired();
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.Phone).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.Email).HasMaxLength(128);
|
||||
builder.Property(x => x.RoleType).HasConversion<int>();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.PermissionsJson).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.MerchantId, x.Phone });
|
||||
}
|
||||
|
||||
private static void ConfigureStoreBusinessHour(EntityTypeBuilder<StoreBusinessHour> builder)
|
||||
{
|
||||
builder.ToTable("store_business_hours");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.HourType).HasConversion<int>();
|
||||
builder.Property(x => x.Notes).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.DayOfWeek });
|
||||
}
|
||||
|
||||
private static void ConfigureStoreHoliday(EntityTypeBuilder<StoreHoliday> builder)
|
||||
{
|
||||
builder.ToTable("store_holidays");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.Reason).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Date }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureStoreDeliveryZone(EntityTypeBuilder<StoreDeliveryZone> builder)
|
||||
{
|
||||
builder.ToTable("store_delivery_zones");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.ZoneName).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.PolygonGeoJson).HasColumnType("text").IsRequired();
|
||||
builder.Property(x => x.MinimumOrderAmount).HasPrecision(18, 2);
|
||||
builder.Property(x => x.DeliveryFee).HasPrecision(18, 2);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ZoneName });
|
||||
}
|
||||
|
||||
private static void ConfigureStoreTableArea(EntityTypeBuilder<StoreTableArea> builder)
|
||||
{
|
||||
builder.ToTable("store_table_areas");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.Description).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureStoreTable(EntityTypeBuilder<StoreTable> builder)
|
||||
{
|
||||
builder.ToTable("store_tables");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.TableCode).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.Tags).HasMaxLength(128);
|
||||
builder.Property(x => x.QrCodeUrl).HasMaxLength(512);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.TableCode }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureStoreEmployeeShift(EntityTypeBuilder<StoreEmployeeShift> builder)
|
||||
{
|
||||
builder.ToTable("store_employee_shifts");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.StaffId).IsRequired();
|
||||
builder.Property(x => x.RoleType).HasConversion<int>();
|
||||
builder.Property(x => x.Notes).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ShiftDate, x.StaffId }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureProductAttributeGroup(EntityTypeBuilder<ProductAttributeGroup> builder)
|
||||
{
|
||||
builder.ToTable("product_attribute_groups");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.SelectionType).HasConversion<int>();
|
||||
builder.Property(x => x.StoreId);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name });
|
||||
}
|
||||
|
||||
private static void ConfigureProductAttributeOption(EntityTypeBuilder<ProductAttributeOption> builder)
|
||||
{
|
||||
builder.ToTable("product_attribute_options");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.AttributeGroupId).IsRequired();
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.ExtraPrice).HasPrecision(18, 2);
|
||||
builder.HasIndex(x => new { x.TenantId, x.AttributeGroupId, x.Name }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureProductSku(EntityTypeBuilder<ProductSku> builder)
|
||||
{
|
||||
builder.ToTable("product_skus");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.ProductId).IsRequired();
|
||||
builder.Property(x => x.SkuCode).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.Barcode).HasMaxLength(64);
|
||||
builder.Property(x => x.Price).HasPrecision(18, 2);
|
||||
builder.Property(x => x.OriginalPrice).HasPrecision(18, 2);
|
||||
builder.Property(x => x.Weight).HasPrecision(10, 3);
|
||||
builder.Property(x => x.AttributesJson).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.SkuCode }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureProductAddonGroup(EntityTypeBuilder<ProductAddonGroup> builder)
|
||||
{
|
||||
builder.ToTable("product_addon_groups");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.ProductId).IsRequired();
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.SelectionType).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.ProductId, x.Name });
|
||||
}
|
||||
|
||||
private static void ConfigureProductAddonOption(EntityTypeBuilder<ProductAddonOption> builder)
|
||||
{
|
||||
builder.ToTable("product_addon_options");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.AddonGroupId).IsRequired();
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.ExtraPrice).HasPrecision(18, 2);
|
||||
}
|
||||
|
||||
private static void ConfigureProductPricingRule(EntityTypeBuilder<ProductPricingRule> builder)
|
||||
{
|
||||
builder.ToTable("product_pricing_rules");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.ProductId).IsRequired();
|
||||
builder.Property(x => x.RuleType).HasConversion<int>();
|
||||
builder.Property(x => x.ConditionsJson).HasColumnType("text").IsRequired();
|
||||
builder.Property(x => x.Price).HasPrecision(18, 2);
|
||||
builder.Property(x => x.WeekdaysJson).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.ProductId, x.RuleType });
|
||||
}
|
||||
|
||||
private static void ConfigureProductMediaAsset(EntityTypeBuilder<ProductMediaAsset> builder)
|
||||
{
|
||||
builder.ToTable("product_media_assets");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.ProductId).IsRequired();
|
||||
builder.Property(x => x.MediaType).HasConversion<int>();
|
||||
builder.Property(x => x.Url).HasMaxLength(512).IsRequired();
|
||||
builder.Property(x => x.Caption).HasMaxLength(256);
|
||||
}
|
||||
|
||||
private static void ConfigureInventoryItem(EntityTypeBuilder<InventoryItem> builder)
|
||||
{
|
||||
builder.ToTable("inventory_items");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.ProductSkuId).IsRequired();
|
||||
builder.Property(x => x.BatchNumber).HasMaxLength(64);
|
||||
builder.Property(x => x.Location).HasMaxLength(64);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductSkuId, x.BatchNumber });
|
||||
}
|
||||
|
||||
private static void ConfigureInventoryAdjustment(EntityTypeBuilder<InventoryAdjustment> builder)
|
||||
{
|
||||
builder.ToTable("inventory_adjustments");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.InventoryItemId).IsRequired();
|
||||
builder.Property(x => x.AdjustmentType).HasConversion<int>();
|
||||
builder.Property(x => x.Reason).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.InventoryItemId, x.OccurredAt });
|
||||
}
|
||||
|
||||
private static void ConfigureInventoryBatch(EntityTypeBuilder<InventoryBatch> builder)
|
||||
{
|
||||
builder.ToTable("inventory_batches");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.ProductSkuId).IsRequired();
|
||||
builder.Property(x => x.BatchNumber).HasMaxLength(64).IsRequired();
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductSkuId, x.BatchNumber }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureShoppingCart(EntityTypeBuilder<ShoppingCart> builder)
|
||||
{
|
||||
builder.ToTable("shopping_carts");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.UserId).IsRequired();
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.TableContext).HasMaxLength(64);
|
||||
builder.Property(x => x.DeliveryPreference).HasMaxLength(32);
|
||||
builder.HasIndex(x => new { x.TenantId, x.UserId, x.StoreId }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureCartItem(EntityTypeBuilder<CartItem> builder)
|
||||
{
|
||||
builder.ToTable("cart_items");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.ShoppingCartId).IsRequired();
|
||||
builder.Property(x => x.ProductId).IsRequired();
|
||||
builder.Property(x => x.ProductName).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.UnitPrice).HasPrecision(18, 2);
|
||||
builder.Property(x => x.Remark).HasMaxLength(256);
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.AttributesJson).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.ShoppingCartId });
|
||||
}
|
||||
|
||||
private static void ConfigureCartItemAddon(EntityTypeBuilder<CartItemAddon> builder)
|
||||
{
|
||||
builder.ToTable("cart_item_addons");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.CartItemId).IsRequired();
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.ExtraPrice).HasPrecision(18, 2);
|
||||
}
|
||||
|
||||
private static void ConfigureCheckoutSession(EntityTypeBuilder<CheckoutSession> builder)
|
||||
{
|
||||
builder.ToTable("checkout_sessions");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.UserId).IsRequired();
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.SessionToken).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.ValidationResultJson).HasColumnType("text").IsRequired();
|
||||
builder.HasIndex(x => new { x.TenantId, x.SessionToken }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureOrderStatusHistory(EntityTypeBuilder<OrderStatusHistory> builder)
|
||||
{
|
||||
builder.ToTable("order_status_histories");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.OrderId).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.Notes).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.OrderId, x.OccurredAt });
|
||||
}
|
||||
|
||||
private static void ConfigureRefundRequest(EntityTypeBuilder<RefundRequest> builder)
|
||||
{
|
||||
builder.ToTable("refund_requests");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.OrderId).IsRequired();
|
||||
builder.Property(x => x.RefundNo).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.Amount).HasPrecision(18, 2);
|
||||
builder.Property(x => x.Reason).HasMaxLength(256).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.ReviewNotes).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.RefundNo }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigurePaymentRefundRecord(EntityTypeBuilder<PaymentRefundRecord> builder)
|
||||
{
|
||||
builder.ToTable("payment_refund_records");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.PaymentRecordId).IsRequired();
|
||||
builder.Property(x => x.OrderId).IsRequired();
|
||||
builder.Property(x => x.Amount).HasPrecision(18, 2);
|
||||
builder.Property(x => x.ChannelRefundId).HasMaxLength(64);
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.Payload).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.PaymentRecordId });
|
||||
}
|
||||
|
||||
private static void ConfigureDeliveryEvent(EntityTypeBuilder<DeliveryEvent> builder)
|
||||
{
|
||||
builder.ToTable("delivery_events");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.DeliveryOrderId).IsRequired();
|
||||
builder.Property(x => x.EventType).HasConversion<int>();
|
||||
builder.Property(x => x.Message).HasMaxLength(256);
|
||||
builder.Property(x => x.Payload).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.DeliveryOrderId, x.EventType });
|
||||
}
|
||||
|
||||
private static void ConfigureGroupOrder(EntityTypeBuilder<GroupOrder> builder)
|
||||
{
|
||||
builder.ToTable("group_orders");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.GroupOrderNo).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.GroupPrice).HasPrecision(18, 2);
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.GroupOrderNo }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureGroupParticipant(EntityTypeBuilder<GroupParticipant> builder)
|
||||
{
|
||||
builder.ToTable("group_participants");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.GroupOrderId).IsRequired();
|
||||
builder.Property(x => x.OrderId).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.GroupOrderId, x.UserId }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureCouponTemplate(EntityTypeBuilder<CouponTemplate> builder)
|
||||
{
|
||||
builder.ToTable("coupon_templates");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Name).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.CouponType).HasConversion<int>();
|
||||
builder.Property(x => x.Description).HasMaxLength(512);
|
||||
builder.Property(x => x.TotalQuantity);
|
||||
builder.Property(x => x.StoreScopeJson).HasColumnType("text");
|
||||
builder.Property(x => x.ProductScopeJson).HasColumnType("text");
|
||||
builder.Property(x => x.ChannelsJson).HasColumnType("text");
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
}
|
||||
|
||||
private static void ConfigureCoupon(EntityTypeBuilder<Coupon> builder)
|
||||
{
|
||||
builder.ToTable("coupons");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.CouponTemplateId).IsRequired();
|
||||
builder.Property(x => x.Code).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.Code }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigurePromotionCampaign(EntityTypeBuilder<PromotionCampaign> builder)
|
||||
{
|
||||
builder.ToTable("promotion_campaigns");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Name).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.PromotionType).HasConversion<int>();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.RulesJson).HasColumnType("text").IsRequired();
|
||||
builder.Property(x => x.AudienceDescription).HasMaxLength(512);
|
||||
builder.Property(x => x.BannerUrl).HasMaxLength(512);
|
||||
}
|
||||
|
||||
private static void ConfigureMemberProfile(EntityTypeBuilder<MemberProfile> builder)
|
||||
{
|
||||
builder.ToTable("member_profiles");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Mobile).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.Nickname).HasMaxLength(64);
|
||||
builder.Property(x => x.AvatarUrl).HasMaxLength(256);
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.Mobile }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureMemberTier(EntityTypeBuilder<MemberTier> builder)
|
||||
{
|
||||
builder.ToTable("member_tiers");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.BenefitsJson).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.Name }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureMemberPointLedger(EntityTypeBuilder<MemberPointLedger> builder)
|
||||
{
|
||||
builder.ToTable("member_point_ledgers");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.MemberId).IsRequired();
|
||||
builder.Property(x => x.Reason).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.MemberId, x.OccurredAt });
|
||||
}
|
||||
|
||||
private static void ConfigureMemberGrowthLog(EntityTypeBuilder<MemberGrowthLog> builder)
|
||||
{
|
||||
builder.ToTable("member_growth_logs");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.MemberId).IsRequired();
|
||||
builder.Property(x => x.Notes).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.MemberId, x.OccurredAt });
|
||||
}
|
||||
|
||||
private static void ConfigureChatSession(EntityTypeBuilder<ChatSession> builder)
|
||||
{
|
||||
builder.ToTable("chat_sessions");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.SessionCode).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.SessionCode }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureChatMessage(EntityTypeBuilder<ChatMessage> builder)
|
||||
{
|
||||
builder.ToTable("chat_messages");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.ChatSessionId).IsRequired();
|
||||
builder.Property(x => x.SenderType).HasConversion<int>();
|
||||
builder.Property(x => x.ContentType).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.Content).HasMaxLength(1024).IsRequired();
|
||||
builder.HasIndex(x => new { x.TenantId, x.ChatSessionId, x.CreatedAt });
|
||||
}
|
||||
|
||||
private static void ConfigureSupportTicket(EntityTypeBuilder<SupportTicket> builder)
|
||||
{
|
||||
builder.ToTable("support_tickets");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.TicketNo).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.Subject).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Description).HasColumnType("text").IsRequired();
|
||||
builder.Property(x => x.Priority).HasConversion<int>();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.TicketNo }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureTicketComment(EntityTypeBuilder<TicketComment> builder)
|
||||
{
|
||||
builder.ToTable("ticket_comments");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.SupportTicketId).IsRequired();
|
||||
builder.Property(x => x.Content).HasMaxLength(1024).IsRequired();
|
||||
builder.Property(x => x.AttachmentsJson).HasColumnType("text");
|
||||
builder.HasIndex(x => new { x.TenantId, x.SupportTicketId });
|
||||
}
|
||||
|
||||
private static void ConfigureAffiliatePartner(EntityTypeBuilder<AffiliatePartner> builder)
|
||||
{
|
||||
builder.ToTable("affiliate_partners");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.DisplayName).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.Phone).HasMaxLength(32);
|
||||
builder.Property(x => x.ChannelType).HasConversion<int>();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.Remarks).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.DisplayName });
|
||||
}
|
||||
|
||||
private static void ConfigureAffiliateOrder(EntityTypeBuilder<AffiliateOrder> builder)
|
||||
{
|
||||
builder.ToTable("affiliate_orders");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.AffiliatePartnerId).IsRequired();
|
||||
builder.Property(x => x.OrderId).IsRequired();
|
||||
builder.Property(x => x.OrderAmount).HasPrecision(18, 2);
|
||||
builder.Property(x => x.EstimatedCommission).HasPrecision(18, 2);
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.AffiliatePartnerId, x.OrderId }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureAffiliatePayout(EntityTypeBuilder<AffiliatePayout> builder)
|
||||
{
|
||||
builder.ToTable("affiliate_payouts");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.AffiliatePartnerId).IsRequired();
|
||||
builder.Property(x => x.Period).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.Amount).HasPrecision(18, 2);
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.Remarks).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.AffiliatePartnerId, x.Period }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureCheckInCampaign(EntityTypeBuilder<CheckInCampaign> builder)
|
||||
{
|
||||
builder.ToTable("checkin_campaigns");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Name).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Description).HasMaxLength(512);
|
||||
builder.Property(x => x.RewardsJson).HasColumnType("text").IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.Name });
|
||||
}
|
||||
|
||||
private static void ConfigureCheckInRecord(EntityTypeBuilder<CheckInRecord> builder)
|
||||
{
|
||||
builder.ToTable("checkin_records");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.CheckInCampaignId).IsRequired();
|
||||
builder.Property(x => x.UserId).IsRequired();
|
||||
builder.Property(x => x.RewardJson).HasColumnType("text").IsRequired();
|
||||
builder.HasIndex(x => new { x.TenantId, x.CheckInCampaignId, x.UserId, x.CheckInDate }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureCommunityPost(EntityTypeBuilder<CommunityPost> builder)
|
||||
{
|
||||
builder.ToTable("community_posts");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.AuthorUserId).IsRequired();
|
||||
builder.Property(x => x.Title).HasMaxLength(128);
|
||||
builder.Property(x => x.Content).HasColumnType("text").IsRequired();
|
||||
builder.Property(x => x.MediaJson).HasColumnType("text");
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.AuthorUserId, x.CreatedAt });
|
||||
}
|
||||
|
||||
private static void ConfigureCommunityComment(EntityTypeBuilder<CommunityComment> builder)
|
||||
{
|
||||
builder.ToTable("community_comments");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.PostId).IsRequired();
|
||||
builder.Property(x => x.Content).HasMaxLength(512).IsRequired();
|
||||
builder.HasIndex(x => new { x.TenantId, x.PostId, x.CreatedAt });
|
||||
}
|
||||
|
||||
private static void ConfigureCommunityReaction(EntityTypeBuilder<CommunityReaction> builder)
|
||||
{
|
||||
builder.ToTable("community_reactions");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.PostId).IsRequired();
|
||||
builder.Property(x => x.ReactionType).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.PostId, x.UserId }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureMapLocation(EntityTypeBuilder<MapLocation> builder)
|
||||
{
|
||||
builder.ToTable("map_locations");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Name).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Address).HasMaxLength(256).IsRequired();
|
||||
builder.Property(x => x.Landmark).HasMaxLength(128);
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId });
|
||||
}
|
||||
|
||||
private static void ConfigureNavigationRequest(EntityTypeBuilder<NavigationRequest> builder)
|
||||
{
|
||||
builder.ToTable("navigation_requests");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.UserId).IsRequired();
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.Channel).HasConversion<int>();
|
||||
builder.Property(x => x.TargetApp).HasConversion<int>();
|
||||
builder.HasIndex(x => new { x.TenantId, x.UserId, x.StoreId, x.RequestedAt });
|
||||
}
|
||||
|
||||
private static void ConfigureMetricDefinition(EntityTypeBuilder<MetricDefinition> builder)
|
||||
{
|
||||
builder.ToTable("metric_definitions");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.Code).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.Name).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Description).HasMaxLength(512);
|
||||
builder.Property(x => x.DimensionsJson).HasColumnType("text");
|
||||
builder.Property(x => x.DefaultAggregation).HasMaxLength(32).IsRequired();
|
||||
builder.HasIndex(x => new { x.TenantId, x.Code }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureMetricSnapshot(EntityTypeBuilder<MetricSnapshot> builder)
|
||||
{
|
||||
builder.ToTable("metric_snapshots");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.MetricDefinitionId).IsRequired();
|
||||
builder.Property(x => x.DimensionKey).HasMaxLength(256).IsRequired();
|
||||
builder.Property(x => x.Value).HasPrecision(18, 4);
|
||||
builder.HasIndex(x => new { x.TenantId, x.MetricDefinitionId, x.DimensionKey, x.WindowStart, x.WindowEnd }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureMetricAlertRule(EntityTypeBuilder<MetricAlertRule> builder)
|
||||
{
|
||||
builder.ToTable("metric_alert_rules");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.MetricDefinitionId).IsRequired();
|
||||
builder.Property(x => x.ConditionJson).HasColumnType("text").IsRequired();
|
||||
builder.Property(x => x.Severity).HasConversion<int>();
|
||||
builder.Property(x => x.NotificationChannels).HasMaxLength(256);
|
||||
builder.HasIndex(x => new { x.TenantId, x.MetricDefinitionId, x.Severity });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TakeoutSaaS.Infrastructure.Common.Persistence.DesignTime;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
@@ -12,7 +13,7 @@ internal sealed class TakeoutAppDesignTimeDbContextFactory
|
||||
: DesignTimeDbContextFactoryBase<TakeoutAppDbContext>
|
||||
{
|
||||
public TakeoutAppDesignTimeDbContextFactory()
|
||||
: base("TAKEOUTSAAS_APP_CONNECTION", "takeout_saas_app")
|
||||
: base(DatabaseConstants.AppDataSource, "TAKEOUTSAAS_APP_CONNECTION")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ public abstract class AppDbContext(DbContextOptions options, ICurrentUserAccesso
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
ApplySoftDeleteQueryFilters(modelBuilder);
|
||||
modelBuilder.ApplyXmlComments();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using TakeoutSaaS.Infrastructure.Common.Persistence;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using TakeoutSaaS.Infrastructure.Common.Options;
|
||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Common.Persistence.DesignTime;
|
||||
|
||||
/// <summary>
|
||||
/// EF Core 设计时 DbContext 工厂基类,提供统一的连接串与依赖替身。
|
||||
/// EF Core 设计时 DbContext 工厂基类,统一读取 appsettings 中的数据库配置。
|
||||
/// </summary>
|
||||
internal abstract class DesignTimeDbContextFactoryBase<TContext> : IDesignTimeDbContextFactory<TContext>
|
||||
where TContext : TenantAwareDbContext
|
||||
{
|
||||
private readonly string _connectionStringEnvVar;
|
||||
private readonly string _defaultDatabase;
|
||||
private readonly string _dataSourceName;
|
||||
private readonly string? _connectionStringEnvVar;
|
||||
|
||||
protected DesignTimeDbContextFactoryBase(string connectionStringEnvVar, string defaultDatabase)
|
||||
protected DesignTimeDbContextFactoryBase(string dataSourceName, string? connectionStringEnvVar = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dataSourceName))
|
||||
{
|
||||
throw new ArgumentException("数据源名称不能为空。", nameof(dataSourceName));
|
||||
}
|
||||
|
||||
_dataSourceName = dataSourceName;
|
||||
_connectionStringEnvVar = connectionStringEnvVar;
|
||||
_defaultDatabase = defaultDatabase;
|
||||
}
|
||||
|
||||
public TContext CreateDbContext(string[] args)
|
||||
@@ -46,15 +54,91 @@ internal abstract class DesignTimeDbContextFactoryBase<TContext> : IDesignTimeDb
|
||||
|
||||
private string ResolveConnectionString()
|
||||
{
|
||||
var env = Environment.GetEnvironmentVariable(_connectionStringEnvVar);
|
||||
if (!string.IsNullOrWhiteSpace(env))
|
||||
if (!string.IsNullOrWhiteSpace(_connectionStringEnvVar))
|
||||
{
|
||||
return env;
|
||||
var envValue = Environment.GetEnvironmentVariable(_connectionStringEnvVar);
|
||||
if (!string.IsNullOrWhiteSpace(envValue))
|
||||
{
|
||||
return envValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $"Host=localhost;Port=5432;Database={_defaultDatabase};Username=postgres;Password=postgres";
|
||||
var configuration = BuildConfiguration();
|
||||
var writeConnection = configuration[$"{DatabaseOptions.SectionName}:DataSources:{_dataSourceName}:Write"];
|
||||
if (string.IsNullOrWhiteSpace(writeConnection))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"未在配置中找到数据源 '{_dataSourceName}' 的 Write 连接字符串,请检查 appsettings 或设置 {_connectionStringEnvVar ?? "相应"} 环境变量。");
|
||||
}
|
||||
|
||||
return writeConnection;
|
||||
}
|
||||
|
||||
private static IConfigurationRoot BuildConfiguration()
|
||||
{
|
||||
var basePath = ResolveConfigurationDirectory();
|
||||
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Development";
|
||||
|
||||
return new ConfigurationBuilder()
|
||||
.SetBasePath(basePath)
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
|
||||
.AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: false)
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static string ResolveConfigurationDirectory()
|
||||
{
|
||||
var explicitDir = Environment.GetEnvironmentVariable("TAKEOUTSAAS_APPSETTINGS_DIR");
|
||||
if (!string.IsNullOrWhiteSpace(explicitDir) && Directory.Exists(explicitDir))
|
||||
{
|
||||
return explicitDir;
|
||||
}
|
||||
|
||||
var currentDir = Directory.GetCurrentDirectory();
|
||||
var solutionRoot = LocateSolutionRoot(currentDir);
|
||||
|
||||
var candidateDirs = new[]
|
||||
{
|
||||
currentDir,
|
||||
solutionRoot,
|
||||
solutionRoot is null ? null : Path.Combine(solutionRoot, "src", "Api", "TakeoutSaaS.AdminApi"),
|
||||
solutionRoot is null ? null : Path.Combine(solutionRoot, "src", "Api", "TakeoutSaaS.UserApi"),
|
||||
solutionRoot is null ? null : Path.Combine(solutionRoot, "src", "Api", "TakeoutSaaS.MiniApi")
|
||||
}.Where(dir => !string.IsNullOrWhiteSpace(dir));
|
||||
|
||||
foreach (var dir in candidateDirs)
|
||||
{
|
||||
if (dir != null && Directory.Exists(dir) && HasAppSettings(dir))
|
||||
{
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"未找到 appsettings 配置文件,请设置 TAKEOUTSAAS_APPSETTINGS_DIR 环境变量指向包含 appsettings*.json 的目录。");
|
||||
}
|
||||
|
||||
private static string? LocateSolutionRoot(string currentPath)
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(currentPath);
|
||||
while (directoryInfo != null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(directoryInfo.FullName, "TakeoutSaaS.sln")))
|
||||
{
|
||||
return directoryInfo.FullName;
|
||||
}
|
||||
|
||||
directoryInfo = directoryInfo.Parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool HasAppSettings(string directory) =>
|
||||
File.Exists(Path.Combine(directory, "appsettings.json")) ||
|
||||
Directory.GetFiles(directory, "appsettings.*.json").Length > 0;
|
||||
|
||||
private sealed class DesignTimeTenantProvider : ITenantProvider
|
||||
{
|
||||
public Guid GetCurrentTenantId() => Guid.Empty;
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Common.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Applies XML documentation summaries to EF Core entities/columns as comments.
|
||||
/// </summary>
|
||||
internal static class ModelBuilderCommentExtensions
|
||||
{
|
||||
public static void ApplyXmlComments(this ModelBuilder modelBuilder)
|
||||
{
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
ApplyEntityComment(entityType);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyEntityComment(IMutableEntityType entityType)
|
||||
{
|
||||
var clrType = entityType.ClrType;
|
||||
if (clrType == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (XmlDocCommentProvider.TryGetSummary(clrType, out var typeComment))
|
||||
{
|
||||
entityType.SetComment(typeComment);
|
||||
}
|
||||
|
||||
foreach (var property in entityType.GetProperties())
|
||||
{
|
||||
var propertyInfo = property.PropertyInfo;
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (XmlDocCommentProvider.TryGetSummary(propertyInfo, out var propertyComment))
|
||||
{
|
||||
property.SetComment(propertyComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class XmlDocCommentProvider
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Assembly, IReadOnlyDictionary<string, string>> Cache = new();
|
||||
|
||||
public static bool TryGetSummary(MemberInfo member, out string? summary)
|
||||
{
|
||||
summary = null;
|
||||
var assembly = member switch
|
||||
{
|
||||
Type type => type.Assembly,
|
||||
_ => member.DeclaringType?.Assembly
|
||||
};
|
||||
|
||||
if (assembly == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var map = Cache.GetOrAdd(assembly, LoadComments);
|
||||
if (map.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = GetMemberKey(member);
|
||||
if (key == null || !map.TryGetValue(key, out var text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
summary = text;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, string> LoadComments(Assembly assembly)
|
||||
{
|
||||
var dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
var xmlPath = Path.ChangeExtension(assembly.Location, ".xml");
|
||||
if (string.IsNullOrWhiteSpace(xmlPath) || !File.Exists(xmlPath))
|
||||
{
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
var document = XDocument.Load(xmlPath);
|
||||
foreach (var member in document.Descendants("member"))
|
||||
{
|
||||
var name = member.Attribute("name")?.Value;
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var summary = member.Element("summary")?.Value;
|
||||
if (string.IsNullOrWhiteSpace(summary))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var normalized = Normalize(summary);
|
||||
if (!string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
dictionary[name] = normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
private static string? GetMemberKey(MemberInfo member) =>
|
||||
member switch
|
||||
{
|
||||
Type type => $"T:{GetFullName(type)}",
|
||||
PropertyInfo property => $"P:{GetFullName(property.DeclaringType!)}.{property.Name}",
|
||||
FieldInfo field => $"F:{GetFullName(field.DeclaringType!)}.{field.Name}",
|
||||
_ => null
|
||||
};
|
||||
|
||||
private static string GetFullName(Type type) =>
|
||||
(type.FullName ?? type.Name).Replace('+', '.');
|
||||
|
||||
private static string Normalize(string text)
|
||||
{
|
||||
var chars = text.Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ');
|
||||
return string.Join(' ', chars.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public static class DictionaryServiceCollectionExtensions
|
||||
public static IServiceCollection AddDictionaryInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddDatabaseInfrastructure(configuration);
|
||||
services.AddPostgresDbContext<DictionaryDbContext>(DatabaseConstants.AppDataSource);
|
||||
services.AddPostgresDbContext<DictionaryDbContext>(DatabaseConstants.DictionaryDataSource);
|
||||
|
||||
services.AddScoped<IDictionaryRepository, EfDictionaryRepository>();
|
||||
services.AddScoped<IDictionaryCache, DistributedDictionaryCache>();
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using TakeoutSaaS.Infrastructure.Dictionary.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
|
||||
{
|
||||
[DbContext(typeof(DictionaryDbContext))]
|
||||
[Migration("20251201094456_AddEntityComments")]
|
||||
partial class AddEntityComments
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("分组编码(唯一)。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasComment("描述信息。");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true)
|
||||
.HasComment("是否启用。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("分组名称。");
|
||||
|
||||
b.Property<int>("Scope")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("分组作用域:系统/业务。");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("dictionary_groups", null, t =>
|
||||
{
|
||||
t.HasComment("参数字典分组(系统参数、业务参数)。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasComment("描述信息。");
|
||||
|
||||
b.Property<Guid>("GroupId")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("关联分组 ID。");
|
||||
|
||||
b.Property<bool>("IsDefault")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否默认项。");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true)
|
||||
.HasComment("是否启用。");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("字典项键。");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(100)
|
||||
.HasComment("排序值,越小越靠前。");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("字典项值。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("GroupId", "Key")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("dictionary_items", null, t =>
|
||||
{
|
||||
t.HasComment("参数字典项。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b =>
|
||||
{
|
||||
b.HasOne("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", "Group")
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("GroupId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Group");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,599 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddEntityComments : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterTable(
|
||||
name: "dictionary_items",
|
||||
comment: "参数字典项。");
|
||||
|
||||
migrationBuilder.AlterTable(
|
||||
name: "dictionary_groups",
|
||||
comment: "参数字典分组(系统参数、业务参数)。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Value",
|
||||
table: "dictionary_items",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: false,
|
||||
comment: "字典项值。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "UpdatedBy",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "最后更新人用户标识,匿名或系统操作时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "dictionary_items",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "最近一次更新时间(UTC),从未更新时为 null。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "TenantId",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "所属租户 ID。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "SortOrder",
|
||||
table: "dictionary_items",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 100,
|
||||
comment: "排序值,越小越靠前。",
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer",
|
||||
oldDefaultValue: 100);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Key",
|
||||
table: "dictionary_items",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
comment: "字典项键。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64);
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "IsEnabled",
|
||||
table: "dictionary_items",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: true,
|
||||
comment: "是否启用。",
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean",
|
||||
oldDefaultValue: true);
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "IsDefault",
|
||||
table: "dictionary_items",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
comment: "是否默认项。",
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "GroupId",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "关联分组 ID。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
table: "dictionary_items",
|
||||
type: "character varying(512)",
|
||||
maxLength: 512,
|
||||
nullable: true,
|
||||
comment: "描述信息。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(512)",
|
||||
oldMaxLength: 512,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "DeletedBy",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "删除人用户标识(软删除),未删除时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DeletedAt",
|
||||
table: "dictionary_items",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "软删除时间(UTC),未删除时为 null。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CreatedBy",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "创建人用户标识,匿名或系统操作时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "dictionary_items",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
comment: "创建时间(UTC)。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "Id",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "实体唯一标识。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "UpdatedBy",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "最后更新人用户标识,匿名或系统操作时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "dictionary_groups",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "最近一次更新时间(UTC),从未更新时为 null。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "TenantId",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "所属租户 ID。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Scope",
|
||||
table: "dictionary_groups",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
comment: "分组作用域:系统/业务。",
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "dictionary_groups",
|
||||
type: "character varying(128)",
|
||||
maxLength: 128,
|
||||
nullable: false,
|
||||
comment: "分组名称。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(128)",
|
||||
oldMaxLength: 128);
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "IsEnabled",
|
||||
table: "dictionary_groups",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: true,
|
||||
comment: "是否启用。",
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean",
|
||||
oldDefaultValue: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
table: "dictionary_groups",
|
||||
type: "character varying(512)",
|
||||
maxLength: 512,
|
||||
nullable: true,
|
||||
comment: "描述信息。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(512)",
|
||||
oldMaxLength: 512,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "DeletedBy",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "删除人用户标识(软删除),未删除时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DeletedAt",
|
||||
table: "dictionary_groups",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "软删除时间(UTC),未删除时为 null。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CreatedBy",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "创建人用户标识,匿名或系统操作时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "dictionary_groups",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
comment: "创建时间(UTC)。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Code",
|
||||
table: "dictionary_groups",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
comment: "分组编码(唯一)。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "Id",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "实体唯一标识。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterTable(
|
||||
name: "dictionary_items",
|
||||
oldComment: "参数字典项。");
|
||||
|
||||
migrationBuilder.AlterTable(
|
||||
name: "dictionary_groups",
|
||||
oldComment: "参数字典分组(系统参数、业务参数)。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Value",
|
||||
table: "dictionary_items",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldComment: "字典项值。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "UpdatedBy",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "dictionary_items",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true,
|
||||
oldComment: "最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "TenantId",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "所属租户 ID。");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "SortOrder",
|
||||
table: "dictionary_items",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 100,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer",
|
||||
oldDefaultValue: 100,
|
||||
oldComment: "排序值,越小越靠前。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Key",
|
||||
table: "dictionary_items",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64,
|
||||
oldComment: "字典项键。");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "IsEnabled",
|
||||
table: "dictionary_items",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: true,
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean",
|
||||
oldDefaultValue: true,
|
||||
oldComment: "是否启用。");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "IsDefault",
|
||||
table: "dictionary_items",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean",
|
||||
oldComment: "是否默认项。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "GroupId",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "关联分组 ID。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
table: "dictionary_items",
|
||||
type: "character varying(512)",
|
||||
maxLength: 512,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(512)",
|
||||
oldMaxLength: 512,
|
||||
oldNullable: true,
|
||||
oldComment: "描述信息。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "DeletedBy",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DeletedAt",
|
||||
table: "dictionary_items",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true,
|
||||
oldComment: "软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CreatedBy",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "dictionary_items",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldComment: "创建时间(UTC)。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "Id",
|
||||
table: "dictionary_items",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "实体唯一标识。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "UpdatedBy",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "dictionary_groups",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true,
|
||||
oldComment: "最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "TenantId",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "所属租户 ID。");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Scope",
|
||||
table: "dictionary_groups",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer",
|
||||
oldComment: "分组作用域:系统/业务。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Name",
|
||||
table: "dictionary_groups",
|
||||
type: "character varying(128)",
|
||||
maxLength: 128,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(128)",
|
||||
oldMaxLength: 128,
|
||||
oldComment: "分组名称。");
|
||||
|
||||
migrationBuilder.AlterColumn<bool>(
|
||||
name: "IsEnabled",
|
||||
table: "dictionary_groups",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: true,
|
||||
oldClrType: typeof(bool),
|
||||
oldType: "boolean",
|
||||
oldDefaultValue: true,
|
||||
oldComment: "是否启用。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
table: "dictionary_groups",
|
||||
type: "character varying(512)",
|
||||
maxLength: 512,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(512)",
|
||||
oldMaxLength: 512,
|
||||
oldNullable: true,
|
||||
oldComment: "描述信息。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "DeletedBy",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DeletedAt",
|
||||
table: "dictionary_groups",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true,
|
||||
oldComment: "软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CreatedBy",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "dictionary_groups",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldComment: "创建时间(UTC)。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Code",
|
||||
table: "dictionary_groups",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64,
|
||||
oldComment: "分组编码(唯一)。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "Id",
|
||||
table: "dictionary_groups",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "实体唯一标识。");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,50 +26,63 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("分组编码(唯一)。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasComment("描述信息。");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true);
|
||||
.HasDefaultValue(true)
|
||||
.HasComment("是否启用。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("分组名称。");
|
||||
|
||||
b.Property<int>("Scope")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("integer")
|
||||
.HasComment("分组作用域:系统/业务。");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -78,65 +91,83 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
|
||||
b.HasIndex("TenantId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("dictionary_groups", (string)null);
|
||||
b.ToTable("dictionary_groups", null, t =>
|
||||
{
|
||||
t.HasComment("参数字典分组(系统参数、业务参数)。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasComment("描述信息。");
|
||||
|
||||
b.Property<Guid>("GroupId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("关联分组 ID。");
|
||||
|
||||
b.Property<bool>("IsDefault")
|
||||
.HasColumnType("boolean");
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否默认项。");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true);
|
||||
.HasDefaultValue(true)
|
||||
.HasComment("是否启用。");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("字典项键。");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(100);
|
||||
.HasDefaultValue(100)
|
||||
.HasComment("排序值,越小越靠前。");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("字典项值。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -145,7 +176,10 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
|
||||
b.HasIndex("GroupId", "Key")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("dictionary_items", (string)null);
|
||||
b.ToTable("dictionary_items", null, t =>
|
||||
{
|
||||
t.HasComment("参数字典项。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b =>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TakeoutSaaS.Infrastructure.Common.Persistence.DesignTime;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
@@ -12,7 +13,7 @@ internal sealed class DictionaryDesignTimeDbContextFactory
|
||||
: DesignTimeDbContextFactoryBase<DictionaryDbContext>
|
||||
{
|
||||
public DictionaryDesignTimeDbContextFactory()
|
||||
: base("TAKEOUTSAAS_APP_CONNECTION", "takeout_saas_app")
|
||||
: base(DatabaseConstants.DictionaryDataSource, "TAKEOUTSAAS_DICTIONARY_CONNECTION")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Identity.Migrations
|
||||
{
|
||||
[DbContext(typeof(IdentityDbContext))]
|
||||
[Migration("20251201094410_AddEntityComments")]
|
||||
partial class AddEntityComments
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
b.Property<string>("Account")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("登录账号。");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("头像地址。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("展示名称。");
|
||||
|
||||
b.Property<Guid?>("MerchantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属商户(平台管理员为空)。");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("密码哈希。");
|
||||
|
||||
b.Property<string>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasComment("权限集合。");
|
||||
|
||||
b.Property<string>("Roles")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasComment("角色集合。");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Account")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("identity_users", null, t =>
|
||||
{
|
||||
t.HasComment("管理后台账户实体(平台管理员、租户管理员或商户员工)。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("头像地址。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("昵称。");
|
||||
|
||||
b.Property<string>("OpenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("微信 OpenId。");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<string>("UnionId")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("微信 UnionId,可能为空。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "OpenId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("mini_users", null, t =>
|
||||
{
|
||||
t.HasComment("小程序用户实体。");
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Identity.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddEntityComments : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterTable(
|
||||
name: "mini_users",
|
||||
comment: "小程序用户实体。");
|
||||
|
||||
migrationBuilder.AlterTable(
|
||||
name: "identity_users",
|
||||
comment: "管理后台账户实体(平台管理员、租户管理员或商户员工)。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "UpdatedBy",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "最后更新人用户标识,匿名或系统操作时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "mini_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "最近一次更新时间(UTC),从未更新时为 null。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "UnionId",
|
||||
table: "mini_users",
|
||||
type: "character varying(128)",
|
||||
maxLength: 128,
|
||||
nullable: true,
|
||||
comment: "微信 UnionId,可能为空。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(128)",
|
||||
oldMaxLength: 128,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "TenantId",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "所属租户 ID。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "OpenId",
|
||||
table: "mini_users",
|
||||
type: "character varying(128)",
|
||||
maxLength: 128,
|
||||
nullable: false,
|
||||
comment: "微信 OpenId。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(128)",
|
||||
oldMaxLength: 128);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Nickname",
|
||||
table: "mini_users",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
comment: "昵称。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "DeletedBy",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "删除人用户标识(软删除),未删除时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DeletedAt",
|
||||
table: "mini_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "软删除时间(UTC),未删除时为 null。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CreatedBy",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "创建人用户标识,匿名或系统操作时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "mini_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
comment: "创建时间(UTC)。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Avatar",
|
||||
table: "mini_users",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true,
|
||||
comment: "头像地址。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "Id",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "实体唯一标识。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "UpdatedBy",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "最后更新人用户标识,匿名或系统操作时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "identity_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "最近一次更新时间(UTC),从未更新时为 null。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "TenantId",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "所属租户 ID。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Roles",
|
||||
table: "identity_users",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
comment: "角色集合。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Permissions",
|
||||
table: "identity_users",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
comment: "权限集合。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "PasswordHash",
|
||||
table: "identity_users",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: false,
|
||||
comment: "密码哈希。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "MerchantId",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "所属商户(平台管理员为空)。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DisplayName",
|
||||
table: "identity_users",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
comment: "展示名称。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "DeletedBy",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "删除人用户标识(软删除),未删除时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DeletedAt",
|
||||
table: "identity_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "软删除时间(UTC),未删除时为 null。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CreatedBy",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
comment: "创建人用户标识,匿名或系统操作时为 null。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "identity_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
comment: "创建时间(UTC)。",
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Avatar",
|
||||
table: "identity_users",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true,
|
||||
comment: "头像地址。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Account",
|
||||
table: "identity_users",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
comment: "登录账号。",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "Id",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
comment: "实体唯一标识。",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterTable(
|
||||
name: "mini_users",
|
||||
oldComment: "小程序用户实体。");
|
||||
|
||||
migrationBuilder.AlterTable(
|
||||
name: "identity_users",
|
||||
oldComment: "管理后台账户实体(平台管理员、租户管理员或商户员工)。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "UpdatedBy",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "mini_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true,
|
||||
oldComment: "最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "UnionId",
|
||||
table: "mini_users",
|
||||
type: "character varying(128)",
|
||||
maxLength: 128,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(128)",
|
||||
oldMaxLength: 128,
|
||||
oldNullable: true,
|
||||
oldComment: "微信 UnionId,可能为空。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "TenantId",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "所属租户 ID。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "OpenId",
|
||||
table: "mini_users",
|
||||
type: "character varying(128)",
|
||||
maxLength: 128,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(128)",
|
||||
oldMaxLength: 128,
|
||||
oldComment: "微信 OpenId。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Nickname",
|
||||
table: "mini_users",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64,
|
||||
oldComment: "昵称。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "DeletedBy",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DeletedAt",
|
||||
table: "mini_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true,
|
||||
oldComment: "软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CreatedBy",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "mini_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldComment: "创建时间(UTC)。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Avatar",
|
||||
table: "mini_users",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldNullable: true,
|
||||
oldComment: "头像地址。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "Id",
|
||||
table: "mini_users",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "实体唯一标识。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "UpdatedBy",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "identity_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true,
|
||||
oldComment: "最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "TenantId",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "所属租户 ID。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Roles",
|
||||
table: "identity_users",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldComment: "角色集合。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Permissions",
|
||||
table: "identity_users",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldComment: "权限集合。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "PasswordHash",
|
||||
table: "identity_users",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldComment: "密码哈希。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "MerchantId",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "所属商户(平台管理员为空)。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DisplayName",
|
||||
table: "identity_users",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64,
|
||||
oldComment: "展示名称。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "DeletedBy",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "DeletedAt",
|
||||
table: "identity_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true,
|
||||
oldComment: "软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CreatedBy",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldNullable: true,
|
||||
oldComment: "创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "identity_users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldComment: "创建时间(UTC)。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Avatar",
|
||||
table: "identity_users",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldNullable: true,
|
||||
oldComment: "头像地址。");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Account",
|
||||
table: "identity_users",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(64)",
|
||||
oldMaxLength: 64,
|
||||
oldComment: "登录账号。");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "Id",
|
||||
table: "identity_users",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uuid",
|
||||
oldComment: "实体唯一标识。");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,58 +26,73 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
b.Property<string>("Account")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("登录账号。");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("头像地址。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("展示名称。");
|
||||
|
||||
b.Property<Guid?>("MerchantId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属商户(平台管理员为空)。");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("密码哈希。");
|
||||
|
||||
b.Property<string>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("text")
|
||||
.HasComment("权限集合。");
|
||||
|
||||
b.Property<string>("Roles")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("text")
|
||||
.HasComment("角色集合。");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -86,53 +101,68 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
|
||||
b.HasIndex("TenantId", "Account")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("identity_users", (string)null);
|
||||
b.ToTable("identity_users", null, t =>
|
||||
{
|
||||
t.HasComment("管理后台账户实体(平台管理员、租户管理员或商户员工)。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("头像地址。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<Guid?>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("昵称。");
|
||||
|
||||
b.Property<string>("OpenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("微信 OpenId。");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<string>("UnionId")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("微信 UnionId,可能为空。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -141,7 +171,10 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
|
||||
b.HasIndex("TenantId", "OpenId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("mini_users", (string)null);
|
||||
b.ToTable("mini_users", null, t =>
|
||||
{
|
||||
t.HasComment("小程序用户实体。");
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TakeoutSaaS.Infrastructure.Common.Persistence.DesignTime;
|
||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
@@ -12,7 +13,7 @@ internal sealed class IdentityDesignTimeDbContextFactory
|
||||
: DesignTimeDbContextFactoryBase<IdentityDbContext>
|
||||
{
|
||||
public IdentityDesignTimeDbContextFactory()
|
||||
: base("TAKEOUTSAAS_IDENTITY_CONNECTION", "takeout_saas_identity")
|
||||
: base(DatabaseConstants.IdentityDataSource, "TAKEOUTSAAS_IDENTITY_CONNECTION")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user