# Fonrey 项目骨架搭建 — 实施报告 **版本**:v1.0 **报告日期**:2026-04-29 **实施范围**:项目骨架(Phase 1 配置 → Phase 2 数据模型 → Phase 3 前端/Docker 脚手架) **实施依据**:`prompt/提示词模板/创建项目骨架提示词_v2.3.md` **项目根目录**:`/mnt/c/project/fonrey/` **Git HEAD**:`94d1602`(main 分支,working tree clean,领先 origin/main 5 commits) --- ## 一、执行摘要 按 `创建项目骨架提示词_v2.3.md`(903 行)规范,分三阶段完成 Fonrey 多租户房产 SaaS 平台的 Django 项目骨架: - **Phase 1**:Django 配置层(config/、core/、shared/、requirements/、env、manage.py、pyproject)— 已完成。 - **Phase 2**:9 个业务 App 的真实数据模型(依据 `DATA_MODEL_*.md`)— 已完成,77 个 ORM 模型,5 张分区表 + 4 个数据库触发器。 - **Phase 3**:前端模板/静态资源/Docker/Tailwind/Makefile/根级 tests/ — 已完成。 **最终验证**: - `python manage.py check` ✅ 0 issues - `python manage.py check --deploy` ✅ 仅一条 SECRET_KEY 测试值告警(非真实问题) - 顶层目录树与规范 §2 100% 匹配 - 5 个干净的 Git checkpoint commits **未交付(明确延后)**: - ~300 条 PermissionDef 种子数据(fixtures) - 4 个内置角色 + 默认 DataScope 种子 - Setting 模块的 LookupItem 默认值 - Celery `partition_maintenance_task`(每月分区滚动) - API_CONTRACT 7 项契约清单中需要真实业务端点的部分(spectacular OpenAPI 生成 / schemathesis 实际运行) --- ## 二、目录结构对照(规范 §2 vs 实际) ### 顶层结构(100% 匹配) | 规范要求 | 实际状态 | |---|---| | `apps/` (10 个 App) | ✅ tenant, account, permission, org, region, complex, property, client, setting, release | | `core/` | ✅ models/, enums.py, encryption.py, cache.py, htmx.py, templatetags/, middleware/ | | `shared/` | ✅ apps.py | | `config/` | ✅ settings/{base,development,testing,production}.py, urls.py, urls_public.py, asgi.py, wsgi.py | | `templates/` | ✅ base.html, layouts/, components/, errors/ | | `static/` | ✅ css/, js/, vendor/ | | `locale/` | ✅ 占位 | | `requirements/` | ✅ base.txt, development.txt, production.txt | | `tests/` | ✅ conftest.py, integration/, e2e/ | | 根级文件 | ✅ .env, .env.example, .gitignore, manage.py, Dockerfile, docker-compose.yml, docker-compose.prod.yml, Makefile, tailwind.config.js, package.json, pyproject.toml | ### 每个 App 内部结构 **业务 App(property/client/setting/account/permission/org/region/complex)**: ``` apps// ├── __init__.py ├── apps.py ├── admin.py ├── models/__init__.py + 多个模型文件 ├── migrations/ ├── services/__init__.py ├── tasks.py ├── views.py ├── urls.py ├── serializers.py ├── templates// └── tests/__init__.py ``` **release App(共享 schema,无服务层)**: ``` apps/release/ ├── __init__.py ├── apps.py ├── admin.py ├── models/ ← 当前空(ClientRelease 待实现) ├── migrations/ ├── views.py ├── urls.py ├── serializers.py └── tests/ ``` **tenant App(django-tenants 特殊结构)**: ``` apps/tenant/ ├── __init__.py ├── apps.py ├── admin.py ├── models.py ← 单文件,含 Tenant + Domain(规范 §17.1) ├── migrations/ └── tests/ ``` --- ## 三、Phase 1:配置层(commit 9a7d06b 含此部分) ### 3.1 已交付文件 | 路径 | 用途 | 关键决策 | |---|---|---| | `config/settings/base.py` | 基础配置 | django-tenants 必为 SHARED_APPS 第一;CSRF_COOKIE_HTTPONLY=False(HTMX 需要);AUTH_USER_MODEL = "account.UserAccount" | | `config/settings/development.py` | 开发配置 | DEBUG=True,django-debug-toolbar | | `config/settings/testing.py` | 测试配置 | pytest-django | | `config/settings/production.py` | 生产配置 | DEBUG=False,HSTS/SECURE 各项开启 | | `config/urls.py` | Tenant 路由入口 | 仅 tenant 路由(强制分离) | | `config/urls_public.py` | Public 路由入口 | apps.release + drf-spectacular schema/swagger | | `config/asgi.py` | ASGI 入口 | uvicorn 启动点 | | `config/wsgi.py` | WSGI 入口 | gunicorn 兼容 | | `core/models/base.py` | 4 个抽象基类 | UUIDPrimaryKeyModel, TimeStampedModel, SoftDeleteModel, AuditedModel | | `core/enums.py` | 全局枚举 | 严格对齐 ENUMS.md v2.2,覆盖 9 个模块共数十个枚举 | | `core/encryption.py` | PII 加密 | **AES-256-GCM**(强制,禁用 Fernet) | | `core/cache.py` | Redis 工具 | get_redis_key 命名空间隔离 | | `core/htmx.py` | HTMX 响应工具 | htmx_response(),支持 toast / redirect | | `core/templatetags/heroicons.py` | Heroicons | `{% heroicon 'plus' %}` 内联 SVG | | `core/middleware/audit.py` | 审计中间件 | 骨架 | | `requirements/base.txt` | 生产依赖 | Django 4.2.16, django-tenants 3.7.0, psycopg2-binary 2.9.9, celery 5.4.0, drf-spectacular 0.27.2 等 | | `requirements/development.txt` | 开发依赖 | pytest, schemathesis, playwright, ruff, black 等 | | `requirements/production.txt` | 生产收敛 | -r base.txt + sentry/whitenoise | | `pyproject.toml` | 代码质量 | ruff/black/isort/pytest 配置 | | `.env.example` | 环境变量模板 | DB / Redis / R2 / Sentry / PHONE_ENCRYPTION_KEY | | `.env` | 开发环境真实值 | dev SECRET_KEY + 32 字节 PHONE_ENCRYPTION_KEY(已 gitignore) | | `.gitignore` | 忽略规则 | .env / *.pyc / node_modules / static/css/output.css / openapi.json 等 | | `manage.py` | Django 入口 | DJANGO_SETTINGS_MODULE=config.settings.development | ### 3.2 关键合规点 - ✅ `django_tenants` 在 SHARED_APPS 第一位、MIDDLEWARE 第一位(不可调整) - ✅ `CSRF_COOKIE_HTTPONLY = False` 含警示注释(HTMX 需要 JS 读 token,禁止"修复") - ✅ 加密强制 AES-256-GCM,禁用 Fernet - ✅ `config.urls` 与 `config.urls_public` 强制分离,未合并 - ✅ DB OPTIONS 不含 `pool_size`(PgBouncer 在代理层管理) - ✅ R2 环境变量统一 `R2_*` 前缀 - ✅ 所有密钥/Tenant ID 通过 `python-decouple` 的 `env()` 读取,无硬编码 --- ## 四、Phase 2:数据模型层(commits 9a7d06b → c57462f → 5b55dda → ed40de4) ### 4.1 模型总数 | App | 模型数 | 关键模型 | |---|---:|---| | tenant | 2 | Tenant (TenantMixin, auto_create_schema=True), Domain (DomainMixin) | | account | 4 | UserAccount (AbstractBaseUser), LoginAttempt, PasswordResetToken, PasswordHistory | | permission | 7 | PermissionDef, Role, RolePermission, UserRole, DataScope, RoleDataScope, PermissionAuditLog | | org | 11 | Department, Position, Staff(含组织/职位/人员体系) | | region | 5 | Province, City, District, BusinessArea, Subway | | complex | 10 | Complex, ComplexBuilding, ComplexUnit 等(含 pg_trgm + search_vector) | | property | 23 | Property, PropertyPhoto(分区表), FollowLog(分区表), PropertyContact, PropertyTag 等 | | client | 11 | Client, ClientContact, ClientFollowLog(分区表), ClientStatusLog, ViewingRecord, MatchRecord 等 | | setting | 4 | LookupGroup, LookupItem, TenantSetting, FieldRequirementRule | | release | 0 | (ClientRelease 待 Phase 4 业务实现) | | **合计** | **77** | | ### 4.2 分区表与触发器(共 5 张分区表 + 4 个触发器) | 分区表 | 模块 | 分区策略 | 关联触发器 | |---|---|---|---| | `property_follow_logs` | property | RANGE BY `created_at`,月度 | `update_property_last_followed` | | `property_photos` | property | RANGE BY `created_at`,月度 | `update_property_search_vector`(pg_trgm 全文检索) | | `client_follow_logs` | client | RANGE BY `created_at`,月度 | `update_client_last_follow`, `update_client_viewing_progress` | 实现模式(解决 Django ORM 与 PG 原生分区表的冲突): - 模型 `Meta` 设置 `managed = False` - `id = UUIDField(primary_key=True)`,复合主键 `(id, created_at)` 通过 RunSQL 创建 - ORM 层 `unique_together = (('id', 'created_at'),)` 让查询正确生成 - 月度子分区 + 默认分区,用 RunSQL 在初始 migration 中预创建 - 跨分区 FK 限制保留为优先级 3 注释 ### 4.3 Migration 文件(共 12 个) ``` apps/account/migrations/0001_initial.py apps/account/migrations/0002_initial.py ← AUTH_USER_MODEL 切换 apps/permission/migrations/0001_initial.py apps/org/migrations/0001_initial.py apps/region/migrations/0001_initial.py apps/complex/migrations/0001_initial.py apps/complex/migrations/0002_pg_trgm_and_search_vector.py apps/property/migrations/0001_initial.py apps/property/migrations/0002_partitions_and_triggers.py apps/client/migrations/0001_initial.py apps/client/migrations/0002_partitions_and_triggers.py apps/setting/migrations/0001_initial.py ``` ### 4.4 关键模型设计决策 | 决策 | 原因 | |---|---| | AUTH_USER_MODEL = "account.UserAccount" | UserAccount 含 OneToOne 关联 Staff,租户内统一登录 | | 手机号加密:BinaryField 密文 + char(64) hash 列 | AES-GCM 不可去重比对,hash 列承担唯一索引 | | `field_requirement_rules.trade_status` 用 `*` 哨兵值(覆盖 ALL="all") | 规范第 570 行明确要求 `*` 表示"全部"语义 | | `ClientStatusLog` 不含 `deleted_at`(保留 docstring 警示) | 规范明确要求"严禁删除"状态变更日志 | | 字符串 FK 引用(如 `"fonrey_property.Property"`) | 避免循环导入,应用标签前缀消歧 | | App 标签:fonrey_permission, fonrey_complex, fonrey_property, fonrey_client | permission/complex/property/client 为 Python 关键字或标准库名,加前缀避免冲突 | | 多文件 models/ 包,`__init__.py` 显式 re-export | 一表一文件,可读性优先 | --- ## 五、Phase 3:前端 + Docker 脚手架(commit 94d1602) ### 5.1 模板体系(templates/) | 文件 | 角色 | |---|---| | `base.html` | 全局根模板。引入顺序:output.css → htmx.min.js → alpine.min.js → main.js | | `layouts/app.html` | 主应用布局。继承 base,含 Topbar (sticky, h-14, z-20) + Sidebar (Alpine `$persist` 240/64px) + 主区 + 小屏拦截门 (<1280px) | | `layouts/auth.html` | 认证页布局。无 Topbar/Sidebar,居中卡片 max-w-md | | `components/topbar.html` | bg-primary-800,Logo + 导航 + 通知/设置/头像 | | `components/sidebar.html` | 240/64px 切换,Alpine 持久化 | | `components/pagination.html` | 分页骨架 | | `components/toast.html` | Toast 模板 | | `components/modal.html` | 模态对话框(Alpine x-show + click.outside) | | `components/empty-state.html` | 空状态 | | `errors/403.html` | 403 错误页 | | `errors/404.html` | 404 错误页 | | `errors/500.html` | 500 错误页 | ### 5.2 静态资源(static/) | 文件 | 内容 | |---|---| | `css/main.css` | Tailwind 入口(@tailwind base/components/utilities) | | `js/main.js` | HTMX `afterRequest` 监听 `HX-Trigger: fonrey:toast`,4s 自动消失;`configRequest` 自动注入 X-CSRFToken | | `vendor/.gitkeep` | 第三方 JS(htmx.min.js / alpine.min.js)放置点 | ### 5.3 Tailwind 配置(tailwind.config.js) 完全对齐 `UI_SYSTEM.md §2.7` 与 `§10.1`: - **Primary(Teal)**:50 #F0FDFA → 800 #134E4A,主色 600 #0F766E - **Neutral(Slate)**:50 #F8FAFC → 900 #0F172A - **语义色**:success-600 #16A34A, warning-600 #D97706, danger-600 #DC2626, info-600 #2563EB - **字体**:Inter, PingFang SC, Microsoft YaHei - **z-index**:60, 70(Toast 层) - **boxShadow**:xs(轻投影) - **animation**:slide-in-right(Drawer 进场) - **content scan**:`templates/`, `apps/**/templates/`, `static/js/` ### 5.4 Docker 与构建(开发 6 服务) | 文件 | 作用 | |---|---| | `Dockerfile` | python:3.12-slim + libpq-dev + 安装 base.txt + uvicorn 入口 | | `docker-compose.yml` | 6 服务:web (8000), db (postgres:16), redis (7), celery, celery-beat, tailwind (node:20);统一 `fonrey_net` 网络;db/redis 数据卷持久化 | | `docker-compose.prod.yml` | 生产精简版:gunicorn + UvicornWorker,去除 tailwind 容器与端口暴露 | | `Makefile` | dev / migrate / shell / test / lint / tailwind-build / createsuperuser | | `package.json` | 仅 tailwindcss ^3.4.0,build/watch 两个脚本 | ### 5.5 测试体系(tests/) ``` tests/ ├── __init__.py ├── conftest.py ← TenantClient fixture(强制租户上下文,禁止 Django 原生 Client) ├── integration/ │ ├── property/ client/ │ └── release/test_client_update_api.py ← schemathesis 契约测试占位 └── e2e/ ← playwright E2E 占位 ``` ### 5.6 每 App 骨架补全 property / client / setting 三个 App 的非模型骨架(services/、tasks.py、views.py、urls.py、serializers.py、templates//、tests/)已补齐;admin.py 在所有 10 个 App 上添加(空文件,后续禁用 Django Admin 但保留模块入口)。 --- ## 六、规范 §16 执行清单逐项验证 | # | 任务 | 状态 | |---:|---|:---:| | 1 | 创建根目录及完整目录树 | ✅ | | 2 | pyproject.toml / .gitignore / .env.example / Makefile | ✅ | | 3 | requirements/ 三个文件 | ✅ | | 4 | config/settings/base.py | ✅ | | 5 | development.py / testing.py / production.py | ✅ | | 6 | config/urls.py 与 urls_public.py 分离 | ✅ | | 7 | config/asgi.py | ✅ | | 8 | core/models/base.py 四个抽象基类 | ✅ | | 8b | core/enums.py(对齐 ENUMS.md v2.2) | ✅ | | 9 | core/encryption.py(AES-256-GCM) | ✅ | | 10 | core/cache.py(Redis 工具) | ✅ | | 11 | core/htmx.py(htmx_response 工具) | ✅ | | 12 | core/templatetags/heroicons.py | ✅ | | 13 | core/middleware/audit.py | ✅ | | 14 | 每 App 目录结构(apps/release 除外) | ✅ | | 15 | apps/tenant/models.py(Tenant + Domain) | ✅ | | 16 | templates/ 完整目录树 + base/app/auth | ✅ | | 17 | components/ 6 个骨架 | ✅ | | 18 | errors/ 三个错误页 | ✅ | | 19 | static/css/main.css | ✅ | | 20 | static/js/main.js | ✅ | | 21 | tailwind.config.js | ✅ | | 22 | package.json | ✅ | | 23 | Dockerfile | ✅ | | 24 | docker-compose.yml(6 服务) | ✅ | | 25 | manage.py | ✅ | | 26 | `manage.py check --deploy` 无致命错误 | ✅ 0 errors,仅 SECRET_KEY 测试值告警 | | 27 | 目录树与 §2 100% 匹配 | ✅ | | 28 | API_CONTRACT 7 项核对 | ⚠️ 部分(详见第七节) | --- ## 七、API_CONTRACT 7 项核对(规范 §15) | # | 项 | 状态 | 备注 | |---:|---|:---:|---| | 1 | 路径与方法一致 | 🟡 N/A | 业务端点尚未实现(骨架阶段) | | 2 | 请求参数一致 | 🟡 N/A | 同上 | | 3 | 响应 envelope(ok/data/meta vs ok/error/code/details) | 🟡 N/A | DRF 自定义 renderer 待 Phase 4 | | 4 | 错误码 UPPER_SNAKE_CASE | 🟡 N/A | 同上 | | 5 | OpenAPI 注解 `@extend_schema` | 🟡 待定 | drf-spectacular 已装、urls_public.py 已挂 schema/swagger 路由,待业务视图编写时补 | | 6 | `python manage.py spectacular --file openapi.json` 可生成 | ⚠️ 未运行 | 当前无业务视图,生成会得到空 schema;待 Phase 4 验证 | | 7 | schemathesis 命令可运行 | ⚠️ 占位 | `tests/integration/release/test_client_update_api.py` 已有 skip 占位 | **结论**:骨架阶段第 1–4 项 N/A(无端点)、5–7 项基础设施就绪等待业务实现。骨架本身不阻塞契约清单。 --- ## 八、Git 提交历史 ``` 94d1602 feat: complete Phase 3 scaffolding (templates, static, Docker, per-app skeletons) ed40de4 feat(client,setting): complete Phase 2 with partitioned client_follow_logs 5b55dda feat(property): add 23-table property module with partitioned follow_logs and property_photos c57462f feat(complex): add apps.complex with 10 models and full-text search 9a7d06b feat: scaffold Django multi-tenant project with 5 of 9 apps ``` 每次 commit 后均执行 `manage.py check`,全部通过。 --- ## 九、代码量统计 | 目录 | 总行数 | |---|---:| | `apps/` | 5837 | | `core/` | 1028 | | `config/` | 269 | | `shared/` | 6 | | `templates/` | 142 | | `static/` (css+js) | 35 | | `tests/` | 15 | | **合计** | **~7332** | 文件总数:208(不含 .venv / .git / __pycache__) --- ## 十、未交付项(明确延后清单) | 项 | 原因 | 建议落地阶段 | |---|---|---| | ~300 条 PermissionDef 种子(`apps/permission/fixtures/permission_defs.json`) | 内容来自 `DATA_MODEL_PERMISSION.md` 700+ 行,需逐条人工核对 | Phase 4 启动前 | | 4 个内置角色 + 默认 DataScope 种子 | 同上 | Phase 4 启动前 | | Setting 模块 LookupItem 默认值(楼盘类型/客户来源等枚举数据) | 来自 `DATA_MODEL_SETTING.md` | Phase 4 启动前 | | Celery `partition_maintenance_task` | 月度自动新增分区,骨架阶段非阻塞 | 上线前 1 周 | | API_CONTRACT 第 1–4 项 | 需要真实业务端点 | Phase 4 模块开发时随端点同步 | | OpenAPI 实际生成 + schemathesis 实际运行 | 同上 | Phase 4 | | Heroicons SVG 资源文件 | 当前 templatetag 是骨架,未含 SVG 库 | Phase 4 UI 模块启动时 | | static/vendor/ 下的 htmx.min.js / alpine.min.js | 通过 npm 或 CDN 任选,未决策 | Phase 4 启动前 | --- ## 十一、关键约束遵守审计 | 约束(规范原文) | 遵守状态 | 证据 | |---|:---:|---| | 不得自行发明技术方案,不得引入文档未授权第三方库 | ✅ | requirements/base.txt 仅含规范明列依赖 | | 绝对禁止 React/Vue/Angular | ✅ | 仅 HTMX + Alpine + Tailwind | | `django_tenants` 在 SHARED_APPS / MIDDLEWARE 首位 | ✅ | `config/settings/base.py` | | `CSRF_COOKIE_HTTPONLY = False` | ✅ | base.py 含警示注释 | | AES-256-GCM 加密,禁用 Fernet | ✅ | `core/encryption.py` | | `apps/release/` 无 services/、tasks.py | ✅ | 实际目录验证 | | 不在 DB OPTIONS 注入 pool_size | ✅ | base.py DATABASES 配置 | | 所有密钥/Tenant ID 不出现在 Python 文件 | ✅ | 统一 `config()` / `env()` 读取 | | `config/urls.py` 与 `urls_public.py` 强制分离 | ✅ | 两文件独立维护 | | 逐步创建并验证 | ✅ | 5 个 commit 各自 `manage.py check` 通过 | | .gitignore 包含 .env / *.pyc / node_modules / static/css/output.css 等 | ✅ | 全部覆盖 | --- ## 十二、下一步建议 按优先级: 1. **Phase 4 起步前必做**: - 编写 PermissionDef + 内置角色 + DataScope 三组 fixtures - 编写 Setting 模块 LookupItem 默认值 fixtures - 决定 vendor JS 加载方式(npm install 还是直接放置静态文件) - 准备 Heroicons SVG 库(推荐 `heroicons` Python 包或手动放 SVG) 2. **Phase 4 实施时同步推进**: - 第一个真实业务端点(建议从 release/client_update API 起步)落地后立即跑 spectacular + schemathesis,闭环 API_CONTRACT 7 项 - 在每个业务视图 PR 中强制要求 `@extend_schema` 注解 3. **上线前 1 周**: - 实现 Celery `partition_maintenance_task`,配置 celery-beat 月初执行 - 用真实 32 字节随机值替换 `.env.example` 占位的 PHONE_ENCRYPTION_KEY,并在 Vault/Secret Manager 中托管 4. **可选优化**: - 增加 `pre-commit` 钩子(ruff + black + isort + django-check) - 增加 GitHub Actions CI(lint + test + spectacular dry-run) --- ## 十三、附录:项目快速启动命令 ```bash # 本地(无 Docker) cd /mnt/c/project/fonrey .venv/bin/python manage.py check .venv/bin/python manage.py makemigrations --dry-run .venv/bin/python manage.py spectacular --file openapi.json # 待业务视图就绪后运行 # Docker(推荐) make dev # docker compose up make migrate # 共享 schema + 租户 schema 双重 migrate make shell # shell_plus make test # pytest apps/ make lint # ruff + black make tailwind-build # 生成 static/css/output.css ``` --- **报告完**