news 2026/9/28 18:31:15

基于Node.js+Vue的中医在线课程购买服务管理系统开发实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Node.js+Vue的中医在线课程购买服务管理系统开发实践

做中医在线学习课程购买服务管理系统,最初是因为帮朋友的中医培训机构做数字化转型。他们原本卖课程全靠微信群发、线下报名,课程资料用网盘分享,订单用 Excel 记录,数据乱得一塌糊涂,用户学完一次就联系不上,复购全靠缘分。当时我分析了一下,最适合的路线就是用 Node.js + Vue 搭一套前后端分离的管理系统:前端做课程展示、购买、学习和个人中心,后端管用户、订单、课程数据和支付回调,整个体系从选课到支付再到课程播放全部在线化。这篇文章就把这个项目的完整开发思路整理出来,包括技术选型、数据库设计、核心功能实现、部署上线,还有几次差点让人崩溃的报错排查实录。想让别人少走弯路,这篇文章够用了。

1. 这个系统要解决什么:需求拆解与功能边界

1.1 中医培训机构的课程售卖困局

中医在线学习和普通职业教育有一个很大的区别:学员的信任成本高。大部分用户愿意付费,是因为冲着“老中医”“名师”去的,而不是单纯冲着平台品牌。所以系统的设计重点不只是把课程挂上去,还要围绕“人”做服务,比如名医介绍、学员评价、学习进度追踪、课后答疑记录。需求调研的时候,机构负责人提了几个很具体的痛点:

  • 课程类型复杂,既有录播视频,又有线上直播答疑,有的还带线下面授班,需要统一管理。
  • 付费流程不能太复杂,很多学员年纪偏大,如果下单要跳转三次以上,基本就流失了。
  • 课程内容是机构核心资产,不能被人轻易下载、转卖。
  • 后台要能看清楚每一天的订单、收入、新增学员数据,方便运营做决策。

这些需求直接决定了系统边界:用户端负责课程浏览、购买、学习,管理端负责课程上架、订单管理、数据统计。不需要做社区、论坛这类重社交模块,避免范围蔓延。我第一次画功能脑图的时候把“积分商城”“拼团”都放进去了,后来一想,对一家小型培训机构来说,这些功能上线后的维护成本远大于收益,果断砍掉。所以第一版重点只保留“平台 + 课程 + 订单 + 学习记录”四条主线。

1.2 功能模块规划:从选课到学习的闭环

整个闭环可以拆成四条链路:

第一,曝光链路。用户在首页看到课程分类、轮播图、热门课程推荐,点击进入课程详情。详情页要包含教学大纲、讲师介绍、学员评价。对于录播课程,还应该有免费试看片段。这个链路解决的是“让用户觉得课程值得买”。

第二,转化链路。用户点击“立即购买”,系统生成订单,跳转到支付页面。支付完成后前端拿到回调结果,后端更新订单状态,并给用户开通课程访问权限。这里有一个容易忽略的点:订单要支持“支付宝支付”和“微信支付”两套方式,但开发时不要直接对接两家官方接口,可以在后端抽象出一个支付服务层,后续切换或扩展成本更低。

第三,学习链路。用户登录后可以在“我的课程”里看到已购课程列表,点开课程就能观看视频。前端根据后端返回的每个章节的播放状态渲染列表进度,看完一章自动解锁下一章。对于学员来说,系统要能记录看过的章节进度,下次继续播放直接从进度点开始。

第四,管理链路。管理员在后台维护课程分类、上下架课程、上传视频、管理订单、管理用户、查看收入统计数据。这个部分看起来不起眼,但实际是业务方最常用的入口,操作必须简单直接。我后来把后台做成一个独立路由模块,用懒加载拆分,这样首屏不会加载后台的重型组件。

2. 技术选型:为什么是 Node.js + Vue,数据库和存储怎么定

2.1 后端选型:Node.js 的适用边界

很多人在技术选型时会把 Node.js 和 Java、Go 放在一起比较,然后问一句“Node.js 能撑住高并发吗”。对这个项目来说,重点不是高并发,而是快速迭代和开发效率。系统同时面向 C 端学员和 B 端管理员,接口数量多、权限逻辑杂,后端如果用 Java Spring Boot,代码模板会非常重;用 Node.js 的 Express 或 Koa 框架,路由和中间件的写法更轻快,一天就能把用户模块的注册登录写出来。

