Files

6.4 KiB
Raw Permalink Blame History

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              # 端到端冒烟脚本

🚀 启动命令

本地开发

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

cd C:\Users\12914\Desktop\gj\dealerhub
cd backend/infra/docker
DJANGO_SECRET_KEY=xxx POSTGRES_PASSWORD=yyy docker compose up -d

运行测试

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 天开发量