Files
nexus/Project/fonrey/实施报告/项目骨架搭建实施报告_v2.md
2026-04-30 13:15:00 +08:00

399 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 13 骨架。v2 在不改动 v1 的前提下追加 Phase 4.0、4.1、5 的实施记录与最终交付状态。
### 1.1 Phase 4.0 — 模型级 verbose_namecommit `79c3cf2`
为全部 74 个具体 ORM 模型(不含抽象基类)补齐 `Meta.verbose_name` / `verbose_name_plural` 中文显示名,对齐 `DATA_MODEL_*.md`
### 1.2 Phase 4.1 — 字段级 verbose_name + help_textcommits `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 | 77PermissionDef 从 `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_defspublic 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 1Batch 2/3 待补 |
| 内置角色 + 默认 DataScope 种子 | 🟡 7 内置角色 + 矩阵已交付DataScope **不 seed**用户明确指示scope 类 PermissionDef `default_value` 承担兜底) |
| Setting LookupItem 默认值 | ✅ 已交付 |
| Celery `partition_maintenance_task` | ⏸ 仍未做,建议上线前 1 周落地 |
| API_CONTRACT 第 14 项 | ⏸ Phase 6+ 业务端点开发时同步推进 |
| OpenAPI 实际生成 + schemathesis 实际运行 | ⏸ 同上 |
| Heroicons SVG 资源文件 | ⏸ Phase 6+ UI 开发时落地 |
| static/vendor/ JShtmx/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 <schema>` 命令 |
---
## 九、关键约束遵守审计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 <schema>` 命令用于 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 CIlint + 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