ARTICLE DETAIL

资讯详情

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

Python+Vue+Django/Flask 前后端分离在线超市系统开发实战

Python+Vue+Django/Flask 前后端分离在线超市系统开发实战 最近我把一套线上超市购物系统从零到能跑起来、能演示的完整状态做了一遍。技术栈就是标题里那一套Python 做后端Vue 做前端Django 和 Flask 两个框架我都实际用上了开发环境全程在 PyCharm 里完成。这套系统不搞花里胡哨的微服务就是一个标准的前后端分离单体项目业务上覆盖了用户登录、商品展示、购物车、下单、支付模拟、订单管理后台管理直接用现成方案。适合正在做课程设计的学生、想练手全栈项目的新手以及产品原型阶段需要快速交付的团队拿来借鉴。做这个项目的过程比我预想的要折腾一些尤其在购物车合并、库存扣减、跨域、静态文件部署这几个点上每个都踩过不止一个坑。所以这篇文章不打算只贴代码而是把我从搭建环境到选型、从数据库设计到接口联调、从本地跑通到服务器部署的完整链路都写出来包括每个关键决策背后的原因以及常规文档里不会写的那些教训。1. 项目定位与技术选型这套系统到底要做什么1.1 线上超市的核心业务模块拆解线上超市听起来是个大工程但剥开来看核心业务就六个字逛、选、下单、付、查、管。逛是用户进来看商品列表、看分类、搜索选是加购物车下单是提交收货信息、生成订单付是模拟支付然后订单状态流转查是查订单列表和详情管是后台要能上架商品、调库存、处理订单。再往后还能加推荐、加优惠券、加社区评价但MVP阶段把这些核心链路打通系统就已经具备完整的骨架了。我拆解出来的模块清单大致如下用户模块注册、登录、个人信息。MVP阶段用手机号加密码就够了收货地址先用一个简单字段存不需要单独建地址库。商品模块分类、商品列表、商品详情、搜索。这里比较容易忽略的是排序逻辑销量排序、价格排序在超市场景里很常用。购物车模块这个模块最容易被低估后面我会单独讲。选品加购看起来简单但涉及到本地状态、持久化、登录后合并比想象中复杂。订单模块创建订单、订单列表、订单详情、状态流转待支付 - 已支付 - 已发货 - 已完成以及已取消。支付模块MVP阶段用模拟支付接口把订单状态从待支付变成已支付即可。后台管理商品管理、库存调整、分类管理、订单处理。这部分直接靠 Django admin 就能解决不需要重新造轮子。每定义一个模块的时候我习惯先画一条用户路径用户打开商城 - 浏览商品 - 加购物车 - 去结算 - 填收货信息 - 提交订单 - 支付 - 查看订单。这串流程就是数据库表和接口设计的出发点后面的模型、接口全是给这条路径服务的。1.2 Django与Flask的选择我为什么两个都用了标题里同时出现 Django 和 Flask有人可能觉得奇怪。这里我把真实情况说清楚这个项目我前后做了两个版本。第一版用 Flask 快速搭了一个原型因为 Flask 实在太轻了两三百行代码就能把一个可用的接口服务拼起来非常适合验证流程。但做着做着问题就来了后台管理没有现成方案需要自己写权限、写列表页、写表单ORM 虽然可以用 SQLAlchemy但序列化、分页、认证这些还是得自己组装。等我把这些轮子一个个拼完发现时间全浪费在了与业务无关的基础设施上。于是第二个版本切到了 Django。Django 的定位是把基础设施全给你配好自带 admin 后台商品、订单、分类的管理界面直接能用内置用户认证体系密码哈希、登录会话都是现成的Django REST Framework 做 API 也相当成熟。对于线上超市这种业务逻辑集中、需要大量增删改查的管理后台的项目Django 的业务匹配度明显更高。Flask 在第二版里也没闲着我用它单独写了一个小服务跑在另一个端口上负责给前端提供商品推荐接口。这个小服务只有两个接口逻辑简单用 Flask 反而比 Django 轻快。所以我的建议是如果项目主体是管理系统、商城这类要频繁操作数据的优先选 Django如果只是写几个小接口、做微服务或快速原型Flask 完全不亏。对比项DjangoFlask自带后台管理有可直接用无需自己集成用户认证内置完整方案需要扩展包ORMDjango ORM自带迁移SQLAlchemy需自己配学习曲线相对陡峭平缓易上手适合场景业务系统、商城、后台管理轻量API、微服务、原型社区生态大而全小而灵活1.3 前后端分离为什么要用 Vue 接 Python商城这类系统有一个特点页面交互复杂状态多。加购物车要即时反馈购物车角标要变下单流程要跳转搜索要防抖。如果用传统的服务端模板渲染这类页面前后端代码揉在一起每次改交互都要动后端模板人力成本很高。所以我把前后端拆开后端 Python 只提供 JSON 接口前端 Vue 负责页面和交互。Vue3 搭配 Vite 开发体验很好热更新快组件化以后页面结构清晰。前端完成npm run build之后是一堆纯静态文件可以交给 Nginx 托管也可以扔进任何静态服务器和后端服务完全解耦。这套架构还有一个好处是接口可以被多个端复用。我同一套后端接口一个 Web 前端在用一个用 Postman 测试在用后续如果要做小程序端后端接口几乎不用改。Vue 本身的上手门槛也不高只要理解了组件、路由、状态管理三个概念写个商城前端完全够用。2. 后端设计数据库模型、接口与核心业务流程2.1 数据库模型设计把商品、购物车、订单串起来后端是整个项目的核心数据库模型又是核心中的核心。我在设计模型时有一条原则一张表只负责一个业务实体的数据多对多关系尽量通过中间表表达凡是涉及金额、数量、状态的数据字段类型一定要选对。商品和分类用 Django 写起来大概是这样的# products/models.py from django.db import models class Category(models.Model): name models.CharField(max_length50, uniqueTrue) sort_order models.IntegerField(default0, help_text排序权重小的靠前) class Meta: verbose_name 商品分类 verbose_name_plural 商品分类 def __str__(self): return self.name class Product(models.Model): category models.ForeignKey(Category, on_deletemodels.CASCADE, related_nameproducts) name models.CharField(max_length200, db_indexTrue) subtitle models.CharField(max_length300, blankTrue, help_text一句话卖点) # 价格一定用 DecimalField不要用 FloatField price models.DecimalField(max_digits10, decimal_places2) stock models.IntegerField(default0) sales models.IntegerField(default0, help_text销量用于排序展示) cover models.ImageField(upload_tocovers/, nullTrue, blankTrue) detail models.TextField(blankTrue, help_text商品详情描述) is_on_sale models.BooleanField(defaultTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: ordering [-created_at] def __str__(self): return self.name订单相关的模型是这套系统里最需要注意的部分。我强烈建议在订单明细里保存商品名称和价格快照而不是只用外键关联商品表。原因很简单商品后来可能改名、改价、甚至下架但用户的历史订单必须保持下单那一刻的信息不变。快照字段就是干这个的。# orders/models.py from django.conf import settings from django.db import models class Order(models.Model): STATUS_CHOICES ( (pending, 待支付), (paid, 已支付), (shipped, 已发货), (finished, 已完成), (canceled, 已取消), ) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_nameorders) order_no models.CharField(max_length32, uniqueTrue, help_text业务订单号) total_amount models.DecimalField(max_digits12, decimal_places2) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending) receiver models.CharField(max_length50) phone models.CharField(max_length20) 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) product models.ForeignKey(products.Product, on_deletemodels.PROTECT) # 快照字段下单时的名称和价格防止商品变更影响历史订单 product_name models.CharField(max_length200) price_snapshot models.DecimalField(max_digits10, decimal_places2) quantity models.IntegerField()这里有两个细节你可能会忽略。第一OrderItem.product外键的on_delete我用了PROTECT意思是如果商品已经被订单引用就不允许直接删除只能下架。这么做是为了避免订单明细里出现找不到商品的脏数据代价是后台删除商品时会报保护错误需要你先处理关联订单。第二订单号order_no不要用自增主键直接暴露给用户可以用时间戳加热随机数生成方便后续对账和排查。另外商品列表页要展示销量、价格、分类这些信息我建议给name、category加上索引。超市商品多起来之后列表查询加上 where 条件如果没有索引数据库会全表扫描体验很差。Django 的 ORM 在CharField上加db_indexTrue就够用了。2.2 RESTful 接口规划与 JWT 认证模型设计好之后接口规划就顺理成章了。我用 Django REST Framework 做接口层接口路径和功能对应如下方法路径功能说明POST/api/users/register/用户注册POST/api/users/login/用户登录返回 JWT TokenGET/api/products/商品列表支持分类筛选、关键字搜索、排序参数GET/api/products/{id}/商品详情GET/api/cart/获取购物车列表POST/api/cart/添加商品到购物车PUT/api/cart/{id}/修改购物车商品数量DELETE/api/cart/{id}/删除购物车商品POST/api/orders/创建订单GET/api/orders/当前用户订单列表GET/api/orders/{id}/订单详情POST/api/orders/{id}/pay/模拟支付认证方案我选了 JWT而不是传统的 Session。原因很简单前后端分离之后后端接口是无状态的前端把 Token 存在本地每次请求带上Authorization: Bearer token后端校验即可。Django 这边推荐用djangorestframework-simplejwt注册、登录、刷新 Token 的接口都是现成的只需要配置好认证方式和权限类。密码存储这件事必须说清楚绝不能明文存密码。Django 自带的用户模型默认用 PBKDF2 算法做哈希注册时传入密码Django 自动完成哈希存储登录时用authenticate校验这些基础能力不用自己写。如果强行自己实现一套密码存储很容易写出有安全隐患的代码。接口层的序列化器在 DRF 里很直观比如商品序列化器# products/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, subtitle, price, cover, sales, category_name]使用ModelSerializer的好处是字段定义与模型绑定后期模型加了字段序列化器如果不需要暴露就不受影响接口不会因为模型变更随意崩掉。2.3 下单扣库存事务与并发处理下单是整个系统里最容易出 bug 的环节。想象一个场景用户把商品加入购物车结算提交订单后端需要做四件事创建订单主表、创建订单明细、扣减库存、清空购物车。这四件事必须要么全成功要么全失败否则就会出现订单创建了但库存没扣或者库存扣了但订单不存在的数据不一致问题。解决办法就是数据库事务。Django 的写法是用transaction.atomic()包裹整个下单逻辑# orders/services.py from django.db import transaction from django.db.models import F def create_order(user, cart_items, receiver_info): with transaction.atomic(): order Order.objects.create( useruser, order_nogenerate_order_no(), total_amountcalc_total(cart_items), statuspending, **receiver_info, ) for item in cart_items: # 用 select_for_update 锁定商品行防止并发超卖 product Product.objects.select_for_update().get(iditem[product_id]) if product.stock item[quantity]: raise StockNotEnough(product.name) OrderItem.objects.create( orderorder, productproduct, product_nameproduct.name, price_snapshotproduct.price, quantityitem[quantity], ) # 用 F 表达式做原子扣减避免自增/自减的竞态问题 product.stock F(stock) - item[quantity] product.sales F(sales) item[quantity] product.save(update_fields[stock, sales]) clear_cart(user)这里有一个非常关键的细节扣减库存用的是select_for_update()行锁加上F()表达式原子操作。如果只是先查库存、判断足够、再更新库存高并发下会出现超卖。两个用户同时查到库存只剩 1 件都判断可以购买结果都扣减成功库存变成负数。行锁会让第二个请求等待第一个请求提交后再读库存F()表达式则让加减操作在数据库层面完成避免先读后写带来的窗口期。下单之后订单处于待支付状态预留一定时间。模拟支付接口的逻辑更简单验证订单属于当前用户、状态是待支付然后把状态改成已支付。真实项目接入微信或支付宝时这一步要做的是生成支付参数、跳转支付、接收回调通知并在回调里做签名验证和幂等处理。幂等处理的要点是同一个支付通知可能被第三方重复发送后端要根据订单号和支付流水号判断是否已经处理过避免重复修改订单状态。2.4 购物车方案localStorage 与后端接口的组合购物车是商城产品经理特别看重的模块技术上也有几种做法。方案一完全存在前端 localStorage。优点是后端零压力实现简单用户未登录也能加购。缺点是换设备数据就没了无法统计用户先加购后不买的弃单行为。方案二完全存在后端数据库。优点是数据持久可靠、利于统计分析。缺点是没有登录就不能用购物车用户第一次进站就得注册转化率受损。方案三我采用的折中方案。未登录用户的购物车数据存在 localStorage登录之后前端把本地购物车数据当作待合并列表提交到后端后端把这些条目合并进数据库购物车合并完清空本地数据。这个方案的好处很明显游客可以愉快地逛加了购物车也不用强制登录等到结算或登录时本地数据自动合入账号体系。实现上的关键点是合并逻辑要处理同一商品加多次的情况后端看到同一个商品 ID 有多个条目时应该累加数量而不是新建多条记录。3. 前端 Vue 实现从页面到联调3.1 工程初始化与路由规划前端我用 Vue3 加 Vite 初始化这一步很简单npm create vitelatest supermarket-web -- --template vue cd supermarket-web npm install npm install vue-router4 pinia axios element-plus页面结构分成四个主要视图商品列表页、商品详情页、购物车我用抽屉组件实现不单独占一个页面路由、订单页。商品列表页默认展示全部商品顶部有分类导航和搜索框商品详情页展示大图、价格、卖点、详情描述加购按钮放在显眼位置订单页展示用户的历史订单和订单状态。路由配置如下// src/router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /, name: home, component: () import(/views/HomeView.vue) }, { path: /product/:id, name: product-detail, component: () import(/views/ProductDetail.vue) }, { path: /orders, name: orders, component: () import(/views/OrderListView.vue) }, ] const router createRouter({ history: createWebHistory(), routes, }) export default router路由懒加载是我特别推荐的做法每个页面组件按需加载首屏体积会小很多。多人协作时每个成员可以只关注自己负责的视图文件基本不会互相冲突。3.2 组件与状态管理购物车抽屉是怎么实现的商城类项目前端最容易乱的地方是状态管理。购物车角标、抽屉里的小计、结算页总价多处地方都要用到购物车数据如果每个页面各自存一份同步起来全是 bug。我用 Pinia 统一管理购物车状态这才是它的用武之地。// src/stores/cart.js import { defineStore } from pinia import { http } from /utils/http export const useCartStore defineStore(cart, { state: () ({ items: JSON.parse(localStorage.getItem(cart_items) || []), }), getters: { count: (state) state.items.reduce((sum, i) sum i.quantity, 0), totalPrice: (state) state.items.reduce((sum, i) sum i.price * i.quantity, 0), }, actions: { addItem(product, quantity 1) { const existing this.items.find((i) i.product_id product.id) if (existing) { existing.quantity quantity } else { this.items.push({ product_id: product.id, name: product.name, price: product.price, cover: product.cover, quantity, }) } this.persist() }, persist() { localStorage.setItem(cart_items, JSON.stringify(this.items)) }, clear() { this.items [] this.persist() }, }, })购物车抽屉组件的交互逻辑也不复杂点加购按钮时调用 store 的addItem角标数字自动刷新打开抽屉能看到商品列表、修改数量、删除条目、合计金额。这里我踩过一个交互坑多个商品修改数量时每次点击都直接改 localStorage结果高频点击时偶发数据错乱。后来调整策略只在修改动作触发时写一次 localStorage并用watch监听 store 变化自动持久化问题就稳定了。Vue 的插槽在商品卡片这类场景中很好用。父组件只负责提供布局卡片内部的按钮、标签、价格展示位置可以由插槽决定不同页面使用同一张卡片时能灵活替换局部内容减少重复代码。3.3 接口联调Axios 封装与跨域处理前后端分离之后接口联调是必经之路。我在前端封装了一个 Axios 实例统一处理基站地址、Token 注入和错误响应// src/utils/http.js import axios from axios export const http axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000, }) http.interceptors.request.use((config) { const token localStorage.getItem(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(token) // 跳转到登录页这里按实际路由处理 } return Promise.reject(error.response?.data || error) } )开发阶段最常见的跨域问题我的解决办法不是在 Django 里开启 CORS而是用 Vite 的代理功能让前端请求走同一个域// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true, }, }, }, })这样在浏览器里前端请求/api/products/Vite 开发服务器会把它转发到http://127.0.0.1:8000/api/products/浏览器层面没有跨域问题后端也不需要考虑 CORS 白名单。我自己实际开发时习惯准备两个环境变量文件.env.development里VITE_API_BASE_URL/api走代理.env.production里VITE_API_BASE_URLhttps://你的域名/api走真实接口。这样代码里不需要写死任何环境相关的地址。4. PyCharm 开发环境让全栈开发更顺手4.1 从零配置 Python 虚拟环境很多人拿到一个项目第一步就卡在环境上。我建议所有 Python 项目都用虚拟环境把项目依赖隔离起来避免和系统 Python 环境互相污染。在 PyCharm 里最简单的方式是用它自带的虚拟环境创建功能。打开项目后在设置里选择 Python 解释器点击新增PyCharm 会帮你新建一个.venv目录并配置好解释器。之后打开终端PyCharm 会自动激活这个虚拟环境命令行前面会出现(.venv)标识这时候执行pip install -r requirements.txt安装依赖所有包都会装进这个虚拟环境。Python 版本建议用 3.10 或 3.11Django 4.x、新版 Flask 对这些版本的兼容性都很稳。如果机器上同时装了多个 Python 版本记得在创建虚拟环境时手动指定版本路径不然容易踩到版本不对的坑。关于 PyCharm 版本社区版免费写 Python 后端完全够用专业版额外带了前端支持、数据库工具、远程调试这些功能。做全栈项目时专业版确实方便但没必要为了省一个会员而去折腾一些不正规的激活手段正常订阅或者先用社区版都行。开发工具是提高效率的不是项目的负担。4.2 调试与接口测试的实用技巧我调试后端接口时有一套固定打法比单纯运行python manage.py runserver然后拿浏览器戳要快得多。第一善用断点调试。在 Django 视图函数里打上行断点然后跑 Debug 模式请求进来后就可以逐步查看变量、确认 SQL 查询是否走了预期索引。比一堆print再删掉优雅太多。第二用 PyCharm 的 Database 工具直接看数据。专业版里可以连接项目用的数据库运行时直接查看商品表、订单表里的数据变化比用命令行敲 SQL 直观得多。不依赖数据库类型SQLite、MySQL 都支持。第三用内置的 HTTP Client 写接口测试文件。在 PyCharm 里新建一个.http文件可以像 Postman 一样直接发送请求、查看响应还支持环境变量。我一般把这个文件提交到仓库里团队成员拉下来后点一下就能测接口减少沟通成本。### 用户登录 POST http://127.0.0.1:8000/api/users/login/ Content-Type: application/json { username: test_user, password: test_pass123 }这套工作流一旦习惯接口联调效率会高出不少。看到接口报错不急着加日志先看调用栈和断点往往能直接定位问题。5. 部署上线Django Vue Nginx 的组合拳5.1 部署方案选择项目做完要给别人看就得部署到服务器上。我用的方案是一台轻量云服务器Ubuntu 系统配置比较普通但跑这个项目已经绰绰有余。后端部署用 Gunicorn 作为 WSGI 服务器不能直接用 Django 自带的runserver那是开发用的性能和安全性都不适合线上。创建 Gunicorn 启动命令gunicorn supermarket.wsgi:application --workers 3 --bind 127.0.0.1:8000三个 worker 可以支撑一般的小流量并发。如果机器内存不够2 个 worker 也行。生产环境我建议用 systemd 管理 Gunicorn 进程这样服务器重启后后端能自动拉起不会出现人去手动重启的情况。前端部署更简单本地执行npm run build会生成dist目录。把这个目录上传到服务器交给 Nginx 托管即可。Vue 打包出来的都是静态文件不依赖 Node 环境这也是前后端分离的一个隐藏好处前端只需一个静态服务器就能跑。之前有人问过 Vue 打包之后能不能放进 Spring Boot 里其实原理一模一样就是把 Vue 的 dist 静态文件交给后端框架或 Nginx 托管。放进 Django 也是同理用python manage.py collectstatic收集后端静态文件再用 Nginx 做静态资源服务后端 API 走代理转发。5.2 Nginx 配置与常见部署坑Nginx 是整个部署中的核心角色。它同时负责托管 Vue 静态文件、代理后端 API 请求、服务 Django 静态资源。配置文件核心部分如下server { listen 80; server_name your_domain_or_ip; client_max_body_size 20m; # Vue 前端打包产物 location / { root /var/www/supermarket/dist; index index.html; try_files $uri $uri/ /index.html; } # 后端 API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Django 静态文件admin 后台和上传图片 location /static/ { alias /var/www/supermarket/staticfiles/; } location /media/ { alias /var/www/supermarket/media/; } }这个配置里有几个容易踩的坑。第一try_files $uri $uri/ /index.html这一行必须有否则 Vue 使用 history 模式路由时用户刷新/product/3这个页面会直接 404。因为 Nginx 先去找物理文件找不到就回退到index.html然后 Vue Router 按路径渲染对应组件。第二Django 部署时DEBUGFalse后Django 自己不再处理静态文件必须先把静态文件收集起来。执行python manage.py collectstatic然后在 Nginx 里配置/static/指向收集目录否则 Django admin 后台的样式会全部丢失。第三ALLOWED_HOSTS要配置正确。如果通过 IP 访问就要把服务器 IP 加进去通过域名访问就把域名加进去。忘记配置的话请求会被 Django 直接拒绝浏览器里看到的是Bad Request (400)。第四上传的图片文件如果不单独配置/media/路径用户上传商品图后图片在页面里永远是破的。这个和DEBUGFalse也有关系开发时能访问是因为 Django 帮你处理了 media生产环境必须交给 Nginx。如果服务器内存和磁盘都很小把 SQLite 直接用于生产也能顶住低流量场景。但正规发货前建议换 MySQL到时候注意建库时统一用utf8mb4字符集不然中文数据容易出现乱码。6. 常见问题与排查技巧实录6.1 开发期高频问题速查整个开发过程我不可能一帆风顺下面这些问题都是实际遇到并解决的整理成速查表方便你对照处理。问题现象根本原因解决办法前端请求接口报 CORS 错误前端和后端端口不一致开发环境用 Vite proxy 代理生产环境走 Nginx 同域转发Django 提示 CSRF 验证失败前后端分离没有携带 CSRF Token使用 JWT 认证DRF 配置为 SessionAuthentication TokenAuthentication 混合模式CSRF 只对 Session 认证生效接口返回中文乱码MySQL 库表字符集不是 utf8mb4建库时指定utf8mb4连接串加charsetutf8mb4库存出现负数或超卖并发下单没有锁库存下单事务中加select_for_update()行锁修改商品价格后历史订单金额变了订单明细没有存价格快照订单明细保存price_snapshot字段pip install下载慢默认源速度慢使用国内镜像源如pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/npm install卡住或报错Node 源连接不上配置 npm 镜像或者使用 nvm 切换 Node 版本这里我要特别提一下 Flask 和 FastAPI 的比较因为我在写 Flask 推荐服务时也研究过这个方向。对于这个项目这种最简单的两个接口服务FastAPI 的自动接口文档和类型校验确实更有优势代码量差不多。所以如果新起一个纯接口类的项目我大概率会优先考虑 FastAPI但 Flask 生态更成熟团队里 Flask 经验多一些时Flask 依然是个稳妥选项。技术选型不是越新越好而是匹配团队和场景。6.2 部署期隐蔽问题集合部署期的坑普遍比开发期更隐蔽因为报错信息往往不出来或者只有一个 500。第一个隐蔽问题Gunicorn 起来了但系统重启后服务没了。解决办法是写一个 systemd 服务文件把 Gunicorn 托管起来设置Restartalways。这是生产环境运维的基本功年轻人容易忽略。第二个隐蔽问题后端接口在服务器本地通了但前端页面请求 502。多数情况是 Nginx 的proxy_pass转发地址写错。记住一个细节proxy_pass http://127.0.0.1:8000;末尾不加路径时会把完整 URI 追加到后端如果加了/路径前缀会被吞掉。这种细节出问题的时候很难排查建议在浏览器开发者工具里看响应头确认是 Nginx 返回的还是 Django 返回的能快速定位问题层级。第三个隐蔽问题部署后图片上传功能正常但图片显示 404。这是因为 Django 的MEDIA_ROOT和MEDIA_URL没配好加上 Nginx 缺少/media/的 location 配置。检查思路是先看图片文件是否真的上传到了服务器对应目录再看 Nginx 的 alias 路径是否正确。目录权限也是个容易被忽略的点Nginx 工作进程要有读取权限。第四个隐蔽问题前端刷新任何非首页路由都是 404。这正是 Vue history 路由模式配合 Nginx 时最经典的问题try_files配置必须加上前面已经强调过这里再次提醒一遍。还有一个小技巧生产环境出问题时先看日志。Django 的日志文件、Gunicorn 的日志、Nginx 的错误日志三个日志基本能把 90% 的问题指向揪出来。与其在服务器上调半天不如先把日志定位看清楚再动手。最后说一个判断前后端问题的通用方法直接用手敲一下后端接口。如果curl http://127.0.0.1:8000/api/products/能正常返回 JSON那问题几乎肯定在前端、Nginx 或网络层如果这个请求都挂了那就是 Python 后端的锅去看日志和进程状态。用这个办法十次有八次能立刻把问题缩小到具体范围。做完整套项目我个人最大的体会是技术栈本身并不是这个项目的难点难点在于把购物车合并、订单状态流转、库存并发扣减这些业务规则理清楚并且每一层都留有可排查的记录。Django 给我提供了可靠的基础设施Vue 解决了复杂交互PyCharm 让调试变得更顺手这三个工具组合在一起确实能在有限人力下快速交付一套可用的商城系统。如果有人想在这个项目上继续往上走我建议下一步优先做这几件事一是把手机验证码登录接上这是国内商城的标配二是接入真实的支付渠道把模拟支付替换成微信或支付宝重点处理好回调验签和幂等三是给商品搜索加上拼音模糊匹配和搜索联想让用户能更自然地找到东西。至于我留着那个 Flask 推荐服务正好可以改造成基于用户行为的推荐接口把它做成整个系统的增长引擎。
返回列表