ARTICLE DETAIL

资讯详情

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

Flask全栈开发实战:从项目结构到部署上线的完整指南

Flask全栈开发实战:从项目结构到部署上线的完整指南 简介这份Flask全栈开发学习资料包面向想从零上手Flask并完成真实项目的Web开发者定位覆盖基础理论与项目实战。资料包大小45.32MB包含PDF课件、ZIP示例代码与论坛项目源码等素材可配合讲解边学边练。目前已有100人学习/下载。内容从Werkzeug网关与Jinja2模板引擎切入系统讲解路由与视图函数、模板渲染、Flask-SQLAlchemy数据库建模、Flask-WTF表单验证、Flask-RESTful接口设计等关键模块论坛项目则展示了用户注册、登录、发帖、回帖等完整流程便于理解真实应用的目录结构、模型关联与请求处理方式。课件中还涉及条件判断、循环、模板继承、宏等Jinja2高级特性能帮助读者写出更简洁、可维护的前端模板。通过边看边练可以逐步形成Flask全栈开发的基本能力。1. 先搞清楚Flask到底能不能算“全栈”“Flask全栈开发”这个词很多人一看到就皱眉头。Flask官方定位是微框架自带的东西少得可怜连数据库接入都要自己装扩展凭什么谈全栈我当年也纠结过这个问题后来想明白了全栈不等于全都要框架给你而是你手里的工具链能覆盖从前端到后端到数据库到部署的完整链路。Flask恰恰是那种“框架很小、生态很大”的东西Django把全家桶塞给你Flask把选择权留给你。从热搜词里能看到一个很典型的现象flask后面永远跟着vue、yolo、mysql这些词。这说明大家用Flask干的事情早就超出了“写个博客练练手”的范畴而是真的拿它在做有前端界面、有算法模型、有数据库存储的完整项目。这和Flask的设计哲学是吻合的——它不限制你的技术选型你想接Vue做界面、接YOLO做目标检测、接MySQL做持久化它都乐意配合。那这篇文章解决什么问题如果你是一个刚把Python基础学完、想试试手写一个完整项目的开发者或者你已经用Flask写过接口但一直没把它串成一条完整的全栈链路这篇文章就是按我的实操路径走的。我不会教你背API文档而是带你从环境搭建一路走到项目上线把中间最容易卡住的地方全部过一遍。文中的所有代码我都用真实项目跑过可以直接抄。2. “轻”是双刃剑——先理解Flask的定位再动手2.1 Flask为什么能做全栈靠的不是框架本身我见过很多人一上来就吐槽Flask觉得它连个ORM都要自己装太麻烦。但恰恰是这种“麻烦”给了你做工程决策的空间。比如Django默认给你配好了ORM和Admin后台但如果你要对接一个已经存在的MySQL数据库光是迁就它的模型定义方式就得花不少时间。Flask的做法是你要用SQLAlchemy就现装要用Peewee也随你甚至直接裸写SQL都没人拦你。这种“轻”还体现在项目结构上。Django用manage.py startapp自动生成一大坨目录新手看着就发怵。Flask从一个app.py跑起来整个项目的生长路径完全由你自己控制。项目大了以后再按业务拆蓝图Blueprint拆分逻辑清晰目录结构也是你一手设计的出了bug排查起来心里有数。2.2 什么场景真正适合Flask全栈不是说所有项目都该用Flask这个问题想不清楚后面写代码处处别扭。我的判断标准有三条第一核心业务逻辑在Python侧。比如你的项目里要调YOLO做图像识别要跑机器学习模型或者要处理复杂的Python库这时候用Flask做Web层就是最顺畅的选择不用像Node.js那样再开一个子进程去调Python脚本。第二前后端可以分开部署。Flask最舒服的项目形态是后端纯出API前端独立用Vue或者React构建两边用JSON通信。这种模式下Flask只需要管好接口逻辑压力小结构清楚。第三团队对Python最熟。如果你们前端用的Vue后端又不想再学一套Java或者GoFlask就是那个成本最低的“胶水”。我自己好几个项目就是这么搭的一个人同时维护前端和后端Flask的代码量控制得住。如果你要做的项目是标准的管理后台需要现成的用户系统、Admin后台、表单验证那Django确实省事得多。这种场景硬要用Flask你会发现光是把用户认证、权限管理、后台管理这些轮子重新造一遍就够你喝一壶的了。所以先想清楚自己要什么再决定要不要用Flask。2.3 搭建开发环境时最容易忽略的细节环境这块看着基础实际上踩坑最多。你搜“flask安装包”会出来一堆结果但很多人装完以后跑不起来问题基本出在Python版本和虚拟环境上。我个人统一用Python 3.10原因很实在新版本对类型注解的支持更好SQLAlchemy 2.0只支持3.7以上而Flask 3.0要求Python 3.8你用老版本的Python装新版Flask会出现依赖冲突。为了避免这种问题我强烈建议你永远用虚拟环境跑项目# 创建项目目录 mkdir flask_fullstack cd flask_fullstack # 创建虚拟环境Windows系统用 python -m venv venv python3 -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate # 安装基础依赖 pip install flask flask-sqlalchemy flask-cors pymysql python-dotenv这里有个细节pymysql要提前装好后面连接MySQL的时候SQLAlchemy要用它做驱动。经常有人在连接数据库那一步报错ModuleNotFoundError: No module named MySQLdb就是因为没装pymysql或者装了但没在连接串里指定。装完以后可以顺手验证一下环境是否正常python -c import flask; print(flask.__version__)能打印出版本号说明Flask本身没问题。接下来才是重头戏——设计项目结构。3. 一个能支撑全栈项目的目录结构比写代码更重要3.1 为什么“单文件跑通”只能留在学习阶段网上大量Flask教程喜欢把代码写在一个app.py里路由、数据库、模板全塞在一起两百行搞定一切。这种写法在“学习Flask语法”的阶段完全没问题但一旦进入全栈开发前端页面不止一个、数据库表十几张、接口几十个单文件的维护成本会指数级上升。举个例子我在第一个真实项目里就是这么干的一开始还挺爽所有函数放一个文件里往上翻是路由往下翻是数据库模型再往下翻是配置。但项目到第1000行的时候我每次改一个接口都要在文件里搜半天函数名改完一处还要确认没有影响到其他接口。最痛苦的是前端同事来问接口的入参出参我说你等我搜一下代码。这种状态下做全栈效率比团队协作还低。后来我重构成了标准的Flask应用工厂模式才真正体会到“结构清晰”这四个字的分量。应用工厂的意思简版理解你不再把app作为一个全局变量直接用而是写一个create_app函数在函数内部组装Flask实例、注册蓝图、初始化数据库。这样做的直接好处有三个项目可以按需创建多个不同配置的实例开发、测试、生产各功能模块拆成独立文件互不干扰测试时可以直接创建临时实例不用污染开发数据3.2 我实战中用的Flask项目目录下面这个结构是我跑了多个项目以后沉淀下来的不算最简但每个目录都有明确职责加了新功能不会迷路flask_fullstack/ ├── app/ │ ├── __init__.py # 应用工厂create_app() │ ├── config.py # 配置文件数据库连接、密钥、上传路径 │ ├── models/ # 数据库模型 │ │ ├── __init__.py │ │ └── user.py │ ├── routes/ # 蓝图路由 │ │ ├── __init__.py │ │ ├── auth.py # 用户认证模块 │ │ ├── main.py # 主页面/公共接口 │ │ └── api.py # 核心业务API │ ├── services/ # 业务逻辑层非路由代码 │ │ ├── __init__.py │ │ └── yolo_service.py # 调用YOLO模型的封装 │ ├── utils/ # 工具函数 │ │ ├── __init__.py │ │ └── response.py # 统一响应格式 │ └── static/ # 静态资源前端构建产物放这里 │ └── index.html ├── frontend/ # 前端源码Vue项目 │ ├── src/ │ ├── package.json │ └── vite.config.js ├── venv/ # 虚拟环境不提交git ├── requirements.txt ├── run.py # 启动入口 └── .env # 环境变量前端用Vue开发时构建出来的dist目录在部署阶段统一拷到app/static下或者用Nginx直接托管Flask专心做API。3.3 配置管理的正确姿势别把密码写代码里config.py里面最容易犯的错误是把数据库账号密码、密钥这些直接硬编码。这个习惯在个人项目里侥幸没事但项目一旦换环境部署从开发机搬到服务器改配置改到你怀疑人生。正确的做法是利用.env文件和python-dotenv库# app/config.py import os from dotenv import load_dotenv # 加载项目根目录的 .env 文件 load_dotenv() class Config: SECRET_KEY os.getenv(SECRET_KEY, dev-secret-key) SQLALCHEMY_DATABASE_URI os.getenv(DATABASE_URI, mysqlpymysql://root:passwordlocalhost:3306/flask_db?charsetutf8mb4) SQLALCHEMY_TRACK_MODIFICATIONS False UPLOAD_FOLDER os.getenv(UPLOAD_FOLDER, ./uploads) MAX_CONTENT_LENGTH 16 * 1024 * 1024 # 上传文件上限16MB项目根目录的.env文件长这样SECRET_KEYyour-super-secret-key DATABASE_URImysqlpymysql://root:yourpasswordlocalhost:3306/flask_db?charsetutf8mb4代码里留了默认值保证不配.env也能跑起来但生产环境一定通过.env覆盖。这样部署到服务器时只需要拷贝一个不含敏感信息的代码仓库再单独创建.env文件即可。3.4 应用工厂的完整写法app/init.py是Flask项目的核心入口我把它做成应用工厂模式# app/__init__.py from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS db SQLAlchemy() def create_app(): app Flask(__name__) app.config.from_object(app.config.Config) # 初始化扩展 db.init_app(app) CORS(app, resources{r/api/*: {origins: *}}) # 开发阶段先放开跨域 # 注册蓝图 from app.routes.auth import auth_bp from app.routes.main import main_bp from app.routes.api import api_bp app.register_blueprint(auth_bp, url_prefix/api/auth) app.register_blueprint(main_bp) app.register_blueprint(api_bp, url_prefix/api) return appdb单独抽出来放在__init__.py里而不是放在create_app里面创建是为了后面models层能用同一个实例定义表结构。这一步很多人会搞错如果在create_app函数里创建SQLAlchemy实例models文件就没法import这个实例会导致“表永远建不上”的诡异问题。启动文件run.py就很简单了# run.py from app import create_app app create_app() if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)4. 全栈链路的关键一环数据库建模与接口对接的“通信协议”4.1 用SQLAlchemy建表前先想清楚表和表的关系搞全栈开发最忌讳的是“代码先写表结构后想”。前端页面需要什么数据后端接口就返回什么数据数据库就要有什么表。顺序一旦乱了后面改起来就是牵一发动全身。我拿一个典型场景举例一个图片检测项目的前端页面用户上传图片后端调用YOLO模型做目标检测把检测结果存到数据库前端通过接口查询历史记录。按热词里的flask vue yolo mysql这正好是典型组合。数据库至少要有三张表用户表、上传记录表、检测结果表。它们的关系是一个用户有多条上传记录一条上传记录对应多个检测结果因为一张图片里可能有多个目标。用户表# app/models/user.py from datetime import datetime from werkzeug.security import generate_password_hash, check_password_hash from app import db class User(db.Model): __tablename__ users id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(64), uniqueTrue, nullableFalse, indexTrue) email db.Column(db.String(120), uniqueTrue, nullableFalse) password_hash db.Column(db.String(255), nullableFalse) created_at db.Column(db.DateTime, defaultdatetime.utcnow) # 关系一个用户有多条上传记录 uploads db.relationship(Upload, backrefuser, lazyTrue) 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)上传记录表和检测结果表# app/models/upload.py 和 app/models/detection.py class Upload(db.Model): __tablename__ uploads id db.Column(db.Integer, primary_keyTrue) user_id db.Column(db.Integer, db.ForeignKey(users.id), nullableFalse) filename db.Column(db.String(255), nullableFalse) # 原始文件名 storage_path db.Column(db.String(255), nullableFalse) # 服务器存储路径 upload_time db.Column(db.DateTime, defaultdatetime.utcnow) detections db.relationship(Detection, backrefupload, lazyTrue) class Detection(db.Model): __tablename__ detections id db.Column(db.Integer, primary_keyTrue) upload_id db.Column(db.Integer, db.ForeignKey(uploads.id), nullableFalse) label db.Column(db.String(64), nullableFalse) # 检测到的物体类别 confidence db.Column(db.Float, nullableFalse) # 置信度 x_min db.Column(db.Float, nullableFalse) # 检测框左上角x y_min db.Column(db.Float, nullableFalse) # 检测框左上角y x_max db.Column(db.Float, nullableFalse) # 检测框右下角x y_max db.Column(db.Float, nullableFalse) # 检测框右下角y定义好模型以后在项目中建表python -c from app import create_app, db; from app.models import user, upload, detection; app create_app(); app.app_context().push(); db.create_all()如果后面改了模型结构db.create_all()不会自动更新已有的表需要先删表重建或者用Flask-Migrate做迁移。早期开发阶段直接删表重建最省事数据要保留的场合务必上迁移工具。4.2 前后端分离时接口返回格式需要统一做全栈开发时Flask后端和Vue前端之间的“通信协议”就是JSON结构。如果每个接口返回的格式都不一样前端解析数据的代码要写一堆分支判断排查问题时也是一种折磨。我踩过这个坑以后固定了一套统一响应格式# app/utils/response.py from flask import jsonify def ok(dataNone, messagesuccess): return jsonify({ code: 0, message: message, data: data }) def fail(code, message): return jsonify({ code: code, message: message, data: None })前端看到的响应永远是三层结构code0表示成功非零表示各类错误、message给用户看或者排错用、data真正的业务数据。比如一个用户上传图片并返回检测结果的接口长这样# app/routes/api.py import os from flask import Blueprint, request, current_app from app.utils.response import ok, fail from app.models.upload import Upload from app.models.detection import Detection from app.services.yolo_service import detect_image from app import db api_bp Blueprint(api, __name__) api_bp.route(/detect, methods[POST]) def detect(): # 1. 参数校验 if image not in request.files: return fail(4001, no file uploaded) file request.files[image] if file.filename : return fail(4002, empty filename) # 2. 保存文件 upload_dir current_app.config[UPLOAD_FOLDER] os.makedirs(upload_dir, exist_okTrue) from werkzeug.utils import secure_filename filename secure_filename(file.filename) save_path os.path.join(upload_dir, filename) file.save(save_path) # 3. 调用YOLO模型 try: results detect_image(save_path) except Exception as e: return fail(5000, fdetection failed: {str(e)}) # 4. 保存记录到数据库 upload Upload(filenamefilename, storage_pathsave_path) db.session.add(upload) db.session.flush() # 先把upload.id生出来 for r in results: detection Detection( upload_idupload.id, labelr[label], confidencer[confidence], x_minr[box][0], y_minr[box][1], x_maxr[box][2], y_maxr[box][3] ) db.session.add(detection) db.session.commit() # 5. 组装返回数据 return ok({ upload_id: upload.id, results: results })这里有个细节值得说db.session.flush()在commit之前调用目的是让数据库为upload生成自增id后面插入Detection表时才能正确关联外键。如果你直接commit了再拿upload.id就会因为session过期而拿不到。4.3 YOLO怎么嵌进Flask算法服务和Web服务解耦热词里出现了yolo我顺便说说怎么把模型推理优雅地接进Flask项目。最大的坑是YOLO模型加载和推理是CPU/GPU密集型操作如果直接在Flask的路由函数里调用torch.load或者yolov8的model.predict第一个请求会卡得离谱同时多个请求还可能因为GPU显存竞争直接崩掉。我的做法是单独封装一个services/yolo_service.py模型加载只做一次用模块级变量或者懒加载推理部分独立出来# app/services/yolo_service.py from ultralytics import YOLO _model None def get_model(): global _model if _model is None: # 模型文件放在服务器本地首次加载后常驻内存 _model YOLO(yolov8n.pt) return _model def detect_image(image_path): model get_model() results model(image_path, verboseFalse) detections [] for result in results: boxes result.boxes for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() detections.append({ label: model.names[int(box.cls[0])], confidence: round(float(box.conf[0]), 4), box: [round(x1), round(y1), round(x2), round(y2)] }) return detections用模块级全局变量_model做单例避免每次请求都重新加载模型。这里有个隐藏的并发问题如果同时多个请求触发get_model()可能重复加载模型。一个简单的方法是在加载逻辑上加个线程锁import threading _lock threading.Lock() _model None def get_model(): global _model if _model is None: with _lock: if _model is None: # 双重检查锁 _model YOLO(yolov8n.pt) return _model再往深走一步如果项目QPS很高模型推理就不能继续压在Flask进程里了应该单独拆一个推理服务比如用Celery做异步任务队列或者单独部署一个PyTorch推理服务。但对于绝大多数中小型全栈项目来说上面的单例模式已经够用。5. 前端对接Flask时这三个问题不解决你会崩溃5.1 跨域问题前端报CORS error的根因Vue开发服务器默认跑在5173端口Flask跑在5000端口端口不同就意味着跨域。前端fetch或者axios发请求浏览器先发一个OPTIONS预检请求如果后端不处理控制台就会冒出一堆CORS error。解决这个问题我在上面应用工厂代码里已经加了flask-corsCORS(app, resources{r/api/*: {origins: *}})开发阶段可以直接放通所有来源省事。但生产环境建议把origins换成真实前端域名避免别人网站直接调你的API。有个细节如果你加了flask-cors还报跨域先确认是不是自己的路由没匹配上。CORS资源匹配规则是/api/*如果你的接口挂在别的路径下预检请求照样过不去。检查顺序先看请求路径是否以/api开头再看后端是否成功处理了OPTIONS请求。5.2 静态文件与前端构建产物的部署关系Flask全栈项目有两种部署形态。第一种是前后端完全分离前端用Vite构建出dist目录部署时由Nginx托管所有/api开头的请求反向代理到Flask前端自己的路由由Nginx处理。第二种是把前端构建产物拷到Flask的static目录用Flask直接托管。我个人推荐第一种原因很简单解耦。前端发布不需要动后端服务后端出问题不会拖垮静态资源。而且Vue的路由模式如果是history模式Nginx需要配置try_files兜底到index.html这类问题排查起来比Flask托管要清晰。如果项目规模不大图省事也可以把dist内容拷到app/static下然后用Flask的send_from_directory提供文件服务# app/routes/main.py from flask import Blueprint, send_from_directory main_bp Blueprint(main, __name__) main_bp.route(/) def index(): return send_from_directory(../static, index.html)注意send_from_directory的路径是相对于蓝图所在文件的这里的../static实际上是app/static目录。5.3 文件上传接口的前后端配合前端用Vue上传图片时FormData的字段名必须和后端request.files里取的一致。我在上面接口里用的是request.files[image]前端就得// Vue前端代码 const formData new FormData() formData.append(image, file) axios.post(/api/detect, formData, { headers: { Content-Type: multipart/form-data } })这里有个非常常见的坑上传大文件时Flask默认限制是1MB超出直接报413。我上面config.py里设置了MAX_CONTENT_LENGTH 16MB如果你要传更大的文件记得调这个值同时Nginx的client_max_body_size也要同步调大否则文件过了Flask这一关可能死在Nginx那里。6. 用户认证怎么做从Session到JWT的完整方案6.1 为什么前后端分离要选JWTFlask传统的用户登录方案是SessionCookie服务器把Session ID塞给浏览器浏览器下次请求带上Cookie服务器查Session判断用户身份。这个方案在两个场景下很顺畅一是服务端渲染页面二是前后端同源部署。但前后端分离之后前端可能跑在5173端口后端跑在5000端口Cookie跨域带不过去除非做复杂的认证配置而且移动端App根本不吃Cookie这套。这时候JWTJSON Web Token就是更合适的方案用户登录成功后后端签发一个带签名的Token返回给前端前端把它存到localStorage或者内存里每次请求在Authorization头里带上后端验签通过就放行。Token本身是自包含的服务器不需要存储Session无状态非常适合现在这种多端接入的架构。6.2 Flask里用JWT做登录认证的完整流程我平时不会为了一个登录功能硬塞一个重型的flask-jwt-extended库虽然它也很好用但大多数项目我自己用PyJWT就足够了。流程分三步登录签发Token、请求校验Token、视图保护。先装依赖pip install pyjwt登录接口# app/routes/auth.py import jwt from datetime import datetime, timedelta, timezone from flask import Blueprint, request, current_app from app.models.user import User from app.utils.response import ok, fail auth_bp Blueprint(auth, __name__) auth_bp.route(/login, methods[POST]) def login(): data request.get_json() username data.get(username) password data.get(password) if not username or not password: return fail(4001, username and password are required) user User.query.filter_by(usernameusername).first() if user is None or not user.check_password(password): return fail(4003, invalid username or password) # 签发Token有效期24小时 payload { user_id: user.id, username: user.username, exp: datetime.now(timezone.utc) timedelta(hours24) } token jwt.encode(payload, current_app.config[SECRET_KEY], algorithmHS256) return ok({token: token, username: user.username})校验Token的装饰器# app/utils/auth_decorator.py from functools import wraps import jwt from flask import request, current_app from app.utils.response import fail def token_required(f): wraps(f) def decorated(*args, **kwargs): token None auth_header request.headers.get(Authorization) if auth_header and auth_header.startswith(Bearer ): token auth_header.split( )[1] if not token: return fail(4004, token is missing) try: payload jwt.decode(token, current_app.config[SECRET_KEY], algorithms[HS256]) request.user_id payload[user_id] request.username payload[username] except jwt.ExpiredSignatureError: return fail(4005, token has expired) except jwt.InvalidTokenError: return fail(4006, invalid token) return f(*args, **kwargs) return decorated有了这个装饰器要保护某个接口只需要一行api_bp.route(/detect, methods[POST]) token_required def detect(): # 此时request.user_id就是当前登录用户的ID ...这里有个小细节jwt.decode如果Token过期会抛ExpiredSignatureError如果签名不对会抛InvalidTokenError两个异常要分开捕获否则用户拿到一个过期的Token前端的提示可能莫名其妙。6.3 Token过期后的前端处理策略JWT方案不是没有槽点最麻烦的是Token一旦签发旧Token在过期前永远有效。用户修改密码、封号、退出登录都不能让Token立即失效。我见过不少项目对这个问题的处理方案是后端把Token的jti存数据库校验时查一下这个jti是否在“黑名单”里。这个方案能用但引入了数据库查询削弱了JWT“无状态”的优势。中小项目我更推荐另一个组合Token有效期设短一些比如2小时前端用axios拦截器统一处理401响应提示用户重新登录// axios响应拦截器 axios.interceptors.response.use( (response) response, (error) { if (error.response error.response.status 401) { // 把用户踢回登录页 localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } )如果再精细一点可以做双Token机制Access Token短效Refresh Token长效刷新Token的接口单独处理。但这个东西带来的复杂度对绝大多数Flask全栈项目来说是超标了我建议先把单Token方案跑稳再说。7. 部署上线的完整链路与生产环境避坑记录7.1 为什么开发服务器不能直接上生产Flask自带的app.run(debugTrue)开发服务器是单进程的只适合本地调试。我曾经图省事直接把它放到服务器上跑结果来了几个请求就卡死。原因很简单开发服务器自带的是Werkzeug的单进程单线程模型既不能充分利用多核CPU也没有超时保护各种安全层面的防护更是约等于零。生产环境的标准组合是Gunicorn或者其他WSGI服务器运行Flask应用Nginx做反向代理和静态资源服务。Gunicorn在前Nginx在后各司其职。安装和启动pip install gunicorn # 4个worker进程每个进程同一时刻处理多个请求 gunicorn -w 4 -b 127.0.0.1:5000 run:app这里run:app指的是run.py文件里的app对象。如果用了应用工厂模式也可以支持gunicorn -w 4 -b 127.0.0.1:5000 app:create_app()Worker数量不是越大越好一般按CPU核心数*21来估算。4核CPU开9个worker是合理值再往上走进程间切换开销变大效果反而下降。7.2 Nginx配置里那些“不配就踩坑”的项Nginx配置是我在生产环境里踩坑最多的地方。下面是一个我实际在用的配置模板server { listen 80; server_name your-domain.com; # 前端静态文件Vue构建产物 root /var/www/flask_fullstack/frontend/dist; index index.html; # 前端history路由模式兜底 location / { try_files $uri $uri/ /index.html; } # API请求反向代理到Gunicorn location /api/ { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 大文件上传限制 client_max_body_size 20m; }这里有几个容易出问题的点第一proxy_set_header必须写全。特别是X-Forwarded-Proto如果不设置Flask里如果用了request.url或者需要生成绝对URL生成出来的协议会是http在HTTPS环境下就是错的了。第二client_max_body_size是Nginx直接拒绝大文件的关卡大小必须大于Flask端的MAX_CONTENT_LENGTH否则你Flask配了16MBNginx默认1MB就直接拦截了前端看到的是Nginx的413页面不是你自己定义的那个JSON错误。第三try_files $uri $uri/ /index.html这个是Vue history路由模式的必配项。不配的话刷新一个子页面路径比如/dashboardNginx找不到这个文件返回404前端路由就崩了。7.3 数据库连接池和日志这两个隐藏坑MySQL和Flask一起部署时有一个特别隐蔽的问题MySQL默认的wait_timeout是8小时如果你的Flask应用用了长连接8小时没有任何请求后MySQL会把连接断掉。下次有请求进来SQLAlchemy用的还是那条已经断掉的连接直接报Lost connection to MySQL server during query。解决方法是设置SQLAlchemy的连接池回收时间# config.py 中追加 SQLALCHEMY_ENGINE_OPTIONS { pool_pre_ping: True, # 每次取连接前先ping一下断线就重建 pool_recycle: 280, # 连接空闲超过280秒自动回收重建低于MySQL的wait_timeout pool_size: 10, # 连接池大小 max_overflow: 5, # 连接池用完时最多额外创建的连接数 }pool_pre_pingTrue和pool_recycle280这两个设置我强烈建议直接写进你的config里它们能帮你省掉大量“程序跑了一晚上第二天早上第一个请求必报错”的排查时间。日志方面Gunicorn默认日志在终端重启就丢。部署时建议把访问日志和错误日志都写到文件里方便出问题回溯gunicorn -w 4 -b 127.0.0.1:5000 \ --access-logfile /var/log/flask/access.log \ --error-logfile /var/log/flask/error.log \ app:create_app()7.4 从开发到上线的checklist我每次部署Flask全栈项目都会过一遍这个清单遇到问题就少很多[ ] .env里SECRET_KEY是否换成了随机字符串而不是默认的dev-secret-key[ ] 数据库连接串里的密码是否通过环境变量注入[ ] debug模式是否关闭app.run不写debugTrue[ ] Nginx的client_max_body_size是否大于Flask的MAX_CONTENT_LENGTH[ ] SQLALCHEMY_ENGINE_OPTIONS是否配了pool_pre_ping[ ] CORS是否限制了来源而不是直接用*[ ] 上传目录是否有写入权限目录是不是在代码仓库之外[ ] 前端构建产物是否是最新版本8. 我跑通Flask全栈项目后的几点体会从最开始用单文件跑通一个hello world到后来用FlaskVueYOLOMySQL做完一个完整项目中间踩过的坑一只脚数不过来但现在回头看最值得强调的经验反而是那些看似“太基础”的东西目录结构从一开始就要按工程化来设计配置项从第一天就不要硬编码前后端接口的返回格式第一时间统一。这些决策越早做后期就越轻松。最后分享一个小技巧开发阶段我习惯把SQLALCHEMY_ECHO设为True这样所有SQL都会打印在终端里。前端调用接口时你能同时看到HTTP请求和SQL语句前后端哪里对不上一眼就能定位。等联调结束再把它关掉避免生产环境刷屏。Flask这个框架最好的地方在于它从不替你做决定一旦你想清楚了项目的边界和结构它能给的自由度是超乎预期的。这套链路我自己跑了多个项目稳定可靠你照着搭一遍踩坑的概率会小很多。本文还有配套的精品资源点击获取
返回列表