当然,Node.js 也有明显的边界。如果团队里没有一个人熟悉 Node 的事件循环和内存管理,后续代码写复杂了很容易出现回调地狱或者内存泄漏。所以我在项目里强制用了 async/await,并且所有操作数据库的项目都写在一个 service 层,控制器只做参数校验和结果返回,不在路由里直接写数据库查询。这一个习惯救了很多次,后来加课程收藏功能时,只需要在 service 层补一个方法,控制器逻辑没动。

框架最终选了 Express,因为生态大、资料多、中间件丰富。像 multer 处理课程封面图上传,jsonwebtoken 做 JWT 认证,都很成熟。没有用 Egg.js 或者 NestJS,原因是项目规模没那么大,引入框架约束反而增加学习成本。但对于多人协作或长期演进的项目,NestJS 的依赖注入和模块化架构会更合适,这个要看团队的实际情况。

2.2 前端选型:Vue 2 还是 Vue 3

前端使用 Vue 是明确的,因为 Vue 的模板语法和指令系统很适合课程详情这种信息密集型页面,而且中文社区活跃,遇到问题基本一搜就有人踩过。当时摆在面前的问题是 Vue 2 还是 Vue 3。考虑到这是一个从零开始的新项目,而且没有依赖旧的 UI 组件库,我选了 Vue 3 + Composition API。组合式 API 在管理课程列表这种涉及多个筛选条件、多个列表状态的功能里,逻辑复用比 options API 顺手得多。

选 Vue 3 的另一个原因是 Element Plus 已经比较成熟,后台表格、表单、弹窗这些基础组件可以直接用。但这里有个坑:Element Plus 只支持 Vue 3,有些在 Vue 2 时代用的很顺手的组件库不能直接迁移。所以如果团队对 Vue 2 特别熟、并且有一些私有组件库绑定,就别强行升级,Vue 2 到 2023年底生命周期才到期,维护老项目安心很多。新项目我建议直接 Vue 3,别再犹豫了。

前端状态管理用了 Pinia,没有用 Vuex 4。原因很简单:Pinia 对 TypeScript 支持更好,而且 API 设计更扁平,去掉了 Vuex 的 mutation 这个概念,直接在 store 里定义 state、getters、actions,写起来省事。用户登录状态、购物车下单状态、课程播放进度这些全局数据都放在 pinia 里,页面里面再不用一层一层传事件了。

2.3 数据库与文件存储方案

数据库用了 MySQL 8.0,MySQL 的事务机制能保证订单生成和课程开通的原子性。比如用户下单后,后端要先扣减库存、创建订单、关联用户课程,这三个操作必须在一个事务里完成,不然支付成功但课程没开通,或者库存扣了订单没生成,都会引发用户投诉。MySQL 的 InnoDB 引擎默认支持行级锁和事务,足够应付这个项目的并发量。

文件存储方面,课程视频是核心资产,不能直接扔在 Node.js 进程所在的服务器磁盘上,因为如果磁盘满了或者系统重装,视频就全没了。我选用了阿里云 OSS,课程视频和封面图都传到 OSS,服务器只存文件的 URL。上传时让前端先把文件传到 OSS,通过后端签发的 STS 临时凭证完成,这样可以避免把 AccessKey 暴露在客户端。如果没有云资源,本地用 MinIO 也是一样的思路,后端只负责签发请求,文件流不经过 Node 进程,减轻带宽压力。

2.4 项目结构规划

整个项目分成两个目录:server 是 Node.js 后端,web 是 Vue 前端。后端目录结构这样分:

server/ src/ config/ # 配置文件,数据库、Redis、OSS controllers/ # 接收请求,调用 service,返回结果 services/ # 核心业务逻辑 models/ # Sequelize 数据模型 middlewares/ # 认证、权限、错误处理中间件 routes/ # 路由定义 utils/ # 公共工具 app.js # 初始化入口

前端目录采用 Vue 最常用的分层方式:

