基于 Django REST Framework 打造电商后端(上):项目架构与核心模块设计
一、项目背景
最近我做了一个基于 Django REST Framework 的电商后端练手项目,目标不是简单写几个 CRUD 接口,而是尽量还原一个小型电商系统的核心业务闭环。项目已经覆盖了用户注册登录、商品展示、购物车、订单提交、库存扣减、订单取消、缓存、异步任务和 Docker 本地开发环境等内容。
很多初学者做 Django 项目时,往往停留在“模型建好了、接口能跑通”的阶段,但真实业务系统更关注的是:
- 用户身份如何鉴权
- 商品读取如何优化
- 购物车如何处理重复添加
- 订单提交如何保证库存一致性
- 超时未支付订单如何自动取消
所以这个项目的重点,就是把这些关键链路串起来,做一个“小而完整”的电商后端。
二、项目实现了哪些功能
当前项目主要支持以下能力:
- JWT 用户注册、登录、刷新 Token
- 用户资料查看与修改
- 商品分类与商品列表查询
- 商品按关键词、价格区间、分类筛选
- 购物车新增、修改、删除、合并数量
- 订单提交、订单列表、订单详情、订单取消
- Redis 缓存商品和分类读取接口
- Celery 处理订单通知和超时自动取消
- Swagger/OpenAPI 接口文档
- Docker Compose 本地开发环境
- 自动化测试覆盖核心流程
从练手项目角度看,这套功能已经不只是“会写接口”,而是开始具备一点业务系统的完整度了。
三、技术栈选型
这个项目采用的技术栈比较务实:
- `Django 6`
- `Django REST Framework`
- `Simple JWT`
- `PostgreSQL`
- `Redis`
- `Celery`
- `drf-spectacular`
- `Docker / Docker Compose`
为什么这样选?因为这套组合基本覆盖了后端项目最常见的几个问题:数据存储、接口开发、身份鉴权、缓存优化、异步任务和环境统一。
四、项目整体架构
项目整体结构如下:
```text
Client
-> Django REST API
-> PostgreSQL
-> Redis
-> Celery Worker
```
可以这样理解:
- Django API 负责业务逻辑和接口响应
- PostgreSQL 负责保存用户、商品、订单等核心数据
- Redis 负责商品与分类缓存
- Celery Worker 负责异步发送通知和延迟取消订单
这种结构虽然不复杂,但已经具备真实后端项目的基本形态。
五、项目模块划分
项目按业务拆成了三个 app:
```text
ecommerce/
ecommerce/ 配置、总路由、Celery
users/ 用户模块
products/ 商品模块
orders/ 购物车与订单模块
utils/ 公共响应工具
docker/ 容器启动脚本
```
这样的拆分方式有一个很明显的好处:每个业务域都比较独立,模型、序列化器、视图和测试也更容易管理。
六、用户模块设计
电商系统第一步一定是用户体系。这个项目使用了自定义用户模型,在 Django 默认用户基础上扩展了昵称、头像、性别、手机号、简介等字段。
代码骨架如下:
```python
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.db import models
class UserManager(BaseUserManager):
def _create_user(self, username, email, password, **extra_fields):
pass
def create_user(self, username, email="", password=None, **extra_fields):
pass
def create_superuser(self, username, email="", password=None, **extra_fields):
pass
class User(AbstractUser):
nickname = models.CharField(max_length=50, blank=True, default="")
avatar = models.URLField(max_length=255, blank=True, default="")
gender = models.CharField(max_length=20, default="unknown")
bio = models.CharField(max_length=500, blank=True, default="")
phone = models.CharField(max_length=20, blank=True, null=True, unique=True)
objects = UserManager()
```
接口层面,用户相关视图也比较完整:
```python
class UserRegisterAPIView(APIView):
def post(self, request):
pass
class UserLoginAPIView(APIView):
def post(self, request):
pass
class UserMeAPIView(APIView):
def get(self, request):
pass
def put(self, request):
pass
class UserPasswordAPIView(APIView):
def put(self, request):
pass
class UserTokenRefreshAPIView(APIView):
def post(self, request):
pass
```
从业务角度看,这一层主要解决两个问题:
- 用户如何注册和登录
- 用户登录后如何维护自己的资料与密码
项目这里采用 JWT 作为认证方案,比较适合前后端分离场景。
七、商品模块设计
商品模块由 `Category` 和 `Product` 两个核心模型组成。
代码骨架如下:
```python
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=120, unique=True, blank=True)
description = models.TextField(blank=True, default="")
is_active = models.BooleanField(default=True)
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name="products")
name = models.CharField(max_length=150)
slug = models.SlugField(max_length=180, unique=True, blank=True)
description = models.TextField(blank=True, default="")
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.PositiveIntegerField(default=0)
cover = models.ImageField(upload_to="products/covers/", blank=True, null=True)
is_active = models.BooleanField(default=True)
```
商品接口并不只是“查出所有商品”,而是加上了更贴近业务的筛选逻辑:
```python
class CategoryListAPIView(APIView):
def get(self, request):
pass
class ProductListAPIView(APIView):
def get(self, request):
pass
class ProductDetailAPIView(APIView):
def get(self, request, product_id):
pass
```
商品列表支持这些能力:
- 按分类筛选
- 按关键词搜索
- 按价格区间筛选
- 按价格或创建时间排序
- 区分普通用户和管理员可见范围
这意味着项目已经开始考虑“公开商品视图”和“后台商品视图”的差异,而不只是简单返回数据库内容。
八、购物车与订单模块设计
订单模块是整个项目最核心的部分。这里我把购物车和订单放在同一个 `orders` app 里处理,因为这两个业务联系非常紧密。
先看模型结构:
```python
class Cart(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="cart")
class CartItem(models.Model):
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, related_name="items")
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="cart_items")
quantity = models.PositiveIntegerField(default=1)
class Order(models.Model):
class Status(models.TextChoices):
PENDING = "pending", "Pending"
CONFIRMED = "confirmed", "Confirmed"
SHIPPED = "shipped", "Shipped"
COMPLETED = "completed", "Completed"
CANCELLED = "cancelled", "Cancelled"
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="orders")
order_no = models.CharField(max_length=32, unique=True)
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
receiver_name = models.CharField(max_length=100)
receiver_phone = models.CharField(max_length=20)
receiver_address = models.CharField(max_length=255)
remark = models.CharField(max_length=255, blank=True, default="")
class OrderItem(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="items")
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True, blank=True, related_name="order_items")
product_name = models.CharField(max_length=150)
product_price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.PositiveIntegerField(default=1)
subtotal = models.DecimalField(max_digits=10, decimal_places=2)
```
这个设计里有一个很重要的点:`OrderItem` 保存了商品名称和价格快照。
这样做的原因是,商品价格未来可能变化,但历史订单金额不能跟着变,所以订单明细必须记录下单时的商品信息。
接口层面,购物车和订单的核心视图如下:
```python
class CartAPIView(APIView):
def get(self, request):
pass
class CartItemAPIView(APIView):
def post(self, request):
pass
class CartItemDetailAPIView(APIView):
def put(self, request, item_id):
pass
def delete(self, request, item_id):
pass
class OrderSubmitAPIView(APIView):
def post(self, request):
pass
class OrderListAPIView(APIView):
def get(self, request):
pass
class OrderDetailAPIView(APIView):
def get(self, request, order_id):
pass
class OrderCancelAPIView(APIView):
def post(self, request, order_id):
pass
```
这部分的业务闭环是:
- 用户先把商品加入购物车
- 购物车支持重复商品数量合并
- 提交订单时校验库存
- 创建订单和订单明细
- 扣减商品库存
- 清空已提交的购物车项
- 支持手动取消订单
这已经是一个比较完整的下单主流程了。
九、项目里已经体现出的工程化思路
虽然这是一个练手项目,但已经不是“单接口堆砌”了,而是开始体现一些工程化意识。
比如在订单取消这件事上,项目没有把逻辑全部塞进视图,而是专门抽出服务函数:
```python
def cancel_order(order, reason=""):
pass
```
在商品缓存这块,也不是直接写死缓存 key,而是单独封装了缓存 key 构建和失效函数:
```python
def build_category_cache_key(scope, query_string):
pass
def build_product_list_cache_key(scope, query_string):
pass
def build_product_detail_cache_key(scope, product_id):
pass
def invalidate_category_cache():
pass
def invalidate_product_cache():
pass
```
这说明项目已经开始关注职责拆分和后续维护,而不只是“能运行就行”。
十、为什么这个项目适合写成实战博客
我觉得这个项目比较适合写博客,不是因为它多么复杂,而是因为它刚好踩在“入门项目”和“真实业务项目”之间。
它有几个特别适合展开写的点:
- 用户鉴权链路完整
- 商品模块不只是简单查询
- 购物车有数量合并逻辑
- 订单提交流程涉及库存一致性
- 订单取消涉及库存恢复
- 商品读接口加入了缓存
- Celery 参与了异步任务处理
- 测试覆盖了主要业务流程
这些内容写进博客里,读者会更容易感受到“这个项目不是拼接口,而是在做系统”。
十一、总结
这个电商后端项目最有价值的地方,是它已经把一个真实电商系统最核心的业务主线串起来了。用户、商品、购物车、订单四个模块之间的关系清晰,技术选型也比较合理,整体结构非常适合作为 Django REST Framework 的进阶练手项目。
如果把这篇看作上篇,那么它主要回答了三个问题:
- 这个项目做了什么
- 这个项目为什么这样拆
- 这个项目的核心模块是怎么设计的
下篇我会继续往深一点写,重点分析这些内容:
- `OrderSubmitAPIView` 为什么要配合事务处理
- `cancel_order()` 为什么要抽到服务层
- `auto_cancel_pending_order()` 是怎么做超时取消的
- 商品缓存为什么用“版本号失效”思路
- 测试用例是怎么覆盖这些关键链路的
更多推荐




所有评论(0)