SSM+MySQL搭建课程答疑微信小程序:从后端接口到联调部署全攻略
简介面向高校计算机相关专业毕业设计场景的课程答疑微信小程序完整项目基于微信小程序前端与SSMSpringSpringMVCMyBatis后端架构配合MySQL数据库实现管理员、教师、学生三类角色的核心功能覆盖课程视频发布、作业布置与提交、提问答疑、系统管理等典型业务模块界面清晰、操作流程完整具备一定的工程实用性与可扩展性。资源包共883个文件、约55.51MB包含java后端源码、vue管理端页面、js与wxml/wxss小程序端代码、png/svg界面素材、2份sql数据库脚本及毕业论文文档并附带mp4演示视频目录按功能模块组织便于查阅。已有245人学习浏览适合需要完成课程答疑类小程序课题、参考SSM与小程序前后端整合方案的本专科毕业生使用可用于系统设计、数据库建模与答辩演示等环节能够帮助读者完整把握从需求分析、数据库设计到前后端联调部署的全过程。1. 课程答疑微信小程序毕业设计选型的第一步为什么是SSMMySql这套组合真正动手做毕业设计的人都知道最折磨人的不是编码而是选哪个题目、用什么框架、能不能跑通、答辩老师问什么。课程答疑微信小程序恰好卡在这个需求上业务闭环完整——学生提问、老师回答、课程分类、收藏点赞每一条都能在论文里写成独立章节技术栈又是课堂里反复讲过的微信小程序SSMMySql。更关键的是这类题目通常附带源码、数据库脚本、毕业论文和视频演示交付物齐全意味着你拿到手之后不是从零开始而是从读懂-改造-跑通-讲清楚这条捷径进入。这套组合不是最时髦的但一定是最稳的。2. SSM后端从0搭建五张核心表与答疑闭环的接口设计2.1 为什么是SSM而不是Spring Boot选型逻辑与能力边界SSM指的是Spring SpringMVC MyBatis三层各管一件事Spring管对象创建和事务SpringMVC管接口路由MyBatis管SQL映射。课程答疑这种业务本质就是一组表的新增、查询、关联、统计SSM这套配置驱动的老架子完全撑得住而且答辩时老师最常问的IOC、AOP、动态代理、#{}和${}的区别全都能在SSM里找到对应的代码去答。我说句实在话Spring Boot确实省事自动配置、内嵌Tomcat、起步依赖一拉就起来。但很多学校的毕业设计大纲和课程设计还是以SSM为基准论文模板里的系统架构图、分层结构、核心代码说明都是按SSM写的。你拿Spring Boot去套反而要在论文里解释我为什么不用SSM。这不是技术对错问题是性价比问题。SSM的边界也很清楚它不适合超高并发、不适合复杂的分布式事务但一个课程答疑小程序日活几百就是天花板SSMMySql绰绰有余。2.2 数据库设计用户、课程、答疑帖、回答、收藏五张表我一般建议把答疑系统的表控制在五张以内表一多外键关系、级联删除、联合查询全来了论文篇幅上去了但答辩时也更容易被问倒。核心五张表用户表、课程表、答疑帖表、回答表、收藏表。建库建表脚本可以直接跑注意字符集用utf8mb4后面讲emoji踩坑时你会明白为什么。CREATE DATABASE course_qa_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL UNIQUE COMMENT 微信openid一个用户一条, nickname VARCHAR(50) NOT NULL, avatar_url VARCHAR(255) DEFAULT , role TINYINT NOT NULL DEFAULT 0 COMMENT 0学生 1老师, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE t_course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(100) NOT NULL, teacher_id INT NOT NULL COMMENT 关联t_user该课程的答疑老师, description VARCHAR(255) DEFAULT ) ENGINEInnoDB; CREATE TABLE t_question ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 提问学生, course_id INT NOT NULL COMMENT 所属课程, title VARCHAR(100) NOT NULL, content TEXT NOT NULL, image_urls VARCHAR(1000) DEFAULT COMMENT 逗号分隔的图片地址最多3张, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待答疑 1已回答 2已关闭, favorite_count INT NOT NULL DEFAULT 0 COMMENT 收藏数冗余统计, answer_count INT NOT NULL DEFAULT 0 COMMENT 回答数冗余统计, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_course_id (course_id), KEY idx_user_id (user_id) ) ENGINEInnoDB; CREATE TABLE t_answer ( id INT PRIMARY KEY AUTO_INCREMENT, question_id INT NOT NULL, user_id INT NOT NULL COMMENT 回答者可以是老师也可以是学生, content TEXT NOT NULL, is_accepted TINYINT NOT NULL DEFAULT 0 COMMENT 是否被采纳为最佳答案, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_question_id (question_id) ) ENGINEInnoDB; CREATE TABLE t_favorite ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, question_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_question (user_id, question_id) ) ENGINEInnoDB;五张表的逻辑关系是这样的答疑帖挂在课程下面回答挂在帖子下面收藏是用户和帖子的多对多关系用唯一索引保证一个用户只能收藏同一帖子一次。favorite_count和answer_count是冗余字段每次新增回答或收藏时在事务里同步更新这样列表页就不用COUNT子查询SQL性能好一截。create_time全部由数据库生成避免各服务器时间不一致。这里有一个容易被忽略的参数status字段。如果学生提问后一直没人答status一直是0列表页默认只展示有待答疑状态的帖子已关闭的进归档。这个小设计在论文里可以写成答疑状态机答辩老师对状态机这个概念是买账的。2.3 接口清单与登录鉴权先定好路径再写代码后端接口不要想到哪写到哪先把接口清单敲定前端和后端各写各的才不打架。课程答疑小程序最少需要八个接口登录、课程列表、发布提问、提问列表、提问详情、发布回答、收藏切换、我的提问。接口方法路径说明登录POST/api/user/login用wx.login的code换openid返回token课程列表GET/api/course/list首页筛选条件用发布提问POST/api/question/add需要登录提问列表GET/api/question/list支持courseId、status、分页提问详情GET/api/question/detail?id返回帖子全部回答发布回答POST/api/answer/add需要登录更新answer_count收藏切换POST/api/favorite/toggle已收藏则取消未收藏则加入我的提问GET/api/question/my当前登录用户的提问列表登录鉴权是SSM的一个考点不建议用Session小程序端不会有Cookie机制自定义token才是常见做法。用拦截器统一处理SpringMVC里配置一个HandlerInterceptor放行登录接口其他接口全部走token校验。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(token); if (token null || token.isEmpty()) { return reject(response, 401, 未登录或token已过期); } Integer userId TokenCache.get(token); if (userId null) { return reject(response, 401, 未登录或token已过期); } request.setAttribute(userId, userId); return true; } private boolean reject(HttpServletResponse response, int code, String msg) throws IOException { response.setStatus(code); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\: code ,\msg\:\ msg \}); return false; } }TokenCache建议用ConcurrentHashMap加过期时间简单实现或者直接引入一个轻量级Redis。我做过两次课程设计用本地缓存就够了但你论文里如果能写一句生产环境可替换为Redis老师会觉得你考虑到了扩展性。登录接口里wx.login换openid这一步需要往微信服务器发起请求SSM里可以用RestTemplate或HttpClient把这部分单独封装成一个WxApiService别写在Controller里。3. 微信小程序前端从页面骨架到提问、回答、我的三个核心页面3.1 小程序端项目结构pages目录与tabBar配置微信小程序的项目结构不需要复杂的分包课程答疑这种体量四个页面加一个公共请求封装就够了首页答疑列表、详情页、发布提问页、我的页面。用tabBar把首页、提问、我的三个入口固化下来用户操作路径最短。{ pages: [ pages/index/index, pages/detail/detail, pages/publish/publish, pages/my/my ], window: { navigationBarTitleText: 课程答疑, navigationBarBackgroundColor: #2c3e50, navigationBarTextStyle: white }, tabBar: { color: #666666, selectedColor: #2c3e50, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/publish/publish, text: 提问 }, { pagePath: pages/my/my, text: 我的 } ] } }这个app.json里有个参数需要注意tabBar的list每一项如果指定了iconPath必须有对应的本地图片文件路径不能是网络地址。很多初学者在这里翻车tabBar配置了图标但图片没放在项目里编译直接报错。建议初期先不配图标把文字tab跑通图表后续再补。每个页面对应一个同名目录页面内包含index.js、index.wxml、index.wxss、index.json四个文件这是小程序的标准组织方式。3.2 登录态管理从wx.login到自定义token的完整链路小程序端的登录不能直接拿openid当密码用正确链路是wx.login拿到临时code把code发给后端后端用code去微信服务器换openid然后签发一个token返回给前端。前端把token存进wx.setStorageSync后续每个请求放进header。token过期时后端返回401前端收到后清理本地token并引导重新登录。// pages/index/index.js login() { wx.login({ success: (res) { if (!res.code) { wx.showToast({ title: 获取code失败, icon: none }); return; } wx.getUserProfile({ desc: 用于完善用户资料, success: (profile) { request(/api/user/login, POST, { code: res.code, nickname: profile.userInfo.nickName, avatar: profile.userInfo.avatarUrl }).then((data) { wx.setStorageSync(token, data.data.token); wx.setStorageSync(userId, data.data.user.id); this.loadQuestions(); }); }, fail: () { // 用户拒绝授权时用默认昵称创建账号 request(/api/user/login, POST, { code: res.code, nickname: 微信用户, avatar: }).then((data) { wx.setStorageSync(token, data.data.token); this.loadQuestions(); }); } }); } }); }这里有个关键细节wx.getUserProfile必须由用户点击行为触发不能在onLoad里自动调用否则会静默失败。拒绝授权是常态你不应该把整个登录流程卡死在授权弹窗上拒绝后用一个默认昵称把账号建出来后续在我的页面再引导补全资料这是微信生态里更稳妥的做法。token的失效时间建议后端设为7天学生答辩演示那天不需要频繁重新登录。3.3 答疑交互实现发布提问、回答列表、点赞收藏发布提问页的核心是校验提交。课程下拉选择器用picker组件加载t_course的数据标题和内容分别做长度校验标题20字以内、内容500字以内图片最多三张。这里我踩过坑因为图片上传是异步的用户点了发布按钮但图片还没传完就会出现帖子发出去了图片还在转圈。解决方式是前端先等所有uploadFile完成再统一提交帖子内容。// pages/publish/publish.js submitQuestion() { if (!this.data.courseId) { wx.showToast({ title: 请选择课程, icon: none }); return; } const title this.data.title.trim(); const content this.data.content.trim(); if (title.length 0 || content.length 0) { wx.showToast({ title: 标题和内容不能为空, icon: none }); return; } wx.showLoading({ title: 发布中 }); this.uploadImages().then((urls) { // uploadImages内部用Promise.all等待所有图片上传完成 return request(/api/question/add, POST, { courseId: this.data.courseId, title: title, content: content, imageUrls: urls.join(,) }); }).then(() { wx.hideLoading(); wx.showToast({ title: 发布成功 }); wx.switchTab({ url: /pages/index/index }); }).catch(() { wx.hideLoading(); wx.showToast({ title: 发布失败, icon: none }); }); }回答列表放在详情页用scroll-view实现回答区滚动、底部固定输入框的布局。每个回答要显示用户昵称、头像、回答时间和采纳为最佳答案的标记。点赞收藏我建议合并成一个接口前端维护一个isFavorite状态点击后调toggle接口接口内部先查t_favorite有没有这条记录有就删并favorite_count减一没有就插并加一。这个接口不是幂等的所以前端要加防重复点击等上一次请求返回再更新按钮状态否则用户手快点了两次收藏状态就反了。4. 前后端联调与本地调试请求封装、图片上传和真机预览4.1 请求封装统一baseURL、token注入和错误码处理小程序端的wx.request只是个基础API直接在每个页面里写会出现大量重复代码而且错误处理分散。我习惯先封装一个request函数集中处理三件事拼接baseURL、从storage里取token塞进header、统一处理HTTP状态码和业务状态码。// utils/request.js const BASE_URL http://192.168.1.100:8080; // 改成你电脑的局域网IP function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method, data: data, timeout: 10000, header: { Content-Type: application/json, token: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 401) { wx.removeStorageSync(token); wx.showToast({ title: 登录已过期请重新登录, icon: none }); reject(res); return; } if (res.data.code 200) { resolve(res.data); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request, BASE_URL };BASE_URL这里值得多说一句开发阶段直接用局域网IP加端口比如192.168.1.100:8080手机和电脑连同一个WiFi就能访问。性能调优方面timeout设成10秒比较合理答疑列表接口正常响应在300毫秒以内如果超过3秒基本是后端SQL慢查询或网络不通不要傻等。所有业务接口约定统一返回格式{code, msg, data}code为200表示成功前端只认这一个值后端不要把成功散装成不同的字符串。4.2 图片上传从wx.chooseMedia到后端接收保存提问带图是答疑场景的刚需学生在问代码报错时直接截屏贴图比纯文字描述高效得多。小程序端选图用wx.chooseMedia替代老旧的wx.chooseImage返回的tempFilePath就是可以传给后端的位置。上传接口用wx.uploadFile注意它的header和request的header是独立的token要重新塞一次。uploadImages() { const tasks this.data.tempFiles.map((file) { return new Promise((resolve, reject) { wx.uploadFile({ url: BASE_URL /api/upload/image, filePath: file.tempFilePath, name: file, header: { token: wx.getStorageSync(token) || }, success: (res) { const data JSON.parse(res.data); if (data.code 200) { resolve(data.data.url); } else { reject(new Error(data.msg)); } }, fail: reject }); }); }); return Promise.all(tasks); }Promise.all的用法是关键三张图片并发上传全部成功才走then一旦有一张失败就整体拒绝这样就不会出现帖子提交成功但图片缺一半的问题。后端接收图片有一个SSM下的特定配置坑SpringMVC的xml里必须显式声明CommonsMultipartResolver否则MultipartFile参数永远是null这个问题在Spring Boot里几乎不会遇到但在SSM里你绕不开。bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namemaxUploadSize value5242880/ property namemaxInMemorySize value1048576/ property namedefaultEncoding valueUTF-8/ /beanmaxUploadSize设为5MB对应一张手机照片的合理上限。图片保存路径别放在src目录里打包战争时会被清掉我在项目根目录下建一个upload目录然后配置一个静态资源映射让http://ip:8080/upload/xxx.jpg直接可访问小程序端的image组件src指向这个完整URL。4.3 真机调试同一局域网下让手机访问到本机后端开发工具里一切正常一扫码到真机就无法连接服务器这是联调阶段最高频的翻车现场。原因有两个层面第一开发工具的模拟器里localhost指向你电脑但手机上的localhost指向手机自己必须写局域网IP第二微信对小程序请求的域名有白名单校验http加IP天然不合法所以开发阶段必须勾选工具的不校验合法域名选项。正确做法是电脑和手机连同一个WiFi查一下电脑的局域网IP在Windows下用ipconfigMac下用ifconfig然后把request.js里的BASE_URL改成这个IP。开发者工具右上角详情-本地设置里勾选不校验合法域名。手机端不要用预览生成二维码用真机调试它会自动拉起一个调试白名单大多数情况下能绕过域名校验。这一步你第一次做会觉得很玄学其实原理就一句话微信只拦正式版的域名开发版加上调试模式放行。还有一个很容易忽略的点Windows防火墙默认拦截外部访问8080端口。后端明明启动了手机却连不上通过浏览器在手机上访问http://IP:8080也打不开那就是防火墙的事。在Windows防火墙里放行Java进程或8080端口或者直接用管理员命令行执行netsh advfirewall firewall add rule namejava8080 dirin actionallow protocolTCP localport8080这是最快的一招。5. 部署与踩坑数据库连不上、域名不合法、答辩翻车的五连排5.1 MySQL连不上的三张面孔sock文件、密码权限、连接串缺失现象后端一启动就报错日志里出现Cant connect to local MySQL server through socket /tmp/mysql.sock (2)或者Access denied for user rootlocalhost还有一种报Unknown database course_qa_db。这三个错误长得不一样原因也各不同但都集中在你第一次配数据库环境的那半小时里。原因第一个是MySQL服务根本没启动或者JDBC URL里的地址写成了localhost而Linux下MySQL默认走unix socket你的Java进程找不到socket文件第二个是root账号密码不对或者密码对了但host不匹配第三个是数据库没建或者建库脚本根本没执行成功。解决先确认MySQL在跑Ubuntu下用systemctl status mysqlWindows下看服务列表。JDBC URL里的localhost改成127.0.0.1强制走TCP。权限问题不要纠结直接新建一个专用账号比折腾root更省事CREATE USER qa_user% IDENTIFIED BY Qa123456!; GRANT ALL PRIVILEGES ON course_qa_db.* TO qa_user%; FLUSH PRIVILEGES;5.2 连接池空闲超时断线的早死问题现象项目早上启动时一切正常学生登录、提问、回答都流畅中午休息一小时回来再点列表页就报Communications link failure. The last packet successfully received from the server was 8,000 milliseconds ago.原因MySQL服务端的wait_timeout默认是8小时但实际上你的电脑可能休眠或断网连接池里的连接被服务端断开而MyBatis池里的连接对象还认为自己是活的拿去用才发现底下已经断气。这种问题在演示当天特别致命因为它只在闲置一段时间后出现开发时根本注意不到。解决两处配置并行做。JDBC连接串加autoReconnecttrue同时把连接池的空闲检测打开C3P0或DBCP都有testWhileIdle类的参数。jdbc.urljdbc:mysql://127.0.0.1:3306/course_qa_db?useUnicodetruecharacterEncodingutf8useSSLfalseautoReconnecttrue jdbc.maxIdleTime1800 jdbc.validationQuerySELECT 1 jdbc.testConnectionOnCheckouttrue5.3 小程序合法域名拦截开发版和正式版是两套规则现象开发者工具里接口全部返回正常手机扫码预览后所有请求都失败调试器报不在以下request合法域名列表中。原因微信小程序有个安全策略真机上运行的小程序只能请求在mp后台配置过的HTTPS域名默认的http局域网IP坚决不放行。开发者工具是开发态能勾选关闭校验但真机是另一个执行环境。解决开发阶段用真机调试模式扫调试码如果项目要走完整上线流程就需要备案域名、配HTTPS证书在微信公众平台的小程序后台把域名加进request合法域名列表而且域名不能带端口。最容易被忽略的是同一个域名如果同时用于图片上传还要在uploadFile合法域名里单独配置这两个白名单是分开的。5.4 emoji和表情包入库变问号的编码血泪现象学生在提问内容里输入这个表情后端不报错但数据库里存的是两个问号或者直接抛Incorrect string value: \xF0\x9F\x98\x80 for column。原因MySQL的utf8字符集一个字符最多3字节而emoji是4字节只有utf8mb4才支持。建库时如果没指定utf8mb4默认就是utf8哪怕你在JDBC连接串里写了characterEncodingutf8也没用。解决建库脚本把字符集写成utf8mb4已经建错的库要转换。ALTER DATABASE course_qa_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE t_question CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE t_answer CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;同时JDBC连接串必须同步写characterEncodingutf8mb4MySQL驱动版本不要太老5.1.47以上的驱动才认这个参数。检查这三个地方——库、表、连接串——全部齐了才彻底根治。5.5 答辩现场端到端演示的兜底预案现象答辩当天站在讲台前双击项目启动IDEA编译报错、MySQL没启动、端口被占用或者手机连着教室的WiFi访问不到电脑上的后端演示必然翻车。原因毕业设计演示的环境与本机开发环境高度耦合电脑休眠恢复后DHCP把局域网IP换了MySQL开机不自启IDEA的Maven依赖第一次加载失败这些都是概率事件叠加起来概率就很高。解决演示前写一个启动脚本按顺序执行MySQL服务启动、Tomcat启动、打开浏览器。把整段演示过程提前录成视频存在手机相册里后端挂了直接放视频这是最靠谱的后悔药。另外把JDBC配置里的localhost全部改成127.0.0.1少一次DNS解析就少一次故障可能。答辩前至少做两次完整的冷启动演练——关掉所有东西从零开始启动一遍你会发现很多平时没暴露的问题。6. 答辩加分项给答疑小程序加三个超出课程设计的优化6.1 发布提问的防重复提交答辩展示时可以主动说我对发布接口做了防重复提交处理。实现是前端在点击发布后把按钮置灰再配合后端拦截同一个用户5秒内不能连续创建两篇内容相同的帖子。这个小功能实现成本低但体现的是并发安全意识。6.2 答疑列表的本地缓存加速首页的答疑列表接口在真机上每次加载要1秒左右把列表数据缓存到wx.setStorageSync设置5分钟有效期用户二次进入直接渲染缓存后台再静默刷新。这一步能让演示时的返回首页操作明显变快老师体验到的流畅感是真实的。6.3 用一张图表收尾答辩引入小程序端的ec-canvas组件在我的页面做一个近7天答疑趋势的柱状图。后端加一个按日期分组的统计接口SELECT DATE(create_time), COUNT(*) FROM t_question GROUP BY DATE(create_time)。这张图放在答辩PPT的最后一页比文字更能说明你的系统有数据积累。我带毕设这几年最深的体会是代码量从来不是答辩好坏的标准真正拉开差距的是闭环是否流畅和异常是否有人处理过。所有能自动化收尾的细节提前跑一遍把最容易被追问的边界问题在代码里留好注释。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →