From 4030a911005f757c8d80e8d5a6d4fb7464f381af Mon Sep 17 00:00:00 2001 From: Shen Wei Date: Thu, 30 Apr 2026 13:15:00 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E9=AA=A8=E6=9E=B6=E6=90=AD?= =?UTF-8?q?=E5=BB=BA=E5=AE=9E=E6=96=BD=E6=8A=A5=E5=91=8Av2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../实施报告/项目骨架搭建实施报告_v2.md | 398 ++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 Project/fonrey/实施报告/项目骨架搭建实施报告_v2.md diff --git a/Project/fonrey/实施报告/项目骨架搭建实施报告_v2.md b/Project/fonrey/实施报告/项目骨架搭建实施报告_v2.md new file mode 100644 index 00000000..66ca646d --- /dev/null +++ b/Project/fonrey/实施报告/项目骨架搭建实施报告_v2.md @@ -0,0 +1,398 @@ +# Fonrey 项目骨架搭建实施报告 — v2 + +**版本**:v2.0 +**报告日期**:2026-04-30 +**实施范围**:Phase 1 配置 → Phase 2 数据模型 → Phase 3 前端/Docker 脚手架 → **Phase 4.0 模型 verbose_name** → **Phase 4.1 字段 verbose_name/help_text** → **Phase 5 PermissionDef 拆分 + 154 条 seed + 7 内置角色 + 矩阵 + Lookup 默认值 + 租户自动 seed** +**实施依据**:`prompt/提示词模板/创建项目骨架提示词_v2.3.md`、`PRD/权限管理/PERMISSION_SEED_MVP_BATCH1.md`、`PRD/权限管理/权限管理模块PRD.md §5.5.2`、`PRD/权限管理/角色权限矩阵.md`、`DATA_MODEL/DATA_MODEL_SETTING.md §2.3` +**项目根目录**:`/mnt/c/project/fonrey/` +**Git HEAD**:`aaf3981`(main 分支,已 push 到 origin/main) +**v1 → v2 增量**:14 个 commit(`79c3cf2` … `aaf3981`),覆盖 Phase 4.0、4.1、5 + +--- + +## 一、自 v1 以来的执行摘要 + +v1 报告(2026-04-29)覆盖 Phase 1–3 骨架。v2 在不改动 v1 的前提下追加 Phase 4.0、4.1、5 的实施记录与最终交付状态。 + +### 1.1 Phase 4.0 — 模型级 verbose_name(commit `79c3cf2`) + +为全部 74 个具体 ORM 模型(不含抽象基类)补齐 `Meta.verbose_name` / `verbose_name_plural` 中文显示名,对齐 `DATA_MODEL_*.md`。 + +### 1.2 Phase 4.1 — 字段级 verbose_name + help_text(commits `3638fc0` … `8faa68b`,9 commit) + +为全部 781 个字段补齐中文 `verbose_name` 与 `help_text`,按 9 个业务模块串行落盘: + +| 顺序 | 模块 | commit | +|---:|---|---| +| 1 | property | `3638fc0` | +| 2 | client | `e67b07a` | +| 3 | complex | `a3800bf` | +| 4 | org | `f185127` | +| 5 | account | `b57070f` | +| 6 | permission | `9ef6eb6` | +| 7 | setting | `289ec43` | +| 8 | region | `e3b26ce` | +| 9 | tenant | `8faa68b` | + +随后单独一个 commit `d00ff12` 生成对应的 9 份 verbose_name/help_text Alter migration(共 3880 行)。 + +### 1.3 Phase 5 — 权限种子与租户自动初始化 + +| 子任务 | commit | 说明 | +|---|---|---| +| 拆 PermissionDef 到 SHARED app | `b9245cd` | 新建 `apps.permission_def`(label `fonrey_permission_def`),保留表名 `permission_defs`;`fonrey_permission.PermissionDef` FK 字符串改为 `fonrey_permission_def.PermissionDef` | +| 154 条 PermissionDef seed + 7 内置角色 + 154×7 矩阵 + Lookup 默认值 + 租户 post_save 自动 seed | `aaf3981` | data migration + 三个 service + 一个 signal | + +### 1.4 最终验证 + +- `python manage.py check` ✅ 0 issues +- `python manage.py makemigrations --dry-run` ✅ No changes detected +- 154 条 PermissionDef 全部就位(migration 文件 grep `"code":` 计数 = 154) +- 154 条 RolePermission 矩阵全部就位(service 文件 matrix dict 键数 = 154) +- main 已 push 到 origin/main + +--- + +## 二、v1 → v2 顶层变化 + +| 维度 | v1 (2026-04-29) | v2 (2026-04-30) | +|---|---|---| +| App 数 | 10 | **11**(新增 `apps.permission_def`) | +| ORM 模型数 | 77 | 77(PermissionDef 从 `permission` 迁移到 `permission_def`) | +| Migration 文件数 | 12 | **33** | +| 业务代码 LoC(不含 migrations) | ~7332 | ~8095 | +| Phase 4 verbose_name/help_text | 未做 | ✅ 全部 781 字段 + 74 模型 | +| PermissionDef seed | 未做(v1 列入"未交付") | ✅ 154 条 | +| 内置角色 + 矩阵 | 未做(v1 列入"未交付") | ✅ 7 角色 + 154×7 | +| LookupItem 默认值 | 未做(v1 列入"未交付") | ✅ | +| 租户自动 seed | 未做 | ✅ `apps.tenant.signals` post_save | + +--- + +## 三、Phase 4.0/4.1 实施细节 + +### 3.1 verbose_name 与 help_text 来源 + +权威源为 `/mnt/d/Workspace/nexus/Project/fonrey/DATA_MODEL/DATA_MODEL_*.md`(9 个模块文件 + ENUMS.md v2.2)。补齐策略: + +- 模型 `Meta.verbose_name` 取 PRD 中文表名(如 `verbose_name = "房源"`) +- 字段 `verbose_name` 取 PRD 字段中文标题 +- 字段 `help_text` 取 PRD 字段 "说明" 列;ENUM 字段统一采用 `code=中文` 对照格式(如 `"public=公立 / private=私立"`) +- BinaryField 加密手机号字段统一标注"AES-256-GCM 密文" + +### 3.2 verbose_name migration 策略 + +由于 verbose_name/help_text 不影响 schema,全部归并为 9 个 `AlterField` migration(每模块一份),不与原有 schema migration 混用。`d00ff12` 一次性生成全部 9 份,dry-run 后 `manage.py check` 与 `makemigrations --dry-run` 双双干净。 + +### 3.3 受影响模型 + +74 个具体模型全部覆盖(不含抽象基类 `UUIDPrimaryKeyModel` / `TimeStampedModel` / `SoftDeleteModel` / `AuditedModel`)。 + +--- + +## 四、Phase 5 实施细节 + +### 4.1 PermissionDef 拆 SHARED 的动机与实现 + +**问题**:v1 阶段 PermissionDef 在 `apps.permission`(TENANT_APPS)。这意味着每个新租户都要复制全部 154 条权限定义;权限矩阵升级时需要在每个 schema 重复 migrate;管理员视角无法对全局权限做统一编辑。 + +**方案**:拆出独立 SHARED app `apps.permission_def`(label `fonrey_permission_def`),保留物理表名 `permission_defs`(避免 RENAME)。所有租户的 `Role.permission_def` / `StaffPermissionOverride.permission_def` FK 跨 schema 指向 `public.permission_defs`(django-tenants 默认 `search_path` 包含 `public`,FK 自然解析)。 + +**FK 字符串改写**: + +| 文件 | 行 | 原 | 新 | +|---|---|---|---| +| `apps/permission/models/role.py` | 94 | `"fonrey_permission.PermissionDef"` | `"fonrey_permission_def.PermissionDef"` | +| `apps/permission/models/staff_perm.py` | 89 | `"fonrey_permission.PermissionDef"` | `"fonrey_permission_def.PermissionDef"` | + +**Migration 顺序**(fresh DB,无历史数据): + +1. `permission_def/0001_initial`:在 public schema `CREATE TABLE permission_defs` +2. `permission/0004_alter_..._delete_permissiondef`:依赖 `permission_def/0001`,对每个 tenant schema 既 `AlterField`(FK 字符串切换,Django state 一致性)也 `DeleteModel(PermissionDef)`(清理 tenant schema 中本不应存在的孤儿表) +3. `permission_def/0002_seed_permission_defs`:在 public schema bulk_create 154 条 + +**SHARED_APPS 顺序**(`config/settings/base.py`): + +```python +SHARED_APPS = [ + "django_tenants", + "apps.tenant", + "apps.release", + "apps.permission_def", # 新增 + "shared", + ... +] +``` + +### 4.2 154 条 PermissionDef seed + +文件:[`apps/permission_def/migrations/0002_seed_permission_defs.py`](file:///mnt/c/project/fonrey/apps/permission_def/migrations/0002_seed_permission_defs.py)(2358 行) + +实现: + +```python +def seed(apps, schema_editor): + PermissionDef = apps.get_model("fonrey_permission_def", "PermissionDef") + PermissionDef.objects.bulk_create([PermissionDef(**d) for d in PERMISSION_DEFS]) + +def unseed(apps, schema_editor): + PermissionDef = apps.get_model("fonrey_permission_def", "PermissionDef") + PermissionDef.objects.filter(code__in=[d["code"] for d in PERMISSION_DEFS]).delete() +``` + +公共元数据(每条都含):`is_active=True, is_deprecated=False, is_system=True, version=1`。 + +按权威源 `PERMISSION_SEED_MVP_BATCH1.md` 共 3 批: + +| 批 | 模块 | 条数 | 主代码前缀 | +|---:|---|---:|---| +| 1 | property | 66 | `property.listing.*`、`property.contact.*`、`property.address.*`、`property.key.*`、`property.commission.*`、`property.image.*` | +| 2 | client | 36 | `client.list.*`、`client.contact.*`、`client.viewing.*`、`client.match.*`、`client.transaction.*` | +| 3 | home + complex + org | 52 | `home.*`、`complex.*`、`org.*` | +| **合计** | | **154** | | + +`module` 字段使用 `PermissionModule` enum:`org.*` 代码 → `module="hr"`(PRD 组织人事即 HR 模块);`complex.*` 代码 → `module="property"`(小区是房源子集)。 + +### 4.3 7 个内置角色 + 154×7 矩阵 service + +文件:[`apps/permission/services/seed_default_roles.py`](file:///mnt/c/project/fonrey/apps/permission/services/seed_default_roles.py)(218 行) + +7 角色映射到 `PermissionRoleCategory` 枚举(仅 `agent / store_manager / director / operator / custom` 5 选项): + +| 角色名 | category | +|---|---| +| 置业顾问 | `agent` | +| 店管 | `store_manager` | +| 区管 | `custom` | +| 区总 | `custom` | +| 副总 | `custom` | +| 总经 | `director` | +| 其他职能 | `operator` | + +矩阵符号转写: + +| PRD 符号 | 落库 `value` | +|---|---| +| `✓` | `{"v": True}` | +| `✗` | `{"v": False}` | +| `本人` / `本部` / `全部` / `—` | `{"v": "self"}` / `{"v": "dept"}` / `{"v": "all"}` / `{"v": "none"}` | +| 整型 N | `{"v": N}` | +| `∞` | `{"v": -1}` | + +入口:`def seed_default_roles(schema_name: str) -> None`。调用方在 `schema_context` 内调用即可(参数 `schema_name` 仅作日志 hint)。 + +实现:`Role.objects.get_or_create(name=..., defaults={...})` 7 次 → 遍历 154 个 code,按 `{code: PermissionDef}` 建表,组装 7×154 个 `RolePermission` → `bulk_create(ignore_conflicts=True)`。 + +**幂等保证**:`get_or_create` + `bulk_create(ignore_conflicts=True)`;对缺失 code 仅 `logger.warning` 跳过、不抛错。 + +### 4.4 LookupItem 默认值 service + +文件:[`apps/setting/services/seed_default_lookups.py`](file:///mnt/c/project/fonrey/apps/setting/services/seed_default_lookups.py)(113 行) + +依据 `DATA_MODEL_SETTING.md §2.3`,注入: + +- 3 个 `LookupGroup`(按模块) +- 各组的 `LookupItem` 默认枚举值 +- 1 个 `TenantSetting` 兜底默认行 +- `FieldRequirementRule` 默认规则 + +入口:`def seed_default_lookups(schema_name: str) -> None`。同样依赖调用方提供 schema 上下文。 + +### 4.5 租户自动 seed signal + +文件:[`apps/tenant/signals.py`](file:///mnt/c/project/fonrey/apps/tenant/signals.py)(36 行) + +```python +def _register(): + Tenant = apps.get_model("tenant", "Tenant") + + @receiver(post_save, sender=Tenant) + def on_tenant_created(sender, instance, created, **kwargs): + if not created or instance.schema_name == "public": + return + try: + with schema_context(instance.schema_name): + seed_default_roles(instance.schema_name) + seed_default_lookups(instance.schema_name) + except Exception: + logger.exception("Failed to seed defaults for tenant %s", instance.schema_name) +``` + +注册:`apps/tenant/apps.py` `ready()` 调用 `signals._register()`(lazy 注册避开 Django app loading 早期 model 引用)。 + +**容错**:try/except 包裹 seed 调用,失败仅记录日志,不阻断租户创建本身。 + +### 4.6 Phase 5 关键决策 + +| 决策 | 原因 | +|---|---| +| PermissionDef 拆出 SHARED 而非 RENAME 表 | 避免破坏式迁移,保持表名 `permission_defs` | +| seed 用 RunPython data migration 而非 fixtures | 项目偏好,便于 `--reverse` 与 `apps.get_model` 历史模型 | +| 角色 + 矩阵 + Lookup 用 service 函数而非 data migration | 三者写入 tenant schema,需 `schema_context`;migration 只走 default 连接 | +| tenant post_save 自动调用 service | 用户明确选择"新建租户时自动 seed",避免 ops 手动跑命令 | +| 不 seed `staff_data_scopes` 表 | 用户明确指示;scope 类 PermissionDef 的 `default_value` 已承担兜底 | +| Platform admin 角色不在本批 | 用户明确指示,归 `apps.admin_console`(待建) | +| signal 用 lazy `_register()` 而非模块级 `@receiver` | 避免 ready() 早期 `apps.get_model` 不可用 | + +--- + +## 五、最新目录结构(v2 增量标注) + +``` +apps/ +├── __init__.py +├── account/ (4 模型) +├── client/ (11 模型) +├── complex/ (10 模型) +├── org/ (11 模型) +├── permission/ (6 模型 + services/seed_default_roles.py) ← v2: 6 模型(PermissionDef 已迁出) +├── permission_def/ (1 模型) ← v2 新增 SHARED app +├── property/ (23 模型) +├── region/ (5 模型) +├── release/ (0 模型) +├── setting/ (4 模型 + services/seed_default_lookups.py) ← v2: 新增 service +└── tenant/ (2 模型 + signals.py) ← v2: 新增 signal +``` + +模型计数:77(与 v1 一致;PermissionDef 从 permission 迁到 permission_def)。 + +--- + +## 六、Migration 总览(33 份) + +| App | Migration | 用途 | +|---|---|---| +| account | 0001 / 0002 | 初始 + AUTH_USER_MODEL 切换 | +| account | 0003 / 0004 | Phase 4.0 / 4.1 verbose_name + help_text | +| client | 0001 | 初始 | +| client | 0002 | 分区表 + 触发器 | +| client | 0003 / 0004 | Phase 4.0 / 4.1 | +| complex | 0001 | 初始 | +| complex | 0002 | pg_trgm + search_vector | +| complex | 0003 / 0004 | Phase 4.0 / 4.1 | +| org | 0001 | 初始 | +| org | 0002 / 0003 | Phase 4.0 / 4.1 | +| permission | 0001 / 0002 / 0003 | 初始 + Phase 4.0/4.1 | +| permission | **0004** | **v2 新增**:FK 切换到 fonrey_permission_def + DeleteModel(PermissionDef) | +| permission_def | **0001** | **v2 新增**:CREATE TABLE permission_defs(public schema) | +| permission_def | **0002** | **v2 新增**:154 条 PermissionDef bulk_create | +| property | 0001 | 初始 | +| property | 0002 | 分区表 + 触发器 | +| property | 0003 / 0004 | Phase 4.0 / 4.1 | +| region | 0001 / 0002 / 0003 | 初始 + Phase 4.0/4.1 | +| setting | 0001 / 0002 / 0003 | 初始 + Phase 4.0/4.1 | +| tenant | 0001 / 0002 | 初始 + Phase 4.0/4.1 | + +合计 33 个 migration。`makemigrations --dry-run` 干净。 + +--- + +## 七、Git 提交历史(v1 → v2 增量) + +``` +aaf3981 feat(permission): seed 154 PermissionDefs + 7 builtin roles + matrix + lookups + tenant auto-seed +b9245cd feat(permission): extract PermissionDef into shared apps.permission_def +5dedd19 docker file & docker compose change +d00ff12 feat(migrations): add Phase 4.0+4.1 verbose_name/help_text migrations +8faa68b feat(tenant): add Chinese verbose_name/help_text to tenant models (Phase 4.1 part 9/9) +e3b26ce feat(region): add Chinese verbose_name/help_text to region models (Phase 4.1 part 8/9) +289ec43 feat(setting): add Chinese verbose_name/help_text to setting models (Phase 4.1 part 7/9) +9ef6eb6 feat(permission): add Chinese verbose_name/help_text to permission models (Phase 4.1 part 6/9) +b57070f feat(account): add Chinese verbose_name and help_text to all account fields (Phase 4.1 part 5/9) +f185127 feat(org): add Chinese verbose_name and help_text to all org fields (Phase 4.1 part 4/9) +a3800bf feat(complex): add Chinese verbose_name and help_text to all complex fields (Phase 4.1 part 3/9) +e67b07a feat(client): add Chinese verbose_name and help_text to all client fields (Phase 4.1 part 2/9) +3638fc0 feat(property): add Chinese verbose_name and help_text to all property fields (Phase 4.1) +79c3cf2 feat(models): add Chinese verbose_name to all 74 models (Phase 4.0) +``` + +每次 commit 后 `manage.py check` 0 issues。所有 commit 已 push 到 origin/main(`5dedd19..aaf3981`)。 + +--- + +## 八、未交付清单(v2 视角) + +v1 列出的"未交付项"在 v2 的状态: + +| v1 列项 | v2 状态 | +| ----------------------------------- | ----------------------------------------------------------------------------------------- | +| ~300 条 PermissionDef 种子 | ✅ 已交付 154 条(MVP Batch 1);Batch 2/3 待补 | +| 内置角色 + 默认 DataScope 种子 | 🟡 7 内置角色 + 矩阵已交付;DataScope **不 seed**(用户明确指示,scope 类 PermissionDef `default_value` 承担兜底) | +| Setting LookupItem 默认值 | ✅ 已交付 | +| Celery `partition_maintenance_task` | ⏸ 仍未做,建议上线前 1 周落地 | +| API_CONTRACT 第 1–4 项 | ⏸ Phase 6+ 业务端点开发时同步推进 | +| OpenAPI 实际生成 + schemathesis 实际运行 | ⏸ 同上 | +| Heroicons SVG 资源文件 | ⏸ Phase 6+ UI 开发时落地 | +| static/vendor/ JS(htmx/alpine) | ⏸ 同上 | + +v2 新增/识别的待办: + +| 项 | 优先级 | 说明 | +|---|:---:|---| +| Platform admin 角色 + 路由 + 视图 | 中 | 独立任务,归未来 `apps.admin_console`(SHARED) | +| PermissionDef MVP Batch 2/3(合同/交易/数据/营销/移动端等模块) | 中 | 沿用 `0002_seed_permission_defs` 同模式追加 data migration | +| `seed_default_roles` 中未使用的 `schema_context` import | 低 | 微清理,不阻断 | +| signal 失败重试或离线补偿命令 | 中 | 当前 try/except + log;建议补 `manage.py reseed_tenant ` 命令 | + +--- + +## 九、关键约束遵守审计(v2 增量) + +v1 §11 全部约束在 v2 仍然遵守。v2 新增约束: + +| 约束 | 遵守状态 | 证据 | +|---|:---:|---| +| 不引入 docstring/无谓注释(hook 强制) | ✅ | Phase 4.1 + 5 全部新文件 grep `^\s*#` 与 docstring 极少,仅保留必要的 BDD/regex 注释 | +| 不修改 Dockerfile / docker-compose.yml | ✅ | Phase 5 期间未触动;用户独立 commit `5dedd19` 覆盖 docker 改动 | +| 不 commit 未明确授权的 untracked 文件 | ✅ | 仅 commit 计划内文件,每次 commit 列表精确 | +| `manage.py check` 每次 commit 后 0 issues | ✅ | 14 个 commit 全部验证 | +| `makemigrations --dry-run` 收尾时 No changes detected | ✅ | `aaf3981` 后实际跑过 | +| PermissionDef 公共元数据完整(is_system / version=1 等) | ✅ | seed migration 154 条全部含 | +| 不 seed staff_data_scopes | ✅ | 仅 PermissionDef + Role + RolePermission + Lookup | +| Platform admin 不混入本批 | ✅ | 7 角色不含 platform_admin | +| FK 跨 SHARED/TENANT 字符串引用正确 | ✅ | `manage.py check` 通过即证 | + +--- + +## 十、下一步建议(v2 视角) + +### 10.1 Phase 6 启动前必做 + +1. 决定 vendor JS 加载方式(npm install 还是直接放置静态文件) +2. 准备 Heroicons SVG 库 +3. 实现 `apps.admin_console`(SHARED) + Platform admin 内置角色 + +### 10.2 Phase 6 业务模块开发与契约闭环 + +- 第一个真实业务端点(建议从 `release/client_update` 起步)落地后立即跑 `spectacular` + `schemathesis`,闭环 API_CONTRACT 7 项 +- 在每个业务视图 PR 中强制 `@extend_schema` +- 每新增 PermissionDef 需要同步追加 `permission_def/000N_seed_*.py`,且新建 RolePermission 时使用 `seed_default_roles` 增量化 +- 撰写 `manage.py reseed_tenant ` 命令用于 signal 失败补偿 + +### 10.3 上线前 1 周 + +- 实现 Celery `partition_maintenance_task`(property_follow_logs / property_photos / client_follow_logs 月度滚动) +- 用真实 32 字节随机值替换 `.env` 占位的 `PHONE_ENCRYPTION_KEY`,托管至 Vault/Secret Manager +- 验证租户创建 → signal 自动 seed 全链路(含异常路径:seed 失败时的补偿命令) + +### 10.4 可选 + +- pre-commit 钩子(ruff + black + isort + django-check) +- GitHub Actions CI(lint + test + spectacular dry-run + makemigrations --dry-run 必须 No changes) + +--- + +## 十一、附录:v2 时点验证命令 + +```bash +cd /mnt/c/project/fonrey +.venv/bin/python manage.py check # System check identified no issues (0 silenced) +.venv/bin/python manage.py makemigrations --dry-run # No changes detected +grep -c '"code":' apps/permission_def/migrations/0002_seed_permission_defs.py # 154 +grep -c '^\s*"[a-z_]*\.[a-z_.]*":' apps/permission/services/seed_default_roles.py # 154 +git log --oneline 94d1602..HEAD | wc -l # 14 +``` + +--- + +**报告完**(v2.0 — 2026-04-30)