ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Django+Vue的前后端分离购物系统设计与实现全解析

基于Django+Vue的前后端分离购物系统设计与实现全解析 简介一个基于Python Django与Vue实现的前后端分离线上购物系统毕业设计项目面向计算机相关专业学生、课程设计与毕业设计场景注重还原商城商品展示、购物车、订单处理等核心业务帮助使用者掌握前后端联调与DjangoVue项目搭建思路。压缩包共678个文件约28.64MB包含前端Vue组件、JavaScript交互脚本、样式与SVG/PNG图片资源后端Python业务逻辑与数据库SQL脚本以及安装/运行/构建相关的bat批处理和使用文档。项目内含可直接运行的源码、数据库及配置说明并配套一键启动脚本便于快速部署体验或在此基础上二次开发扩展用户管理、支付流程等功能模块。已有332人学习或下载尤其适合需要完整毕设参考或希望系统学习前后端分离商城开发的人群。1. 前后端分离线上购物系统这个毕业设计真正要交付的是什么基于 PythonDjangoVue 的前后端分离线上购物系统是毕业设计里出现频率几乎与图书管理系统并列的题目。原因直白它同时覆盖商品展示、登录、购物车、下单与管理后台每个点都被答辩老师高频追问又都有现成方案可查。但这个题目的分差也最大。低分版是 Django 渲染模板加几页 Vue 拼凑前后端分离只停在目录层面高分版都能当场讲清哪个数据放哪张表、哪个请求经过哪一层、失败时回滚了什么。这里把这条可复现的路径完整拆一遍。适合正在做该课题的本科生以及想快速理解前后端分离电商落地方案的全栈开发。下面从接口契约讲到数据库、部署与验收每个环节都有可直接抄走的代码和参数。2. Django 后端与 Vue 前端的职责切分先定接口契约再写页面2.1 为什么购物系统值得用前后端分离架构而非 Django 模板Django 自带模板系统理论上一个项目内就能完成全部页面渲染。购物系统仍然值得拆开原因是页面形态差异大用户端是频繁切换的列表、详情与购物车管理端是表格和表单后续大概率要接小程序或移动端。把数据以 JSON 形式通过 API 输出前端只负责渲染与交互是这套架构在工程上的核心动机。理解前后端分离不能只看Django 与 Vue 是否分属两个目录关键在于接口契约。前后端各自开发接口字段一旦变更就互相踩脚。我一般建议开工第一周先写一版接口清单把方法、入参、返回结构定死再分头开发这也是前后端分离项目实战里最容易被跳过、却最影响进度的环节接口方法用途鉴权/api/products/GET商品分页列表公开/api/products/{id}/GET商品详情公开/api/cart/GET / POST购物车查询与添加需登录/api/orders/GET / POST订单列表与创建需登录/api/token/POST获取 JWT 令牌公开这个表同时是答辩 PPT 里系统架构一页的素材前端每个页面组件对应一到两个接口后端每个 app 对外暴露一组资源路由。契约先行之后前后端分离才不只是把代码放进两个文件夹。2.2 Django 只暴露 API模型、序列化器与视图集的搭建步骤后端第一步是创建 app。常见做法是按业务域拆分而不是按页面拆分商品、购物车、订单各一个 app后续加优惠券或支付模块时不需要改动已有代码。django-admin startproject config . python manage.py startapp goods python manage.py startapp cart python manage.py startapp orderconfig 是项目配置目录goods/cart/order 是业务 app。新手常见的错误是把所有逻辑堆进同一个 app导致 models.py 上千行、迁移文件互相依赖、答辩时完全讲不清结构。按业务域拆分后各 app 的 models.py、serializers.py、views.py 保持独立这是 Django 项目能持续维护的前提。以商品模块为例模型只描述数据序列化器负责把模型转成前端可消费的 JSON视图集负责接收请求并处理分页# goods/models.py from django.db import models class Category(models.Model): name models.CharField(max_length50, uniqueTrue) parent models.ForeignKey( self, nullTrue, blankTrue, on_deletemodels.CASCADE, related_namechildren ) class Product(models.Model): category models.ForeignKey(Category, related_nameproducts, on_deletemodels.PROTECT) name models.CharField(max_length128) price models.DecimalField(max_digits10, decimal_places2) stock models.PositiveIntegerField(default0) cover models.URLField(blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: ordering [-created_at]# goods/serializers.py from rest_framework import serializers from .models import Product class ProductSerializer(serializers.ModelSerializer): category_name serializers.CharField(sourcecategory.name, read_onlyTrue) class Meta: model Product fields [id, name, price, stock, cover, category_name]# goods/views.py from rest_framework import viewsets from rest_framework.pagination import PageNumberPagination from .models import Product from .serializers import ProductSerializer class ProductPagination(PageNumberPagination): page_size 12 page_size_query_param page_size class ProductViewSet(viewsets.ReadOnlyModelViewSet): queryset Product.objects.select_related(category).all() serializer_class ProductSerializer pagination_class ProductPagination几个需要讲清楚的选择价格用 DecimalField 而非 FloatField浮点累加会产生 0.0000001 级误差分类外键用 PROTECT防止分类下有商品时被误删列表加 select_related 一次性查出分类名避免 N1 查询。视图集注册到路由后DRF 自动生成 list 与 retrieve 两个只读接口# config/urls.py from django.urls import path, include from rest_framework.routers import DefaultRouter from goods.views import ProductViewSet router DefaultRouter() router.register(rproducts, ProductViewSet, basenameproduct) urlpatterns [ path(api/, include(router.urls)), ]访问 /api/products/1/ 就能拿到单条商品的 JSON不需要额外写详情视图。商品模块不开放写接口写操作放到 Django 管理后台这是购物系统读多写少的典型取舍。2.3 Vue 侧的分层api 层、路由参数与状态管理Vue 端我一般按 views、components、api、store 四层组织views 放页面级组件components 放可复用片段api 层统一封装请求store 放登录态与购物车这类跨页面状态。页面里不直接写 axiostoken 和购物车数量也不散落在各组件里这是后期维护的分界线。商品详情页需要拿到当前商品的 id用 vue-router 的参数传递是标准做法// src/router/index.js { path: /product/:id, name: ProductDetail, component: () import(/views/product/ProductDetail.vue) }// src/views/product/ProductDetail.vue import { useRoute } from vue-router import { getProduct } from /api/product const route useRoute() const product ref(null) getProduct(route.params.id).then(res { product.value res })route.params.id 取到的就是路由里 :id 占位符的值这样详情、加购、结算全链路只靠一个 id 串联前端不需要保存整条商品数据。购物车建议放进 store 而不是各页面各自维护因为导航栏角标、购物车页、结算确认页都可能读取同一份数据。用 Pinia 定义 cart storeitems 作为 stateaddItem 与 removeItem 放 actions各组件通过 store 读写天然响应式。前端只管展示最终金额与库存校验一律以后端订单接口的返回为准这个边界要在设计文档里写明。3. 数据库设计与 Django ORM 落地商品、购物车、订单的联动3.1 核心表的字段设计与关联关系购物系统的核心表通常不超过六张但表间关系必须画得清。数据库课程设计的评分点之一就是能否解释订单为什么拆主表和明细表一张订单可能包含多个商品总价与明细必须分开存否则后续调整商品价格会污染历史订单。表关键字段说明auth_userusername, passwordDjango 内置用户表扩展字段用 OneToOne 关联goods_categoryname, parent_id分类自关联支持两级菜单goods_productname, price, stock, category_id商品主表price 用 Decimal(10,2)cart_cartitemuser_id, product_id, quantity购物车项userproduct 加唯一约束order_orderuser_id, total_amount, status订单主表status 存状态码order_orderitemorder_id, product_id, unit_price, quantity订单明细unit_price 是下单快照价订单明细里必须冗余一份 unit_price这是很多初学者会漏掉的设计。下单后商品价格可能调整历史订单金额不能跟着变所以下单那一刻的价格要单独存一份。这是反范式的合理取舍答辩时主动讲出来通常是加分项。如果不打算用默认 SQLite 而是切 MySQL先装驱动再改配置pip install mysqlclient# config/settings.py DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: shop, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }mysqlclient 在 Windows 上偶尔装不上常见做法是改用 pymysql并在项目__init__.py里执行pymysql.install_as_MySQLdb()。切换引擎后必须删除旧 SQLite 文件再重新执行迁移用残留旧表判断迁移成功是高频踩坑点。3.2 用事务保证下单扣库存的原子性购物车的增删改查可以直接用 DRF 的 ModelViewSet 生成但下单不能。下单是跨表操作创建订单主表、创建订单明细、扣减库存、清空购物车任何一个环节失败都要求整体回滚否则会出现库存扣了订单没建或相反的情况。用 Django 的 transaction 原子块处理这件事# order/services.py from decimal import Decimal from django.db import transaction from .models import Order, OrderItem from goods.models import Product transaction.atomic def create_order(user, items): order Order.objects.create(useruser, statuspending) total Decimal(0.00) for item in items: product Product.objects.select_for_update().get(pkitem[product_id]) if product.stock item[quantity]: raise ValueError(f{product.name} 库存不足) product.stock - item[quantity] product.save(update_fields[stock]) OrderItem.objects.create( orderorder, productproduct, unit_priceproduct.price, quantityitem[quantity] ) total product.price * item[quantity] order.total_amount total order.save(update_fields[total_amount]) return order三个关键参数select_for_update 在事务内给商品行加锁防止两个并发请求同时读到相同库存导致超卖update_fields 只回写变更字段避免整行覆盖总价用 Decimal 累加而不是 float。库存不足时抛异常atomic 装饰器自动回滚整个事务前面创建的订单一并撤销不需要手动 delete。3.3 删除对象的边界与订单状态的查询更新购物系统里删除不能照搬 Django 教程中的 delete()。用户取消订单正确做法不是删掉 order 记录而是把 status 改为 cancelled 并把库存加回去——历史订单属于账务数据物理删除会让对账无据可查。这也是 Django 执行查询与删除对象这个知识点在业务里的真实边界。def cancel_order(order_id): order Order.objects.select_for_update().get(pkorder_id) if order.status not in (pending, paid): raise ValueError(当前状态不可取消) order.status cancelled order.save(update_fields[status]) for item in order.items.all(): item.product.stock item.quantity item.product.save(update_fields[stock])订单状态用 TextChoices 限定取值集合避免脏数据class Order(models.Model): class Status(models.TextChoices): PENDING pending, 待支付 PAID paid, 已支付 SHIPPED shipped, 已发货 COMPLETED completed, 已完成 CANCELLED cancelled, 已取消 status models.CharField(max_length16, choicesStatus.choices, defaultStatus.PENDING)按用户加状态过滤是最高频的查询路径建议在 Meta 里加复合索引indexes [models.Index(fields[user, status])]否则订单量上来后会全表扫描。数据量小时看不出差别但索引设计是数据库方向答辩必问的落点。4. 联调与部署跨域、JWT 鉴权、打包与 Nginx 配置4.1 django-cors-headers 与 JWT前后端分离的鉴权闭环前端跑在 5173 端口、Django 跑在 8000 端口时浏览器会拦截跨域请求。常见做法是加 django-cors-headers而不是在视图里手动拼 Access-Control-Allow-Origin——后者处理预检请求OPTIONS容易漏。注意 CorsMiddleware 要放在 CommonMiddleware 之前# config/settings.py INSTALLED_APPS [ corsheaders, rest_framework, rest_framework_simplejwt, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # 注意置于 CommonMiddleware 之前 ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, http://localhost:8080, ] REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework_simplejwt.authentication.JWTAuthentication, ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticatedOrReadOnly, ], }提示生产环境不要用 CORS_ALLOW_ALL_ORIGINSTrue那等于允许任意站点调用你的 API。登录接口直接用 simplejwt 内置路由不需要手写视图# config/urls.py from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView urlpatterns [ path(api/token/, TokenObtainPairView.as_view(), nametoken_obtain_pair), path(api/token/refresh/, TokenRefreshView.as_view(), nametoken_refresh), ]令牌有效期是可调参数建议显式配置而不是用默认值from datetime import timedelta SIMPLE_JWT { ACCESS_TOKEN_LIFETIME: timedelta(hours2), REFRESH_TOKEN_LIFETIME: timedelta(days7), }前端拿到 access 与 refresh 两个 token 后把 access 存 localStorage每次请求在 axios 拦截器里附加 Authorization 头。这就是 vue 前后端分离请求 token 处理的完整链路。刷新逻辑单独封装收到 401 时先用 refresh token 换取新 access token成功则重放原请求失败才强制回登录页。4.2 Vue 开发阶段的代理配置与环境切换跨域配好之后开发阶段更顺手的方式是让 Vite 把 /api 开头的请求代理到 Django浏览器里只有 5173 一个源Network 面板看到的仍是相对路径// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } })changeOrigin 会改写请求头里的 Host避免后端校验来源时误判。接口地址不要写死在页面里用环境变量区分开发与生产# .env.production VITE_API_BASE/apirequest.js 里写baseURL: import.meta.env.VITE_API_BASE || /api打包后想换接口域名只需要改这个文件重新构建不用进源码全局替换。4.3 打包部署与 vue 打包后布局异常的排查前端构建产物是纯静态文件用 Nginx 托管Django 只监听回环地址由 Nginx 反代。后端启动用 gunicornnpm run build # 产物在 dist/ pip install gunicorn gunicorn config.wsgi:application -b 127.0.0.1:8000 --workers 3Nginx 配置里最容易出错的是前端路由的 history 模式。刷新 /product/3 这个地址时Nginx 会去磁盘找 product/3 目录必须用 try_files 回退到 index.htmlserver { listen 80; server_name shop.example.com; root /var/www/shop/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }vue 打包后布局异常通常来自两个根源一是静态资源用了绝对路径 /assets/xxx部署到子目录时全部失效解决方式是在 vite.config.js 里设置base: ./二是 Django admin 的静态文件没有收集单独访问管理后台时样式全丢需要配置 STATIC_ROOT 后执行python manage.py collectstatic并在 Nginx 里把 /static/ 指到该目录。定位这类问题时先开开发者工具 Network 面板看 CSS/JS 是 404、MIME 报错还是路径偏移多数情况一眼就能分清根源。生产环境前端与 API 同域之后4.1 里的 CORS_ALLOWED_ORIGINS 可以收紧为只保留后台管理用的地址。5. 答辩与交付前必查的 6 项验收清单从压缩包解出来的项目验收重点不是功能跑没跑通而是换一台干净机器能否按文档复现。以下 6 项是高分项目必备的检查按顺序做每项都对应答辩时一个易被追问的点检查项操作说明依赖清单新建 venv 执行pip install -r requirements.txt后 runserver缺包会当场翻车清单必须手写完整数据库重建删库后依次 makemigrations、migrate确认从零建表成功验证不依赖本机残留数据初始数据确认 sql 或 fixture 能导入分类与示例商品演示不能从空分类开始前端构建npm install npm run build静态预览 dist验证依赖锁定与打包脚本全链路闭环注册新用户到登录、加购、下单、后台改状态注意 JWT 过期时间别设太短管理后台createsuperuser 后登录 django admin确认数据可编辑顺带确认 admin 样式未丢失其中数据库重建最容易漏本机库里残留着历次调试的脏表migrate 报 already exists、ORM 查询报字段缺失基本都是在没验证过迁移脚本的新库上才会暴露。把导出的 sql 文件也列入交付物评审时就能展示从空库到可用系统的全过程这是设计文档之外最有说服力的运行证据。答辩讲解建议按这个顺序先用路由表讲整体架构前端有哪些页面、后端挂了哪些 API再拿订单表讲主表与明细表的冗余设计最后用下单接口现场演示事务回滚——把库存改成 0下单报错后查库确认没有半截订单。三分钟覆盖架构、数据库、并发三个高分点比念 PPT 有效。时间充裕再加一个低成本加分项给 django admin 装 simpleui 皮肤pip install 后加入 INSTALLED_APPS 即可管理端界面观感立马上一个台阶也是文档里最直观的截图素材。本文还有配套的精品资源点击获取
返回列表