恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Django+Vue线上超市购物系统全栈实战:从选型到部署复盘

  • 首页
  • 资讯中心
  • /
  • Django+Vue线上超市购物系统全栈实战:从选型到部署复盘

相关资讯

claude-mem:为Claude Code打造AI编程助手的长期记忆层 2026/10/7 22:20:43
Agent技能体系实战:从零搭建LLM应用的可复用技能层 2026/10/7 22:20:43
对话上下文管理实战:context-mode设计与落地 2026/10/7 22:15:42

最新资讯

市值冲破万亿!智谱GLM-5.2开源即登顶,TaoToken统一Key实测Hugging Face权重拉取与MIT License商用边界
我把 AI 最容易改坏真实 App 的地方,整理成了 skills:TaoToken 统一 Key 接入实战
SDKMAN! CLI:基于 Unix 的多版本软件开发工具包管理器实战指南
AI编程工具默认上传工作区?一份数据信任审计清单帮你把关
Redis 之 【常见面试题总结】(从数据类型到高可用、缓存一致性全解析)
swiftui-ui-patterns - split-views

今日推荐

context-mode实战指南:从全量塞入到结构化裁剪与检索增强
大模型对话上下文管理实战:三种模式与Token优化
抖音用户主页视频数据爬虫详解:点赞、收藏、分享字段抓取与 TaoToken 统一 Key 配置

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Django+Vue线上超市购物系统全栈实战:从选型到部署复盘

