简介:基于Vue、Python Flask、uwsgi、Nginx与MySQL构建的外包项目网站源码,提供完整前后端实现,适合计算机、数学、电子信息等专业学生作为课程设计、期末大作业或毕业设计参考,也适合想了解真实外包项目结构的开发者学习借鉴。压缩包共222个文件,大小约6MB,主要包含35个Vue前端组件、34个Python后端脚本、32个JavaScript逻辑文件、31个pyc编译文件,以及HTML页面、图片、JSON配置、Nginx部署配置等,覆盖前端展示、后端接口、数据库交互与服务器部署等关键环节。源码下载后可直接运行,可对照学习前后端分离项目的开发流程、接口设计与uwsgi+Nginx的生产部署方式;包内附带的构建配置和静态资源也有助于理解工程化工具链,便于二次开发与功能扩展。目前已有1160人学习下载,适合需要参考实际外包项目目录结构、快速搭建类似业务网站并完成毕设或课设的场景。
1. 外包项目网站的完整源码长什么样:一套能演示、能改、能上线交付的工程
接手外包项目的时候,客户要的往往不是一个漂亮页面,而是一套能演示、能修改、能部署的完整工程。这套 vue 写前端、python 写接口、mysql 存数据、uwsgi 和 nginx 负责上线的外包项目网站源码,就是标准的业务型网站骨架。对于正在找 python vue 源码的人来说,它比零散 demo 有价值的地方在于:前后端是通的,数据库脚本是齐的,改一改就能给客户跑演示。适合三类人——接小型外包的开发者、刚学 flask 和 vue 想跑通全流程的、以及要给甲方做效果验收再谈尾款的。后面的章节我按本地复现、二次开发、服务器部署的顺序走一遍,顺手把最容易翻车的位置标出来。
2. 五个组件各管哪一段:vue、flask、uwsgi、nginx、mysql 的职责边界
2.1 flask 为什么在外包项目里比 django、fastapi 更稳
外包网站的需求特征是变化快、周期短、甲方经常在验收前临时加表单和列表页。flask 的核心优势是轻,一个入口文件就能起服务,路由写法直观,加接口的成本很低。django 自带 admin 后台、ORM 和完整目录约束,功能确实强,但外包项目的页面量通常不到二十张,引入 django 反而让结构变重,新人接手时连「该往哪个目录放代码」都要想半天。fastapi 的异步性能和自动文档是加分项,但你需要考虑团队里其他人熟不熟。接外包的人应该深有体会:项目终归要交给甲方或下一个人维护,用 flask 是在「开发效率」和「交接成本」之间取平衡。
flask 还有一个对外包很实际的优点:它不强求你用某一种数据库访问方式。pymysql 直连、SQLAlchemy ORM、甚至简单的 json 文件都能跑通。小项目用直连最省事,后面要加表的时候复制粘贴改 SQL 就行,不需要维护复杂的模型映射关系。这就是为什么市面上大量外包项目网站、数据可视化后台、管理系统的源码都选 flask——它留给开发的自由度最大,踩坑时也最容易定位。
2.2 vue 在前端扮演的角色:路由、组件与插槽
外包网站的前端通常包含首页、列表页、详情页、后台管理页这几类,vue 的组件化特性让这些页面可以拆成多个单文件组件来维护。路由由 vue-router 控制,每个 url 对应一个 view 组件,导航菜单和页面内容的切换不需要刷新浏览器,演示的时候客户观感会好很多。组件复用是另一个直接受益点,比如「弹窗确认」这类交互,我用插槽封装成一个通用组件,页面里传不同的内容就行,改样式也只改一处。这几个特性保证了页面数量再多,代码量也能控制在合理范围内。
很多人在「vue 项目源码怎么发给别人」这个问题上翻过车:把 src 目录一打包就发过去,对方拿到根本跑不起来。原因在于 vue 工程里 node_modules 不参与交付,对方需要 package.json、vue.config.js、路由配置和接口代理配置全部齐全才能还原环境。这套源码既然是完整工程,这些文件都是配套的,这也是它比「纯页面模板」适合外包交付的原因。
2.3 uwsgi 与 nginx 的真正分工:进程托管和反向代理
flask 自带开发服务器只适合本地调试,生产环境需要 uwsgi 来把 flask 应用跑成稳定进程。uwsgi 做的事情是加载 python 应用、分配进程和线程、接收来自 nginx 的请求再把响应还回去。nginx 站在更前面,对外监听 80 端口,把请求转发给 uwsgi 的 socket,同时兼任静态文件服务器,前端打包出来的 js、css、图片直接由 nginx 处理,不需要经过 python。这就是「nginx 反向代理」的标准模型:用户只看得到 nginx,flask 和 uwsgi 藏在后面。
mysql 在这套架构里承担数据存储。外包项目用 sqlite 当然能省事,但甲方经常要单独导数据、要自己装一个客户端看表,mysql 是更通用的交付约定。这套五个组件的组合,边界非常清晰:vue 负责页面和交互,flask 负责业务接口,mysql 负责持久化,uwsgi 把 flask 变成稳定服务,nginx 统一收口对外流量。它不擅长高并发访问和重 IO 场景,但业务管理型网站恰好是它的舒适区。
| 组件 | 职责 | 选型理由 |
|---|---|---|
| vue | 页面渲染、路由切换、数据展示 | 组件化好维护,演示效果直观 |
| flask | 业务接口、数据读写 | 轻量灵活,开发与交接成本低 |
| uwsgi | 托管 flask 进程 | 生产环境稳定运行 python 应用 |
| nginx | 对外端口、静态文件 | 反向代理成熟,静态资源性能好 |
| mysql | 业务数据存储 | 甲方接受度高,交付通用 |
3. 从零复现:环境准备、依赖安装与本地联调跑通
3.1 mysql 建库建表:字符集和账号授权先搞定
先把 mysql 装好。Windows 上安装时最容易出问题的是安装路径带空格、命令行工具不在 PATH 里、以及 root 默认密码策略太严。我一般的做法是安装时选「Developer Default」,端口默认 3306,root 密码设一个自己记得住但稍微复杂的值,安装完成后到系统服务里确认 mysql 服务已启动。装好之后,第一步不是导数据,而是先建库并设置字符集。
mysql -u root -p CREATE DATABASE project_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'project_user'@'localhost' IDENTIFIED BY 'Project@123'; GRANT ALL PRIVILEGES ON project_db.* TO 'project_user'@'localhost'; FLUSH PRIVILEGES; USE project_db; SOURCE /path/to/database.sql;这里把数据库名、账号、密码都定义成项目可识别的形式。字符集用 utf8mb4 而不是 utf8,是因为 utf8mb4 能完整支持中文和特殊符号,避免后续存储用户输入内容时出现「?” 乱码。SOURCE命令执行的是资源里自带的 sql 文件,它会把表结构和初始数据一次性导入。如果你用的是 Navicat 或 DataGrip,直接运行 sql 文件效果也一样,重点是导入后确认表数量和预期一致。
导入完成后建议用一条命令验证一下数据是否真的进来了,这一步能拦住后面一大半的「接口没数据」问题。
mysql -u project_user -p project_db -e "SHOW TABLES; SELECT COUNT(*) FROM project_db.t_example;"SHOW TABLES列出所有表,COUNT(*)验证初始数据不为零。如果表是空的,后面启动前端看到的列表页大概率空白,排查起来容易误判成接口问题。如果项目里用的是 docker 装 mysql,注意容器默认字符集也要额外指定,否则中文乱码问题依旧存在。
3.2 后端 flask 环境:虚拟环境、依赖与启动参数
后端环境准备的核心是虚拟环境。外包项目交付源码时通常不会带上整个 python 环境,而是给一份 requirements.txt,接收方自己装依赖。资源里应该已经包含这个文件,内容大致是 flask、uwsgi、pymysql、flask-cors 这几类基础包。我习惯在项目根目录建一个 venv,把环境隔离在项目内部,这样换机器时只需删除 venv 重新安装,不会污染系统 python。
cd backend python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt pip listpython -m venv venv创建虚拟环境,激活后终端前缀会变成(venv),此时安装的依赖都落在项目里。pip install -r requirements.txt会按资源里锁定的版本安装。装完后用pip list核对关键包是否就位,缺少任何一个,启动阶段都会直接报 ModuleNotFoundError,这类问题越早暴露越好。
依赖就绪后,先确认数据库连接配置跟 3.1 节创建的账号一致。后端一般有一个配置文件,里面是数据库地址、账号、密码、库名。连接串写法通常是这样的:
DB_CONFIG = { 'host': '127.0.0.1', 'port': 3306, 'user': 'project_user', 'password': 'Project@123', 'database': 'project_db', 'charset': 'utf8mb4' }host写 127.0.0.1 而不是 localhost,可以避免部分环境里 socket 连接和 TCP 连接不一致的问题。charset和建库的 utf8mb4 对齐。改完配置就可以启动后端开发服务:
python app.py # 或 flask run --host 0.0.0.0 --port 5000app.py 是资源里常见的后端入口文件名称,--host 0.0.0.0表示监听所有网卡,这样前端 dev server 跨机器访问时也能通。本地联调时也可以只监听 127.0.0.1,减少不必要的暴露。启动成功后在终端会看到Running on http://127.0.0.1:5000,此时后端已经就绪,先别急着关窗口。
3.3 前端 vue 环境:node 版本、npm install 与代理联调
前端需要 node 环境。安装 node 时注意版本不要太新也不要太旧,一般 LTS 版本即可,过新的版本可能导致部分依赖编译失败。装好后在项目根目录执行:
cd frontend npm install npm run devnpm install会读取 package.json 并生成 node_modules,这一步耗时较长,如果网络状况不理想可以配置镜像源,但不要随意升级依赖版本。npm run dev启动的是 vue 开发服务器,默认端口通常是 8080,终端会打印出访问地址。本地联调的关键一步在前端配置里:
// vue.config.js 或 vite 配置中的 server.proxy devServer: { port: 8080, proxy: { '/api': { target: 'http://127.0.0.1:5000', changeOrigin: true } } }proxy的作用是让前端开发服务器把/api开头的请求转发到后端 5000 端口,这样浏览器里访问http://localhost:8080/api/xxx时,实际请求会落到 flask。changeOrigin: true确保请求头里的 Host 字段被改写,避免后端根据 Host 做校验时报错。这一步做对,前后端就打通了。
3.4 本地全链路验证:从浏览器请求到 mysql 数据库
打开浏览器访问http://localhost:8080,正常能看到网站首页。然后按 F12 打开开发者工具,切到 Network 面板,刷新页面,观察/api开头的请求状态。如果返回 200,说明前端到后端通;再看返回数据是否包含实际内容,包含则说明后端到 mysql 也通。
curl http://127.0.0.1:5000/api/project/list这条命令直接打后端接口,绕过前端观察接口原始返回。出现 JSON 数组或者对象,后端链路就确认没问题。如果这步能跑通,整条链路已经验证完毕:浏览器 → vue dev server → nginx 代理转发(本地是 proxy)→ flask → mysql。后续所有开发都建立在这个已验证的链路上,排查问题会轻松很多。
4. 改成你要的项目:接口、页面与数据库的三层改造
4.1 读懂目录:一套外包项目源码的结构拆解
拿到源码先别急着打开每一个文件,先看目录层级。外包项目网站的常见组织方式分为前端和后端两个根目录,前端里面是 src 为主,后端里面是入口文件和业务模块。我一般会先看五个位置:后端入口文件、数据库配置文件、路由文件、前端路由配置、前端接口请求封装。
project/ ├── frontend/ │ ├── src/ │ │ ├── api/ # 接口请求封装 │ │ ├── components/ # 复用组件 │ │ ├── router/ # vue 路由表 │ │ ├── views/ # 页面组件 │ │ └── utils/ # 工具函数 │ ├── package.json │ └── vue.config.js ├── backend/ │ ├── app.py # flask 入口 │ ├── requirements.txt │ ├── config.py # 数据库等配置 │ └── database.sql # 建库建表与初始数据 └── deploy/ ├── uwsgi.ini └── nginx.conffrontend/src/views里放的是每个路由对应的页面,想要改首页文案就去对应的 view 里找;frontend/src/api里封装了所有后端接口的调用函数,后端接口路径有调整时只需要改这里。backend/app.py一般是 flask 的主入口,路由可能直接写在里面,也可能通过 blueprint 拆到多个文件。deploy目录下的 uwsgi 和 nginx 配置,是后面上线的素材。
读目录的目的不是背结构,而是建立一个心理位置:改页面去 views,改接口去后端路由,改请求地址去 api 目录,改数据库去 sql 文件。定位路径明确了,改起来才有章法。
4.2 加一个业务接口:flask 路由到 mysql 查询的完整链路
外包项目经常要加新功能,比如给首页加一个「最新成交记录」列表。这个过程从后端做起,在 flask 里新增一个路由。资源里的后端一般会封装好数据库连接工具,我习惯用 pymysql 直连,逻辑直观,排查时每一行都看得懂。
# app.py 中新增接口 from flask import jsonify, request import pymysql @app.route('/api/project/latest', methods=['GET']) def get_latest_projects(): limit = request.args.get('limit', default=5, type=int) conn = pymysql.connect(**DB_CONFIG) cursor = conn.cursor() sql = "SELECT id, title, price, create_time FROM t_project ORDER BY create_time DESC LIMIT %s" cursor.execute(sql, (limit,)) rows = cursor.fetchall() cursor.close() conn.close() result = [] for row in rows: result.append({ 'id': row[0], 'title': row[1], 'price': row[2], 'create_time': str(row[3]) }) return jsonify({'code': 0, 'data': result})flask的request对象用来读取前端传的参数,limit从 url query 里取,带默认值可以避免前端不传参数时报错。SQL 查询用了%s占位符,而不是把参数直接拼进字符串,这是防止 SQL 注入的底线做法。ORDER BY create_time DESC控制排序,LIMIT %s限制返回条数。fetchall()拿回所有行,然后转成字典列表,通过jsonify返回 JSON。注意conn.close()要记得执行,否则频繁请求会占满数据库连接,本地调试时症状就是接口偶尔卡死。
实现后需要重启 flask 服务让新路由生效。开发模式下 flask 默认开了 reload,代码改动会自动重启,但我还是习惯手动重启一次,确保加载的是最新代码。
4.3 前端页面对接:axios 请求、vue 路由与组件渲染
接口写好了,前端要把数据拿下来展示。vue 项目中通常会用 axios 发起请求,接口地址集中放在src/api目录里,页面组件只负责调用。
// src/api/project.js import axios from 'axios' export function getLatestProjects(limit = 5) { return axios.get('/api/project/latest', { params: { limit } }) }<!-- 首页组件中调用接口 --> <template> <div class="project-list"> <div v-for="item in list" :key="item.id" class="project-item"> <h3>{{ item.title }}</h3> <span>{{ item.price }} 元</span> <p>{{ item.create_time }}</p> </div> </div> </template> <script setup> import { ref, onMounted } from 'vue' import { getLatestProjects } from '@/api/project' const list = ref([]) onMounted(async () => { const res = await getLatestProjects(5) if (res.data.code === 0) { list.value = res.data.data } }) </script>src/api/project.js把接口调用封装成函数,页面里import进来即可,路径用@/api/project这种别名,需要确认工程里有没有配置@指向src目录,没配置的话改成相对路径。onMounted在组件挂载完成后发起请求,ref用来存响应式数据,页面模板里的v-for自动渲染列表。前端 request 的 url 是/api/project/latest,本地联调时由 dev server 的 proxy 转发到后端,上线后由 nginx 做同样的事情,所以接口路径里不需要写后端的 IP 和端口。
需要注意接口返回结构和后端jsonify的结果保持一致。资源里如果前后端已经约定好{ code: 0, data: ... }这种格式,新写的接口也按这个格式返回,前端判断起来才统一。改完页面,浏览器访问对应路由,Network 面板里能看到新接口返回并渲染到页面上。
4.4 网页里「页面-接口-数据」一套改完怎么自测
改完一个功能模块,先不要急着通知客户,自己把三个层面都验证一遍。先是接口层,用 curl 带上参数请求新接口,确认返回数据结构和预期一致。再是页面层,打开对应的路由页面,看控制台有没有报错。最后是数据层,在 mysql 里手动插入一条测试数据,刷新页面确认前端能查到新数据。三步都通过,这个功能才算真的完成。
我见过很多人在这个环节偷懒,接口返回 200 就认为没问题,结果页面空白。问题往往出在字段名对不上——后端返回create_time,前端取createDate,取不到自然不显示。自测的时候用开发者工具看一遍实际返回的 JSON 字段,再对照前端代码里的取值,这一条能避免八成前后端联调翻车。
5. 部署到 linux 服务器:uwsgi 进程托管与 nginx 反向代理
5.1 服务器环境准备:python、node、mysql 的安装确认
部署前先在服务器上确认三个基础组件:python3、node、mysql。外包项目交给客户或部署到云服务器时,环境大多是干净的,需要从头装一遍。apt 装 python3 之后,python3-venv 通常要单独装,否则后面创建虚拟环境会报错。node 在服务器上主要为了打包前端,装 LTS 版本即可。mysql 安装完成后要记得执行安全初始化脚本,设置 root 密码和允许远程访问的账号。
这里有一个容易忽略的细节:服务器上 python 命令可能指向 python2,需要确认python3 --version和pip3 --version都正常。虚拟环境这一步在服务器上尤其重要,直接用系统 python 装包容易和系统自带的包冲突,导致莫名奇妙的版本错误。先把前后端项目代码上传到服务器目录,前端和后端分开放,方便后面分别部署。
5.2 uwsgi 配置:ini 文件里的进程数、socket 与日志
本地跑通只是第一步,让项目稳定地跑在服务器上才是外包交付的关键。uwsgi 需要通过配置文件启动,这样进程数、socket、日志路径都是可控的。资源里的 deploy 目录通常已经有一份可以参考的配置,我自己的标准配置是这样的:
[uwsgi] chdir = /home/deploy/project/backend module = app:app master = true processes = 4 threads = 2 socket = /tmp/project_uwsgi.sock chmod-socket = 666 vacuum = true die-on-term = true logto = /home/deploy/project/logs/uwsgi.logchdir切换到后端项目目录,module = app:app表示加载 app.py 文件里的 flask 应用实例,第一段是文件名,冒号后面是变量名。processes和threads控制并发能力,外包网站一般 4 进程 2 线程足够。socket使用的是 unix socket 而不是端口,uwsgi 和 nginx 在同一台机器上时,unix socket 比 TCP 端口更高效。chmod-socket = 666确保 nginx 进程有权限访问这个 socket 文件。
最关键的是日志配置。没有logto的话,uwsgi 的报错会直接输出到终端,但你用的是 ssh 连接,断开就看不到了。日志落盘后,出问题可以tail -f实时看,也可以事后翻。vacuum = true会在进程退出时清理 socket 文件,避免下次启动时提示 socket 已存在。先把日志目录建好:
mkdir -p /home/deploy/project/logs uwsgi --ini /home/deploy/project/deploy/uwsgi.ini启动后确认进程存在:
ps aux | grep uwsgi如果 ps 输出里没有看到 uwsgi 进程,说明启动即失败,立刻去看logs/uwsgi.log的最后几百行,绝大多数原因会在里面写清楚。
5.3 nginx 配置:location 匹配规则与静态资源分离
nginx 要处理两类流量:一类是前端静态文件,另一类是/api或/开头的动态请求转发给 uwsgi。外包项目通常会先把前端打包成 dist 目录,再由 nginx 托管。配置的核心是区分动静请求。
server { listen 80; server_name your-domain.com; root /home/deploy/project/frontend/dist; index index.html; location /api { include uwsgi_params; uwsgi_pass unix:/tmp/project_uwsgi.sock; } location / { try_files $uri $uri/ /index.html; } location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 7d; add_header Cache-Control "public"; } }location /api把接口请求转发给 uwsgi 的 socket,include uwsgi_params补全必要参数。location /用try_files处理 vue 路由,如果用户直接刷新某个子路由,nginx 会兜底到 index.html,由前端路由接管渲染。这是 vue 路由 history 模式部署时必需的配置,少了这段话刷新页面就会 404。
nginx 中 location 的匹配优先级是实战中经常踩的坑:精确匹配=优先于前缀匹配,^~优先于正则,正则~*优先于普通前缀。上面对静态文件用了一个正则~*,它会在location /之前被尝试匹配,所以 js、css 请求不会打到前端路由兜底逻辑里。静态资源单独设置 7 天缓存,可以减少后端压力,这也是「nginx 静态文件访问」需求的标准做法。配置写好后先测试再加载:
nginx -t nginx -s reloadnginx -t检查配置语法,有错误会直接提示具体行号。reload优雅重载配置,不会中断现有连接。这一步做完,访问域名应该能看到前端页面,接口也能通。如果你在本地虚拟机里用多端口 nginx 做多站点联调,server_name 用虚拟机的 IP 加不同的端口即可,原理一致。
5.4 正式启动:前端构建、uwsgi 托管与验证清单
服务器上部署的完整顺序是:先构建前端,再启动后端,最后加载 nginx。前端构建在项目目录执行:
cd /home/deploy/project/frontend npm install npm run build构建产物默认输出到 dist 目录,nginx 的 root 已经指向这里。构建过程如果报错,先确认是依赖问题还是配置问题,常见的是 npm install 没装全导致运行时报错。构建完成后ls dist确认 index.html 和静态资源目录存在,nginx 才能正常读取。
然后启动 uwsgi,再 reload nginx。全部配置完成后,按下面这个清单逐项验证:
| 验证项 | 命令或方法 | 预期结果 |
|---|---|---|
| uwsgi 进程 | ps aux | grep uwsgi | 多条 uwsgi 进程 |
| socket 文件 | ls -l /tmp/project_uwsgi.sock | 文件存在且权限正确 |
| nginx 配置 | nginx -t | syntax is ok |
| 前端页面 | 浏览器访问域名 | 首页正常渲染 |
| 接口连通 | curl http://域名/api/project/list | 返回 JSON 数据 |
如果接口报 502,先看 uwsgi 日志,多半是 uwsgi 挂了或者 socket 路径两边不一致。报 404 则先看 nginx 的 location 规则,确认 /api 有没有被错误地匹配到了静态资源的正则上。按这个顺序排查,比瞎猜快得多。
6. 避坑记录与排查技巧:几个让我熬夜的典型问题
6.1 后端接口 500:开发环境不报错,上线后全是 500
现象:本地flask run跑得好好的接口,部署到服务器后用域名访问全部返回 500,而 uwsgi 日志里只有一行笼统的错误提示。
原因:本地开发时 flask 的 debug 模式是开的,出错会在浏览器里直接显示堆栈信息;上线后配置里通常关闭了 debug,真正的异常信息只写到日志里,如果你没看日志,就是两眼一抹黑。
解决:把 uwsgi 日志路径记牢,出事第一时间tail -n 100 /home/deploy/project/logs/uwsgi.log。日志里会打印具体的异常堆栈,比如某个依赖没装、数据库连不上、某个 python 语法在生产环境不兼容。不要用关闭 debug 的方式掩盖问题,而是让异常信息暴露出来。从那以后我每次部署完都会强制走一遍「curl 打接口、看 uwsgi 日志尾部、再处理错误」的循环。
6.2 mysql 中文乱码:接口返回正常,前端显示问号
现象:数据库里中文正常,curl 接口得到的中文也是正常编码,但前端页面显示???或者乱码。
原因:数据库表字符集和连接字符集不一致。建库时如果用了 utf8 而不是 utf8mb4,部分中文生僻字和 emoji 会丢;或者后端数据库连接串里没指定charset,pymysql 默认按 utf8 建连,也会出现类似问题。
解决:建库时统一DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,后端 DB_CONFIG 里明确写'charset': 'utf8mb4',再检查 mysql 配置文件里character-set-server的值。如果表已经建好,用ALTER TABLE t_example CONVERT TO CHARACTER SET utf8mb4;转换。字符集问题影响面大,排查时先把「建库字符集、连接字符集、表字符集」三处对齐,基本能根除。
6.3 vue 打包后白屏:本地 dev 正常,上线后页面一片空白
现象:npm run dev访问正常,npm run build后把 dist 放到 nginx 下,访问页面空白,控制台报 js 加载失败或路径 404。
原因:vue 打包时资源默认使用绝对路径/js/app.js,如果站点部署在域名根路径没问题,但部署在子路径下就会找不到文件;另一个常见原因是路由用了 history 模式,服务器没配置回退,刷新子路由时 404,看起来也像白屏。
解决:vue-cli 工程在 vue.config.js 里把publicPath改成'./'或具体子路径,vite 工程则在构建配置里设置base。路由模式如果没有特殊需求,改为 hash 模式最省事;必须用 history 模式时,nginx 里location /加上try_files $uri $uri/ /index.html;。这个坑的诡异之处在于它不影响接口,只影响静态资源,所以排查时先看 Network 面板里 js 文件的请求路径对不对。
6.4 nginx 转发失效:uwsgi_pass 和 socket 都对,接口仍然 404
现象:nginx 配置没问题,nginx -t通过,uwsgi 也启动了,但访问/api接口返回 404,nginx 错误日志里出现open() failed或connect() failed。
原因:uwsgi_pass使用的 socket 路径和 uwsgi.ini 里配置的 socket 路径不一致。两处明明写的是同一个路径,但由于 chdir 生效时机不同,uwsgi 实际创建的 socket 在项目目录下相对路径位置,nginx 却去绝对路径找,自然找不到。此外 location 匹配里/api不带斜杠时,如果配置里写成了proxy_pass而不是uwsgi_pass,也会出现 404 或 502。
解决:uwsgi.ini 里 socket 一律写绝对路径,nginx 里uwsgi_pass unix:后面同样写完整路径,不要用相对路径。改完后ls -l确认 socket 文件真实存在,然后同时重启 uwsgi 和 reload nginx。nginx 对 location 的匹配是「先最长前缀,再正则」,加了静态资源正则后会影响所有包含.js的请求,别把接口路径命名成带.json后缀。总之,静态和动态严格分离,是这套架构不容易出错的前提。
6.5 一个长期有用的习惯:按日志时间戳对齐前后端排查问题
外包项目上线后,客户报一个问题往往只有一个现象描述,比如「页面打不开」「点按钮没反应」「数据不对」。我自己的排查顺序是固定的:先看 nginx 的 access log,确认这个请求有没有到 nginx,返回状态是多少;再看 uwsgi 日志,确认 flask 有没有处理请求、有没有异常堆栈;最后看 mysql 慢查询日志或应用日志,确认 SQL 有没有执行、数据是否正常。
三个日志的时间戳如果对上了,链路中的问题点就会被压缩到一个很小的范围——nginx 日志有记录但 uwsgi 没记录,问题在转发层;uwsgi 有记录但返回异常,问题在后端代码;后端正常但数据不对,问题在 SQL 或者业务逻辑。这个习惯帮我避免了很多次「感觉是前端问题,其实是后端字段变了」的无谓消耗。到现在我每次处理服务器问题都会强制打开这三个日志文件,而不是凭记忆猜。
这个态度放到本地联调也一样适用,希望帮到你。
本文还有配套的精品资源,点击获取