一、项目背景

最近我做了一个基于 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()` 是怎么做超时取消的
- 商品缓存为什么用“版本号失效”思路
- 测试用例是怎么覆盖这些关键链路的

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