多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Flask+Vue精品课程网站开发实战:从环境搭建到部署踩坑全记录

Flask+Vue精品课程网站开发实战:从环境搭建到部署踩坑全记录 前段时间接了个精品课程网站的项目后台用 Python Flask前端用 Vue开发环境是 PyCharm。标题里带了 django其实我一开始确实在 Flask 和 Django 之间纠结过后来才定下 Flask 这套方案。这个项目不算大但“课程展示、视频播放、后台管理、用户登录”这一整条链路完整做下来踩的坑一点不比大项目少。如果你正准备用 Python 做网站或者在学校、培训机构接到类似的课程网站需求这篇内容应该能帮你省不少时间。我会把技术选型、环境搭建、后端接口、Vue 前端、联调部署、还有各种坑一次讲清楚全部基于我实际开发验证过的方案。1. 技术选型复盘为什么是FlaskVue而不是Django全家桶1.1 这个课程网站到底需要做什么动手写代码之前我先把需求从头到尾列了一遍。精品课程网站的核心场景其实很固定无非是这么几块课程列表展示、课程详情页、视频在线播放、教师信息展示、用户注册登录、后台课程管理再加上公告栏这类边角功能。听着不多但每一块拆开都有细节。比如视频播放课程网站的片源通常是 mp4 或者 m3u8 格式m3u8 这种流媒体格式在浏览器里的兼容性处理就是个典型的坑后面我会专门讲。再比如后台管理如果全部自己手写增删改查页面工作量瞬间就上去了所以当时我就决定后台尽量用现成插件解决。需求梳理清楚之后技术选型就不是拍脑袋而是对着需求一项一项匹配。1.2 Flask与Django的真实对比标题里同时出现 flask 和 django说明很多人在选型时都卡在这两个框架上。我自己的感受是Django 是一个“全家桶”框架ORM、Admin 后台、表单校验、认证系统、模板引擎全都内置了你装好 Django 之后哪怕一行业务代码没写自带的后台管理界面已经能跑了。Flask 则是一个微框架核心只做路由和请求分发ORM、后台管理、表单这些全部靠第三方库自己组装。两者没有绝对的好坏只有合不合适。对精品课程网站这个项目来说我的判断依据是页面交互主要在 Vue 前端处理后端只需要提供 JSON 接口数据模型不算复杂课程、章节、用户、公告四张表就能覆盖后台管理用 Flask-Admin 插件可以十分钟搭出来。这种情况下 Django 自带的一大堆东西反而用不上相当于为了吃碗面买了整套厨房设备。Flask 轻、透明、出了问题容易定位Python 的版本要求也更宽松所以最终定了 Flask。我还做了一张对比表给同样纠结的人一个参考对比维度FlaskDjango学习曲线平缓一个文件就能跑起服务陡峭目录结构和概念较多体积与依赖核心极小按需装扩展全家桶自带ORM/Admin/认证数据库操作常用 SQLAlchemy自己配置内置 ORM迁移工具完善后台管理接 Flask-Admin 或自己写自带 Admin开箱即用适合场景前后端分离的 API 服务、中小型项目全栈快速开发、管理后台密集型项目Django MTV 模式的影响不需要关心 MTV接口层自己控制强制按 MTV 组织代码1.3 什么情况下该选Django选型这事最容易犯的错就是跟风。我见过有人拿 Django 做一个只返回 JSON 的小接口光是处理跨域和序列化就绕了半天。反过来如果项目是服务端渲染为主、后台管理页面特别多比如公司内部 OA 系统、内容管理系统那 Django 的 Admin 加上 MTV 模式的优势就很明显了开发效率能快出一大截。Django 的 MTV 模式说白了就是 Model数据表映射、Template页面模板、View业务逻辑三层分离它的模板语言和服务端渲染能力非常成熟适合页面逻辑集中在后端的场景。而像课程网站这种页面交互复杂、需要做视频播放器定制、选课流程要靠前端状态管理来控制的项目服务端渲染反而碍手碍脚前后端分离才是更顺手的路子。2. PyCharm开发环境的搭建与项目骨架规划2.1 Python虚拟环境建好后最容易翻车的一步很多人第一步就卡在环境上而且卡得莫名其妙。PyCharm 里新建项目时它会自动给你创建一个虚拟环境 venv但如果你是从已有的文件夹打开项目PyCharm 可能没有把解释器指向虚拟环境里的 python于是你在 PyCharm 的终端里执行 pip install flask 装到了全局环境项目运行却用的还是虚拟环境结果就是ModuleNotFoundError: No module named flask。这个问题我帮同事排查过好几次原因全是解释器没选对。解决办法是在 PyCharm 的 Settings 里找到 Project Interpreter选到项目目录下的 venv 或者 conda 环境然后确认终端里python --version和pip --version指向的路径一致。这一步做对了后面所有依赖安装才谈得上。环境就绪后建议把常用依赖一次性装齐pip install flask pip install flask-sqlalchemy pip install flask-cors pip install flask-admin pip install flask-login pip install flask-migrate pip install mysqlclient # 如果用MySQL的话开发初期我建议直接用 SQLite一行配置就能跑不需要单独装数据库服务。到项目后期要上线了再切 MySQL。别一上来就上 MySQL很多新手把时间浪费在数据库连接配置上其实业务逻辑一点没写。2.2 后端目录结构Flask 的灵活意味着目录结构没有标准答案但我强烈建议别把代码全塞进一个 app.py后面会改到怀疑人生。我用的结构是这样course_website/ ├── app.py # 应用入口 ├── config.py # 配置文件 ├── requirements.txt # 依赖清单 ├── models/ │ ├── __init__.py │ ├── user.py │ ├── course.py │ └── announcement.py ├── blueprints/ │ ├── __init__.py │ ├── auth.py # 登录注册 │ ├── course_api.py # 课程接口 │ └── admin_api.py # 后台管理接口 ├── uploads/ # 上传文件目录 ├── static/ # 前端打包后的静态文件 └── templates/ # 如果要用Flask-Admin需要app.py 只做三件事创建应用、注册蓝图、初始化扩展。业务逻辑全部放到 blueprints 和 models 里。这样每加一个功能模块新建一个 Python 文件注册进去就行不会动到主文件。2.3 数据库模型设计课程网站的模型设计关键要处理好一对多的关系。一个课程下面有多个章节一个章节对应一个视频或一套资料这是一对多用户和课程的收藏关系则是多对多。我建了四个核心模型Course 课程、Chapter 章节、User 用户、Announcement 公告。用 Flask-SQLAlchemy 写出来大概是这么个样子from flask_sqlalchemy import SQLAlchemy from datetime import datetime db SQLAlchemy() class Course(db.Model): __tablename__ course id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(120), nullableFalse) subtitle db.Column(db.String(200)) cover_url db.Column(db.String(255)) category db.Column(db.String(50)) teacher db.Column(db.String(50)) description db.Column(db.Text) create_time db.Column(db.DateTime, defaultdatetime.now) chapters db.relationship(Chapter, backrefcourse, lazydynamic) class Chapter(db.Model): __tablename__ chapter id db.Column(db.Integer, primary_keyTrue) course_id db.Column(db.Integer, db.ForeignKey(course.id)) title db.Column(db.String(120)) video_url db.Column(db.String(255)) video_type db.Column(db.String(20), defaultmp4) # 区分mp4还是m3u8 duration db.Column(db.Integer) # 视频时长单位秒 sort_order db.Column(db.Integer, default0)几个细节我想提醒一下。第一lazydynamic这个参数要注意它让 chapters 变成一个查询对象而不是直接加载列表数据量大时性能更好但如果是小项目直接用默认值反而省事。第二video_type字段很多人会忘记设计等到要兼容 m3u8 流媒体播放时才发现不知道前端该用哪种播放方式这时候补字段已经晚了。第三数据库里的时间字段建议统一存 UTC 时间显示时再转北京时间不然部署到服务器上时间差八小时的坑迟早会踩到。建完模型后记得在应用初始化时执行db.create_all()或者直接用 Flask-Migrate 做迁移我一般直接用db.create_all()模型变动频繁的阶段用 Flask-Migrate 更稳妥。3. 后端接口实现课程模块的核心逻辑3.1 用Blueprint把业务拆开Blueprint 是 Flask 里组织路由的核心机制说白了就是把不同业务的路由拆到不同文件里。不拆分的话几十个路由堆在一个文件里每加一个接口都要往下翻半天而且合并冲突是家常便饭。拆开后每个文件只管自己的事auth.py 管认证course_api.py 管课程admin_api.py 管后台。注册蓝图只需要在主文件里几行代码from blueprints.auth import auth_bp from blueprints.course_api import course_bp from blueprints.admin_api import admin_bp app.register_blueprint(auth_bp, url_prefix/api/auth) app.register_blueprint(course_bp, url_prefix/api/courses) app.register_blueprint(admin_bp, url_prefix/api/admin)url_prefix 这个参数建议从一开始就规划好。前后端分离的项目所有接口统一带/api前缀前端代理、后端路由、Nginx 转发都方便。我见过有人每个接口手写完整路径注册蓝图时不加前缀结果改一个前缀要改动几十个地方。蓝图加前缀的方式路径规划是集中在注册这一行的这是最省心的做法。3.2 课程增删改查与文件上传课程接口的核心就是 RESTful 风格的增删改查。列出课程用 GET 请求带分页参数查看详情用 GET 请求带课程 id新增和修改分别用 POST 和 PUT删除用 DELETE。Flask 里处理起来很直接但有几个坑要注意。第一个是分页参数要校验前端传过来的 page 和 per_page 如果不做类型转换和边界判断传一个负数或者字符串进去SQLAlchemy 会直接报错。我一般会写一个统一的参数解析函数把所有参数都走一遍校验再传给数据库。第二个是删除课程时要级联处理章节数据否则会留下孤儿数据。手动处理方式是先删 chapters 再删 course或者在模型中配置cascadeall, delete-orphan。我用的是后者省心很多。文件上传是课程网站的一个重头戏封面图、课程视频、课件资料都要靠它。Flask 处理上传文件用的是 request.files核心逻辑是这样from werkzeug.utils import secure_filename app.route(/api/upload, methods[POST]) def upload_file(): file request.files.get(file) if not file: return jsonify({error: 没有收到文件}), 400 filename secure_filename(file.filename) # 用时间戳重命名避免文件名冲突 ext filename.rsplit(., 1)[-1].lower() new_name f{datetime.now().strftime(%Y%m%d%H%M%S)}_{uuid.uuid4().hex[:8]}.{ext} save_path os.path.join(app.config[UPLOAD_FOLDER], new_name) file.save(save_path) return jsonify({url: f/uploads/{new_name}})这里有几个细节是实战中总结出来的。secure_filename一定要用它能过滤掉文件名里的特殊字符和路径信息防止用户传一个../../../etc/passwd这种文件名。但它对中文文件名不友好会直接把中文过滤掉所以稳妥的做法是不直接使用原始文件名而是用时间戳加随机字符串重新命名原始文件名存到数据库里作为展示名。另外上传目录的路径一定要用os.path.join拼接不要手动拼字符串Windows 和 Linux 的路径分隔符不一样手动拼在跨平台部署时必出问题。还有一个容易被忽略的是上传文件大小限制Flask 默认不限制请求体大小一个几 GB 的视频直接传到内存里的后果是服务器卡死。要在配置里加上MAX_CONTENT_LENGTH同时前端也要做文件大小预检。3.3 用Flask-Admin快速搭后台管理后台管理功能如果全部手写至少要写课程列表页、章节管理页、公告发布页三个模块的前端页面。我用 Flask-Admin 插件十分钟就搞定了。Flask-Admin 的用法是定义 Admin 实例然后把模型注册进去from flask_admin import Admin from flask_admin.contrib.sqla import ModelView admin Admin(app, name课程网站管理后台, template_modebootstrap4) admin.add_view(ModelView(Course, db.session)) admin.add_view(ModelView(Chapter, db.session)) admin.add_view(ModelView(Announcement, db.session))注册完以后访问/admin路径就能看到完整的后台管理界面列表、筛选、编辑、删除全部自动生成不需要写一行前端代码。Flask-Admin 这套东西虽然界面朴素了一点但对内部管理来说完全够用。如果想控制权限可以重写 ModelView 的is_accessible方法判断当前登录用户是不是管理员。我知道很多人觉得用现成插件显得不专业非要自己写后台。我的看法是后台管理页面是给自己人和老师用的不是给最终学生用户看的它的核心价值是快速录入和编辑数据。把节约下来的时间投入到前端的课程展示和交互体验上投入产出比高得多。项目上线后如果后台需要更复杂的定制功能再单独针对那块功能写页面也不迟。4. Vue前端落地从环境配置到课程页渲染4.1 在PyCharm里跑通Vue项目PyCharm 对前端开发的支持很多人没用好这里说下我的操作路径。先确认本机装了 Node.js然后通过 PyCharm 的 Terminal 窗口执行 Vue CLI 的创建命令PyCharm 打开 Vue 项目时会自动识别并支持高亮、补全、调试。创建项目时我选择手动配置因为默认的 ESLint 规则经常把代码格式检查开得太严保存时自动报错对新手极不友好。下面是完整流程# 安装Vue CLI如果还没装 npm install -g vue/cli # 创建项目project-name 自己起 vue create course-frontend # 进入目录安装依赖 cd course-frontend npm install axios vue-router4 element-plus这里有个版本坑要提醒一下。Vue CLI 创建项目时可以选 Vue 2 还是 Vue 3对应的 Router 版本完全不同。Vue 2 配 vue-router3Vue 3 配 vue-router4API 还大变样。如果装错版本最常见的就是router对象找不到routes配置项或者new VueRouter()报错。我的建议是新项目直接上 Vue 3生态已经很成熟了别再纠结兼容老项目的问题了。PyCharm 里跑 Vue 项目有个很实用的功能编辑完代码后会自动热更新浏览器即时生效。控制台里看到的App running at Local: http://localhost:8080就是开发服务器地址。这里要注意的就是端口尽量不要和 Flask 的 5000 端口冲突Vue 默认 8080Flask 默认 5000两者互不干扰对接时再通过代理转发请求。4.2 路由与Axios请求封装Vue Router 的路由设计对课程网站来说保持扁平化即可不需要过度嵌套。我的路由表大概是这样const routes [ { path: /, component: HomeView }, // 首页课程列表 { path: /course/:id, component: CourseDetail }, // 课程详情 { path: /course/:id/play/:chapterId, component: VideoPlayer }, // 视频播放 { path: /admin, component: AdminLayout, meta: { requiresAdmin: true } } ]路由的meta字段用来标记权限配合路由守卫做登录检查。axios 的封装是前端项目的重头戏我会创建一个统一的 api 模块把 baseURL、超时时间、请求拦截器、响应拦截器都配好。响应拦截器里统一处理错误码比如 401 就跳登录页500 就弹错误提示这样每个页面调用接口时不用重复写错误处理。大概这样// src/utils/request.js import axios from axios const request axios.create({ baseURL: /api, // 开发环境走代理生产环境由Nginx转发 timeout: 15000 }) // 请求拦截器把token加到请求头 request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } const params new URLSearchParams() return config }) // 响应拦截器统一处理业务错误 request.interceptors.response.use( response response.data, error { const status error.response error.response.status if (status 401) { localStorage.removeItem(token) window.location.href /login } else if (status 500) { console.error(服务器错误, error.response.data) } return Promise.reject(error) } )注意上面的代码里我用URLSearchParams是为了演示参数序列化的思路实际项目中如果后端接口要 JSON 格式直接传对象即可不需要手动序列化。请求封装的粒度要控制好不要把所有接口都放在一个文件里变成巨型模块按业务模块拆分成 courseApi、authApi、adminApi每个模块引入封装好的 request 实例即可。4.3 课程列表、详情与视频播放页面课程列表页是门面我用了 Element Plus 的卡片布局每个课程卡片展示封面、标题、教师、分类和课程简介。封面图直接用后端返回的 URL这里要注意前端配置一个图片域名的白名单避免部署后图片加载不出来。课程详情页的重点是章节列表我按章节的 sort_order 字段排序展示点击章节后进入播放页。视频播放是课程网站技术含量最高的地方。热词里有人搜了“vue播放m3u8”这说明大家普遍在这个功能上卡过。简单解释一下m3u8 是苹果提出的流媒体播放列表格式它本身不是视频文件而是一个包含多个 ts 分片文件地址的索引文件。浏览器原生 video 标签不支持直接播放 m3u8必须借助 hls.js 这个库在支持 Media Source Extensions 的浏览器里把分片拉下来拼接播放。我用的方案是 video.js 加 hls.js// VideoPlayer.vue 关键部分 import videojs from video.js import video.js/dist/video-js.css export default { mounted() { const options { autoplay: false, controls: true, sources: [{ src: this.videoUrl, type: this.videoType m3u8 ? application/x-mpegURL : video/mp4 }] } this.player videojs(this.$refs.videoPlayer, options) }, beforeUnmount() { if (this.player) this.player.dispose() } }播放器这块有两件事特别重要。第一video.js 的播放器实例一定要在组件销毁时调用dispose()释放掉否则路由切换后播放器资源没释放会导致内存泄漏连续切换几个视频页面浏览器就明显变卡。第二m3u8 跨域请求比普通接口严格得多服务端响应一定要带上 CORS 头否则 hls.js 拉取分片时会被浏览器拦截表现为播放器一直黑屏或者报Cross origin requests are only supported for protocol schemes错误。我一开始没注意这点前端折腾了一下午最后发现是后端没配 CORS。5. 前后端联调跨域、Proxy与生产部署5.1 跨域问题的三种解法前端跑在 8080 端口后端跑在 5000 端口端口不同就产生了跨域问题。浏览器安全策略限制从 8080 的页面去请求 5000 的接口解决办法有下边三种根据场景选择。第一种是后端加 CORS 头Flask 里用 flask-cors 插件最省事from flask_cors import CORS CORS(app)这行代码一加所有接口就都允许跨域了。但要注意开发阶段这样配没问题生产环境里把 CORS 全放开是有风险的任何来源的网页都能向你的接口发请求容易被人恶意调用。生产环境应该限定允许的来源CORS(app, resources{r/api/*: {origins: [http://yourdomain.com]}})第二种是 Vue 开发服务器的代理。在vue.config.js里配置 devServer 的 proxy把符合特定路径前缀的请求转发到后端地址。这样前端的接口请求路径是相对路径/api/courses浏览器看到的是同源的请求跨域问题从根源上消失。第三种是 Nginx 反向代理这个留给生产环境用。5.2 开发环境的Proxy配置开发阶段我推荐用代理方案因为它最贴近生产环境的设计思路。在项目根目录的vue.config.js里加上module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:5000, changeOrigin: true }, /uploads: { target: http://localhost:5000, changeOrigin: true } } } }这里两个前缀分别对应接口和上传文件的访问地址。注意/uploads也要代理否则前端页面里的图片 URL/uploads/xxx.jpg是相对于前端域名访问的拿不到后端服务器上的图片。有一个配置细节是changeOrigin要设为true这样请求头里的 Host 会改成目标地址有些后端框架会根据 Host 做校验不改的话会报 400 或者 403。代理配置完以后前端代码里请求的 baseURL 直接写/api就行不用写全路径。5.3 生产部署方案开发完以后部署上线我遇到的核心问题是两个一是怎么把 Flask 服务和 Vue 打包产物整合到一起二是性能和安全怎么保证。标准做法是把 Vue 项目构建成静态文件放到 Flask 的 static 目录下然后把 Flask 的服务交给生产级 WSGI 服务器运行。Vue 构建很简单npm run build构建完成后dist目录下就是纯静态文件。可以直接把dist的内容拷贝到 Flask 的static目录然后在 Flask 里注册一个路由把前端页面 serve 出去。但更推荐的做法是用 Nginx 托管前端静态文件和反向代理后端接口server { listen 80; server_name yourdomain.com; # 前端静态文件 location / { root /var/www/course-frontend; index index.html; try_files $uri $uri/ /index.html; # 解决Vue路由history模式404问题 } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:5000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 上传文件访问 location /uploads/ { alias /var/www/course_website/uploads/; } }Nginx 配置里最容易踩的坑就是 Vue 路由的 history 模式刷新页面时返回 404。Vue Router 默认用 history 模式时URL 里没有#号比如访问/course/1会直接向服务器发起 GET 请求而 Nginx 静态目录里并没有course/1这个文件就会 404。解决办法就是上面配置里的try_files $uri $uri/ /index.html这一行把所有不存在的路径都重写到 index.html让 Vue Router 自己接管路由分发。这个坑我印象太深了因为开发环境下完全正常部署上线一刷新页面就白屏。部署的后端进程我一般用 gunicorn 或者 waitress。Windows 服务器上 waitress 更友好Linux 上用 gunicorn。热词里有朋友搜过 “django windows10 waitressnginx 部署”Flask 也可以照搬这个思路。命令很简单# Linux服务器 gunicorn -w 4 -b 127.0.0.1:5000 app:app # Windows服务器 waitress-serve --listen127.0.0.1:5000 app:app-w 4表示开 4 个 worker 进程多核 CPU 机器上能明显提升并发能力。开发阶段app.run(debugTrue)够用但生产环境一定不要开 debug 模式不然出错时会直接把堆栈信息暴露给用户而且 debug 模式自带的重载器在多进程下会重复启动 worker导致各种诡异问题。6. 开发踩坑记录十几个真实问题的排查思路这个项目做完我整理了一份踩坑清单每个都是我或者同事实际遇到过并排查解决的按顺序列出来你看看有没有同款经历。第一坑PyCharm 解释器指向错误。症状是明明 pip list 里能看到 flask运行却报 ModuleNotFoundError。原因就是解释器用的是全局环境不是项目虚拟环境。排查方法是在 PyCharm 右下角或 Settings 里确认当前解释器路径。这个坑在新手那里出现频率极高所以项目一开始就把它解决掉后续能省很多心。第二坑Flask-SQLAlchemy 版本和 SQLAlchemy 版本不兼容。症状是运行时报找不到db.relationship之类的方法。原因是 Flask-SQLAlchemy 和 SQLAlchemy 主版本号不一致。解决办法很简单用pip install flask-sqlalchemy时让它自动解析依赖不要手动单独装 SQLAlchemy。如果项目里已经出了问题就统一重装一次。版本冲突在 Python 项目里极其常见我处理这类问题的标准姿势是创建一个干净的虚拟环境把所有依赖打进 requirements.txt 后重装。第三坑中文乱码。前端显示数据库里的中文内容变成了æè¯´这种乱码一般出在两个环节。一是 MySQL 建表时没有指定 utf8mb4 字符集二是连接串里没加charsetutf8mb4。SQLAlchemy 的连接串要注意写成mysqlpymysql://user:passhost/dbname?charsetutf8mb4。SQLite 本身没这个问题切数据库之后容易踩到。乱码问题排查起来很痛苦因为有时数据库里存的是对的是连接层读出来才乱。解决办法是先在数据库客户端里直接查询确认数据本身是否正确再判断是写入环节还是读取环节的问题。第四坑CORS 预检请求失败。前端发 PUT、DELETE 请求时浏览器会先发一个 OPTIONS 预检请求确认服务器允许跨域如果后端没有处理 OPTIONS 请求实际请求就发不出去。Flask-CORS 插件已经处理了这个问题但如果你用了自定义的 before_request 钩子可能会把 OPTIONS 请求拦截下来导致 CORS 失效。排查时在浏览器 Network 面板里如果看到 OPTIONS 请求返回 4xx/5xx基本就是这个问题。第五坑Flask debug 重载器导致代码重复执行。开了debugTrue后代码里的模块级别的代码会被执行两遍如果你在模块级初始化数据库或者开线程会出现重复数据或者线程冲突。这是开发模式的正常行为不是 bug。但有时候会造成错觉以为是自己的代码写错了。排查方式是把 debug 关掉再跑一次如果问题消失就是重载器导致的。第六坑Vue 安装依赖卡住或者失败。国内网络环境下 npm install 经常超时。解决办法是配置 npm 镜像源或者用 yarn/pnpm 换一种包管理器。还需要注意的是不要随便 npm upgrade 一个项目的全部依赖尤其是 Vue 项目一次升级可能带来破坏性变更。我一般只安装需要的包不主动做全量升级。热词里那批人搜“vue安装依赖”应该就是卡在这一步了。第七坑Vue 项目启动时 ESLint 报错。创建项目时默认开启了 ESLint稍有不符合规范的代码就编译失败。对新手来说这会极大地打击积极性。解决方案是在vue.config.js里把 lintOnSave 设为 false或者创建项目时就选择不启用 ESLint。等代码风格稳定了再开启也不迟。第八坑文件上传后访问图片 404。后端返回的图片 URL 是/uploads/xxx.jpg但 Nginx 配置里没把/uploads/路径映射到上传目录前端访问自然 404。这个在开发环境不会出现因为 Vue 代理把/uploads转发给了 FlaskFlask 自己可以 serve 静态文件。生产环境用了 Nginx 后就容易漏掉这一项。排查时先看 Network 面板里图片请求的状态码再用 curl 直接访问服务器上的路径确认文件是否存在。第九坑m3u8 播放黑屏。症状是播放器有控制条但画面全黑。原因通常是三个hls.js 没正确加载、后端 CORS 头没配好、或者 m3u8 里引用的 ts 文件路径是相对路径导致浏览器解析不到。排查时先在浏览器控制台看有没有脚本报错再用抓包工具看 ts 分片请求是否发出、是否被拦截。我印象最深的一次是后端返回的 ts 文件地址是内网 IP浏览器打开的是公网页面自然加载不了这个要找运维改流媒体服务的出口地址。第十坑Windows 下端口占用。Flask 跑在 5000 端口经常被其他程序占用启动时直接报 address already in use。排查命令是netstat -ano | findstr 5000然后结束对应进程或者改端口。Vue 的 8080 端口也是重灾区。我习惯在项目配置里把端口号放到环境变量方便切换。第十一坑数据库数据丢失。开发阶段用 SQLite 建了个.db文件后来把它加进了.gitignore结果同事 clone 代码后跑起来发现所有数据都没了。这个不算 bug但很影响协作。解决办法是给项目提供一个初始化数据的脚本init_db.py写好测试数据新环境跑一遍就能起来。生产数据的备份是另一个话题但课程网站这种项目至少要有每日定时备份。第十二坑前端在 PyCharm 里无法打开终端执行 npm 命令。这个一般是 PyCharm 的 Shell Path 配置问题Windows 上要保证配置的终端是 cmd 或者 PowerShell不要指向奇怪的 shell。另外一个变体是 npm 命令找不到需要在系统环境变量里配置 Node.js 的安装目录。我的习惯是把 Node.js 和 npm 的路径统一放在环境变量开头然后重启 PyCharm 让配置生效。上面这些坑里有一半排查下来都不是复杂的技术问题而是环境、配置、版本、路径这些看起来特别基础的东西。但恰恰是这些基础问题最消耗时间。所以我现在的做法是每到一个新项目先花二十分钟把环境问题一次性处理干净再开始写业务代码。环境问题越早暴露代价越小等代码写了一千行再回过头排查环境问题那才是真正的折磨。这套课程网站从需求梳理到部署上线整个流程走下来我最深刻的体会是技术选型没有标准答案但要有清晰的依据。Flask 和 Django 之间二选一说到底就是看项目的业务形态适合轻量还是重量。课程网站这种前后端分离的中小型项目Flask 加 Vue 是一套开发效率高、运行负担小、排错路径清晰的组合。如果你也是用 PyCharm 做 Python Web 开发我建议把虚拟环境、依赖管理、目录结构这三件基础事项认真对待它们会直接影响你后面所有的开发体验。项目跑通之后再继续往里面加功能、优化细节路会越走越顺。
返回列表