# dealerhub · 经销商 SaaS — 阶段完成报告 > 详细计划见 `PLAN.md`;本文档是阶段 0 + 阶段 1 的 **执行结果**。 ## ✅ 阶段 0(骨架 + 异步链路打通) - ✅ Django 5.2 + DRF 3.17 + adrf 0.1.14 + Granian 2.8.2 + psycopg 3.2 + django-environ - ✅ 完整 settings 分层:`config/settings/{base,dev,prod,test}.py` - ✅ `config/asgi.py`(Granian 加载入口)+ `config/wsgi.py` - ✅ `pytest.ini` + `conftest.py` + `tests/`(9 个测试全通过) - ✅ 数据库迁移:`core/catalog` 两个 app 的初始 migration 已落库 ## ✅ 阶段 1(多租户预留 + 商品中心 CRUD) - ✅ 多租户中间件 `TenantMiddleware`(基于 `X-Tenant-Id` header) - ✅ 抽象基类 `TenantScopedModel`:自动 `tenant_id`/`org_id`/`ext_data`/`source_channel`/`is_deleted`/审计字段 - ✅ `Org` 模型(多组织预留,`org_path` 层级路径) - ✅ 商品中心 4 个 ViewSet(分类/品牌/单位/商品)+ DRF async 分页 - ✅ JWT 鉴权(SimpleJWT)+ admin 路径 + ping 路径公开 - ✅ `acreate` 重写自动注入 `tenant/created_by/updated_by` ## ✅ 关键技术问题与解法(实战踩坑) 1. **`adrf.routers` 没有 `Router` 类** → 改用 `from adrf.routers import DefaultRouter` 2. **DjangoFilterBackend 与 async view 不兼容**(filter_queryset 同步调用 queryset.model,触发未 await 协程) → 自写 async 分页 + 简单 search 3. **DRF 默认 PageNumberPagination 同步** → 自写 `StandardAsyncPagination`(兼容传 coroutine) 4. **同步 ORM 在 async view 里需要 sync_to_async** → 但直接写 ORM 调用(adrf 0.1.14 会自动 wrap) 5. **`asave()` 在 adrf 0.1.14 是必须的** → 抽象基类 `AsyncModelSerializer` 提供 `asave/acreate` 6. **create 时 tenant_id NOT NULL 约束** → 重写 `acreate` 自动注入 tenant/org/user 7. **pytest 的 AsyncClient + adrf view 触发 AsyncToSync RuntimeError** → 改用同步 APIClient(Django 自动 wrap async view) 8. **pytest 设置 `DJANGO_SECRET_KEY` 太短导致 JWT warning** → 强制 ≥32 字符 9. **ping 路径被全局 `IsAuthenticated` 拦截** → 在路由处覆盖 `@authentication_classes([]) @permission_classes([AllowAny])` ## ✅ 验证证据 ### 1. Django check 通过 ``` $ python manage.py check System check identified no issues (0 silenced). ``` ### 2. 9/9 测试通过 ``` $ python -m pytest tests/ -p no:cacheprovider ......... [100%] 9 passed, 21 warnings in 0.38s ``` ### 3. 端到端同步验证脚本 ``` [setup] tenant=default user=alice token-len=228 [ping] 200 body={"ok":true,"service":"dealerhub",...,"granian":"2.8.2"}} [catalog/list] 200 body={"count":0,"next":null,"previous":null,"results":[]} [catalog/create] 201 body={"id":1,"code":"P001","name":"...","status":"active",...} [catalog/search] 200 body={"count":1,"next":null,"previous":null,"results":[{...}]} ``` ## 📁 当前文件结构 ``` dealerhub/ ├── PLAN.md # 总体实施计划(5 个阶段) ├── README.md # 快速开始 ├── PROGRESS.md # 本文件 └── backend/ ├── manage.py ├── requirements.in ├── pytest.ini ├── conftest.py ├── .env ├── .gitignore ├── config/ │ ├── __init__.py │ ├── settings/ │ │ ├── __init__.py │ │ ├── base.py # 共享 │ │ ├── dev.py # SQLite + DEBUG │ │ ├── prod.py # PostgreSQL + 安全 │ │ └── test.py # 内存 SQLite │ ├── asgi.py # Granian 加载入口 │ ├── wsgi.py │ └── urls.py # 根路由 + ping ├── apps/ │ ├── core/ │ │ ├── __init__.py │ │ ├── apps.py │ │ ├── base_models.py # TenantScopedModel 抽象基类 │ │ ├── middleware.py # TenantMiddleware │ │ ├── models.py # Tenant/Org/AuditLog │ │ ├── serializers.py # AsyncModelSerializer 基类 │ │ ├── views.py │ │ ├── urls.py │ │ └── management/commands/seed_initial_data.py │ └── catalog/ │ ├── __init__.py │ ├── apps.py │ ├── models.py # Category/Brand/Unit/Product │ ├── serializers.py │ ├── views.py # async ViewSets + 异步分页 + tenant 注入 │ └── urls.py ├── tests/ │ ├── test_ping.py # 1 测试 │ ├── test_catalog.py # 6 测试 │ └── test_tenant.py # 2 测试 ├── infra/ │ ├── docker/ │ │ ├── Dockerfile.backend │ │ └── docker-compose.yml │ └── scripts/ │ ├── run_dev.sh │ └── run_prod_granian.sh └── verify_sync.py # 端到端冒烟脚本 ``` ## 🚀 启动命令 ### 本地开发 ```bash cd C:\Users\12914\Desktop\gj\dealerhub\backend sh infra/scripts/run_dev.sh # 或: DJANGO_SETTINGS_MODULE=config.settings.dev granian config.asgi:application \ --interface asgi --host 0.0.0.0 --port 8000 --reload ``` ### Docker ```bash cd C:\Users\12914\Desktop\gj\dealerhub cd backend/infra/docker DJANGO_SECRET_KEY=xxx POSTGRES_PASSWORD=yyy docker compose up -d ``` ### 运行测试 ```bash cd C:\Users\12914\Desktop\gj\dealerhub\backend DJANGO_SETTINGS_MODULE=config.settings.test \ DJANGO_SECRET_KEY="this-is-a-long-secret-key-for-jwt-tests-min-32-chars-OK" \ python -m pytest tests/ -p no:cacheprovider ``` ## ⏭️ 下一步(阶段 2) 按 `PLAN.md` 推进: - `apps/inventory`:仓库 + 库存 + 批次 + 库存流水日志 - `apps/purchase`:采购订单/进货单/付款/退货 - `apps/sales`:销售订单/销售单/收款/退货 - `apps/partner`:客户/供应商/联系人/价格等级 - `apps/finance`:应收/应付/对账单 - `core.workflow.StateMachine`:单据状态机服务化 - 库存原子服务(杜绝并发错票) 每一层都有 pytest 集成测试 + 端到端冒烟验证,结果会追加到本文件。 --- **当前阶段:** 阶段 1 完成(基础 + 多租户预留 + 商品 CRUD) **下一步:** 阶段 2(进销存主线业务),预计 10–15 天开发量