# 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)