尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Django商城项目实战:数据库设计、DRF接口与MySQL部署指南

Django商城项目实战:数据库设计、DRF接口与MySQL部署指南 简介这是一套基于Python Django框架开发的购物商城系统项目主要面向计算机专业正在完成毕业设计、课程设计或期末大作业的学生也适合需要项目实战练习的入门学习者。项目属于经导师指导并通过的高分设计评审得分99分代码完整并确保可以运行能够帮助读者快速理解Django商城项目的目录结构、业务逻辑、前后端数据交互以及数据库表设计思路。压缩包共294个文件大小约11.94MB文件类型覆盖45个Python源码、36个HTML页面模板、CSS样式与JavaScript脚本、jpg/png图片素材、SQL数据库文件同时提供docx格式的运行文档和接口文档以及pyc编译文件、日志、配置文件等辅助内容整体目录规整便于按需查阅。目前该资源已有92人学习下载。下载后可以获得完整可运行的项目源码、数据库初始化脚本、部署运行说明和接口调用说明既支持课程作业的直接使用也可作为二次开发与实战练习的基础是一份实用性很强的毕业设计参考方案。1. Python Django 商城项目源码、数据库、接口文档怎么拼起来把一个标注“源码数据库运行文档接口文档”的 Django 商城系统下载下来最容易出现的局面是源码能打开接口文档能看但本地就是起不来。报错往往不在业务代码而在数据库版本不匹配、Python 解释器版本不对、依赖库缺了编译环境。这类资源包的价值不在于代码本身而在于四个部分能否互相咬合——settings.py里连的数据库、迁移脚本里建的表、接口文档里声明的字段、运行文档里写的启动方式任何一处对不上商城就跑不起来。这篇文章把一条完整路径拆开讲先看 Django 商城的数据库表设计理解商品、用户、订单之间怎么用外键关联再落到 DRF 接口文档的写法与状态码约定然后从零配置 Python 虚拟环境和 MySQL把项目真正跑起来最后处理几个上线前必踩的坑包括查询优化和部署环境检查。适合拿到源码但没跑通的人也适合想自己搭一套商城后端做课程设计或毕设的读者。2. Django 商城数据库表设计从模型外键到迁移报错2.1 用户模型为什么建议自建而不是用默认 UserDjango 自带django.contrib.auth.models.User字段够用密码加密、权限系统、session 都齐全。但商城系统的用户通常需要手机号、头像、积分、收货地址列表这些字段如果硬塞到User的扩展表里每次查询都要多一次 JOIN。更合理的做法是项目一开始就自定义用户模型继承AbstractUser。# users/models.py from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): phone models.CharField(max_length11, uniqueTrue, verbose_name手机号) avatar models.URLField(blankTrue, verbose_name头像地址) points models.IntegerField(default0, verbose_name积分) class Meta: db_table users verbose_name 用户这段代码里有三个值得注意的参数uniqueTrue放在phone上表示一个手机号只能注册一个账号商城场景下这是硬约束blankTrue只影响表单校验不影响数据库层面头像允许为空但phone必须有值db_table显式指定表名避免 Django 默认生成users_user这种带前缀的表名后续写 SQL 或排查数据时更容易定位。注意自定义用户模型必须在第一次migrate之前完成。如果已经执行过迁移再改AUTH_USER_MODELDjango 会报RuntimeError只能删库重来或者用复杂的数据迁移脚本处理。拿到一份商城源码时第一步就检查settings.py里有没有AUTH_USER_MODEL users.User没有的话要确认它是故意用默认模型还是漏配了。2.2 商品、分类、购物车、订单的模型脚手架商城核心链路是“浏览商品 → 加入购物车 → 下单 → 支付 → 订单状态流转”对应的数据模型至少要有五张表加上用户表共六张。商品与分类用外键关联购物车用UniqueConstraint保证同一用户同一商品只有一条记录订单用choices管理状态。# goods/models.py from django.db import models class Category(models.Model): name models.CharField(max_length50) parent models.ForeignKey(self, nullTrue, blankTrue, on_deletemodels.CASCADE) class Goods(models.Model): category models.ForeignKey(Category, on_deletemodels.PROTECT, related_namegoods) name models.CharField(max_length200) price models.DecimalField(max_digits10, decimal_places2) stock models.IntegerField(default0) is_on_sale models.BooleanField(defaultTrue) created_at models.DateTimeField(auto_now_addTrue) # orders/models.py from django.conf import settings from django.db import models class Cart(models.Model): user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE) goods models.ForeignKey(goods.Goods, on_deletemodels.CASCADE) count models.PositiveIntegerField(default1) class Meta: constraints [ models.UniqueConstraint(fields[user, goods], nameunique_cart_item) ] class Order(models.Model): STATUS_CHOICES [ (unpaid, 待支付), (paid, 已支付), (shipped, 已发货), (finished, 已完成), (canceled, 已取消), ] order_no models.CharField(max_length64, uniqueTrue) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_nameorders) total_amount models.DecimalField(max_digits10, decimal_places2) status models.CharField(max_length10, choicesSTATUS_CHOICES, defaultunpaid)这里需要重点理解on_delete在商城里的取舍。Category的parent用CASCADE因为删掉一个分类时子分类一起删掉是合理行为Goods的category用PROTECT分类下还有商品时禁止删除分类防止出现商品指向空白分类订单里的user也用PROTECT或SET_NULL用户注销时订单历史必须保留。DecimalField是价格字段的推荐类型数据库层存储的是精确十进制数不会出现FloatField那种 0.1 0.2 ! 0.3 的误差。UniqueConstraint使用fields指定联合唯一之后migrate时会在cart表上创建唯一索引。这个索引一方面保证数据一致性另一方面也加速“查购物车中某个商品是否存在”这种高频查询。真实商城还会把Cart和Order拆成更细的订单项模型但这个五表结构足以撑起一个课程设计级别的商城系统也是大多数“Django 购物商城源码包”里的常见设计。2.3 迁移执行的顺序与 mysqlclient 报错处理模型定义完成后执行下面的命令生成并应用迁移python manage.py makemigrations users goods orders python manage.py migratemakemigrations后面带上 app 名称只对指定模块生成迁移脚本避免把django.contrib自带的模型也混进来migrate会按依赖顺序建表。首次迁移时如果settings.py连的是 MySQL常在这里遇到两个问题。第一个是ModuleNotFoundError: No module named MySQLdb需要安装mysqlclient。在 Ubuntu 或 Debian 上要先装系统依赖再 pip installsudo apt install python3-dev default-libmysqlclient-dev build-essential pip install mysqlclientmacOS 上则先把mysql-connector-c装好再执行相同命令。mysqlclient是编译型库Windows 上经常因为缺少MSVC编译器失败简单方案是换成pymysql在项目__init__.py里加pymysql.install_as_MySQLdb()但生产环境仍推荐mysqlclient的性能。第二个报错是django.db.utils.OperationalError: (1045, Access denied)这说明数据库账号密码或权限不对。先确认 MySQL 里是否创建了对应库和用户CREATE DATABASE shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER shopuserlocalhost IDENTIFIED BY your_password; GRANT ALL PRIVILEGES ON shop.* TO shopuserlocalhost; FLUSH PRIVILEGES;注意utf8mb4是必选项utf8在 MySQL 里不是完整的 UTF-8存储 emoji 或生僻字会报Incorrect string value错误。migrate执行成功后可以用python manage.py showmigrations检查每个 app 的迁移状态全部打上[X]标记说明数据库表结构已建立。拿到带db.sqlite3文件的源码包时可以直接先用 SQLite 跑通再换 MySQL但要注意两者字段类型差异例如AutoField在 SQLite 和 MySQL 中生成的 DDL 完全不同迁移文件通常不通用。3. DRF 接口文档与路由设计把后端能力变成可交接的接口3.1 REST_FRAMEWORK 配置块分页、认证、限流商城系统不只是页面渲染更需要给小程序或前端 App 提供 JSON 接口。Django REST FrameworkDRF是这类源码包中最常用的接口层框架。它的默认配置偏教学化拿到手必须改三个地方# settings.py REST_FRAMEWORK { DEFAULT_PAGINATION_CLASS: rest_framework.pagination.PageNumberPagination, PAGE_SIZE: 10, DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework_simplejwt.authentication.JWTAuthentication, ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticatedOrReadOnly, ], DEFAULT_THROTTLE_RATES: { anon: 30/minute, user: 300/hour, }, DEFAULT_RENDERER_CLASSES: [ rest_framework.renderers.JSONRenderer, ], }DEFAULT_PAGINATION_CLASS决定了商品列表接口返回的是分页结构而不是全量数组PAGE_SIZE控制每页条数商城场景一般 10 到 20 条移动端建议更小。DEFAULT_AUTHENTICATION_CLASSES换成 JWT 后登录接口返回access和refresh两个 token前端在请求头加Authorization: Bearer token即可不再依赖 session 和 cookie。DEFAULT_THROTTLE_RATES限流配置里anon和user分别限制未登录与登录用户访问频率防止爬虫拖垮数据库。注意DEFAULT_RENDERER_CLASSES只保留JSONRenderer可以避免浏览器在调试时显示 DRF 自带的 HTML 页面接口文档由单独工具生成线上环境不需要暴露调试页。3.2 序列化器校验与关联字段展开序列化器决定接口返回什么字段、客户端传什么字段、如何校验。商品列表接口通常需要把分类名直接带出来而不是只给category_id这就要在SerializerMethodField或source参数上做文章。# goods/serializers.py from rest_framework import serializers from .models import Goods, Category class CategorySerializer(serializers.ModelSerializer): class Meta: model Category fields [id, name] class GoodsSerializer(serializers.ModelSerializer): category_name serializers.CharField(sourcecategory.name, read_onlyTrue) sales serializers.SerializerMethodField() class Meta: model Goods fields [id, name, price, stock, category_name, is_on_sale, sales] def get_sales(self, obj): return getattr(obj, total_sales, 0)sourcecategory.name表示该字段从关联对象的name属性取省去了在视图中手动拼数据。SerializerMethodField用来暴露模型上没有的字段比如销量实际值可能来自订单表的聚合结果在视图里用annotate计算后塞给total_sales属性。read_onlyTrue标识这个字段只参与序列化输出不参与反序列化输入客户端传了也会被忽略。校验这块容易被忽略的是价格和库存的类型。价格字段在模型层是DecimalFieldDRF 默认会把它序列化成字符串而不是浮点数目的是防止精度丢失。前端拿到99.00这种字符串通常没问题但如果前端期望数字类型就需要在序列化器里显式加serializers.DecimalField(max_digits10, decimal_places2, coerce_to_stringFalse)。3.3 ViewSet router 的路由注册视图函数写好后路由是另一个容易出问题的环节。大多数源码包用函数视图加api_view装饰器接口多了之后 URL 配置冗余。推荐的写法是 ViewSet 配合 router 自动生成路由。# goods/views.py from rest_framework import viewsets from .models import Goods from .serializers import GoodsSerializer class GoodsViewSet(viewsets.ModelViewSet): queryset Goods.objects.filter(is_on_saleTrue).order_by(-created_at) serializer_class GoodsSerializer http_method_names [get, post]# urls.py from django.urls import path, include from rest_framework.routers import DefaultRouter from goods.views import GoodsViewSet router DefaultRouter() router.register(rgoods, GoodsViewSet, basenamegoods) urlpatterns [ path(api/, include(router.urls)), ]http_method_names [get, post]表示这个视图只接受查询和新增删除和修改接口不暴露避免客户端直接改商品价格或删除商品。queryset里的filter(is_on_saleTrue)是一个默认过滤条件客户端在 GET 请求里传?category3时可以再叠加django_filters或手动在get_queryset里处理。router 会自动生成四个接口GET /api/goods/商品列表POST /api/goods/新增商品GET /api/goods/{id}/商品详情DELETE /api/goods/{id}/被http_method_names禁掉后返回 4053.4 接口文档的形式与状态码约定“接口文档”这部分在源码包里可能是一个 word 文档、一个 Markdown 文件也可能是一份 Postman 导出文件。不管形式如何核心内容要覆盖接口地址、请求方法、请求参数、返回示例、错误码五类信息。DRF 项目可以用drf-spectacular自动生成 OpenAPI Schemapip install drf-spectacular# settings.py INSTALLED_APPS [drf_spectacular] SPECTACULAR_SETTINGS { TITLE: 商城系统 API, VERSION: 1.0.0, SERVE_INCLUDE_SCHEMA: False, }然后在urls.py里挂载文档路由浏览器访问/api/schema/swagger-ui/就能看到可视化的接口页面。自动生成的文档有一个好处字段列表和分析器完全同步不会出现代码改了字段、文档忘记更新。但自动文档不会替你写业务约束比如订单金额上限、库存不足时的错误码这些必须在retrieve或create的代码注释里说明。状态码含义使用场景200查询成功列表、详情接口201创建成功POST 新增订单或地址400参数校验失败缺字段、格式错误、库存不足401未登录未携带 token 或 token 过期403无权限普通用户访问管理接口404资源不存在商品下架或订单号错误状态码约定写入接口文档后前端可以做统一拦截401跳登录页400弹后端返回的错误信息403提示无权限。很多二手源码的问题正在于返回码混乱有的接口返回0表示成功有的返回200还有的直接返回{code: -1}这样交接到别人手里时对接成本非常高。4. 从源码到跑通Python 环境、MySQL 数据库与启动命令4.1 Python 版本与虚拟环境搭建运行 Django 商城项目第一步不是看代码而是确认 Python 版本。Django 4.2 LTS 支持 Python 3.8 到 3.12Django 5.0 要求 Python 3.10 以上。拿到源码后先看requirements.txt里的Django4.2.x这类锁定版本再检查本地python3 --version版本不匹配会出现SyntaxError或ImportError最常见的是高版本 Python 遇到了只支持旧版 Django 的第三方库。python3 -m venv venv source venv/bin/activate python -m pip install --upgrade pippython3 -m venv venv创建的虚拟环境隔离了项目依赖避免把包装到系统 Python 里污染其他项目。激活后命令行提示符会出现(venv)之后的pip install都会安装到这个虚拟环境的site-packages目录下。Windows 上激活命令是venv\Scripts\activate其它步骤相同。注意如果服务器上只有 Python 3.12而项目要求 Django 3.2 mysqlclient 旧版最稳的方案不是硬适配而是用pyenv多装一个 Python 3.10。这一条同时适用于本地开发和后续的宝塔部署场景。4.2 requirements.txt 的正确打开方式源码包里通常有requirements.txt但内容质量参差不齐。有的直接冻住了所有间接依赖上百行有的只有 Django 和 djangorestframework 两行。正确做法是手动维护核心依赖版本用或锁定到已验证版本。Django4.2.13 djangorestframework3.15.1 django-filter24.2 django-cors-headers4.3.1 mysqlclient2.2.4 djangorestframework-simplejwt5.3.1 drf-spectacular0.27.2 redis5.0.4 django-redis5.4.0安装命令pip install -r requirements.txtmysqlclient2.2.4这个版本比较特殊Python 3.12 下也能编译通过如果因为安装它而卡住可以先装pymysql临时顶替。django-cors-headers是前后端分离必须的依赖没有它前端在localhost:5173访问后端localhost:8000会被浏览器 CORS 策略拦截。装完依赖后执行python manage.py check这一步不做数据库操作只检查配置和模型定义是否有明显错误。报TypeError: __init__() got an unexpected keyword argument之类错误多半是依赖版本不匹配回退或升级对应包版本。4.3 数据库连接与字符集、时区设置settings.py里数据库配置决定了连接目标。拿到源码时这个文件里可能写死了一套账号比如 root/123456跑通之前先改成本地实际账号DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: shop, USER: shopuser, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, }, CONN_MAX_AGE: 60, } }同时确认两处配置LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ TrueUSE_TZ True时Django 在 Python 层面统一使用 UTC 时间存储渲染时按TIME_ZONE转换。DateTimeField(auto_now_addTrue)写入数据库的是 UTC 时间前端拿到后要按Asia/Shanghai转一次很多商城项目的时间显示差 8 小时就是这里的问题。如果项目里大量代码直接调用了datetime.datetime.now()更实际的做法是把USE_TZ改成False让数据库存本地时间。提示CONN_MAX_AGE 60表示连接复用 60 秒避免每个请求都建立一次 MySQL 连接Django 默认是 0意为请求结束即关闭连接。高并发场景这个参数收益明显。4.4 最小启动命令序列数据库建好、依赖装好、配置改好之后按顺序执行以下命令python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver 0.0.0.0:8000makemigrations不指定 app 名时扫描全部应用建议第一次整体扫描之后增量只扫描修改过的 app。migrate建表后createsuperuser需要交互输入用户名、邮箱和密码口令要符合常见强度校验规则。最后runserver监听0.0.0.0:8000允许局域网其他机器访问本机调试可以省略0.0.0.0。启动后访问http://127.0.0.1:8000/admin/用刚创建的超管账号登录 Django 后台进入商品表添加一条测试数据再访问http://127.0.0.1:8000/api/goods/看接口返回。这套链路通了说明数据库、模型、序列化器、路由四个层全部健康。商城项目跑不起来时先回到这个最小链路验证而不是去翻前端代码或接口文档。5. 跑通之后迁移排错、N1 查询与宝塔部署的三个检查点5.1 用 select_related 干掉商品列表的 N1 查询商城首页的商品列表每个商品要带出分类名。如果序列化器里有sourcecategory.nameDjango ORM 默认在每个商品的序列化过程中再查一次分类表显示 20 个商品就会额外执行 20 条 SQL。解决方式是在get_queryset里加select_relatedclass GoodsViewSet(viewsets.ModelViewSet): queryset Goods.objects.select_related(category).filter(is_on_saleTrue).order_by(-created_at)select_related生成LEFT OUTER JOIN一次查询把分类字段取回来适合ForeignKey和OneToOneField关系。prefetch_related用于ManyToManyField比如商品的多图列表两者不要混用。验证是否生效可以在页面打印 SQL 数from django.db import connection print(len(connection.queries))在runserver的DEBUGTrue模式下Django 调试工具栏或connection.queries能直接看到执行过的 SQL 条数改造前后对比非常直观。5.2 商品详情接口的缓存伪装商城首页和商品详情是高访问量接口每次请求都打数据库一旦订单表膨胀关联查询会明显变慢。用django-redis给接口加一层读缓存语义上不用改前端代码pip install django-redis# settings.py CACHES { default: { BACKEND: django_redis.cache.RedisCache, LOCATION: redis://127.0.0.1:6379/1, OPTIONS: {CLIENT_CLASS: django_redis.client.DefaultClient}, } }from django.core.cache import cache def get_goods_detail(goods_id): cache_key fgoods_detail:{goods_id} data cache.get(cache_key) if data is None: data Goods.objects.select_related(category).get(idgoods_id) cache.set(cache_key, data, 60 * 5) return datacache_key带goods_id做粒度区分超时时间 5 分钟因为商品上架状态和价格不需要秒级实时。更新商品时要用cache.delete(fgoods_detail:{goods_id})手动失效否则后台改了价格前端 5 分钟内看到的还是旧价格。5.3 宝塔部署时的三个本机不会出现的配置本地跑通不代表部署到 Linux 服务器能直接跑。用宝塔面板部署 Django 项目时最容易出问题的三个点第一静态文件路径。settings.py里必须有STATIC_ROOT BASE_DIR / staticfiles执行python manage.py collectstatic收集全部静态文件后在 nginx 配置里把它指向本地目录。漏掉这一步/admin/页面会显示全无样式的裸 HTML。第二ALLOWED_HOSTS。本机调试时可以是[*]部署后在 DEBUG 关闭的情况下必须改为实际域名或公网 IP否则 Django 直接抛DisallowedHost异常。第三uWSGI 或 Gunicorn 的 socket 文件权限。mysite.sock如果权限不足nginx 访问会报502 Bad Gateway运行目录给当前用户授权socket 文件权限至少 755。最后检查python manage.py check --deploy它会列出一堆安全提示包括 DEBUG 必须为 False、SECRET_KEY不要硬编码、CSRF 和安全头中间件是否启用。这些项不全影响运行但会影响生产安全等级评定按提示逐条改完再开放公网端口。实际部署时建议先用python manage.py runserver 0.0.0.0:8000在服务器上确认项目能启动再切到 gunicorn 加 nginx 反向代理两步相隔越久越容易混淆问题是出在 Django 还是反向代理。宝塔面板中创建 Python 项目时选择对应的虚拟环境并把启动命令设为gunicorn -w 4 -b 127.0.0.1:8000 shop.wsgi:application其中4是 worker 数一般为 CPU 核数加 1。本文还有配套的精品资源点击获取
返回列表