volksec/0009098
GitHub: volksec/0009098
采用 Clean Architecture 和 DDD 的保险经纪人全流程管理平台,深度整合 PostgreSQL 高级特性以实现多租户隔离、领域建模与可观测性。
Stars: 0 | Forks: 0
# Portal do Corretor
**保险经纪人管理平台**
对象关系数据库 · 丰富的领域模型 · 模块化架构
[](https://dotnet.microsoft.com/)
[](https://www.postgresql.org/)
[](https://react.dev/)
[](https://docs.docker.com/compose/)
[](#9-testes)
[](LICENSE)
## 目录
| | 章节 | | 章节 |
|---|---|---|---|
| 1 | [概述](#1-visão-geral) | 7 | [应用安全](#7-segurança-de-aplicação) |
| 2 | [**启动环境**](#2-subindo-o-ambiente) | 8 | [可观测性](#8-observabilidade) |
| 3 | [仓库结构](#3-estrutura-do-repositório) | 9 | [测试](#9-testes) |
| 4 | [架构](#4-arquitetura) | 10 | [工程工具](#10-ferramentas-de-engenharia) |
| 5 | [领域模型](#5-modelo-de-domínio) | 11 | [架构决策](#11-decisões-arquiteturais-adrs) |
| 6 | [对象关系数据库](#6-banco-objeto-relacional) | | |
## 1. 概述
Portal do Corretor 涵盖了保险经纪的商业生命周期 —
**客户 → 可保资产 → 报价 → 提案 → 保单 → 分期付款 → 佣金 → 续保 →
理赔** — 作为一个采用 Clean Architecture、战术 DDD 和以 PostgreSQL 作为对象关系真实数据库的模块化单体实现。
### 技术能力
| 领域 | 实现 |
|---|---|
| **持久化** | PostgreSQL 16,使用复合类型、domains、enums、`daterange`、排他约束、部分索引和 GIN、按月分区 |
| **领域** | 丰富的领域模型 — 带有不变量的聚合,19个不可变的 Value Objects,领域事件,specifications,领域服务 |
| **并发** | 使用原生 `xmin` 的乐观锁,幂等键,`SELECT ... FOR UPDATE SKIP LOCKED` |
| **多租户** | 5层独立隔离,最终由带有 `FORCE` 的 Row-Level Security 实现 |
| **异步** | 事务性 Outbox — 事件和状态在同一事务中确认 |
| **审计** | 通过数据库中的 `REVOKE` 强制执行的仅追加追踪,按月分区 |
| **可观测性** | 端到端的 OpenTelemetry,correlation ID 传播到数据库,业务、性能和完整性指标 |
| **质量** | 187个测试(单元、属性、架构),`TreatWarningsAsErrors`,通过 NetArchTest 验证模块边界 |
### 访问角色
- **Corretor(经纪人)** — 操作型用户,在其经纪公司的 tenant 内操作。
- **Regulatório(监管)** — 只读监督角色,按授权范围进行多租户访问,
必须提供访问目的且数据最小化。
安全、审计和管理功能是由技术账户(`Outbox Dispatcher`、`Renewal Scanner`、`Billing Scheduler`、`Integrity Checker`)执行的**内部能力**,而不是由用户角色执行。
## 2. 启动环境
### 2.1 前置条件
| 工具 | 版本 | 验证 |
|---|---|---|
| [.NET SDK](https://dotnet.microsoft.com/download/dotnet/9.0) | 9.0+ | `dotnet --version` |
| [Docker Desktop](https://www.docker.com/products/docker-desktop/) | 24+ | `docker --version` |
| Docker Compose | v2 | `docker compose version` |
| [Node.js](https://nodejs.org/) | 20+ | `node --version` |
| [Git](https://git-scm.com/) | 2.40+ | `git --version` |
### 2.2 克隆仓库
```
git clone https://github.com/volksec/0009098.git
```
```
cd 0009098
```
### 2.3 配置本地变量和密钥
没有任何凭据被纳入版本控制。请根据示例创建本地文件:
```
cp .env.example .env
```
```
cp infrastructure/secrets/db_password.txt.example infrastructure/secrets/db_password.txt
```
使用您自己的值编辑这两个文件:
```
# .env
POSTGRES_APP_USER_PASSWORD=defina_um_valor
POSTGRES_APP_REGULATOR_PASSWORD=defina_um_valor
POSTGRES_APP_WORKER_PASSWORD=defina_um_valor
```
如果缺少任何变量,Compose **会显式失败**,而不是以不安全的默认值启动。
### 2.4 启动数据库和缓存
```
docker compose up -d secure-database redis
```
确认容器是否健康:
```
docker compose ps
```
预期:`pdc-secure-db` 和 `pdc-redis` 的状态为 `healthy`。如果失败:
```
docker compose logs secure-database
```
PostgreSQL 启动时带有 `pg_stat_statements`、`log_statement=all` 和 `log_lock_waits=on` — 这些检测机制为 Query Inspector 提供了真实的执行计划和统计信息。
### 2.5 应用 migrations
```
dotnet run --project tools/PortalDoCorretor.DbMigrator -- migrate
```
按顺序应用9个 migrations:类型和 domains → 身份和经纪公司 → 客户和资产 →
产品和报价 → 提案和保单 → 计费、佣金和理赔 → 审计、
Outbox 和分区 → RLS 和权限 → 监管视图和完整性检查。
要回滚整个链条:
```
dotnet run --project tools/PortalDoCorretor.DbMigrator -- rollback
```
### 2.6 加载数据量
```
dotnet run --project tools/PortalDoCorretor.DbMigrator -- seed
```
**确定性**生成(固定 seed):在任何机器上都能重现相同的数据库,这使得不同执行之间的基准测试具有可比性。
| 表 | 数据量 |
|---|---|
| 经纪公司 (tenants) | 8 |
| 经纪人 | 40 |
| 客户 | 25,000 |
| 可保资产 | 38,000 |
| 报价 | 60,000 |
| 提案 | 22,000 |
| 保单 | 14,000 |
| 分期付款 | 84,000 |
| 佣金 | 14,000 |
| 理赔 | 1,800 |
| 审计事件 | 400,000 |
该数据量经过专门设定,以便能够衡量出“有索引”和“无索引”之间的差异。
如果只有几百行数据,任何执行计划都会很快,比较就失去了意义。
要从零开始重建数据库:
```
dotnet run --project tools/PortalDoCorretor.DbMigrator -- reset
```
### 2.7 启动后端
```
dotnet run --project apps/secure-api
```
API 可在 **http://localhost:8080** 访问。
| 地址 | 内容 |
|---|---|
| http://localhost:8080/swagger | 交互式 API 文档 |
| http://localhost:8080/health/live | 存活检测 |
| http://localhost:8080/health/ready | 就绪检测 — 检查数据库、缓存以及已应用的 migrations |
| http://localhost:8080/api/events/stream | Live Processing Console 的 SSE 流 |
在另一个终端中,启动 workers:
```
dotnet run --project apps/workers
```
workers 负责处理 Outbox、检测续保、推进分期付款并执行完整性检查。没有它们 API 仍能继续工作,但 Outbox 中的消息会不断累积 — 顺便说一句,这是一种直接在 Transaction Inspector 中观察该模式实际运行情况的好方法。
### 2.8 启动前端
在另一个终端:
```
cd apps/frontend
```
```
npm install
```
```
npm run dev
```
前端运行在 **http://localhost:5173**,通过 `apps/frontend/.env.development` 指向 `http://localhost:8080`。如果后端使用的是另一个端口,请调整 `VITE_API_BASE_URL`。
### 2.9 访问用户
由 `seed` 创建,仅存在于本地环境中:
| 角色 | 邮箱 | 密码 |
|---|---|---|
| 经纪人 (tenant A) | `ana.souza@corretoraalfa.test` | `Demo@2026!` |
| 经纪人 (tenant A) | `bruno.lima@corretoraalfa.test` | `Demo@2026!` |
| 经纪人 (tenant B) | `carla.dias@corretorabeta.test` | `Demo@2026!` |
| 监管 | `regulador@susep.test` | `Demo@2026!` |
监管角色需要 MFA;TOTP 验证码会打印在 `seed` 的日志中。
### 2.10 可观测性(可选)
```
docker compose --profile observability up -d
```
| 服务 | 地址 |
|---|---|
| Grafana | http://localhost:3000 |
| Prometheus | http://localhost:9090 |
| Tempo (traces) | 通过 Grafana |
| Loki (logs) | 通过 Grafana |
### 2.11 安全实验室
```
docker compose --profile security-lab up --build
```
启动第二个 API 和第二个数据库,故意不设约束、索引、RLS 和审计,以便与安全实现进行并排比较。它运行在 Docker 网络 `internal: true` 中,没有外部路由,带有 CPU 和内存限制以及自动重置。
当该 profile 激活时,可在 http://localhost:5173/labs/security 访问。
### 2.12 全部容器化
```
docker compose up --build
```
启动数据库、缓存、API、workers 和前端。最接近生产环境;在开发过程中,为了使用 *hot reload*,更推荐使用 `dotnet run` + `npm run dev`。
### 2.13 快速验证
1. 以 `ana.souza` 登录并注册一名客户 — 观察 Value Objects 的验证过程。
2. 在另一个标签页中打开 **Live Processing Console**。
3. 创建报价,转换为提案并签发保单。
4. 在控制台中浏览签发过程的 **24个步骤**;点击任意一步即可查看层、类、方法、前后状态、查询、索引和持续时间。
5. 打开 **Query Inspector** 并查看加载该聚合的查询的真实 `EXPLAIN (ANALYZE, BUFFERS)`。
6. 复制保单 ID,以 `carla.dias`(另一个 tenant)身份登录并尝试访问它 — 返回 `404`,同时会记录一条安全事件。
### 2.14 常见问题
| 症状 | 原因 | 解决方案 |
|---|---|---|
| Compose 因未定义变量而失败 | 缺少 `.env` | 重新执行步骤 [2.3](#23-configurar-variáveis-e-segredos-locais) |
| 5432 端口被占用 | 机器上已安装 PostgreSQL | 停止本地服务或更改 `docker-compose.yml` 中的端口 |
| `/health/ready` 返回 503 | 未应用 migrations | 执行步骤 [2.5](#25-aplicar-migrations) |
| 前端出现 CORS 错误 | 后端使用了不同的端口 | 调整 `VITE_API_BASE_URL` |
| 查询未报错但返回空 | RLS 生效但无 tenant 上下文 | 正确行为 — 没有 `SET LOCAL app.tenant_id` 策略就会拒绝。可在 Live Processing Console 中查看 |
| 集成测试失败 | Docker 未运行 | Testcontainers 需要正在运行的 Docker |
## 3. 仓库结构
```
0009098/
├── apps/
│ ├── frontend/ React · TypeScript · Vite · design system próprio
│ ├── secure-api/ Host ASP.NET Core do monólito modular
│ ├── vulnerable-api/ Contraparte para comparação (profile security-lab)
│ ├── attack-simulator/ 18 cenários executados contra as duas APIs
│ ├── ai-agent-service/ Runtime dos agentes, com guardrails
│ └── workers/ Outbox Dispatcher · Renewal Scanner · Billing Scheduler
│
├── modules/ Um projeto por bounded context
│ ├── identity/ brokers/ customers/ products/
│ ├── quotations/ proposals/ policies/ billing/
│ ├── commissions/ claims/ documents/ notifications/
│ ├── regulatory/ auditing/ observability/ ai/
│ │
│ └── React · Vite · :5173"] end subgraph app["Rede: pdc-app (interna)"] API["secure-api
ASP.NET Core 9 · :8080"] AI["ai-agent-service"] WRK["workers
Outbox · Renewal · Billing"] end subgraph dados["Rede: pdc-data (sem saída externa)"] PG[("secure-database
PostgreSQL 16 · :5432")] RD[("redis · :6379")] end subgraph lab["Rede: pdc-lab (internal · profile security-lab)"] VAPI["vulnerable-api"] VPG[("vulnerable-database")] ATK["attack-simulator"] end subgraph obs["Rede: pdc-observability"] OTEL["otel-collector"] --> PROM["prometheus"] & LOKI["loki"] & TEMPO["tempo"] GRAF["grafana · :3000"] --> PROM & LOKI & TEMPO end U --> FE -->|"REST + SSE"| API API --> PG & RD & AI WRK --> PG API & AI & WRK -->|OTLP| OTEL ATK --> VAPI --> VPG ATK -->|"replica o mesmo teste"| API classDef labc fill:#F2A93B,stroke:#B87A18,color:#141821 classDef sec fill:#1F6FEB,stroke:#0B2447,color:#fff class VAPI,VPG,ATK labc class API,PG sec ``` ### 模块内的层 ``` Infrastructure → Application → Domain │ ▲ └──────── implementa portas ─────┘ ``` 依赖规则向内指向。领域不认识任何人 — 这使得我们能够在没有数据库、没有 HTTP 且不 mock 框架的情况下测试所有业务逻辑。 ### Bounded Contexts | 分类 | 上下文 | |---|---| | **核心 (Core)** | 报价 · 提案 · 保单 佣金 | | **支撑** | 客户管理 · 经纪人管理 · 产品目录 · 理赔 · 计费 · 监管监督 | | **通用** | 身份与访问 · 文档 · 通知 · 审计与合规 · 可观测性 · AI | [完整的上下文映射](docs/architecture/bounded-contexts.md) ### 技术栈 | 层 | 选型 | 理由 | |---|---|---| | **后端** | .NET 9, ASP.NET Core (Minimal API), EF Core, Dapper, FluentValidation, Serilog, OpenTelemetry, Polly | 类型足够强大以表达 Value Objects 和聚合;EF Core 提供 *owned types*、全局 query filters 和原生 `xmin`;Dapper 用于分析型读取,在此场景下 ORM 价值不大 | | **前端** | React, TypeScript, Vite, Tailwind, shadcn/ui, TanStack Query, React Hook Form + Zod, Storybook, Cytoscape.js, Monaco | 端到端类型化;shadcn 组件保留在仓库中,因此设计系统是量身定制的;Cytoscape 用于 Database Explorer 的图形展示;Monaco 用于 SQL 和执行计划 | | **数据** | PostgreSQL 16, Redis | 在[第6节](#6-banco-objeto-relacional)详述 | | **消息传递** | 无 — 使用 PostgreSQL 中的 Outbox | 没有外部 Broker 能在没有 2PC 的情况下提供事务保证 ([ADR-0007](docs/adr/0007-sem-message-broker.md)) | | **测试** | xUnit, FluentAssertions, FsCheck, Testcontainers, Respawn, NetArchTest | 集成测试中使用真实的 PostgreSQL:RLS、`EXCLUDE`、复合类型和 `xmin` 在内存数据库中不存在 | | **基础设施** | Docker Compose, GitHub Actions, Prometheus, Grafana, Loki, Tempo | 通过一条命令提供完整的环境 | [被放弃的替代方案和权衡](docs/architecture/overview.md) ## 5. 领域模型 丰富的领域模型:规则存在于实体、聚合或领域服务中 — 而不是在 controller 或 repository 中。 ### 聚合 ``` Customer (root, abstrato) Quotation (root) ├── Contacts ├── Items (um por plano) ├── Addresses ├── RiskProfile ├── Consents [append-only] ├── SelectedCoverages └── InsurableAssets [polimórfica] └── CalculationSnapshots [imutáveis] Proposal (root) Policy (root) ├── Documents ├── Coverages [congeladas na emissão] ├── Pendencies ├── Endorsements [versionamento] ├── UnderwritingDecision [imutável] ├── Renewals └── StatusHistory [append-only] └── → InstallmentPlanId, CommissionId Claim (root) ├── Events [append-only] ├── Documents ├── Damages └── StatusHistory [append-only] ``` ### 继承与多态 ``` classDiagram class Customer { <
tenant do
JWT assinado"] L2["2. Contexto
imutável na
requisição"] L3["3. Query filter
global do
ORM"] L4["4. Autorização
por recurso
RBAC + ABAC"] L5["5. RLS
FORCE ROW
LEVEL SECURITY"] classDef l fill:#DCE9FD,stroke:#1F6FEB,color:#0B2447 class L1,L2,L3,L4,L5 l ``` 第1层由**类型系统**保证:Value Object `TenantId` 没有接受用户输入的公共构造函数。 ``` public readonly record struct TenantId { public Guid Value { get; } private TenantId(Guid value) => Value = value; // Única origem: claim autenticado ou leitura do banco. public static TenantId FromTrustedSource(Guid value) => ...; } ``` 请求 DTO 无法生成有效的 `TenantId` — 通过 payload 的篡改被类型系统所阻止,而不是依赖于可能会被遗忘的验证。如果有公共重载被添加,架构测试将会使构建失败。 第5层使用 `FORCE ROW LEVEL SECURITY`:如果没有它,表的所有者用户将绕过策略。 ``` ALTER TABLE customers ENABLE ROW LEVEL SECURITY; ALTER TABLE customers FORCE ROW LEVEL SECURITY; ``` 除了 tenant 之外,`commissions` 还有基于 `broker_id` 的**严格**策略:即使是在同一个 tenant 内,一个经纪人也无法访问另一个经纪人的佣金。 [ADR-0004](docs/adr/0004-defesa-em-profundidade-multitenant.md) ### 敏感数据处理 ``` // ToString() retorna a forma mascarada — interpolação acidental em log não expõe o dado DocumentNumber.Parse("52998224725").ToString() // "***.***.247-**" // A exceção não ecoa o valor recebido DocumentNumber.Parse("12345678901") // DomainException: "Documento inválido." // Busca por HMAC com pepper mantido fora do banco document.SearchHash(pepper) ``` 文档在静止状态下使用由外部密钥通过会话上下文提供的密钥进行加密 — 当密钥不存在时,解密功能会安全失效(关闭)。 ### 数据库权限 | 角色 | 权限 | |---|---| | `nexus_migrator` | DDL,仅供 migrations 使用 | | `app_user` | tenant 内的 DML;无 DDL,无 `DELETE`,无 `BYPASSRLS` | | `app_worker` | Outbox 和 jobs,范围受限 | | `app_regulator` | 仅对脱敏视图具有 `SELECT` 权限;无权访问基础表 | 物理 `DELETE` 已从应用程序中撤销 — 删除是逻辑上的,需要强制填写原因,并且级联由聚合处理。 ### 安全测试面 `attack-simulator` 针对易受攻击的 API 执行18个场景(SQL Injection、IDOR、broken access control、tenant 篡改、mass assignment、race condition、重复签发、不安全的文件上传等),并针对安全实现复制每一个场景,记录所起作用的控制措施、生成的 `SecurityEvent` 以及到 CWE、OWASP 和 ASVS 的映射。 易受攻击的环境由 Docker profile 隔离,位于 `internal: true` 网络中,具有自动重置和资源限制。([ADR-0009](docs/adr/0009-laboratorio-vulneravel-isolado.md)) ## 8. 可观测性 端到端的 OpenTelemetry(traces、metrics、logs),通过 OTel Collector 发送到 Prometheus、Loki 和 Tempo,并在 Grafana 中进行可视化。Correlation ID 从前端传播到数据库。 | 分类 | 指标 | |---|---| | **业务** | 已签发的保单、已批准的提案、已计算的佣金、领域事件 | | **性能** | 查询延迟(平均值、p95、p99)、每次操作的查询数、检测到的 N+1、顺序扫描、缓存命中/未命中、事务时间、锁、死锁、吞吐量 | | **完整性** | `constraint_violations_total`、`optimistic_lock_conflicts_total`、`outbox_pending_age_seconds`、`audit_coverage_ratio`、`tenant_violation_attempts_total`、`integrity_check_failures_total` | 函数 `app.run_integrity_checks()` 对数据库执行10个 SQL 断言 — 分期付款总额、缺少承保范围的保单、存在差异的保费、按提案重复的保单、缺少规则的佣金、缺少子类型的资产、有效期之外的理赔、缺少联系方式的客户、卡住的 Outbox 以及缺少对应审计的签发。如果模型正确,所有查询都将返回零。 ## 9. 测试 ``` dotnet test ``` ``` dotnet test tests/unit # sem dependência de Docker ``` ``` dotnet test tests/integration # requer Docker (Testcontainers) ``` ``` dotnet test tests/architecture # fronteiras de módulo e regras de modelagem ``` | 类型 | 范围 | |---|---| | 单元测试 | Value Objects、聚合、不变量、领域服务、状态机 | | 属性测试 | 针对财务和分配不变量的 FsCheck | | 集成测试 | 带有真实 PostgreSQL 16 的 Testcontainers + Respawn | | RLS 和隔离 | 逐层打破隔离,验证其他层是否正常拦截 | | 授权 | RBAC、ABAC、作用域、意图 | | 并发 | 并发签发、乐观锁、`SKIP LOCKED` | | 幂等性与 Outbox | 重放、至少一次交付、幂等消费 | | 回滚 | 在事务的每个步骤中注入失败 | | 架构 | NetArchTest | | 性能 | BenchmarkDotNet + k6 | | E2E | Playwright | | 安全 | 18个自动化场景 | 集成测试中不使用内存数据库:RLS、排他约束、复合类型、部分索引和 `xmin` 在 SQLite 中不存在。 ### 工程笔记 — 通过属性测试发现的 bug `Money.Allocate` 的第一个实现将所有舍入残差加到了首期付款上。总和保持正确,示例测试(`R$ 1.000,00 ÷ 3`)也通过了。但是对于 `R$ ,05 ÷ 12`,结果是一期 `R$ 0,05` 和十一期 `R$ 0,00`。 *“对于任何金额和任意分期数,总和精确且最大误差 ≤ 1分钱”* 这一属性在针对生成的500个用例的测试中不到一秒钟就失败了。已通过将一分钱按顺序分配给每一期(最大余额法)进行了修复。 ## 10. 工程工具 | 工具 | 功能 | |---|---| | **Live Processing Console** | 通过 SSE 实现实时事件,具有14个过滤器和16个类别,自动对敏感数据进行脱敏 | | **Database Explorer** | 从真实 catalog 读取的可导航图表:表、关系、基数、ORM 映射、索引、约束、RLS 策略、分区 | | **Query Inspector** | 已执行的 SQL、经过掩码处理的参数、时间、行数、`EXPLAIN (ANALYZE, BUFFERS)`、使用的索引、扫描类型、代码来源 | | **Transaction Inspector** | 持续时间、隔离级别、锁、`COMMIT`/`ROLLBACK`、事件、Outbox、审计 | | **Data Browser** | 具有类型化过滤器和 FK 导航的交互式数据查询。无自由 SQL:过滤器由服务器根据白名单转换为参数化查询 | | **Engineering Lab** | 实测对比:ORM 对比 Dapper、有无索引、N+1 对比投影、延迟加载对比贪婪加载、分页对比非分页 | | **Security Lab** | 针对两种实现执行的18个场景 | 显示的性能数据来自于 `EXPLAIN (ANALYZE, BUFFERS)` 以及在本地环境中的测量结果,发布时附带了机器规格、PostgreSQL 版本和数据量。 ## 11. 架构决策 (ADRs) | ADR | 决策 | |---|---| | [0001](docs/adr/0001-nome-e-identidade-do-produto.md) | 名称与视觉识别 | | [0002](docs/adr/0002-monolito-modular.md) | 选择模块化单体而非微服务 | | [0003](docs/adr/0003-postgresql-como-banco-objeto-relacional.md) | 将 PostgreSQL 作为对象关系数据库 | | [0004](docs/adr/0004-defesa-em-profundidade-multitenant.md) | 五层多租户隔离 | | [0005](docs/adr/0005-estrategia-de-heranca-tph-e-tpt.md) | `Customer` 使用 TPH,`InsurableAsset` 使用 TPT | | [0006](docs/adr/0006-outbox-transacional.md) | PostgreSQL 中的事务性 Outbox | | [0007](docs/adr/0007-sem-message-broker.md) | 无外部 message broker | | [0008](docs/adr/0008-cqrs-seletivo.md) | 选择性 CQRS,无事件溯源 | | [0009](docs/adr/0009-laboratorio-vulneravel-isolado.md) | 由 profile 隔离的对比环境 | | [0010](docs/adr/0010-governanca-de-agentes-de-ia.md) | 具有最小权限的 AI Agent | ### 技术文档 | 文档 | 内容 | |---|---| | [需求](docs/architecture/requirements.md) | 带有验收标准的功能和非功能性需求 | | [用例](docs/architecture/use-cases.md) | 各角色的主流程和备用流程 | | [Bounded Contexts](docs/architecture/bounded-contexts.md) | 16个上下文及上下文映射 | | [架构](docs/architecture/overview.md) | 风格、C4、技术栈、权衡 | | [领域模型](docs/domain/domain-model.md) | 类、继承、多态、specifications | | [聚合](docs/domain/aggregates.md) | 不变量、事务边界、并发 | | [Value Objects](docs/domain/value-objects.md) | 验证规则与持久化策略 | | [物理模型](docs/database/physical-model.md) | 表、约束、索引、RLS、分区 | | [ER 图](docs/database/er-diagram.md) | 完整 ER 图及不变量 → 约束映射 | | [仓库结构](docs/plan/repository-structure.md) | 布局与依赖规则 | | [实施计划](docs/plan/implementation-plan.md) | 阶段、风险、缓解措施 | | [第2阶段报告](docs/plan/phase-02-report.md) | 交付物与验证 |
[文档](docs/) · [ADRs](docs/adr/) · [MIT 许可证](LICENSE)
标签:Clean Architecture, DDD, .NET 9, PostgreSQL, React 19, 企业级管理平台, 保险经纪系统, 搜索引擎查询, 用户代理, 请求拦截