发布时间:2026/10/7 22:20:43
Django+Vue线上超市购物系统全栈实战:从选型到部署复盘 做这个基于Python Vue的线上超市购物系统前前后后大概花了三周。从技术选型时的纠结到环境配置踩坑再到最后前后端联调和部署整个过程走下来收获很大。今天把这套“Python后端 Vue前端”的组合做一个系统的复盘重点说清楚每一步为什么这么做、怎么做最省力以及哪些坑是我替你们踩过的。这套系统最大的价值在于它并不小众——电商购物是目前需求最稳定的业务场景之一涉及商品管理、用户认证、购物车、订单事务、库存并发这些核心模块几乎涵盖了Web开发的所有基本功。如果你准备做毕业设计、找工作练手项目或者帮身边的小超市做一套简单线上端这套方案的参考价值都很高。1. 项目定位与技术选型思路1.1 一个线上超市到底需要什么功能很多人一听“超市购物系统”就以为做个商品列表加一个加入购物车按钮那基本是错误预估。真实跑一个可用的线上超市业务链路比想象的长得多用户浏览、搜索、筛选商品加入购物车结算生成订单支付成功后减库存管理员在后台维护商品信息、处理订单状态。每一步都不是独立的背后是状态流转和数据库一致性的问题。我在实际设计时把系统拆成了两个端用户端前台商品展示、商品搜索、购物车管理、订单生成与查看、模拟支付管理端后台商品上下架、库存管理、订单状态更新、销售数据统计其中订单模块是整个系统的核心因为它牵扯到用户、商品、库存多张表的联动。你加一件商品进购物车很简单但“下单”这个动作要同时处理三件事扣减库存、生成订单主表记录、生成订单明细快照。这三件事必须放在同一个数据库事务里否则很容易出现“订单建好了但库存扣了两次”或者“库存扣了但订单没生成”的脏数据。1.2 Django还是Flask我的选择逻辑标题里同时出现了Django和Flask那我正好把这两个的选择逻辑展开说说。我在这套系统里最后选了Django而不是Flask原因有三个第一个是自带的内容多省去大量重复造轮子。线上超市系统需要的不是花哨功能而是稳定性。Django自带ORM、Admin后台、用户认证、CSRF防护、数据库迁移机制几乎每一项都是电商项目的基础需求。尤其是Admin后台开发期就能直接往里录入商品数据连前端管理页面都省了。如果换成Flask这些全得自己找第三方库拼装光是权限认证和模型迁移就够折腾一阵子。第二个是数据库事务和并发控制的支持更成熟。订单模块必须使用事务Django的transaction.atomic()用起来非常顺手。Flask搭配SQLAlchemy也能做事务但需要手动管理session对于新手来说出错率更高。第三个是项目结构规范带来的安全感。Django的MTV结构强制你按app拆分模块goods、user、order各归各位代码组织清晰后期扩展不会变成一团乱麻。Flask则全靠开发者自律项目一大就容易出现视图函数遍地走的情况。但反过来如果你只是做一个非常轻量的接口服务或者想学一学微服务里最小的一个服务单元怎么办Flask就很适合启动快、依赖少、心智负担低。我的建议很直接做完整业务系统优先Django做小接口或纯API优先Flask。从实际情况看大多数自学者最终在网上找到的资源都是Django的完整项目案例。这也从侧面说明Django的生态和社区积累更适合购物系统这种重业务逻辑的场景。1.3 为什么坚持用Vue做前后端分离现在的Web开发基本都跑在前后端分离这条路上。后端只提供JSON数据接口前端用Vue做视图渲染和交互两边各干各的用HTTP协议沟通。原因说白了就一句话前端体验更流程、页面更互动后端更专注于逻辑开发效率其实更高。选Vue而不是React或Angular主要是Vue的上手曲线比较平缓。Vue的模板语法接近原生HTML一个写过静态页面的前端很快就能转型。而且Vue 3 Vite的组合开发时热更新速度快到飞起改一行代码浏览器几乎即时刷新这对调试购物车这类联动交互非常友好。这套方案里前端负责渲染商品列表、管理购物车状态、引导结算流程后端负责提供商品数据、校验用户登录、生成订单。两边的职责边界清晰出问题时定位也快。1.4 PyCharm在这套组合里的角色有读者可能会问Python用PyCharm前端用VSCode这样混搭行不行当然行。但我的实际体验是PyCharm这一个工具能把后端开发全流程覆盖住。PyCharm对Django有专门的支持新建项目时直接选Django模板自动设置好manage.py、settings.py的目录结构。虚拟环境的管理也是图形化操作一个项目配一个venv依赖版本不打架。调试器更是良心右键打个断点就能看到Django视图函数里Every变量值的变化排查“接口返回500”这类问题效率极高。有一点要坦白说我这里用的是PyCharm社区版免费且完全够用。如果你用的是专业版当然更舒服——Django调试支持更完善、还能直接跑终端命令。但我不鼓励在这个环节花额外的钱或走什么捷径社区版加中文插件日常开发已经非常顺了。2. 数据库设计与环境搭建2.1 先画清楚核心实体关系动手写代码之前数据模型一定要先想透。很多人上来就写models.py写到一半发现要加字段再回头改迁移文件折腾得很难受。我建议先在纸上把实体关系列清楚。线上超市系统里核心实体有五张表用户表、商品表、购物车表、订单表、订单明细表。关系是这样的用户表和购物车表一对多一个用户可以有多个购物车记录商品表和购物车表一对多一个商品可以被多个用户加进购物车用户表和订单表一对多一个用户可以有多笔订单订单表和订单明细表一对多一笔订单包含多个商品明细关键点在于为什么购物车和订单要分开建表因为购物车是临时的用户随时会修改数量甚至清空订单是永久的一旦生成就不能被用户随手改掉。订单明细表里需要保存一份“商品快照”——商品名、下单时单价这些信息必须固定在订单里否则商家那边商品改价或删除后历史订单就全乱了。2.2 后端环境与Django项目初始化我使用的是Python 3.10配合Django 4.2 LTS版本这个组合比较成熟。环境搭建的步骤直接给你复现一遍# 创建虚拟环境PyCharm里可以直接操作 python -m venv venv source venv/bin/activate # Windows下执行 venv\Scripts\activate # 安装后端依赖 pip install django4.2 pip install djangorestframework pip install django-cors-headers pip install djangorestframework-simplejwt然后创建Django项目和appdjango-admin startproject supermarket cd supermarket python manage.py startapp goods python manage.py startapp user python manage.py startapp order python manage.py startapp cart这里每个app各司其职goods管商品user管用户cart管购物车order管订单。pyCharm里打开项目之后直接Run manage.py Task也能执行同样的迁移命令。记得在settings.py里把rest_framework、corsheaders和四个app注册进去。特别注意corsheaders中间件要放在CommonMiddleware前面不然跨域配置不起作用。别问我怎么知道的都是踩出来的经验。2.3 数据模型落地每个字段都有讲究我直接贴核心的数据模型代码你会发现每个字段的选型都是有原因的# goods/models.py from django.db import models class Goods(models.Model): class CategoryChoices(models.TextChoices): FRESH fresh, 生鲜 DRINK drink, 饮料 SNACK snack, 零食 DAILY daily, 日用 name models.CharField(商品名称, max_length100) cover models.ImageField(封面图, upload_togoods/, blankTrue) price models.DecimalField(价格, max_digits10, decimal_places2) stock models.IntegerField(库存, default0) sales models.IntegerField(销量, default0) category models.CharField(分类, choicesCategoryChoices.choices, max_length10) desc models.TextField(描述, blankTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Meta: ordering [-created_at]# order/models.py from django.db import models from django.contrib.auth.models import User from goods.models import Goods class Order(models.Model): class Status(models.TextChoices): CREATED created, 待付款 PAID paid, 已付款 DELIVERED delivered, 已发货 COMPLETED completed, 已完成 CANCELED cancelled, 已取消 user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameorders) total_amount models.DecimalField(总金额, max_digits10, decimal_places2) status models.CharField(状态, choicesStatus.choices, defaultStatus.CREATED, max_length10) address models.CharField(收货地址, max_length255) created_at models.DateTimeField(下单时间, auto_now_addTrue) class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems) goods models.ForeignKey(Goods, on_deletemodels.SET_NULL, nullTrue) goods_name models.CharField(商品名称快照, max_length100) goods_price models.DecimalField(商品单价快照, max_digits10, decimal_places2) quantity models.IntegerField(数量, default1)有几个容易忽略的点单独强调一下价格字段为什么用DecimalField而不是Float因为浮点数在计算机里是二进制表示的0.10.2会变成0.30000000000000004涉及钱的运算绝对不能让它出现这种误差。DecimalField用十进制存储虽然速度略慢但精度绝对可靠。订单明细里为什么要冗余goods_name和goods_price这就是上面提到的商品快照。下单时把名称和单价复制过来之后商品改名、调价都不影响历史订单的数据正确性。用数据库行话说这是通过冗余换取一致性。on_delete为什么有的用CASCADE有的用SET_NULL用户注销了他的订单应该一起删所以用CASCADE商品被删除了订单明细里的快照字段仍然要留着只有外键字段会置空所以用SET_NULL。2.4 Vue环境初始化前端我用的Vue 3 Vite Pinia Vue Router组合这套已经是目前Vue项目的主流标配。npm create vitelatest frontend -- --template vue cd frontend npm install npm install vue-router4 pinia axios初始化完的目录结构里我会习惯把项目按模块划分src/views里放页面组件Home.vue、Cart.vue、Login.vue等src/api里放接口请求模块src/stores里放Pinia状态存储src/router里放路由配置。Vite的好处是开发服务器启动快依赖预构建做得好不用像老Webpack项目那样每次启动等半天。再加上热更新改完组件立刻就能看到效果调试前端时心情都好了不少。3. 后端接口与核心业务实现3.1 商品列表分页与搜索接口线上超市的商品会越来越多不可能把所有商品一次性返回给前端。我做了分页 搜索 分类筛选三合一接口前端传page、category和search参数就行。# goods/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.pagination import PageNumberPagination from django.db.models import Q from .models import Goods from .serializers import GoodsSerializer class GoodsListAPIView(APIView): def get(self, request): queryset Goods.objects.all() category request.query_params.get(category) search request.query_params.get(search) if category: queryset queryset.filter(categorycategory) if search: queryset queryset.filter(Q(name__icontainssearch) | Q(desc__icontainssearch)) paginator PageNumberPagination() paginator.page_size 12 page paginator.paginate_queryset(queryset, request) serializer GoodsSerializer(page, manyTrue) return paginator.get_paginated_response(serializer.data)分页参数这里设置了page_size12对应前端一排显示4个商品、一共3排的布局。接口返回的响应体里会自动带上count、next、previous三个字段前端展示“共多少件商品”和“上一页/下一页”的数据直接就有来源了。3.2 用户注册登录与Token方案前后端分离场景下传统的Session机制不太合适因为前端和后端很可能部署在不同的域名或端口下。我选了JWTJSON Web Token方案具体用的是djangorestframework-simplejwt这个库配置零成本、文档清晰。settings.py里做三件关键配置REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: ( rest_framework_simplejwt.authentication.JWTAuthentication, ), } from datetime import timedelta SIMPLE_JWT { ACCESS_TOKEN_LIFETIME: timedelta(days1), REFRESH_TOKEN_LIFETIME: timedelta(days7), }Access Token有效期设为1天的原因很简单用户逛线上超市属于高频操作如果token半小时就过期用户结账前还得重新登录体验很差。7天的Refresh Token则保证用户在过期后可以通过刷新接口无缝续期不用频繁输密码。用户登录成功后后端返回一对tokenaccess和refresh。前端把access存到localStorage或者Pinia里后续每个请求都在Header里带上Authorization: Bearer token。前端封装axios拦截器时统一在这个位置处理token注入和401跳转后面会有详细说明。3.3 购物车接口设计购物车在Django里的模型设计我用了最直观的“一个用户一行记录对应一个商品、带数量字段”的方案。这样增删改查都很好实现用户点击加购时先查一下购物车表里有没有同一个用户同一个商品的历史记录如果有就直接把数量加一否则就新建一条。# cart/models.py from django.db import models from django.contrib.auth.models import User from goods.models import Goods class CartItem(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namecart_items) goods models.ForeignKey(Goods, on_deletemodels.CASCADE, related_namecart_items) quantity models.IntegerField(数量, default1) checked models.BooleanField(是否选中, defaultTrue) created_at models.DateTimeField(添加时间, auto_now_addTrue) class Meta: unique_together (user, goods)unique_together这个约束非常关键它能保证同一个用户加同一个商品时数据库层面不会出现两条重复记录防住了并发情况下“连点两次加购按钮变成两行记录”的脏数据。当然真正减少脏数据的做法是在视图函数里先查询再创建并用事务保证原子性。购物车的接口我提供了四个获取购物车列表、添加商品、修改数量、删除记录。前端每次刷新购物车页面时调用获取列表接口后端根据当前登录用户过滤数据。这里用到了request.user它依赖于JWT认证成功后自动把用户注入到request对象里的机制。3.4 订单创建事务、库存与状态订单模块是整个系统的核心也最值得说道。创建订单的逻辑流程是这样的前端从购物车勾选状态的数据里把选中商品的id列表、数量以及收货地址一起发给后端后端循环处理每一个商品检查库存是否充足如果库存不足直接把整个订单创建事务回滚返回错误信息如果库存充足扣减库存、增加销量、创建订单和订单明细最后清空购物车中对应的商品记录# order/views.py from django.db import transaction from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from cart.models import CartItem from goods.models import Goods from .models import Order, OrderItem class CreateOrderAPIView(APIView): permission_classes [IsAuthenticated] transaction.atomic def post(self, request): cart_ids request.data.get(cart_items, []) address request.data.get(address, ) if not cart_ids or not address: return Response({detail: 参数不完整}, statusstatus.HTTP_400_BAD_REQUEST) cart_items CartItem.objects.filter(id__incart_ids, userrequest.user, checkedTrue) total 0 order Order.objects.create(userrequest.user, total_amount0, addressaddress) for item in cart_items: goods item.goods if goods.stock item.quantity: return Response( {detail: f商品 {goods.name} 库存不足}, statusstatus.HTTP_400_BAD_REQUEST ) goods.stock - item.quantity goods.sales item.quantity goods.save() OrderItem.objects.create( orderorder, goodsgoods, goods_namegoods.name, goods_pricegoods.price, quantityitem.quantity ) total goods.price * item.quantity order.total_amount total order.save(update_fields[total_amount]) cart_items.delete() return Response({order_id: order.id, total: total}, statusstatus.HTTP_201_CREATED)看到这里面几个重点了吗第一个是transaction.atomic装饰器的作用。有了它函数里任意一步出错前面的数据库改动全部自动回滚。比如第十个商品库存不足时前九个商品已经扣减的库存会被全部恢复订单记录也会回滚这样数据永远是一致的状态。第二个是goods.stock - item.quantity直接读取后修改。这种写法在高并发下会有问题后面第五章我会专门讲怎么用乐观锁解决超卖问题。第三个是OrderItem创建时同时写入了goods_name和goods_price的冗余快照。这样订单明细不依赖商品表当前的值历史订单永远是历史价格。订单状态的流转我设计的简单而完整。前端展示“待付款-已付款-已发货-已完成”的状态流管理员每次更新状态只需修改Order的status字段。模拟支付就是前端发起一个支付请求后端直接把订单状态从created更新为paid。4. 前端页面与接口联调4.1 路由与页面骨架前端页面我把它们组织成了登录页、商品列表页、商品详情页、购物车页、订单结算页、订单列表页这几个核心视图。路由配置用了Vue Router 4的合理写法按需懒加载页面组件// router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /, component: () import(../views/Home.vue) }, { path: /login, component: () import(../views/Login.vue) }, { path: /cart, component: () import(../views/Cart.vue), meta: { requiresAuth: true } }, { path: /order, component: () import(../views/OrderConfirm.vue), meta: { requiresAuth: true } }, { path: /orders, component: () import(../views/OrderList.vue), meta: { requiresAuth: true } }, ] const router createRouter({ history: createWebHistory(), routes }) router.beforeEach((to, from, next) { if (to.meta.requiresAuth !localStorage.getItem(access_token)) { next(/login) } else { next() } }) export default router路由守卫这里的实现很实用购物车和订单这类页面必须登录才能访问前端通过localStorage里有没有token来判断。这是一个初级的防线——真正的安全校验全部在后端接口的permission_classes里完成前端守卫只是改善体验防止用户看到接口报错。4.2 axios封装与请求拦截axios是整个前端和后端沟通的桥梁我把所有请求统一封装在一个api模块里。全局配置好baseURL开发环境指向http://127.0.0.1:8000然后利用拦截器把token自动加进请求头// api/http.js import axios from axios import router from ../router const http axios.create({ baseURL: http://127.0.0.1:8000, timeout: 10000 }) http.interceptors.request.use(config { const token localStorage.getItem(access_token) if (token) { config.headers.Authorization Bearer ${token} } return config }) http.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(access_token) router.push(/login) } return Promise.reject(error.response ? error.response.data : error) } ) export default http这套拦截器设计让我在前端任何组件里调用接口都特别省心发起请求时不用手动拼token接口返回401时自动跳登录页连APP.vue里的全局逻辑都不用写了。4.3 购物车状态管理购物车是购物系统里面交互最多、状态最复杂的模块——加购、选数量、勾选、删除、合计金额每一步都要即时反馈。如果每个子组件各自维护状态几个地方不同步调试起来会非常痛苦。这时候用Pinia状态管理就合适了。// stores/cart.js import { defineStore } from pinia import { getCart, addToCart, updateCartItem, removeCartItem } from ../api/cart export const useCartStore defineStore(cart, { state: () ({ items: [], loading: false }), getters: { checkedItems: (state) state.items.filter(item item.checked), totalPrice: (state) { return state.items .filter(item item.checked) .reduce((sum, item) sum item.goods.price * item.quantity, 0) } }, actions: { async fetchCart() { this.items await getCart() }, async addItem(goodsId, quantity 1) { await addToCart(goodsId, quantity) await this.fetchCart() }, async updateQuantity(id, quantity) { await updateCartItem(id, quantity) await this.fetchCart() } } })totalPrice这个getter体现了状态管理的便利页面上所有“合计金额”都从同一个store里取数据不管用户在购物车列表里改了多少种商品数量结算金额都会自动响应。举一个踩过的坑如果不用Pinia、而是每个页面各存各的购物车数据用户在商品详情页加购一件商品后切回购物车页很可能发现列表还是旧的。你得自己监听事件或传参来刷新页面非常麻烦。有了Pinia后只要切回页面时执行一次fetchCart()所有数据立刻同步。4.4 跨域问题的三种解法前后端分离开发时前端跑在5173端口Vite默认后端跑在8000端口Django默认浏览器会认为这是两种不同源直接拦截后端的响应——这就是经典的跨域问题。我试过三种方案按推荐程度排序方案一最推荐Django侧配置django-cors-headers。在settings.py里设置CORS_ALLOWED_ORIGINS [http://localhost:5173]一劳永逸。它是全局生效的所有接口都能正常响应浏览器的跨域请求。方案二Vite配置开发代理。在vite.config.js里设置server: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } }前端请求路径写成/api/goods/Vite开发服务器自动把请求转发到8000端口浏览器以为和前端同源跨域问题就消失了。但这种方式只在开发阶段有效打包上线后依然要处理Nginx层面的代理。方案三Nginx反向代理。部署时前后端分别放在两个app里Nginx作为统一入口转发/api前缀到Django服务其余请求指向Vue打包后的静态文件。这是生产环境的标准部署方式。实际开发时我建议先用方案一改一行配置就完事不用折腾代理。前端请求就直接写全路径http://127.0.0.1:8000简单直观。4.5 图片上传与播放商品图是电商系统绕不开的一环。Django处理图片上传需要先安装Pillow库pip install pillow然后在settings.py配置媒体文件目录。Django在开发环境里支持直接通过URL访问媒体文件只需要在项目的urls.py里加一行路由from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)前端上传图片时用FormData构造请求让axios不要自动序列化数据保持multipart/form-data格式const formData new FormData() formData.append(cover, file) await http.post(/goods/, formData, { headers: { Content-Type: multipart/form-data } })用ImageField接图片Django会自动把文件保存到MEDIA_ROOT目录并把文件路径存到数据库里。图片访问URL就是http://127.0.0.1:8000/media/goods/xxx.jpg。5. 常见问题与排查技巧实录5.1 前端请求接口一直404如果你确认接口路径是对的但前端请求始终404首先查一下Django控制台有没有收到请求。如果没有任何打印日志说明请求压根没到后端——大概率是跨域或代理配置问题如果收到了但返回404则是URL配置不对。我遇到过一次真实的情况前端请求POST/goods/但我在urls.py里写成了path(goods/, views.GoodsListAPIView.as_view())没带末尾斜杠导致POST请求永远匹配不上。排查这类问题有个铁律先用浏览器DevTools的Network面板看请求状态码和响应体再用Postman/Apifox直接请求后端接口。如果Postman能通、浏览器不行那就是跨域或浏览器环境的问题如果Postman也404那就是后端路由的问题。二分定位法三步就能锁定问题范围。5.2 图片上传后不显示图片上传成功但页面显示404除了上面说的MEDIA_URL路由没配置还有几个容易踩的坑。一个是MEDIA_ROOT路径用的相对路径导致Django找不到上传目录另一个是前端拼接URL时写错路径比如漏了/media前缀。我建议在settings.py中明确用绝对路径定义媒体目录import os MEDIA_ROOT os.path.join(BASE_DIR, media) MEDIA_URL /media/这样图片实际存储在项目根目录下的media文件夹里URL前缀统一为/media/前后端拼路径时不容易出错。5.3 migrate时报错“no such table”Django的migrate命令会按app顺序执行迁移文件如果你改了models.py但没有生成新的迁移文件直接migrate不会生效。正确流程是python manage.py makemigrations python manage.py migrate还有一个常见问题你在models.py里加了新字段但没给默认值执行makemigrations时Django会提示“为已存在的数据行加什么默认值”。这时候可以在命令行交互里设置默认值但更规范的做法是直接在models.py字段定义里写好default参数。5.4 高并发下单导致库存超卖这是一个让我印象深刻的坑。我在本地用两个浏览器同时下单同一件商品库存从10变成了8只减少了一次但生成了两笔订单——超卖了。原因是Django视图函数里先读取库存、判断充足、再扣减这个判断和扣减之间有一小段时间间隔两个请求同时进入后都能通过库存判断。解决办法最常用的是乐观锁给商品表加一个乐观锁版本号字段version更新库存时带上条件“version 之前读到的版本号”。如果更新影响行数为0说明版本被别人改了重新读取再重试。from django.db.models import F updated Goods.objects.filter(idgoods.id, versionversion).update( stockF(stock) - quantity, versionF(version) 1 ) if updated 0: raise Exception(商品数据已变化请重试)我实际测试过加上乐观锁后并发下单时只有一笔订单能成功另一笔会收到明确的库存冲突提示。这是从“能用”到“能用稳”的关键一步。5.5 后端改了接口前端还是拿旧数据开发期经常遇到改了Django接口后前端刷新页面数据没变。多数情况下是浏览器缓存了接口响应——GET请求默认会被浏览器缓存。最简单的验证方式是打开DevTools的Network面板勾选Disable cache再刷新页面。如果数据正确就说明是缓存问题。另外如果你在Vite开发服务器和后端Django两套环境同时运行切记前端请求的是后端接口的数据后端代码修改后需要Django自动热加载django自带auto-reload或手动重启。Vite热更新只管前端文件两边是独立的。这不算Bug是前后端分离架构下新手最容易混淆的一个点。经验收尾这套系统做下来我最大的体会是技术选型的核心不是追逐最新最炫的技术而是找到一套自己掌控得住、能稳定解决问题的组合。Django Vue PyCharm这个组合足够支撑一个功能完整、逻辑严谨的在线购物系统从零到上线全过程没有一处是碰运气的。有读者问这个项目后续还能怎么扩展我简单给你几个方向接入真实支付网关微信支付/支付宝、加入Redis做商品热度缓存、用Celery做订单超时自动取消、管理端引入数据可视化图表。每一条路都基于现有代码自然延伸不会推倒重来。最后分享一个小技巧在PyCharm里配置好下面的启动组合——Django的manage.py runserver和一个npm run dev的终端任务两边共同启动前端页面一刷新就联动后端数据。这就是全栈项目最适合的开发节奏。踩过的坑都梳理成上文这份清单了照着做能省下至少一周的弯路。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号