基于SSM的在线课堂微信小程序项目全解析:从数据库到部署
最近接了一个挺典型的在线课堂微信小程序项目后端用的是 SSMSpring Spring MVC MyBatis前端是微信原生小程序项目名里还带着完整的文档和源码。这种组合在毕业设计、课程设计里非常常见几乎是标准答案式的一整套解决方案。但正因为常见很多朋友上手时反而容易踩进代码能跑但不知道改了哪里会出问题的坑里。我这篇就把整个项目从设计到落地过一遍包括数据库怎么建、接口怎么定、小程序端怎么调、上线要注意什么顺便把我实际做项目时踩过的坑和排查过程也写出来。不管你是要做毕设还是想拿这套东西当练手项目应该都能找到点有用的东西。1. 项目整体拆解在线课堂小程序到底在做什么1.1 核心业务模块与用户角色先把这个项目当产品看。它面向的是学生端用户和系统管理员两边。学生端就是微信小程序主要干三件事看课程、选课程、学课程。管理员端一般是一个 Web 管理后台通过 SSM 提供接口小程序的业务数据全部由这个后台支撑。先说小程序端的功能清单做这类系统基本逃不出这几块首页课程推荐、轮播图、分类导航课程列表页按分类筛选、关键词搜索、分页加载课程详情页课程封面、讲师信息、目录章节、价格、购买按钮视频播放页支持在线播放具备一定防盗链能力个人中心登录状态、我的订单、我的课程、收藏、学习记录订单流程提交订单、模拟支付或真实微信支付、支付回调更新订单状态管理员后台那边就是常规的增删改查管理用户、管理课程分类、管理课程信息、管理章节视频、处理订单、发布公告等。前后端配合起来才算一个完整系统。这里容易有一个误区很多人觉得 SSM 就是换个框架写 CRUD没什么含金量。但真正做起来你会发现用户身份校验、课程权限、订单状态机、视频播放鉴权这些业务逻辑才是整个项目的核心难点框架反而是最不费脑子的一部分。1.2 为什么选微信小程序 SSM 这套组合微信小程序作为客户端的好处不用多说免安装、分享方便、微信生态内天然有用户基础。对于学生用户扫开就能用对于开发者微信官方提供了完整的开发工具和 API 文档界面虽说是原生编写但组件和 API 足够覆盖在线课堂这种业务场景。SSM 作为后端Spring 负责对象管理和事务Spring MVC 负责 Web 层路由和参数绑定MyBatis 负责数据库操作。这套组合的最大优势是结构清晰、社区资料极多、遇到问题基本上都能搜到解决方案。跟 Spring Boot 比SSM 配置繁琐一点但正因为繁琐反而更适合用来理解 Java Web 的运行原理。很多高校的课程还在要求用 SSM 做项目所以这个技术栈在毕设领域常年不衰。另外一个很现实的原因这类项目的文档和源码往往是配套的评审老师看重的是设计文档、数据库设计、核心流程实现。SSM 加微信小程序恰好能满足评分标准里的几个关键点前后端分离、数据库事务、RESTful 风格接口设计。再加上这项目已经有现成的文档和源码打底你需要做的更多是理解并完善而不是从零硬写这符合大多数学生的能力曲线。1.3 数据流转从前端到数据库的一条链路我习惯先画一条数据流再动手写代码。以用户在首页点开一门课程为例完整链路是这样的小程序页面发起wx.request请求携带课程 ID 和一个Authorization请求头存的是登录后拿到的 token。请求经过小程序服务器域名校验后到达后端的 Spring MVC 拦截器。拦截器验证 token 是否有效、有效期是否过期。通过校验后请求路由到 Controller 层Controller 负责接收参数并调用 Service 层。Service 层写业务逻辑比如判断当前用户是否已购买该课程如果没买就只给课程简介如果已购买才能返回全部章节和视频地址。Service 层调用 MyBatis 的 Mapper 接口Mapper 通过 XML 映射文件执行 SQL返回结果。结果一层层返回最后以 JSON 形式发回给小程序前端再渲染到页面上。这条链路里最容易被忽略的是权限判断的位置。一定要把核心控制在 Service 层而不是只依赖前端隐藏入口。小程序端代码可以被反编译对视频地址的访问不设后端的权限校验等于把课程白送出去。我做这套系统时在 Service 层专门加了一个当前用户是否拥有该课程权限的方法所有返回完整视频的接口都先过这个方法没有权限的一律只返回课程信息不带播放地址。2. 数据库设计与核心表结构2.1 用户、课程、订单三张主表的设计要点在线课堂系统的数据库设计重点是三张基础表用户表、课程表、订单表。除此之外还有分类表、章节表、收藏表、学习记录表等。用户表user我通常这样设计id主键自增或雪花 IDopenid微信用户唯一标识小程序登录后拿到的 openid 要存这里nickname昵称avatar头像地址phone手机号可选role角色0 学生1 管理员管理员一般单独配置但表里保留字段create_time、update_time你可能会疑惑登录时是否还要用户名密码在纯微信小程序里一般不设账号密码直接用wx.login获得的 code 换 openid 作为身份标识。但做毕设的时候老师往往希望看到传统登录方式所以很多人会同时保留用户名密码字段再用 openid 做关联绑定。我建议在主表里加一列account和password但实际小程序端登录优先走微信登录后台管理员用账号密码登录。这样既满足了业务需要又让文档能多写一段多角色认证的设计。课程表course设计则要注意title课程名称cover封面图 URLcategory_id所属分类price价格用分存储避免浮点误差。比如 99.90 元存为 9990 分。original_price原价用于显示划线价description课程简介富文本内容teacher讲师名称status上下架状态0 下架1 上架sales_count销量冗余计数避免每次去 count 订单表这里最关键的教训是价格字段一定要用整数分而不是小数。Java 里double算金额很容易出现 0.1 0.2 不等于 0.3 的问题。用Integer或Long存分展示时再除 100既安全又精确。订单表order设计要围绕一个订单状态机order_no订单号全局唯一一般用时间戳加随机数生成user_id下单用户course_id购买的课程amount实付金额单位分status订单状态0 待支付1 已支付2 已取消3 已关闭pay_time支付时间transaction_id微信支付订单号有真实支付时回填我是强烈建议把订单状态设计成 int 并枚举写注释的。很多人用字符串零散地存状态后面做统计或者退款时就乱了。用数字加注释配合状态流转方法能少踩很多坑。2.2 关联表与状态字段的设计细节除了主表还需要几张关联表。章节表course_section归属于课程id、course_id、title、video_url、duration、sort、is_free是否可试看视频存储地址我建议直接存相对路径或占位符不要硬编码本地绝对路径。项目部署迁移时绝对路径几乎必炸。用户课程关联表user_course用来记录哪个用户买过哪些课程这是判断权限的关键。学生支付成功后事务里同时写订单表状态和这张表。收藏表favorite和 学习记录表study_record属于辅助表。学习记录表建议设计成user_id course_id section_id progress的结构记录看到第几集、进度百分比方便断点续播。还有一个细节几乎所有业务表都要有create_time和update_time。MyBatis 可以在插入和更新时用数据库的now()填充也可以在代码里统一由 MyBatis 的属性自动填充。我习惯在 SQL 里直接用DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP这样代码里少写两个字段减少出错概率。2.3 数据库脚本与初始化数据拿到项目文档和源码后第一件事一定要运行数据库脚本而不是先跑代码。常见的问题是脚本里缺数据导致页面一片空白。我会分三步处理先跑建库建表脚本确认所有表结构生成成功。再跑初始化数据脚本插入分类、一个测试课程、一个测试用户、一条测试订单。最后手动查一下关键表的数据条数比如SELECT COUNT(*) FROM course;确保不是 0。做毕设时建议在文档里贴出数据库 ER 图并配上核心表字段说明表。很多老师在答辩时会问你这个字段为什么设计成 int 而不是 String提前写好能少回答很多问题。3. 后端 SSM 架构与接口设计3.1 SSM 各层职责与包结构划分SSM 的项目结构我习惯按实体 - 控制器 - 服务 - 数据访问四层切com.example.onlineclass ├── controller // 接收请求返回 Result 包装数据 ├── service // 业务接口和实现类 ├── mapper // MyBatis Mapper 接口 ├── entity // 数据库实体对象 ├── dto // 接收前端的参数对象 ├── vo // 返回给前端的视图对象 ├── config // 拦截器、跨域等配置 ├── interceptor // 登录拦截器、管理员权限拦截器 └── utils // JWT 生成解析、加密工具等这个分层如果一开始就定好后面加功能会非常顺手。尤其要注意entity和vo不要混用。entity对应数据库字段返回给前端时经常要隐藏某些字段比如密码或者额外拼一些关联数据。直接用实体返回容易暴露隐私字段而且无法适配前端需要的结构。我通常在 Controller 返回前把实体转成 VO。举例来说课程列表页只需要课程基本资料和价格不需要完整富文本描述那返回的 VO 里就只放列表需要的字段。3.2 关键接口契约登录鉴权、课程列表、下单支付接口设计是整个系统的主心骨定好之后前后端并行开发才不打架。下面这些接口是必有的我直接写出约定的格式。登录接口请求方式POST /api/user/login请求参数JSON{ code: wx.login 返回的临时 code }后端逻辑调用微信接口用 code 换取 openid查库新用户则注册然后签发 JWT token 返回返回结果{ token: xxxx, userInfo: { ... } }课程列表接口请求方式GET /api/course/list?categoryId1page1size10keywordjava返回结果{ total: 100, list: [ { id: 1, title: ..., cover: ..., price: 9990 } ] }注意点这个接口不需要登录但需要隐藏内部字段比如课程状态为下架的必须过滤。课程详情接口请求方式GET /api/course/detail/{courseId}返回逻辑分两种情况游客或未购买用户只返回简介和前 1-2 节免费章节已购买用户返回全部章节与真实视频地址。下单接口请求方式POST /api/order/create请求参数{ courseId: 1 }后端逻辑校验课程存在、用户未购买过、课程状态为上架然后生成订单状态为待支付。注意要加事务插入订单的同时锁定课程和用户的关系避免重复下单。支付回调接口模拟支付时请求方式POST /api/order/pay/notify后端逻辑根据订单号更新订单为已支付同时往user_course表插入记录。这里一定要处理幂等性同一个回调可能触发多次如果已经处理过就直接返回成功。这些接口的返回统一用Result包装包含code、message、data三个字段。前端根据code判断成功还是失败方便统一弹提示。200表示成功401表示未登录500表示业务异常。3.3 配置文件与运行环境准备拿到源码后第一个卡点通常是环境配置。SSM 项目有两个核心配置文件Spring 配置文件applicationContext.xml和 Spring MVC 配置文件spring-mvc.xml另外还有数据库连接文件jdbc.properties常混在一起。jdbc.properties里最关键的四项jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/online_class?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password123456这里特别提醒serverTimezone必须配尤其是在高版本 MySQL 8 上不配时区会直接报错导致连接失败。另外数据库名如果跟脚本里的不一致一定要同步改掉。Maven 依赖也是重灾区。很多 SSM 源码是从旧项目拷的用的 Spring 版本是 4.xMySQL 驱动却是 8.x容易出现驱动类报错。我建议直接统一框架版本Spring 5.2.x、MyBatis 3.5.x、MySQL Connector 8.0.x。这些版本组合稳定网上资料也多。运行 Spring MVC 项目需要部署到 Tomcat。开发时建议用 Tomcat 8.5 或 9.0。把项目打成 war 包放进去启动即可。如果你用的是 IDEA配合本地的 Tomcat 启动也很快。注意访问路径的 context path小程序端baseUrl里要留出/项目名/前缀因为 Servlet 容器会自动加上项目根路径。很多人前后端联调失败就卡在这个前缀上。4. 微信小程序端实现要点4.1 页面结构与路由配置微信小程序的页面一般放在pages目录下我会按照业务模块分子目录pages/ ├── index/ // 首页 ├── course/ // 课程列表与详情 ├── play/ // 视频播放 ├── order/ // 订单确认与记录 ├── user/ // 个人中心 └── login/ // 登录引导页在app.json里配置页面路由和底部 tabBar。tabBar 一般放三个入口首页、课程、我的。课程页如果还要细分列表和详情详情页不能放 tabBar 里因为 tabBar 页面只能固定几个其他页面用wx.navigateTo跳转。页面开发时踩得最多的坑是组件通信。小程序没有像 Vue 那种全局事件总线跨页面传参只能靠 URL 参数、全局getApp()对象、本地缓存wx.setStorageSync。课程详情跳转到播放页时我会用 URL 参数带上courseId和sectionId播放页再通过这两个 ID 去请求后端拿播放地址而不是在 URL 里塞一长串视频地址这样才能保证地址不过期且不暴露。4.2 调用后端 API 的封装与鉴权处理小程序调用接口统一用wx.request但不要每个页面直接写必须封装一个request.js。封装的核心有两点第一点是请求头自动带 token。登录成功后 token 存在wx.getStorageSync(token)里每次请求前从缓存读出来加到header.Authorization。如果返回码是401说明 token 过期需要跳转登录页重新登录。第二点是统一错误处理。后端返回code ! 200时前端弹wx.showToast提示。网络请求失败的时候要根据statusCode或errMsg做差异化提示避免白屏不说话。实际项目里小程序端的baseUrl需要区分开发环境和生产环境。开发时候选不校验合法域名直接填http://localhost:8080/项目名可以跑通。但上线体验版或正式版时必须在微信公众平台配置服务器域名而且只支持 HTTPS。所以项目文档里通常也会预留一个config.js文件让你统一改baseUrl。我把这个文件放在项目根目录所有页面通过require(../../config.js)引用。4.3 视频播放与富文本展示的坑视频播放是在线课堂的重头戏。小程序原生组件video只要给定src就能播放但有几个大坑要提前避掉src必须指向微信后台配置的业务域名或云存储域名否则真机上报加载失败。视频地址不能返回一个临时带签名且有效期很短的 URL因为播放器加载会缓存过期后拖动进度条会失效。如果视频存的是本地服务器流量带宽不够的时候播放会卡。测试阶段可以用外链视频地址代替比如测速链接页面能正常播放即可。如果你希望通过防盗链做得安全一点可以给视频地址加上token参数后端在 Controller 里校验token有效后再把视频流返回。Spring MVC 可以用ResponseEntityResource输出文件流但这套逻辑会比较复杂文档里常常一带而过实际做的时候别把它当核心点。富文本展示也有坑。课程简介通常是一段 HTML 文本小程序不能直接渲染 HTML必须用rich-text nodes{{html}}。后端返回的富文本里如果带着style样式和外部图片链接很容易出现样式丢失或图片无法显示。我建议后端保存富文本时尽量用纯文本加换行或者把图片地址转成绝对 URL 后再返回给小程序否则页面上的图片全是破图。5. 常见问题与排查实录5.1 跨域问题与开发环境配置SSM 后端默认是不放开跨域的小程序请求本地 Tomcat 时如果后端没有配置跨域过滤器会出现request:fail或者即使请求成功也拿不到数据。实际上微信小程序对跨域有一定的容错但后端必须设置允许跨域请求的响应头。最省事的做法是在 Spring MVC 配置里加一个 CorsFilterpublic class CorsFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletResponse response (HttpServletResponse) res; response.setHeader(Access-Control-Allow-Origin, *); response.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); response.setHeader(Access-Control-Allow-Headers, Authorization, Content-Type); chain.doFilter(req, response); } }不过要注意开发时*可以生产环境最好换成实际域名。如果小程序请求的是 HTTPS 域名*有时会不生效需要精确匹配。5.2 微信登录 code 换 session_key 的流程问题用wx.login拿到的 code 只能使用一次而且有效期只有几分钟。很多人把 code 直接传给后端后端调用微信官方接口换openid这一步没问题。但常见错误是把 code 存在本地缓存第二次登录继续用同一个 code结果微信接口报invalid code。正确的流程是每次登录弹窗或进入首页时重新调用wx.login()把新 code 传给后端后端再换。这个换到的openid是用户唯一的所以业务上靠openid识别用户而不是靠 code。另一个容易踩的坑是换session_key时需要在后端配置小程序的appid和secret。这两个值在微信公众平台里面一定要放到服务端配置别放进小程序前端代码里否则会被别人抄走存在滥用风险。做毕设的时候很多人图省事把secret写在app.js里这是很不安全的答辩时也容易被挑毛病。5.3 支付回调掉单问题如果项目接的是真实微信支付支付回调掉单是高频问题。用户付了钱但订单状态没有变成已支付课程也学不了。这个问题多发生在回调处理逻辑里忘记更新user_course关联表或者回调没做幂等导致重复插入主键冲突。我的处理办法是把支付成功后的业务操作全部放在一个事务方法里先更新订单状态再调用userCourseService.addUserCourse()。同时用订单号和腾讯侧的transaction_id做唯一索引防止重复通知时插入两次。调试时可以先用模拟支付接口直接调pay/notify传入一个已知的待支付订单号测试整个链路是否完整。如果你只是做毕设建议不要真接微信支付因为商户号申请流程复杂而且需要营业执照。文档里写模拟支付是常见做法。模拟支付的逻辑可以设计成一个断点接口或在订单确认页一键确认支付后端直接把订单改成已支付并发放课程权限。5.4 真机预览与体验版注意事项开发工具里预览正常但真机上一片空白这类问题我遇到很多次。排查顺序依次为域名是否备案且配置在微信公众平台后台的服务器域名里。是否开启了不校验合法域名——这个开关只对开发工具有效真机上必须配置真实 HTTPS 域名。请求是否存在证书问题测试时如果用的是自签名证书真机会直接拒绝。小程序的发布版本没更新缓存了旧代码。在手机上杀掉小程序重进再检查。体验版和正式版的最大区别是appid和服务器环境。很多时候正式版报错是因为后端接口是测试地址没有切到线上服务器。所以我在config.js里习惯写两个环境常量一个 dev 环境一个 prod 环境通过注释切换避免上线时改一堆代码。6. 部署上线与项目文档编写建议6.1 云服务器部署步骤如果项目要真正跑起来建议搞一台轻量云服务器装好 JDK、MySQL、Tomcat 和 Nginx。标准流程是本地用 Maven 打包mvn clean package -DskipTests。把打出来的war包上传到服务器 Tomcat 的webapps目录。启动 Tomcat配置jdbc.properties为远程数据库地址。用 Nginx 反向代理把 HTTPS 域名指向 Tomcat 端口。如果小程序要求 HTTPS这一步必须做。配置数据库迁移先导入建表脚本测试数据匹配。服务器上最常出问题的点是 Tomcat 端口冲突和内存不够。小项目 2G 内存完全够但 Tomcat 默认 JVM 参数可能不够大启动时加JAVA_OPTS-Xms256m -Xmx512m会稳定很多。6.2 文档结构从需求到测试用例怎么组织这种带文档的项目文档质量直接影响最终评价。我见过很多源码自带文档但写得很敷衍。一份合格的项目文档至少要有项目背景和需求分析系统功能结构图和流程图数据库设计ER 图 表结构说明接口文档每个接口的请求参数和返回字段核心代码说明重点讲事务、权限、支付回调测试用例表和测试结果文档不用写得像专业产品说明书但逻辑结构要清楚。我发现最加分的是事件状态图和接口调用时序图用手绘或工具有条理地画出来答辩时直接讲一两张图就能把思路表达清楚。而且画图的过程本身就是梳理代码逻辑的过程经常能发现有 bug 的边界条件。6.3 项目扩展方向这个项目做完后如果还想更进一步有几个方向值得延伸一是把小程序的播放器换成腾讯云音视频点播支持加密和防盗链适合商业级课程平台。不过这会增加额外成本毕设阶段不必做。二是在管理端加入讲师角色让讲师自己上传课程和管理学员多一层分权设计。这会让后台逻辑复杂不少但对理解 RBAC 权限模型很有帮助。三是加入数据统计模块统计课程的点击量、完播率、付费转化率。这里一旦要统计完播率就需要前端埋点上报学习进度后端表结构也要扩字段。做毕设时如果时间充足用ECharts画几个统计图表是很大的加分项。如果只是想在原有代码上做优化我建议先重构一下接口返回的数据结构把所有返回字段都改成驼峰命名同时封装好统一的分页对象。这些改动不改变业务但让代码看起来专业很多。最后说点我自己的实操体会。拿到一套现成的源码最大的价值不是能跑而是通过修改和 debug 把链路理清楚。我第一次做这类项目时光是把数据库、后端、小程序三端的环境配通就花了一个下午后面反反复复栽在跨域和支付回调上。当时觉得烦过了两个月再看这些坑恰恰变成了自己能讲清楚的东西。所以如果你也在做这个在线课堂小程序不要急着拉通所有功能先按模块一个个过每个功能块走通后再加新的。比如先只做课程列表 详情不碰登录登录之后再加购买 权限最后补播放和支付。这样每一步都是可控的出问题也知道去哪找。等这些功能都稳了你会发现原来一整套系统也没那么可怕。