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

20 KiB
Raw Blame History

Fonrey 项目骨架搭建实施报告 — v2

版本v2.0 报告日期2026-04-30 实施范围Phase 1 配置 → Phase 2 数据模型 → Phase 3 前端/Docker 脚手架 → Phase 4.0 模型 verbose_namePhase 4.1 字段 verbose_name/help_textPhase 5 PermissionDef 拆分 + 154 条 seed + 7 内置角色 + 矩阵 + Lookup 默认值 + 租户自动 seed 实施依据prompt/提示词模板/创建项目骨架提示词_v2.3.mdPRD/权限管理/PERMISSION_SEED_MVP_BATCH1.mdPRD/权限管理/权限管理模块PRD.md §5.5.2PRD/权限管理/角色权限矩阵.mdDATA_MODEL/DATA_MODEL_SETTING.md §2.3 项目根目录/mnt/c/project/fonrey/ Git HEADaaf3981main 分支,已 push 到 origin/main v1 → v2 增量14 个 commit79c3cf2aaf3981),覆盖 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 3638fc08faa68b9 commit

为全部 781 个字段补齐中文 verbose_namehelp_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_deflabel fonrey_permission_def),保留表名 permission_defsfonrey_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_*.md9 个模块文件 + 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 checkmakemigrations --dry-run 双双干净。

3.3 受影响模型

74 个具体模型全部覆盖(不含抽象基类 UUIDPrimaryKeyModel / TimeStampedModel / SoftDeleteModel / AuditedModel)。


四、Phase 5 实施细节

4.1 PermissionDef 拆 SHARED 的动机与实现

问题v1 阶段 PermissionDef 在 apps.permissionTENANT_APPS。这意味着每个新租户都要复制全部 154 条权限定义;权限矩阵升级时需要在每个 schema 重复 migrate管理员视角无法对全局权限做统一编辑。

方案:拆出独立 SHARED app apps.permission_deflabel fonrey_permission_def),保留物理表名 permission_defs(避免 RENAME。所有租户的 Role.permission_def / StaffPermissionOverride.permission_def FK 跨 schema 指向 public.permission_defsdjango-tenants 默认 search_path 包含 publicFK 自然解析)。

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 既 AlterFieldFK 字符串切换Django state 一致性)也 DeleteModel(PermissionDef)(清理 tenant schema 中本不应存在的孤儿表)
  3. permission_def/0002_seed_permission_defs:在 public schema bulk_create 154 条

SHARED_APPS 顺序config/settings/base.py

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.py2358 行)

实现:

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 enumorg.* 代码 → module="hr"PRD 组织人事即 HR 模块);complex.* 代码 → module="property"(小区是房源子集)。

4.3 7 个内置角色 + 154×7 矩阵 service

文件:apps/permission/services/seed_default_roles.py218 行)

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 个 RolePermissionbulk_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.py113 行)

依据 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.py36 行)

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 项目偏好,便于 --reverseapps.get_model 历史模型
角色 + 矩阵 + Lookup 用 service 函数而非 data migration 三者写入 tenant schemaschema_contextmigration 只走 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/main5dedd19..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_consoleSHARED
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_consoleSHARED + 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_taskproperty_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 时点验证命令

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