web/src/ api/ # axios 请求封装 assets/ # 静态资源 components/ # 通用组件 layouts/ # 用户端和后台的布局 router/ # 前端路由 stores/ # pinia 状态 views/ # 页面组件,按模块分子目录

3. 数据库设计与后端 API 实现

3.1 核心数据模型:用户、课程、订单、学习记录

数据库设计直接决定了后面所有功能好不好做。第一版的关键表有五张。

用户表 users 存基础信息:id、手机号、密码哈希、昵称、头像、角色(user/admin)、状态、注册时间。密码不能用明文,我用 bcrypt 做哈希,每次校验比对哈希值。额外加一个 user_profile 表存学员感兴趣的领域,方便后续做课程推荐。

课程表 courses 存课程基础信息:id、标题、封面图、分类、价格、原价、讲师简介、课程简介、上下架状态、是否推荐。另外需要一个 course_chapters 表存章节信息,每个章节有标题、排序号、时长、试看标志。课程表不存章节列表,因为列表长度不固定,用关联表更灵活。

订单表 orders 保存每一次购买记录:订单号、用户 id、课程 id、支付金额、支付方式、支付状态、创建时间、支付时间。订单号不能用自增 id,否则容易被遍历,我用了时间戳加随机数生成一个 20 位以内的业务订单号,接口层只暴露这个号码。

学习进度表 learning_progress 记录用户每个章节的观看状态:用户 id、课程 id、章节 id、最后一次观看的位置、是否完成。这个表的数据量会随用户增长变大,我在查询时加了只查最近学习课程的过滤条件,避免以后数据量大了接口拖慢。

还有一个 user_courses 表,记录用户已购买的课程 id,用于判断用户是否有权限观看。这是用户服务和课程服务的中间关系表,每次用户请求播放权限时先查这张表。

3.2 用户认证与权限控制

认证方案选了 JWT(JSON Web Token),流程是:用户登录成功后后端签发一个 token,前端把 token 存在 localStorage,每次请求在 axios 拦截器里放进 Authorization 头。后端用 jsonwebtoken 中间件解析 token,拿到用户 id 和角色。这种方案对前后端分离很友好,不用去维护 session 会话表。

但是 JWT 有一个不可忽略的问题:token 一经签发,在过期之前是没法立即失效的。所以我在做“用户修改密码后要把其他设备挤下线”的时候,简单地在用户表加了一个 password_updated_at 字段,在鉴权中间件里判断 token 的签发时间是否早于密码修改时间,如果是就拒绝。这个方案不用引入 Redis 就能解决大部分问题,实现成本很低。

权限控制分两层:登录用户可以访问个人中心和已购课程;管理员可以访问 /api/admin 开头的一组接口。后端在中间件里判断角色,如果角色不是 admin,直接返回 403。前端路由也要配合做路由守卫,在 beforeEach 里判断角色,避免用户直接输 URL 跳到后台页面。

3.3 课程购买与支付回调处理

课程购买流程最容易出错,因为涉及外部支付回调,而回调可能乱序、重试甚至延迟。我的实现流程:

  1. 前端点击购买,向后端发送 createOrder 请求。
  2. 后端生成订单,订单状态初始为 pending,返回订单号和支付参数。
  3. 前端调起支付宝/微信支付,用户完成支付。
  4. 支付平台异步通知后端回调地址 /api/payment/notify。
  5. 后端收到回调,先校验签名,再根据订单号查订单,确认订单状态为 pending 后,将订单状态更新为 paid,并在同个事务里向 user_courses 表插入用户和课程的关联记录。这一步就是把用户开通课程权限。
  6. 回调接口返回 success 字符串给支付平台,否则支付平台会持续重试。

有一个特别容易被忽视的点:支付回调接口的幂等性。我一开始没有判断订单状态,结果支付平台同一个通知发了两次,用户被插了两遍 user_courses 记录,虽然不影响观看,但数据不干净。后来在创建关联记录前加了“如果 user_courses 已存在同用户同课程记录就跳过”的判断。这个幂等判断一定要写在事务里,否则还是会有并发问题。

