156 lines
6.4 KiB
Markdown
156 lines
6.4 KiB
Markdown
# 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 天开发量
|