Files

156 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 天开发量