用户支付完成后,前端不能直接跳转“我的课程”,因为回调是异步的,可能用户已经看到支付成功页,但后端还没收到通知。前端要轮询查询订单状态,比如每隔 2 秒查询一次,最多查 10 次,查到 paid 再跳转。这块体验做得好不好,直接影响用户对系统的信任感。

3.4 课程视频加密与播放控制

课程视频是机构的真金白银,必须防止盗录和盗链。简单地在视频 URL 上做签名防盗链是最低要求。我在 OSS 上给每个视频生成带时效的播放地址,过期时间默认 30 分钟,用户每次打开播放页时向后端请求一个临时播放 URL。

对于更安全的需求,我采用了 HLS 协议配合 AES-128 加密。具体做法:用 ffmpeg 把录播视频转成多码率的 m3u8 切片,并对切片做 AES-128 加密。加密密钥存在后端,前端播放器拿到 m3u8 地址后,还需要请求后端获取密钥才能播放。这样即使切片被人下载,没有密钥也无法完整播放。

这里要提醒一下:前端 Vue 里播放 m3u8 需要用到 hls.js 插件。安装依赖后要在组件里动态加载,不然首屏会把整个播放器逻辑打包进去,卡顿明显。我实际用的是 hls.js + video.js 组合,hls.js 负责拉流,video.js 负责 UI。直播答疑课上架时,用同样的 m3u8 地址协议,搭建成本很低。

4. 前端 Vue 页面与状态管理

4.1 首页与课程列表页

首页的重点是信息展示和流量分发。视觉上用了 Element Plus 的栅格布局,顶部是滚动的 banner 图,下面分类标签栏,再往下是“推荐课程”和“最新上架”两个板块。这一页的数据来源是后端提供的聚合接口,一次返回 banner 列表、分类列表、推荐课程列表,前端不需要发多个请求。

课程列表页要支持筛选,我的设计是左侧固定分类,右侧按价格、销量、最新排序。这里有一个前后端配合的点:前端用路由 query 参数把分类 id 和排序字段传给后端,后端根据 query 条件拼 SQL。筛选状态变了,页面 URL 也变,用户刷新页面后状态还在,这个体验比把所有筛选状态存在组件里更好。Vue 组件挂载时从 this.$route.query 初始化筛选项,再监听 query 变化重新请求数据。

列表页的接口要支持分页,分页参数用 page 和 pageSize。后端返回总条数、总页数和当前列表。前端配合 el-pagination 组件渲染分页器,切页时更新 query 参数,而不是直接改 data。第一次开发时直接把分页数据放在组件 data 里,后来页面层级多了,发现每个列表页都要写一遍相同逻辑,索性抽了一个 usePagination 组合式函数,把列表、加载、分页、刷新都封装进去。

4.2 课程详情与购买流程

课程详情页是转化率的核心。页面从上到下依次是讲师介绍条、课程视频试看窗口、课程大纲(章节列表)、学习评价、购买卡片。这个顺序其实是有讲究的:视频和讲师介绍是第一个信任点,课程大纲是第二个信任点,购买卡片始终悬浮在右侧,用户在任何位置都可能触发购买。

购买按钮分三种状态:如果用户未登录,点击后跳转登录页;如果已购买,按钮变成“去学习”;否则显示价格和“立即购买”。这个状态判断的逻辑放在 Pinia 的 userCourseStore 里,页面只负责读取状态和呼叫 action。因为用户登录后返回课程页,要立刻刷新购买状态,不能等他重新刷新页面。

下单成功后的支付方式选择,我用了一个弹窗组件,展示支付宝和微信支付的二维码。二维码内容由后端根据支付参数生成,前端用 qrcode 插件把字符串渲染成图片。用户扫码支付后,前端开始轮询订单状态。要注意轮询不能太频繁,否则会给支付平台造成压力,2 到 3 秒一次比较合适。我把轮询逻辑写进了订单 store 的 action 里,组件销毁时自动停止清理。

4.3 个人中心与学习进度

个人中心有四个 tab:我的课程、学习记录、订单记录、个人资料。我的课程列表显示课程封面、标题、学习进度条和“继续学习”按钮。进度条数据来自后端 learning_progress 表的聚合查询,用户每看一个章节,前端通过 video.js 的 timeupdate 事件,节流后向后端上报播放位置和时长。

