ARTICLE DETAIL

资讯详情

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

Flask+Vue助农商城全栈开发实战:从数据库设计到部署

Flask+Vue助农商城全栈开发实战:从数据库设计到部署 助农商城这类题目很多人在选题阶段都会被标题里同时出现 Flask 和 Django 搞迷糊。我在实际做一个完全基于 Flask 的助农电商项目时把这套流程完整走了一遍——从功能拆解、数据库建模到 Vue 前端对接再到 PyCharm 里的调试和最终部署。这套方案最后跑通了注册登录、商品展示、购物车、下单、订单跟踪以及农产品价格趋势图表。如果你正准备做类似的商城项目或者刚学完 Python 想找一个能练手的完整案例这篇文章应该能省掉不少弯路。1. 为什么这套助农商城最终选择了 Flask Vue1.1 先把 Django 和 Flask 的角色拆明白一个助农商城项目里出现 Django其实不奇怪。很多选题表、参考论文和技术资料会把 Python 的几个 Web 框架列在一起Django 自带 Admin 后台、ORM 和认证模块拿来做一个内部管理系统确实很顺手。但这套项目最后的落脚点是“基于 Flask”我做技术选型时主要考虑了三个原因。第一是可控性。Flask 只提供一个精简的核心路由、请求、响应、模板这些基本能力够用其他能力靠扩展自己按需安装。商城项目的细节非常多从登录验证、文件上传到订单流水每一块的需求都不一样Flask 不会用框架既定的约定绑死你的实现方式。第二是前后端分离的开发效率。Flask 最常见的用法是写纯 JSON API配合 SQLAlchemy 操作数据库前端由 Vue 管理页面状态两边用 HTTP 交互职责边界非常清楚。很多同学想在一个 Django 项目里同时塞模板和 Vue简单场景可以模块一多就容易乱。Flask 的“弱约束”反而让项目分层更直观。第三是学习成本。Flask 源码量小调试报错信息直白出问题时能顺着堆栈逆推对刚接触 Python Web 开发的读者特别友好。Django 的中间件、Admin 后台和复杂 ORM 体系需要单独花时间理解不是不能用而是在助农商城这个规模的项目里没必要给自己加难度。1.2 技术选型的整体布局和职责划分把选型定下来之后我习惯用一张职责表来固定整套系统的边界后面开发的时候就不会出现“这个逻辑到底放前端还是放后端”的争论。层次用到的技术主要负责的内容前端界面Vue Vue Router Pinia/Vuex页面路由、电商组件、购物车和订单状态前端构建Vite 或 vue-cli本地开发服务器、生产静态资源打包后端 APIPython Flask Flask-SQLAlchemy用户认证、商品、购物车、订单、统计接口数据库MySQL 或 SQLite用户表、商品表、订单表等的持久化存储开发工具PyCharm 虚拟环境断点调试、数据库迁移、前后台接口联调部署运行Gunicorn Nginx后端进程托管、前端页面和静态资源服务我在开发阶段用的主力工具是 PyCharm。社区版已经够用如果你有正式授权的专业版也没问题但不需要折腾额外的激活工具。Flask 项目在 PyCharm 里可以直接选择虚拟环境运行配合 Flask 的自动重载改动代码后立即生效对日常调试非常方便。实际项目目录我一般组织成下面这种结构视图层拆成 auth、product、cart、order 几个模块保证后期加功能时不会把代码越堆越乱rural_market/ ├── app/ │ ├── __init__.py │ ├── models.py │ ├── extensions.py │ └── views/ │ ├── auth.py │ ├── product.py │ ├── cart.py │ └── order.py ├── run.py ├── config.py └── requirements.txt1.3 为什么榜单和资料里仍然会出现 Django标题里出现 django很多人的第一反应是后端要同时用 Flask 和 Django。我在整理资料时发现常见的实际情况有两种第一种选题模板里只是把 Python Web 框架的常见词一起塞进去最后要求认准的是“Python 商城”这个主题第二种原型先用 Flask 快速跑通接口后续要扩展后台管理或者完整的权限体系时再往 Django 迁移。Django 的 Admin 界面适合内部维护但面对消费者端的页面还是得重写前端。所以在这套方案里Django 更多扮演的是备选方案和对比参照核心开发框架始终是 Python Flask。2. 从业务需求到数据库模型商城功能先拆后建2.1 助农商城的用户故事和角色划分动手写代码之前我会先把助农商城的业务场景拆成三类角色来看游客和消费者注册登录、浏览商品、按分类查找、搜索、加入购物车、下单支付、查看订单状态。农户和商家上架商品、设置价格和库存、下架商品、处理订单。管理员维护商品分类、查看销售统计、给特色农产品打推荐标。按这个规模设计数据库最少需要六张表用户表、商品分类表、商品表、购物车表、订单表、订单明细表。如果后续要做农产品价格可视化可以再加一张价格快照表。下面就是我常用的一套模型设计你可以根据自己的业务增加字段但核心的表关系不需要再动。2.2 用户表与商品表的建模要点用户表的核心字段包括id、username、password_hash、phone、avatar_url、role、address、created_at。密码一定不要存明文用 werkzeug.security 的 generate_password_hash 生成哈希值登录时再用 check_password_hash 比对。这个细节比任何框架特性都重要很多新手项目直接把明文密码写入数据库属于非常低级的安全问题。商品表建议包含id、name、category_id、description、price、stock、unit、image_url、is_on_sale、seller_id、created_at。seller_id 关联用户表和 role 字段配合使用就能区分商品到底是哪个农户发布的。价格字段用 Numeric(10,2)保证小数精度不要用 Float 存金额否则累计计算时会出现 0.1 加 0.2 不等于 0.3 的问题。库存字段用 Integer在下单时做原子扣减这是后面防止超卖的基础。from datetime import datetime from werkzeug.security import generate_password_hash, check_password_hash from flask_sqlalchemy import SQLAlchemy db SQLAlchemy() class User(db.Model): __tablename__ users id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(64), uniqueTrue, nullableFalse) password_hash db.Column(db.String(255), nullableFalse) role db.Column(db.String(16), defaultuser) phone db.Column(db.String(32)) created_at db.Column(db.DateTime, defaultdatetime.utcnow) def set_password(self, password): self.password_hash generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) class Product(db.Model): __tablename__ products id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(128), nullableFalse) category_id db.Column(db.Integer, db.ForeignKey(categories.id)) price db.Column(db.Numeric(10, 2), nullableFalse) stock db.Column(db.Integer, default0) unit db.Column(db.String(16), default斤) image_url db.Column(db.String(255)) is_on_sale db.Column(db.Boolean, defaultTrue) seller_id db.Column(db.Integer, db.ForeignKey(users.id)) created_at db.Column(db.DateTime, defaultdatetime.utcnow) def to_dict(self): return { id: self.id, name: self.name, price: float(self.price), stock: self.stock, unit: self.unit, image_url: self.image_url, category_id: self.category_id }2.3 购物车、订单和订单明细的设计约束购物车表是一张典型的关联表id、user_id、product_id、quantity、created_at。建议给 user_id 和 product_id 加唯一约束避免同一个商品在购物车里出现多行记录。用户在页面上加了两次商品后端应该把 quantity 做累加而不是再插入一行。订单表的设计要多花点心思我强调三个点。第一订单号 order_no 必须唯一用“yyyyMMddHHmmss 用户ID 随机数”生成就能满足需求第二总金额 total_amount 必须由后端在下单时根据商品当前价格重新计算不能信任前端传过来的金额第三订单状态 status 可以用数字枚举0 表示待支付1 表示已支付2 表示已发货3 表示已完成4 表示已取消。为什么必须有订单明细表因为商品价格和库存都是会变的。一张订单生成后就必须把当时的商品名称、单价、数量、小计快照保存下来。以后即便商品改了价格、下了架用户的订单依然可以还原完整信息。只存一个总金额后面查账和售后都会很麻烦。class Order(db.Model): __tablename__ orders id db.Column(db.Integer, primary_keyTrue) order_no db.Column(db.String(64), uniqueTrue, nullableFalse) user_id db.Column(db.Integer, db.ForeignKey(users.id)) total_amount db.Column(db.Numeric(10, 2), nullableFalse) status db.Column(db.Integer, default0) receiver_name db.Column(db.String(64)) receiver_phone db.Column(db.String(32)) receiver_address db.Column(db.String(255)) created_at db.Column(db.DateTime, defaultdatetime.utcnow) class OrderItem(db.Model): __tablename__ order_items id db.Column(db.Integer, primary_keyTrue) order_id db.Column(db.Integer, db.ForeignKey(orders.id)) product_id db.Column(db.Integer, db.ForeignKey(products.id)) product_name db.Column(db.String(128)) price db.Column(db.Numeric(10, 2)) quantity db.Column(db.Integer) subtotal db.Column(db.Numeric(10, 2))2.4 农产品价格趋势表该单独建吗如果有人提到“农产品价格数据可视化 - flask”那多半是想在商城页面上加一个价格走势图。我的做法是单独建一张 price_records 表字段包含 id、product_id、price、record_date每天或者每周记录一次商品均价。前端调用 /api/stats/price-trend 时后端按日期返回最近 7 天或 30 天的数据折线图直接有源可用。单独建表的好处是历史数据不会被商品当前价格覆盖。商品表里的 price 是实时售价随时会改价格记录表则是定时快照两者互不干扰。后面无论是做趋势图、销售分析还是给农户展示自家产品的行情波动都不需要再翻订单表推算。3. Flask 后端 API 的设计与实现细节3.1 接口清单先定代码跟着清单走写后端代码前把接口清单列清楚比什么都重要。接口路径、请求方法、作用、是否需要登录四项内容固定下来前端同事或者你自己写 Vue 页面时就不会反复改。路径方法作用权限/api/auth/registerPOST用户注册公开/api/auth/loginPOST登录返回 JWT公开/api/productsGET商品分页列表支持关键词和分类筛选公开/api/products/GET获取商品详情公开/api/cart/itemsGET / POST查看购物车、加入购物车登录/api/cart/items/PUT / DELETE修改数量、删除购物车项登录/api/ordersPOST从购物车创建订单登录/api/ordersGET查看我的订单列表登录/api/orders/ /payPOST模拟支付更新订单状态登录/api/stats/price-trendGET返回农产品价格趋势数据公开这套接口和前端页面是严格对应的。页面需要什么数据后端就提供什么接口不要等页面开发到一半才发现缺字段被来回返工。3.2 注册登录和 JWT 认证的落地Flask 做 JWT 认证我推荐直接用 Flask-JWT-Extended不要自己写 Session 服务端逻辑。流程分三步注册或登录成功后用 create_access_token 生成 token受保护的接口加 jwt_required() 装饰器视图函数里用 get_jwt_identity() 拿当前用户 ID。这个方案对前后端分离的项目很自然移动端后续想复用接口也方便。from flask import Blueprint, request, jsonify from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity from models import db, User auth_bp Blueprint(auth, __name__) auth_bp.route(/register, methods[POST]) def register(): data request.get_json() if User.query.filter_by(usernamedata.get(username)).first(): return jsonify(msg用户名已存在), 400 user User(usernamedata.get(username)) user.set_password(data.get(password)) db.session.add(user) db.session.commit() token create_access_token(identitystr(user.id)) return jsonify(tokentoken, user_iduser.id), 201 auth_bp.route(/login, methods[POST]) def login(): data request.get_json() user User.query.filter_by(usernamedata.get(username)).first() if not user or not user.check_password(data.get(password)): return jsonify(msg用户名或密码错误), 401 token create_access_token(identitystr(user.id)) return jsonify(tokentoken, user_iduser.id), 200我在一个实际项目里遇到过一个问题前端页面已经登录成功但请求接口时一直返回 422。后来查下来是前端把 token 放在请求头 Authorization 字段时带了多余的空格JWT 扩展在校验 Bearer 前缀时对空格特别敏感。建议前端封装公共请求时统一处理 header不要每个页面自己拼。3.3 商品分页接口与搜索条件的组合商品列表是商城流量最大的接口一定要做分页不能一次把全表数据全返回。Flask-SQLAlchemy 的 paginate 方法可以直接用page_size 要做上限限制防止有人传一个 10000 的参数把服务器拖垮。product_bp.route(/products, methods[GET]) def product_list(): page request.args.get(page, 1, typeint) per_page min(request.args.get(page_size, 12, typeint), 100) keyword request.args.get(keyword, ).strip() category_id request.args.get(category_id, typeint) query Product.query.filter(Product.is_on_sale True) if keyword: query query.filter(Product.name.ilike(f%{keyword}%)) if category_id: query query.filter(Product.category_id category_id) result query.paginate(pagepage, per_pageper_page, error_outFalse) return jsonify({ items: [p.to_dict() for p in result.items], total: result.total, pages: result.pages, page: page })这里用 ilike 而不是 like是因为 MySQL 的字段排序规则在部分环境下对中文大小写和编码敏感ilike 在丢掉匹配的情况下兼容性更好符合当前项目的快速开发场景。数据库是 MySQL 时需要注意表字符集用 utf8mb4商品名里出现生僻字或者特殊符号也不容易乱码。3.4 购物车接口与下单事务控制购物车接口本身不复杂核心就是先查用户有没有已经放入同一商品有就改数量没有就新增行。真正容易出错的是用户从购物车提交订单的那一刻。创建订单是一个多步写入操作先锁住商品行检查库存然后扣减库存再创建订单头最后把购物车里的商品批量转成订单明细并清空对应购物车项。这几步操作必须包在同一个事务里中间任何一步失败都要整体回滚。比如用户买了最后一斤苹果而另一个用户同时也在买如果不加行锁两边都能读到库存为 1最后就会把库存扣成负数。from flask import Blueprint, request, jsonify from flask_jwt_extended import jwt_required, get_jwt_identity from extensions import db from models import Product, Cart, Order, OrderItem import uuid from datetime import datetime order_bp Blueprint(order, __name__) order_bp.route(/orders, methods[POST]) jwt_required() def create_order(): user_id int(get_jwt_identity()) data request.get_json() cart_ids data.get(cart_ids, []) cart_items Cart.query.filter( Cart.id.in_(cart_ids), Cart.user_id user_id ).all() if not cart_items: return jsonify(msg购物车没有选中商品), 400 order_no datetime.now().strftime(%Y%m%d%H%M%S) str(user_id) uuid.uuid4().hex[:4] total_amount 0 order Order( order_noorder_no, user_iduser_id, total_amount0, receiver_namedata.get(receiver_name), receiver_phonedata.get(receiver_phone), receiver_addressdata.get(receiver_address) ) db.session.add(order) db.session.flush() try: for item in cart_items: product Product.query.filter_by(iditem.product_id).with_for_update().first() if not product or product.stock item.quantity: db.session.rollback() return jsonify(msgf{product.name}库存不足), 400 product.stock - item.quantity subtotal product.price * item.quantity total_amount subtotal db.session.add(OrderItem( order_idorder.id, product_idproduct.id, product_nameproduct.name, priceproduct.price, quantityitem.quantity, subtotalsubtotal )) db.session.delete(item) order.total_amount total_amount db.session.commit() except Exception: db.session.rollback() return jsonify(msg下单失败请重试), 500 return jsonify(order_noorder_no, total_amountfloat(total_amount)), 201with_for_update()在 MySQL 的 InnoDB 引擎下会对商品行加锁事务提交后自动释放这是解决并发超卖最直接的手段。对规模不大的助农商城项目这一行代码就能避免大量库存异常问题。4. Vue 前端与 Flask 对接的关键环节4.1 Vue 项目初始化与代理配置Vue 前端我用 Vue 3 Vite 为主组件库选 Element Plus 可以加快页面成型速度。初始化时执行npm create vuelatest按提示选择 Vue Router 和状态管理再安装 axios、pinia、echarts 这些依赖就行。前后端分离以后最大的坑是跨域。本地开发时Vue 页面运行在 5173 端口Flask 运行在 5000 端口两个端口不同浏览器会拦截跨域请求。最简单的方案不是在 Flask 里配 flask-cors而是在 Vite 里配代理让前端所有 /api 开头的请求转发到后端浏览器层面上就感受不到跨域了。// vite.config.js export default { server: { proxy: { /api: { target: http://127.0.0.1:5000, changeOrigin: true } } } }4.2 请求库封装与登录状态管理我习惯在 src/utils/request.js 里统一封装 axios 实例把 token 读取和错误处理集中在拦截器里。登录成功后把 JWT 存进 localStorage请求拦截器给每个请求头加上 Authorization: Bearer 。这样页面代码只需要关心业务参数不用每个组件都重复处理 token。import axios from axios; const request axios.create({ baseURL: /api, timeout: 8000 }); request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); request.interceptors.response.use( res res.data, err { if (err.response err.response.status 401) { localStorage.removeItem(token); window.location.href /login; } return Promise.reject(err); } ); export default request;路由守卫里再加一层判断需要登录的页面如果 localStorage 没有 token直接跳转登录页。这个小逻辑能拦下大多数未登录就访问购物车或订单页的请求。4.3 商品列表、购物车和订单页面的实现思路商品列表页是最简单的部分请求 /api/products 拿到 items 数组循环渲染商品卡片分页组件绑定 total 和 page。分类筛选和搜索只是把参数拼进请求里后端已经按参数过滤了前端不用在已经返回的数据里二次过滤。购物车页需要注意数量和单价的关系。每个购物车条目的价格应该从商品接口读取而不是把历史价格存在前端。用户修改数量后小计立即用当前单价重新计算。提交订单时前端只需要提交购物车条目 ID 和收货人信息金额计算全部交给后端。这样即使有人篡改前端请求后端订单金额也是基于真实商品价格算出来的。订单页的核心是状态机管理。展示待支付、已支付、已发货、已完成、已取消五类订单前端用状态字段过滤请求就行。已支付的订单可以允许用户模拟确认收货商家后台则负责把订单状态从待支付改成已发货。4.4 价格趋势图表的接入农产品价格可视化用 ECharts 实现成本很低。先安装 echarts然后在组件挂载后初始化图表把接口返回的日期数组和价格数组填进 option 即可。import * as echarts from echarts; export default { data() { return { dates: [], prices: [] }; }, mounted() { this.fetchPriceTrend(); }, methods: { async fetchPriceTrend() { const res await request.get(/stats/price-trend, { params: { product_id: this.productId, days: 30 } }); this.dates res.dates; this.prices res.prices; this.renderChart(); }, renderChart() { const chart echarts.init(this.$refs.chartRef); chart.setOption({ xAxis: { type: category, data: this.dates }, yAxis: { type: value, name: 均价元 }, series: [{ type: line, data: this.prices, smooth: true }] }); } } };有一个被很多人忽略的坑如果图表组件放在 Tab 切换里第一次渲染时 DOM 可能还没显示init 就会失败或者出现空白。解决方法是组件隐藏后重新显示时调用 chart.resize()或者干脆在显示后的 nextTick 里再初始化图表。5. PyCharm 环境搭建与前后端联调排错5.1 创建虚拟环境与依赖管理PyCharm 新建 Flask 项目时建议直接选择 Virtualenv 作为解释器环境。依赖装完以后一定要把版本固定写入 requirements.txt不要边写边装。一个项目如果你今天在公司电脑装明天在家里电脑装没有 requirements.txt 就会经历一遍缺库排查的折磨。我这份项目的 requirements.txt 大致是这样flask3.0.2 flask-sqlalchemy3.1.1 flask-jwt-extended4.6.0 pymysql1.1.1安装的时候执行pip install -r requirements.txt即可。注意 pymysql 是让 Python 连 MySQL 用的如果你的项目用的是 SQLite就不需要装它。从一个库换到另一个库只需要改动配置里的数据库连接字符串。5.2 数据库初始化与中文编码问题MySQL 建库时字符集建议直接指定 utf8mb4CREATE DATABASE rural_market DEFAULT CHARACTER SET utf8mb4;Flask 配置里连接串也要带上 charset 参数SQLALCHEMY_DATABASE_URI mysqlpymysql://root:password127.0.0.1/rural_market?charsetutf8mb4这个细节我吃了不少亏。忘记设置 utf8mb4 的话商品名一旦出现“”这类四字节字符数据库就会报错或者显示成问号。助农商城经常会上架各种水果生鲜名字里带生僻字的概率不低编码问题应该提前处理。初始化表结构时简单场景直接在 Flask 启动入口调用db.create_all()开发阶段可以先用这种方式跑通。等字段开始改了再上 Flask-Migrate 管理迁移。不建议一开始就上复杂的迁移体系项目还小的时候反而浪费时间。5.3 双服务联调时常见的三个问题前后端同时跑起来以后我用很短篇幅记录三个高频问题测试时优先检查。第一个是接口 404。前端请求 /api/auth/login后端路由却是 /auth/login。此时需要在 Flask 端给蓝图统一加 url_prefix/api或者把 Nginx 代理里的路径统一。检查方法很简单浏览器直接访问后端接口地址看是 Flask 返回的路由还是 Nginx 返回的前端错误页。第二个是跨域报错。Vite 代理配了以后大多数跨域问题会消失。如果你发现 Vite 里页面请求带上了完整后端地址比如 axios baseURL 写成了 http://127.0.0.1:5000那就绕过了代理跨域还会出现。正确做法是浏览器请求相对路径 /api/xxx让代理层去转发。第三个是 token 失效但前端没有跳转。JWT 过期后Flask 返回 401axios 响应拦截器里要统一清 token 并跳登录页。很多项目只处理了 200 的情况失败响应就会在控制台报 unhandled rejection用户却一脸懵。5.4 提高调试效率的小工具PyCharm 的 Flask 调试配置很好用但如果你更喜欢命令行也可以直接在终端设置环境变量启动export FLASK_APPrun.py export FLASK_DEBUG1 flask run接口出问题时我建议先用浏览器 DevTools 的 Network 面板看响应体判断是后端逻辑问题还是前端传参问题。后端可以随手加日志但更快的定位方式是直接打断点看数据库查询结果和循环里的变量值。PyCharm 里断点命中后会冻结整个 Flask 工作线程前端请求会一直等待这一点是正常的方便你查看调用栈和局部变量不是程序卡了。6. 从本地跑通到上线部署的完整过程6.1 生产环境为什么不用 flask run项目本地能跑之后下一步是部署。很多人第一次部署时习惯直接flask run把服务跑起来挂在公网这样做非常危险。Flask 自带的开发服务器是单进程的性能差而且部分错误页面会直接暴露源码路径。生产环境下我会用 Gunicorn 启动 Flask再用 Nginx 处理前端静态文件和反向代理。前后端构建完以后整个部署目录分成两部分Vue 打包生成的 dist 目录是前端页面Flask 是一个运行在内部端口的 API 服务。Nginx 接收外部请求如果路径以 /api 开头就把请求转给 Flask如果访问其他路径就直接返回 dist 里的静态页面。这种方式既解决了跨域也提高了静态文件的加载速度。6.2 Gunicorn 启动脚本与基础参数Gunicorn 使用前要先安装pip install gunicorn假设你的入口文件是 run.py里面通过app create_app()创建了 Flask 实例启动命令如下gunicorn -w 2 -b 127.0.0.1:8000 run:app参数解释-w 2 表示启动两个 worker 进程对助农商城这个量级够用-b 指定绑定地址和端口。绑 127.0.0.1 是因为外面有 Nginx 做入口不必让 Gunicorn 直接暴露公网端口。如果你的服务器有多个核心worker 数量一般设置为2 * CPU核数 1不需要盲目调大。6.3 Nginx 配置前端与后端入口一个最简的 Nginx 配置长这样server { listen 80; server_name your_domain.com; 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; } location / { root /var/www/rural-market/dist; try_files $uri $uri/ /index.html; } }try_files 那一行的作用是Vue 路由使用的是 history 模式刷新页面时如果没有这一行Nginx 会直接报 404找不到 /product/123 这样的路径加了以后会把请求退回 index.html由前端路由接管。6.4 上线前容易漏掉的检查项我每次部署商城项目前都会检查下面几项列成清单能省不少半夜被叫醒的时间Flask 的 DEBUG 关闭SECRET_KEY 不能沿用默认值。MySQL 的连接账号只给业务库权限不要用 root。图片上传目录权限要正确Nginx 能读但 Flask 能写。前端 axios baseURL 在生产环境改成相对路径 /api配合 Nginx 代理。数据库启动 MySQL检查网络和防火墙端口。Gunicorn 启动进程用 systemd 或 supervisor 托管防止 SSH 断开后服务挂掉。订单表定期备份至少每天一次。我在实际运维中发现真正影响稳定性的往往不是框架代码而是部署细节。比如图片上传权限没配好农户上传商品图就会报 500再比如 Nginx 没有配置客户端请求体大小上传高清产品图超过默认 1M 限制页面就会一直转圈。这些坑不是框架的问题而是经验问题。如果项目做到这里还有余力我强烈建议给后端补上 Flask-Migrate 的迁移记录给前端加一个简单的 Pinia 模块管理购物车数量角标。这样系统从演示项目往生产级项目迈进的每一步都是在为后续加销量统计、农户结算、多级分类这些功能打基础。
返回列表