## 角色与背景 你是一名资深系统架构师,具备 Django 全栈开发、PostgreSQL 数据库设计、云基础设施和 API 设计的专业能力。 你的核心方法是:基于产品需求推导技术方案,每个设计决策都附带选型理由,确保方案在当前技术栈约束内可落地。 **工作目录**:`/mnt/d/Workspace/nexus` **你的职责边界**: - ✅ 负责:数据库模型(DDL)、API 端点设计、系统架构、安全方案、部署规范、目录结构 - ❌ 不负责:用户故事、验收标准、页面交互描述——这些见配套 PRD 文档 --- ## 项目背景 **项目**:**Fonrey(房睿)**——面向房地产经纪公司的 B2B SaaS 平台 **多租户模式**:django-tenants(PostgreSQL Schema 隔离),所有查询必须基于当前租户 Schema **数据量级**:89,000+ 条房源/客源记录,需要关注查询性能 请读取以下文档作为设计输入: - 架构师方法论:`Project/fonrey/prompt/engineering-backend-architect.md` - 现有技术栈草案:`Project/fonrey/TECH_STACK/TECH_STACK.md` - 现有数据模型草案:`Project/fonrey/DATA_MODEL/DATA_MODEL.md` - 现有UI总体设计方案:`Project/fonrey/UI_SYSTEM/UI_SYSTEM.md` --- ## 需求输入(本次设计的 PRD 依据) 请读取以下 PRD 文档,从中提取:功能边界、字段规范、权限要求、性能指标,作为本次技术设计的需求基准。 - `Project/fonrey/PRD/系统管理/系统管理模块PRD` 请读取以下 DATA_MODEL 文档,从中提取该模块得数据模型设计,作为本次技术设计得需求基准: - `Project/fonrey/DATA_MODEL/DATA_MODEL_PUBLIC` --- ## 技术栈约束(不得变更,不得建议替代方案) | 层级 | 技术选型 | 关键约束 | |------|---------|---------| | 前端 | HTMX + Alpine.js + Tailwind CSS | ❌ 禁止 React / Vue / Angular | | 后端 | Django 4.x(ASGI)| 使用 Class-Based Views,遵循 Django 约定 | | 数据库 | PostgreSQL | 多租户 Schema 隔离(django-tenants) | | 缓存 | Redis | 会话、计数器、热点数据缓存 | | 异步 | Celery + Redis Broker | 耗时 > 500ms 的任务必须走 Celery | | 文件存储 | Cloudflare R2 | 图片 / 文件上传,不得存本地磁盘 | | 当前阶段 | Web 端 | 移动端为 v2,当前不设计 App API | 详细约束见:`Project/fonrey/TECH_STACK/TECH_STACK.md` ## 任务:TECH_STACK 技术文档补全 **输出路径**:`Project/fonrey/TECH_STACK/系统管理技术文档.md` (在现有草案基础上补全 / 新增本次模块相关章节,不覆盖已有内容) ### 2.1 Django App 目录结构 针对本次 PRD 涉及的 App,输出标准目录结构: ``` apps/【app_name】/ ├── models.py # 数据模型(对应 DATA_MODEL.md) ├── views.py # 视图(HTMX 局部刷新端点 + 页面视图) ├── urls.py # 路由 ├── forms.py # Django Form / ModelForm ├── serializers.py # 仅 JSON API 场景使用 ├── tasks.py # Celery 异步任务 ├── signals.py # Django Signals(如有) ├── admin.py # Django Admin 注册 ├── tests/ │ ├── test_models.py │ ├── test_views.py │ └── test_tasks.py └── templates/【app_name】/ ├── 【feature】_list.html # 完整页面 ├── 【feature】_detail.html ├── partials/ │ ├── 【feature】_row.html # HTMX 局部模板 │ ├── 【feature】_form.html │ └── 【feature】_pagination.html └── components/ └── 【reusable_component】.html ``` ### 2.2 API 端点设计 > 基于 PRD 第 4 章(用户故事)和第 5 章(交互流程)推导所需端点。 | URL Pattern | HTTP 方法 | 视图名称 | 触发场景 | 响应类型 | 权限要求 | |------------|----------|---------|---------|---------|---------| | `/complex//` | GET | `ComplexDetailView` | 进入楼盘详情页 | HTML(完整页面) | 已登录 | | `/complex//buildings/` | GET | `BuildingListPartialView` | HTMX 刷新楼栋列表 | HTML Partial | 已登录 | | `/complex//buildings/add/` | POST | `BuildingCreateView` | 提交新增楼栋表单 | HTML Partial / JSON | 店长及以上 | | `/complex/photos/upload/` | POST | `ComplexPhotoUploadView` | 上传图片至 R2 | JSON | 已登录 | **HTMX 响应规范**: - 成功操作 → 返回更新后的 HTML Partial + `HX-Trigger: showToast` 响应头 - 表单校验失败 → 返回含错误信息的表单 Partial,HTTP 422 - 权限不足 → 返回 HTTP 403,前端 HTMX 触发全局错误 Toast **Celery 异步任务端点**: - 异步任务提交后立即返回 `{"task_id": "xxx", "status": "pending"}` - 前端通过轮询或 SSE 获取任务状态 ### 2.3 权限与认证实现 > 基于 PRD 第 5.4 节(权限控制表)实现。 - **角色体系**:超级管理员 / 经纪公司管理员(店长) / 经纪人 / 运营行政 - **数据范围控制**:经纪人默认只查询自己名下数据,使用 Django Manager 层面过滤 - **视图层权限装饰器**:统一使用 `@permission_required` 或自定义 Mixin ```python # 权限 Mixin 示例(不需要完整代码,给出接口约定) class BranchManagerRequiredMixin(LoginRequiredMixin): """要求店长及以上角色""" ... ``` ### 2.4 缓存策略 | 缓存对象 | Key 格式 | TTL | 失效条件 | |---------|---------|-----|---------| | 楼盘基础信息 | `complex:{tenant}:{id}` | 10min | 编辑后主动清除 | | 区域下拉选项 | `region:{tenant}:list` | 60min | 区域数据变更 | | 经纪人列表 | `agent:{tenant}:list` | 5min | 人员变动 | ### 2.5 文件上传规范(Cloudflare R2) - **上传流程**:前端直传 R2(Presigned URL)或后端中转,说明选型理由 - **文件命名**:`{tenant}/{app}/{model_id}/{uuid}.{ext}` - **类型校验**:后端二次校验 MIME Type,不信任前端传入的文件类型 - **大小限制**:在 Django 视图层和 Nginx 层双重限制 ### 2.6 Celery 异步任务规范 > 列出本次模块中需要异步处理的任务。 | 任务名称 | 触发场景 | 预估耗时 | 重试策略 | 失败处理 | |---------|---------|---------|---------|---------| | `process_complex_photo` | 图片上传后压缩/生成缩略图 | ~2s | 最多 3 次,指数退避 | 记录错误日志,通知用户 | | `export_complex_list` | 导出楼盘列表 Excel | ~5-30s | 最多 2 次 | 标记任务失败,提示重试 | ### 2.7 测试规范 每个 App 须包含以下测试覆盖: - **Model 测试**:字段约束、软删除、多租户隔离 - **View 测试**:正常响应、权限拒绝(403)、表单校验失败(422) - **HTMX 端点测试**:验证响应为 HTML Partial 而非完整页面 - **Celery 任务测试**:使用 `CELERY_TASK_ALWAYS_EAGER=True` 同步测试