这里有一个上报策略问题:如果每秒钟向服务器上报一次,数据量太大而且没必要。我在前端做了节流,每 20 秒上报一次,同时在组件销毁和页面隐藏事件里上报最后的位置。播放结束时上报 completed 状态。后端收到上报后更新 learning_progress,如果当前章节已经 completed,就检查下一章节是否解锁。这样用户中断学习后,下次打开还能从上次位置继续。

订单记录页要展示订单号、课程名、金额、支付状态、下单时间。后端提供分页接口,前端表格直接渲染。注意订单金额要用分为单位返回,前端展示再转换成元,避免浮点数精度问题。我在第一版时后端返回的是元,用 parseFloat 处理后出现 19.99 变成 19.990000000002 的问题,后来全部改成整数分。

4.4 管理员后台

后台是独立布局,侧边栏菜单包括课程管理、课程章节、订单管理、用户管理、数据统计。课程管理用 el-table 展示课程列表,支持搜索、上下架切换、编辑、删除。上下架切换我用的是 el-switch 组件,绑定一个 change 事件,修改后立刻调用后端接口。这里要注意:删除课程是软删除,只做 is_deleted 标记,避免用户已经买了课程但管理员误删导致课程记录消失。

章节管理是树形结构,每个课程下面有多级章节。后端用 parent_id 字段支持多级关联,前端用 el-table 嵌套展开行,管理员可以给每个章节上传视频、设置试看、设置排序。表单提交时,我把整个章节树一次性作为 JSON 传给后端,后端在事务里先删除旧的章节数据,再插入新数据。这种粗暴方式在数据量小的时候最可靠,因为避免了逐条增删改的复杂比对。

数据统计页用 ECharts 渲染折线图和柱状图,展示每日订单量和收入。后端要提供一个按日期聚合的统计接口,SQL 里按 DATE(created_at) 分组统计订单数和总收入。前端在初始化时请求最近 30 天的数据,然后用 ECharts 的 setOption 绘制。这个页面是整个后台最直观的商业价值展示,机构负责人每天都在看。

5. 开发环境配置与高频报错排查实录

5.1 Node.js 安装与环境变量配置

刚开始接触 Node.js 的同事最容易卡在环境配置上。安装包从官网下载 v18 LTS 版本,安装完成后在命令行里执行 node -v 和 npm -v 能正常显示版本号,说明安装成功。但有时候命令行工具里能识别,IDE 的终端里却提示“node 不是内部或外部命令”,十有八九是环境变量的问题。

Windows 下安装 Node.js 时,如果安装的时候没有勾选“Add to PATH”,或者安装到非默认目录,环境变量就不会自动配好。手动配置的方法:右键“我的电脑” -> 属性 -> 高级系统设置 -> 环境变量。在系统变量里找到 Path,编辑并在末尾追加 Node.js 安装目录,比如:

D:\Program Files\nodejs\

如果你用的是 nvm 管理多个 Node 版本,那么环境变量要指向 nvm 目录,而不是某个具体 Node 安装目录。我在团队里统一规定用 nvm,因为不同项目可能需要不同 Node 版本,Vue 3 配合 Vite 至少要 Node 14.18+,老项目可能停在 Node 12。没有 nvm,切换版本全靠重新安装,太痛苦。

另外,npm 全局安装的包默认路径在用户目录下的 AppData\Roaming\npm,有时候全局命令找不到,也要确认这个路径有没有被加到 Path。我遇到过安装 vite 后执行 vite 命令提示找不到,最后发现是 npm 全局路径没配置。可以在命令行执行:

npm config get prefix

拿到全局安装路径后,把它配到环境变量里就好了。但更推荐的做法是给 npm 设置一个项目的本地安装目录,避免全局污染。

5.2 系统禁止运行 npm 脚本的解决

在做项目的时候几乎每个人都有过这种经历:在 PowerShell 里执行 npm install,结果报错:

npm : 无法加载文件 D:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。

这个错误的原因很清楚:PowerShell 的执行策略默认是 Restricted,禁止运行 .ps1 脚本文件,而 npm 命令是通过 npm.ps1 这个脚本运行的。解决办法也简单,以管理员身份打开 PowerShell,执行:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

