1. 项目概述:为什么一个“FLASK+VUE+axios前后端交互”的小demo值得花三天重做三遍
你有没有过这种经历:照着教程跑通了一个 Flask + Vue 的登录页,表单能提交,后端也能打印出 request.form,但一刷新页面就404,F5一下数据全丢,控制台还报一堆 CORS 警告?或者 Vue 里用 axios.get('/api/user'),Flask 写了 @app.route('/api/user'),结果浏览器 Network 面板里永远显示 pending,最后发现是 Flask 默认只监听 127.0.0.1,而 Vue 开发服务器跑在 localhost:8080——这两个“本地”根本不是同一个网络上下文。这不是你代码写得差,而是这个组合看似简单,实则横跨了三个技术栈的运行时边界:Python 进程的 WSGI 生命周期、Node.js 的 webpack-dev-server 代理机制、以及浏览器同源策略对 XMLHttpRequest 的硬性约束。我带过的某高校前端实训班里,超过七成学员卡在“能连上但拿不到数据”这一步,问题不出在语法,而出在对“交互”二字的物理理解——它不是函数调用,是三次 HTTP 往返、两次进程间通信、一次跨域协商。所以这篇不讲“怎么写路由”,而是拆解“为什么必须这样配代理”、“为什么 axios 的 baseURL 不能填 http://localhost:5000”、“为什么 Flask 的 jsonify() 返回中文会乱码而 json.dumps() 不会”。核心关键词就是FLASK+VUE+axios前后端交互,它解决的不是“能不能通”,而是“通得稳、看得清、改得快”。适合两类人:一是刚从 jQuery 时代转过来、对现代前端构建流程陌生的后端开发者;二是 Vue 能写组件但没亲手搭过真实 API 管道的前端新手。接下来所有内容,都来自我在某跨平台系统中落地 17 个微服务接口的真实踩坑记录,没有理论堆砌,只有命令行回显、Network 面板截图逻辑和 config 文件的逐行注释。
2. 整体架构设计与方案选型:为什么拒绝“Flask 直接 serve Vue”这种偷懒思路
2.1 两种常见错误架构及其致命缺陷
很多初学者会本能选择“Flask 全包”方案:把 Vue 编译后的 dist 目录整个扔进 Flask 的 static 文件夹,然后用 send_from_directory 返回 index.html,再靠 Vue Router 的 history 模式实现前端路由。表面看很优雅,实际埋了三颗雷:
第一颗雷:开发调试完全失能
Vue 的热更新(HMR)依赖 webpack-dev-server 的 WebSocket 长连接,一旦被 Flask 拦截,修改 .vue 文件后浏览器不会自动刷新,必须手动 F5——这直接废掉了 Vue 生态最核心的开发体验。我试过用 Flask-SocketIO 强行桥接,结果 CPU 占用飙到 90%,因为两个服务都在抢 event loop。第二颗雷:API 调试链路断裂
当 axios 请求 /api/login 时,如果 Flask 同时承担静态资源和 API 服务,Network 面板里所有请求都显示为 200(因为 Flask 把 /api/login 当作静态路径 fallback 到 index.html),你根本分不清是接口真返回了数据,还是前端路由兜底了。某次线上事故就是因为这个,测试环境一切正常,生产环境因 Nginx 配置差异导致所有 API 请求被重写为首页,日志里全是 200,查了六小时才发现是架构缺陷。第三颗雷:跨域问题伪解决
有人会加@cross_origin()装饰器,但这只是让浏览器放行 OPTIONS 预检请求,真正的数据传输仍受同源策略限制。更危险的是,@cross_origin(origin='*')在生产环境等于裸奔,任何网站都能用 script 标签发起 CSRF 攻击。我们某实验室曾因此被爬虫扫走全部用户邮箱。
2.2 我们采用的“进程分离+代理穿透”方案
最终选定的方案是:Vue CLI 自带的 devServer 代理功能,这是目前最轻量、最符合现代前端工作流的解法。它的本质不是“让 Flask 接收前端请求”,而是“让 Vue 开发服务器在收到 /api/* 请求时,悄悄转发给 Flask,再把响应原样塞回浏览器”。整个过程对前端代码完全透明,axios 依然写/api/user,浏览器 Network 面板里也只看到一个请求,但背后发生了三次进程通信:
- 浏览器 → Vue DevServer(localhost:8080)
- Vue DevServer → Flask(localhost:5000)
- Flask → Vue DevServer → 浏览器
这个方案的优势在于:
- 零侵入前端代码:axios 不用改 baseURL,Vue Router 不用切 hash 模式,所有 Vue 插件(如 vuex-persistedstate)照常工作;
- 调试链路完整:Network 面板里能看到真实的请求/响应头、状态码、耗时,甚至能用 Chrome 的 “Replay XHR” 功能重发请求;
- 安全边界清晰:生产环境只需把 Vue 构建产物部署到 Nginx,Flask 单独跑在 Gunicorn,两者通过内网通信,天然隔离公网暴露面。
提示:这个方案唯一的要求是——你的 Vue 项目必须由 Vue CLI 创建(即有 vue.config.js),而不是用 Vite 或纯手工 webpack。Vite 的代理配置逻辑不同,本文后续所有配置均基于 Vue CLI 4.5+ 和 Flask 2.2+。
2.3 为什么不用 Nginx 做开发代理?
有经验的运维可能会说:“直接配 Nginx 反向代理不更专业?” 理论上没错,但开发阶段会极大拖慢迭代速度。Nginx 配置修改后必须sudo nginx -s reload,每次都要输密码,且配置语法严格(少个分号就启动失败)。而 Vue CLI 的代理是内存级热重载,改完 vue.config.js 保存,终端自动提示 “Proxy created: /api -> http://localhost:5000”,全程无需重启任何服务。某次紧急修复接口字段,用 Nginx 方案平均耗时 4 分钟/次,用 Vue CLI 代理仅需 12 秒——这差距在一天改 20 次接口时就是 1 小时的生产力。
3. 核心细节解析与实操要点:vue.config.js 里的每一行都是血泪教训
3.1 代理配置的黄金三参数:target / changeOrigin / pathRewrite
Vue CLI 的代理功能藏在vue.config.js文件中,其核心是devServer.proxy对象。网上很多教程只写一行'/api': 'http://localhost:5000',这在 80% 的场景下会失效。真正稳定的配置必须包含以下三个参数:
// vue.config.js module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:5000', // 必须写 http://,不能只写 localhost:5000 changeOrigin: true, // 必须为 true,否则 Flask 收到的 Host 头仍是 localhost:8080 pathRewrite: { // 必须重写路径,否则 Flask 收到的是 /api/user 而非 /user '^/api': '' } } } } }我们来逐行解释为什么缺一不可:
target 必须带 http:// 协议头
如果写成target: 'localhost:5000',Vue DevServer 会尝试用相对协议(可能是 ws://)连接,导致 ECONNREFUSED 错误。实测下来,即使 Flask 明确监听 0.0.0.0:5000,不加协议头的 target 也会在 Windows 系统上 100% 失败。这是因为 Node.js 的 http-proxy-middleware 库底层调用的是url.parse(),它要求 protocol 字段存在。changeOrigin 必须为 true
这个参数的作用是修改代理请求的 Host 请求头。当浏览器访问http://localhost:8080/api/user时,Vue DevServer 会向 Flask 发起新请求,如果不设changeOrigin: true,这个新请求的 Host 头仍是localhost:8080,而 Flask 默认只信任localhost:5000。结果就是 Flask 返回 404 或 400,日志里显示Invalid host header。我踩过的最深的坑是:某次在公司内网开发,Flask 配置了app.config['SERVER_NAME'] = 'dev-api.company.local',结果changeOrigin: false导致所有请求被 Flask 的 Host 校验拦截,查日志查到凌晨两点才发现是这行配置。pathRewrite 是路径清洗的关键
假设 axios 发起axios.get('/api/user/123'),Vue DevServer 默认会把整个路径(包括/api)原样转发给 Flask,即 Flask 收到的请求路径是/api/user/123。但你的 Flask 路由写的是@app.route('/user/<int:id>'),显然不匹配。pathRewrite的作用就是在转发前把/api前缀砍掉,让 Flask 收到干净的/user/123。注意正则'^/api'中的^表示开头匹配,避免误删/user/api/token这类路径。
注意:pathRewrite 的值必须是字符串空值
'',不能写成null或undefined,否则代理会静默失败,Network 面板里请求状态变成(cancelled),没有任何错误提示。
3.2 Flask 端必须做的三件事:CORS、JSON 中文支持、调试日志增强
光配好前端代理还不够,Flask 端若不做适配,依然会返回乱码或 400 错误。以下是三个必须执行的初始化操作:
第一,禁用 Flask-CORS 的全局 wildcard
很多教程教from flask_cors import CORS; CORS(app),这在开发阶段看似省事,但会埋下安全隐患。正确做法是只对开发环境开启精确跨域:
# app.py from flask import Flask, jsonify, request from flask_cors import CORS app = Flask(__name__) # 仅在开发模式下启用 CORS,且只允许 localhost:8080 if app.debug: CORS(app, origins=['http://localhost:8080'], methods=['GET', 'POST', 'PUT', 'DELETE'], allow_headers=['Content-Type', 'Authorization']) @app.route('/user/<int:user_id>') def get_user(user_id): return jsonify({'id': user_id, 'name': '张三', 'role': 'admin'})这里origins=['http://localhost:8080']比origins='*'安全十倍,因为前者明确指定了可信来源,后者会让任何网站都能用<script>标签发起请求。
第二,强制 JSON 响应 UTF-8 编码
Flask 2.0+ 默认用jsonify()返回的 Content-Type 是application/json,但不指定 charset,某些旧版 Chrome 会按 ISO-8859-1 解析中文,导致{"name": "张三"}显示为{"name": "\u5f20\u4e09"}。解决方案是在app.config中强制设置:
app.config['JSON_AS_ASCII'] = False # 关键!禁用 ASCII 转义 app.config['JSON_SORT_KEYS'] = False # 可选,保持字典顺序便于调试这两行加在CORS()初始化之后,就能确保中文原样输出。
第三,添加请求日志中间件,看清 axios 真实发了什么
axios 默认不发送 Cookie,但很多接口需要 session 认证。为了确认请求头是否正确,我在 Flask 里加了简易日志:
@app.before_request def log_request_info(): app.logger.info('Headers: %s', dict(request.headers)) app.logger.info('Body: %s', request.get_data(as_text=True))配合app.logger.setLevel(logging.INFO),启动 Flask 时加上--log-level info,就能在终端实时看到 axios 发送的每个字段。某次发现 axios 的Content-Type被设成了text/plain(因为没传 data 对象),导致 Flask 的request.json为 None,就是靠这个日志秒定位。
3.3 Axios 实战避坑指南:超时、错误拦截、请求取消的正确姿势
axios 不是 jQuery 的$.ajax,它的 Promise 链和错误处理机制完全不同。以下是三个高频问题的解决方案:
问题一:请求超时后页面卡死,loading 状态无法关闭
错误写法:
// ❌ 错误:timeout 触发后 catch 里没重置 loading this.loading = true axios.get('/api/user').then(res => { this.user = res.data }).catch(err => { console.error(err) }) this.loading = false // 这行永远不执行!正确写法(使用 async/await + try/catch):
// ✅ 正确:finally 确保 loading 一定关闭 async fetchUser() { this.loading = true try { const res = await axios.get('/api/user') this.user = res.data } catch (err) { if (err.code === 'ECONNABORTED') { this.$message.error('请求超时,请检查网络') } else { this.$message.error('获取用户失败:' + err.response?.data?.message || '未知错误') } } finally { this.loading = false } }问题二:重复提交表单导致多次创建数据
解决方案是用 axios 的 CancelToken(Vue 2)或 AbortController(Vue 3):
// Vue 2 写法(兼容性更好) export default { data() { return { cancelToken: null } }, methods: { async submitForm() { // 取消上一次未完成的请求 if (this.cancelToken) { this.cancelToken.cancel('重复提交被取消') } // 创建新 token this.cancelToken = axios.CancelToken.source() try { await axios.post('/api/order', this.formData, { cancelToken: this.cancelToken.token }) } catch (err) { if (axios.isCancel(err)) { console.log('请求被取消:', err.message) } } } } }问题三:401 未授权错误不跳转登录页
全局拦截 401 响应:
// utils/request.js const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.response.use( response => response, error => { if (error.response?.status === 401) { // 清除本地 token localStorage.removeItem('token') // 跳转登录页(Vue Router 3) router.push({ path: '/login', query: { redirect: router.currentRoute.value.fullPath } }) } return Promise.reject(error) } )4. 实操过程与核心环节实现:从零搭建可调试的交互管道
4.1 环境准备与版本锁定(避免玄学错误)
先明确本文实测有效的版本组合,这是稳定性的基石:
| 组件 | 推荐版本 | 为什么不是最新版 |
|---|---|---|
| Node.js | 16.18.0 LTS | Node 18+ 的 fetch API 与 axios 冲突,导致 mock 数据时出现TypeError: fetch is not a function |
| Vue CLI | 4.5.15 | Vue CLI 5 的代理模块重构过,changeOrigin行为不一致,某次升级后所有 POST 请求丢失 body |
| Flask | 2.2.5 | Flask 2.3+ 默认启用 strict_slashes,导致/api/user/和/api/user被视为不同路由,而 axios 默认不带尾部斜杠 |
安装命令:
# 全局安装 Vue CLI(不要用 npx,避免每次重装) npm install -g @vue/cli@4.5.15 # 创建 Vue 项目(选择 Manually select features,勾选 Babel、Router、Vuex、CSS Pre-processors) vue create my-flask-vue-app cd my-flask-vue-app # 创建 Flask 后端目录 mkdir ../flask-backend cd ../flask-backend python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install flask==2.2.5 flask-cors==3.0.10提示:务必用
source激活虚拟环境,不要用venv\Scripts\activate.bat(Windows PowerShell 下会报错)。这是某次在客户现场部署时,因激活方式错误导致 pip 安装到全局 Python,引发版本冲突的惨痛教训。
4.2 Flask 后端:一个可调试的用户管理 API
创建app.py,包含基础 CRUD 和调试支持:
# flask-backend/app.py from flask import Flask, jsonify, request, abort from flask_cors import CORS import logging import json app = Flask(__name__) # 配置日志 logging.basicConfig(level=logging.INFO) app.logger.setLevel(logging.INFO) # 开发环境 CORS if app.debug: CORS(app, origins=['http://localhost:8080'], methods=['GET', 'POST', 'PUT', 'DELETE'], allow_headers=['Content-Type', 'Authorization']) # 全局 JSON 配置 app.config['JSON_AS_ASCII'] = False app.config['JSON_SORT_KEYS'] = False # 模拟数据库(实际项目替换为 SQLAlchemy) USERS = [ {'id': 1, 'name': '张三', 'email': 'zhangsan@example.com', 'role': 'admin'}, {'id': 2, 'name': '李四', 'email': 'lisi@example.com', 'role': 'user'} ] @app.route('/') def hello(): return jsonify({'message': 'Flask API is running!'}) @app.route('/api/users', methods=['GET']) def list_users(): app.logger.info(f'GET /api/users from {request.remote_addr}') return jsonify(USERS) @app.route('/api/users/<int:user_id>', methods=['GET']) def get_user(user_id): app.logger.info(f'GET /api/users/{user_id} from {request.remote_addr}') user = next((u for u in USERS if u['id'] == user_id), None) if user is None: abort(404, description="User not found") return jsonify(user) @app.route('/api/users', methods=['POST']) def create_user(): app.logger.info(f'POST /api/users from {request.remote_addr}') if not request.is_json: abort(400, description="Request must be JSON") data = request.get_json() # 简单校验 if not data.get('name') or not data.get('email'): abort(400, description="Name and email are required") new_id = max(u['id'] for u in USERS) + 1 if USERS else 1 new_user = { 'id': new_id, 'name': data['name'], 'email': data['email'], 'role': data.get('role', 'user') } USERS.append(new_user) return jsonify(new_user), 201 @app.errorhandler(404) def not_found(e): return jsonify({'error': 'Not Found', 'message': e.description}), 404 @app.errorhandler(400) def bad_request(e): return jsonify({'error': 'Bad Request', 'message': e.description}), 400 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)启动 Flask:
cd flask-backend python app.py此时访问http://localhost:5000/api/users应返回 JSON 数组,且终端能看到GET /api/users from 127.0.0.1日志。
4.3 Vue 前端:创建可调试的用户列表页
进入 Vue 项目目录,创建src/views/UserList.vue:
<template> <div class="user-list"> <h2>用户列表</h2> <el-button type="primary" @click="fetchUsers">刷新列表</el-button> <el-button type="success" @click="showAddDialog">添加用户</el-button> <el-table :data="users" style="width: 100%; margin-top: 20px"> <el-table-column prop="id" label="ID" width="80"></el-table-column> <el-table-column prop="name" label="姓名"></el-table-column> <el-table-column prop="email" label="邮箱"></el-table-column> <el-table-column prop="role" label="角色" width="100"></el-table-column> <el-table-column label="操作" width="180"> <template #default="scope"> <el-button size="small" @click="editUser(scope.row)">编辑</el-button> <el-button size="small" type="danger" @click="deleteUser(scope.row.id)">删除</el-button> </template> </el-table-column> </el-table> <!-- 添加/编辑弹窗 --> <el-dialog v-model="dialogVisible" :title="dialogTitle"> <el-form :model="form" label-width="80px"> <el-form-item label="姓名"> <el-input v-model="form.name"></el-input> </el-form-item> <el-form-item label="邮箱"> <el-input v-model="form.email"></el-input> </el-form-item> <el-form-item label="角色"> <el-select v-model="form.role" placeholder="请选择"> <el-option label="管理员" value="admin"></el-option> <el-option label="普通用户" value="user"></el-option> </el-select> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="submitForm">确定</el-button> </span> </template> </el-dialog> </div> </template> <script> import axios from 'axios' export default { name: 'UserList', data() { return { users: [], dialogVisible: false, dialogTitle: '添加用户', form: { id: null, name: '', email: '', role: 'user' } } }, methods: { async fetchUsers() { try { const res = await axios.get('/api/users') this.users = res.data } catch (err) { this.$message.error('获取用户列表失败:' + (err.response?.data?.message || err.message)) } }, showAddDialog() { this.dialogTitle = '添加用户' this.form = { id: null, name: '', email: '', role: 'user' } this.dialogVisible = true }, editUser(row) { this.dialogTitle = '编辑用户' this.form = { ...row } this.dialogVisible = true }, async submitForm() { try { if (this.form.id) { // 更新 await axios.put(`/api/users/${this.form.id}`, this.form) this.$message.success('更新成功') } else { // 创建 await axios.post('/api/users', this.form) this.$message.success('添加成功') } this.dialogVisible = false this.fetchUsers() // 刷新列表 } catch (err) { this.$message.error('操作失败:' + (err.response?.data?.message || err.message)) } }, async deleteUser(id) { try { await axios.delete(`/api/users/${id}`) this.$message.success('删除成功') this.fetchUsers() } catch (err) { this.$message.error('删除失败:' + (err.response?.data?.message || err.message)) } } }, mounted() { this.fetchUsers() } } </script>关键点说明:
- 所有 axios 请求路径都是
/api/users,不带http://localhost:5000,完全依赖 vue.config.js 的代理; mounted()钩子自动调用fetchUsers(),确保页面加载即拉取数据;- 错误处理统一用
err.response?.data?.message,避免undefined报错。
4.4 vue.config.js 代理配置详解(含多 API 前缀场景)
创建vue.config.js(与 package.json 同级):
// vue.config.js const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, devServer: { port: 8080, hot: true, // 关键:代理配置 proxy: { // 主 API 前缀 '/api': { target: 'http://localhost:5000', changeOrigin: true, pathRewrite: { '^/api': '' } }, // 文件上传 API(单独配置,避免和 /api 冲突) '/upload': { target: 'http://localhost:5000', changeOrigin: true, pathRewrite: { '^/upload': '/file' } } } } })这里演示了多前缀代理:/api转发到 Flask 根路径,/upload转发到 Flask 的/file路由。这样前端可以写axios.post('/upload/avatar', formData),而 Flask 只需定义@app.route('/file/avatar')。
启动 Vue 项目:
cd my-flask-vue-app npm run serve此时打开http://localhost:8080,Network 面板里应看到:
GET /api/users状态 200,Preview 显示用户数组;- 点击“添加用户”,
POST /api/users返回 201; - 点击“删除”,
DELETE /api/users/1返回 200。
实测心得:如果 Network 面板里请求状态是
(pending),90% 是 Flask 没启动或端口被占用;如果是404,80% 是pathRewrite没生效,检查正则是否写了'^/api';如果是400,大概率是 axios 发送的 Content-Type 不是application/json,在axios.post()里显式声明:axios.post('/api/users', data, { headers: { 'Content-Type': 'application/json' } })
5. 常见问题与排查技巧实录:那些让你怀疑人生的 17 个报错
5.1 网络层问题速查表
| 现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
Network 面板显示(pending) | Flask 进程未启动或端口被占 | lsof -i :5000(Mac/Linux) 或netstat -ano | findstr :5000(Windows) | kill -9 <PID>或换端口启动 Flask |
请求返回404 Not Found | pathRewrite未生效,Flask 收到/api/users | 在 Flask 日志里搜索GET /api/users | 检查vue.config.js中'^/api'的正则,确认有^符号 |
请求返回400 Bad Request | axios 发送的 JSON 格式错误或 Flask 未启用 JSON 解析 | curl -X POST http://localhost:5000/api/users -H "Content-Type: application/json" -d '{"name":"test"}' | 在 axios 请求中显式设置headers: {'Content-Type': 'application/json'} |
请求返回504 Gateway Timeout | Vue DevServer 代理超时(默认 10s) | 修改vue.config.js的proxy配置 | 增加timeout: 30000参数(单位毫秒) |
5.2 浏览器控制台典型错误解析
错误1:Access to XMLHttpRequest at 'http://localhost:5000/api/users' from origin 'http://localhost:8080' has been blocked by CORS policy
这是最经典的跨域错误,但它不是代理没配好,而是 axios 直连了 Flask!检查你的 axios 请求 URL 是否写了http://localhost:5000/api/users。正确写法必须是/api/users,让 Vue DevServer 拦截。如果用了axios.create({baseURL: 'http://localhost:5000'}),立刻删掉。
错误2:Failed to load resource: the server responded with a status of 404 ()
这个 404 后面没有路径,说明请求根本没发出去。常见于 Vue Router 的mode: 'history'未配 Nginx 回退规则,但开发阶段更可能是vue.config.js文件没被识别。验证方法:在vue.config.js里加一行console.log('config loaded'),启动npm run serve时看终端是否输出。如果没输出,说明文件名错了(比如写成vue.config.js.)或位置不对(必须在项目根目录)。
错误3:Cannot read property 'data' of undefined
这是 JavaScript 常见错误,根源是 axios 的response对象结构理解错误。axios.get()返回的是{ data, status, statusText, headers, config, request },而response.data才是后端返回的 JSON。错误写法:res.data.data(以为后端又包了一层),正确写法:res.data。建议在utils/request.js里统一拦截,把response.data提到顶层:
service.interceptors.response.use( response => response.data, // 直接返回 data 字段 error => Promise.reject(error) )5.3 Flask 端隐藏陷阱与调试技巧
陷阱1:Flask 的debug=True导致双重加载
当app.run(debug=True)时,Flask 会启动 reloader 进程,导致app.py被执行两次。如果你在文件顶部写了print("App started"),会看到两行输出。这本身无害,但如果在app.py里初始化了全局变量(如数据库连接池),可能导致连接数翻倍。解决方案:用if __name__ == '__main__':包裹app.run(),并确保所有初始化逻辑都在app = Flask(__name__)之后。
陷阱2:POST 请求 body 为空
即使 axios 发送了 data,Flask 的request.get_json()仍可能返回None。原因有三:
- 请求头
Content-Type不是application/json(axios 默认是,但手动设置时可能写错); - 发送的是 FormData(如文件上传),要用
request.form而非request.json; - Flask 版本低于 2.0,
get_json()默认不解析request.data。
调试命令:
# 在路由函数里加 app.logger.info(f'Request content-type: {request.content_type}') app.logger.info(f'Request data: {request.get_data()}') app.logger.info(f'Request json: {request.get_json(silent=True)}')陷阱3:中文日志乱码(Windows 系统特有)
在 Windows CMD 中运行 Flask,app.logger.info('用户:张三')会显示为用户:??。这是因为 CMD 默认编码是 GBK,而 Python 3 默认 UTF-8。解决方案:在app.py开头加:
import sys import io sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')或者直接用 PowerShell 启动(PowerShell 默认 UTF-8)。
5.4 生产环境部署注意事项(提前规避上线雷区)
开发阶段用代理很爽,但生产环境必须切换。以下是必须修改的三点:
Vue 构建时注入 API 基地址
在vue.config.js中添加:module.exports = defineConfig({ // ...其他配置 configureWebpack: { plugins: [ new webpack.DefinePlugin({ 'process.env.VUE_APP_API_BASE_URL': JSON.stringify( process.env.NODE_ENV === 'production' ? 'https://api.yourdomain.com' : '/api' ) }) ] } })然后在 axios 配置里用:
const service = axios.create({ baseURL: process.env.VUE_APP_API_BASE_URL })Flask 生产启动必须用 Gunicorn
flask run只适用于开发。生产环境用:pip install gunicorn gunicorn -w 4 -b 0.0.0.0:8000 app:app-w 4表示 4 个工作进程,-b指定绑定地址。Nginx 配置必须包含 API 转发和静态资源服务
server { listen 80; server_name yourdomain.com; # 前端静态资源 location / { root /var/www/my-flask-vue-app/dist; try_files $uri $uri/ /index.html; } # API 转发到 Gunicorn 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-