执行后选择 Y 确认,再重开一个终端窗口,npm 命令就正常了。RemoteSigned 的意思是本地创建的脚本可以运行,从网上下载的脚本需要有数字签名。如果觉得 RemoteSigned 还是太严格,有人会改成 Unrestricted,但我不建议这么做,那会让系统直接执行所有下载的脚本,安全风险太大。

如果你不想改变执行策略,还有一个绕开方案:在 cmd 或 Git Bash 里运行 npm,而不是 PowerShell。cmd 执行的是 npm.cmd,不受 PowerShell 执行策略限制。所以 Git Bash 里跑 npm install 一直都很正常。但既然代码终端经常开的是 PowerShell,还是建议把执行策略改过来。

5.3 跨域问题与开发代理配置

前后端分离开发时,前端地址通常是 localhost:5173,后端接口地址是 localhost:3000,直接请求必然跨域。开发阶段我习惯配置 Vite 的 proxy 代理,让前端发送 /api 开头的请求时,由 Vite 服务器转发到后端服务。这样浏览器里看到的请求是同源的,不存在跨域问题。Vite 配置文件里这样写:

server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } }

生产环境部署时,我建议用 Nginx 做反向代理:所有 /api 请求转发到 Node.js 服务,其他请求走前端静态文件。同时 Nginx 配合 HTTPS 证书,保证接口数据加密传输。如果后端接口有跨域需求,比如给其他小程序提供接口,就可以在后端加 cors 中间件,设置白名单域名。我通常把这两个方案结合,开发阶段用 Vite proxy,生产环境用 Nginx proxy,后端不需要把 CORS 开得特别大,这样最安全。

5.4 依赖版本冲突的处理

Vue 3 项目经常遇到的版本冲突集中在 node-sass 和 dart-sass 之间。node-sass 是老项目用的比较多,但它在 Node 版本升级后经常编译失败,报错信息一长串。新版项目我统一用 sass(dart-sass),它避免了很多原生编译问题。如果不小心中了一个需要 node-sass 的旧组件库,那就要考虑升级组件库,而不是硬碰硬。

另一个版本冲突是 Electron 和 Vue 的 TypeScript 类型定义。如果项目要打包成桌面应用,同时使用 electron 和 vue,需要确保它们的 Node 类型、DOM 类型不冲突。最稳妥的做法是把 TypeScript 配置里的 types 数组只保留必要的库,不要用自动引入全部类型。我在网上搜索时也看到很多人把 Electron 和 Vue 放在一起做桌面端课程点播软件,这个方向可以用,但要格外小心包版本兼容。

npm install 的时候经常出现 ERESOLVE 错误,说明依赖树有冲突。一个新项目想要快速解决这类问题,可以在安装依赖时加 --legacy-peer-deps,比如:

npm install --legacy-peer-deps

这个命令会跳过 peerDependencies 的自动安装,能解决一部分版本冲突,但不能作为长期方案。根本解决办法是找出版本不一致的依赖,升级或者降级到兼容版本。

6. 部署上线与性能优化

6.1 服务器环境与进程管理

部署阶段我选了 Linux 云服务器,2核4G 配置,对这个小项目来说完全够用。Node.js 安装用 NVM 管理,生产环境使用 LTS 版本。后端代码通过 git 拉到服务器,执行 npm install --production 安装生产依赖。前端代码执行 npm run build 构建,生成的 dist 目录交给 Nginx 托管。

Node.js 服务不能直接用 node app.js 启动,因为终端一关服务就没了,而且进程崩溃后不会自动重启。我用了 PM2 做进程守护,启动命令:

pm2 start app.js --name tcm-course-api pm2 save pm2 startup

PM2 会记录日志,进程崩溃后自动重启,开机时自动拉起服务。查看后端日志用 pm2 logs tcm-course-api 命令,排查问题时比无头苍蝇强太多。我几次遇到接口超时,都是先去 pm2 看有没有堆栈报错,再决定优化方向。

6.2 前端构建优化与 CDN 加速

Vue 项目打包后发现首屏体积太大,这是正常的。我在 vite.config.js 里做了三件事:第一,把 vue、vue-router、pinia、axios 这些基础库用 manualChunks 拆分到单独的 vendor 包;第二,ECharts 改成按需引入,不要直接 import 整个 echarts;第三,开启 gzip 压缩,Nginx 配置 gzip 模块让静态资源体积缩小近一半。

课程视频如果走云厂商的 OSS + CDN,播放体验会好很多。OSS 是存储源,CDN 做边缘缓存。用户在不同地区播放视频时,CDN 会自动调度到最近的节点,大幅降低播放卡顿。如果预算有限,最低限度也要让图片走 CDN,因为图片请求量大,源站带宽扛不住。我把课程封面图都做了尺寸裁剪,列表用 400x300,详情用 800x600,避免页面加载大图拖慢速度。

6.3 数据备份和安全加固

上线之前最重要的一件事是数据库备份。我写了一个每晚 3 点自动执行 mysqldump 的定时任务,备份文件保留最近 7 天,上传到 OSS 一份。虽然云数据库通常自带备份,但多一层异地备份总是放心的。哪怕整台服务器挂掉,也能在另一台服务器上快速恢复。

安全加固方面有几个基本动作要做:生产环境强制 HTTPS,Nginx 隐藏版本号,禁止显示目录列表;后端对所有接口做参数校验,防止 SQL 注入;密码哈希永远用 bcrypt,不要用 md5;管理员后台单独绑定 IP 访问或二次验证;定期更新依赖包,避免已知漏洞。我在项目的安全中间件里还加了简单的接口限流,比如同一个 IP 对登录接口每分钟不能超过 20 次,能挡住一些粗暴的暴力破解。这个限流用内存 Map 实现就够了,不需要引入 Redis;但如果有多台服务器负载均衡,就得换成 Redis 做集中限流。

最后再分享一段实战心得

整个系统从需求分析到上线,我最大的感受是:技术选型没有绝对的最好,只有最适合当前团队的方案。Node.js + Vue 的组合,让这个项目的开发节奏非常快,前端组件丰富,后端生态成熟,一个人也能维护得过来。但如果没有一开始把数据模型设计得足够稳,后面每加一个功能都要改表,代价会非常大。

另外想提醒的是,在线课程系统的核心永远是内容和信任。技术只是把课程的交付、支付、服务体验做得更顺滑。如果课程内容本身不行,再花哨的系统也留不住学员。这套系统上线后,机构把原来线下的几百个学员导了进来,复购率提升得很快,原因不是系统有多高级,而是他们第一次能清晰地看到每个学员学了多少课、哪些课卖得好,运营终于有了抓手。

如果你正打算做类似的在线课程平台,先把课程分类、订单状态、分类支付方式这些基础模型理清楚,再考虑云存储和播放加密。项目做完之后,把 npm 权限、跨域、版本冲突这些坑的解决办法写进团队文档,节省的不只是个人时间,是后面每一个接手这个项目的人的时间。这个内容后续还可以考虑加课程评价审核、优惠券、直播预约功能,但那是业务验证之后再考虑的事了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 18:30:42

【部署】Docker部署OpenClaw及常见问题解决(win11)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 18:30:07

2026最新百度网盘直链助手使用技巧,体验PanDownload极致满速

在日常学习和办公的过程中,网盘已经成为大家存储和共享文件不可或缺的好帮手。但是每次遇到大文件传输,看着那缓缓移动的进度百分比,确实很容易让人感到焦急。 很多朋友一看到速度不理想,第一反应往往是网盘本身出了差错。其实我…

作者头像 李华
网站建设 2026/9/28 18:29:50

基于图神经网络的文本分类:TextGCN、TextING与LEAM复现指南

简介:这份资源是面向计算机、人工智能及相关专业学生的NLP期末大作业完整实现,聚焦TextGCN、TextING、LEAM三种文本分类模型的Python复现,适合课程设计、毕业设计或项目入门参考。压缩包共91个文件,约805.69MB,以32个p…

作者头像 李华
网站建设 2026/9/28 18:29:17

仿射变换矩阵入门:用 TaoToken 统一 Key 跑通图像配准最小示例

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华