简介:一份学校心理咨询聊天室微信小程序毕业设计资料,包含完整源码、说明文档和演示录像。项目基于微信开发者工具和 Java SSM 框架构建,后端使用 MySQL 存储数据,整体分为微信小程序端与后台管理端。小程序端面向大学生,提供用户注册与登录、心理文章浏览、心理医生列表查看、在线咨询提交与回复,以及聊天室互动发言等;后台端由管理员使用,负责用户信息验证、健康信息管理、公告发布,并可对聊天信息进行回复或删除。资源包为 RAR 格式,共含 1342 个文件,压缩后约 28.57MB,主要文件类型为 Vue 页面、Java 类、JavaScript 脚本、WXML/WXSS 小程序页面、JSON/XML 配置、PNG/JPG 图片及 SQL 数据库脚本等,目录结构按前后端模块划分,便于索引查找。目前已有 370 人学习下载,提供的源码、说明文档和演示录屏能帮助理解系统设计与业务逻辑,也方便在此基础上进行二次开发,适合毕业设计、课程设计或个人项目实战。
1. 微信小程序毕业设计做心理咨询聊天室:这个项目到底值不值得选
如果你正在为毕业设计选题发愁,又不想做那种「登录 + 一个列表 + 一个详情页」的模板型小程序,那「学校心理咨询聊天室」这个方向值得认真考虑。它的核心不是聊天本身,而是把咨询场景里的匿名身份、值班导师、敏感词预警、消息审核这些真实业务规则,一件件落到小程序前端和后端服务里——做完以后你能讲清楚的东西,比一个普通聊天室多出一整层。
这个题的工程量也刚好卡在毕设的合理区间:小程序端要写页面和交互,后端要有 WebSocket 服务和消息存储,还要考虑掉线重连、消息重复、记录查询这类实战问题。评审老师关心的是「你自己的代码在哪里、解决了什么问题」,而不是功能有多花哨。一个能现场演示断线重连、消息不重不漏、敏感词触发预警的聊天室,比堆十个页面更有说服力。后面几章我就按我自己带毕设时常用的方案,把技术选型、代码结构和踩过的坑完整拆开讲,从零到一能跑通为止。
2. 先把架构立住:聊天室不是「登录 + 发消息」这么简单
2.1 小程序端技术选型:为什么原生写法对毕业设计最稳妥
很多同学一上来就想用第三方跨平台框架,理由是「写起来快」。但我的建议很直接:这个项目老老实实用微信小程序原生 WXML / WXSS / JavaScript 写,最多在工具库层面引入一个自己封装的请求模块,不要引入整套 UI 框架。
原因有三条。第一,毕业设计的源码是要被评阅老师翻开逐行看的,原生代码结构清晰,每个文件对应一个页面或一个模块,老师能直接看出你的工作量;引入框架之后,老师点开几十个依赖文件,反而说不清哪些是你写的。第二,原生小程序的基础能力对聊天室场景完全够用,scroll-view做消息列表,input做输入框,wx.login做登录态,官方 API 都能覆盖,不需要框架提供的额外封装。第三,第三方框架在真机预览时偶尔会出现样式兼容问题,而毕设演示最怕的就是「开发者工具里好好的,手机上一打开就白屏」。
页面结构上我一般会拆成四个:登录/角色选择页、房间列表页、聊天页、我的(记录与评价)页。聊天页是核心,后面单独讲。这里有一个容易忽略的点:咨询场景下用户不一定是学生,还可能是值班导师,所以页面要做角色区分,至少要在登录后有一个角色标识,后续的消息渲染、快捷回复、审核操作都依赖这个标识。角色分配的逻辑放在后端做,前端只展示,不要在前端写死。
2.2 通信方案对比:WebSocket 还是 HTTP 轮询,各踩过什么坑
聊天室的消息通道只有两个选择:WebSocket 长连接,或 HTTP 定时轮询。我的结论是:答辩想拿高分就上 WebSocket,但必须把心跳和断线重连写好;如果时间确实不够,轮询也能跑,不过你要能说清楚为什么在低并发场景下轮询「够用」。
轮询的实现思路是前端每 2~3 秒调一次接口拉取新消息,代码量确实少,服务端就是一个普通查询接口。但它有两个明显问题:一是消息延迟是固定的轮询间隔,用户会觉得「卡」;二是每次轮询都会打到后端接口,并发上来后数据库压力明显。心理咨询场景本身消息频率不高,轮询能跑,但演示的时候老师如果问「为什么不用 WebSocket」,你只能承认是为了省事。
WebSocket 的工程量其实也就多一两百行代码,核心是三步:建立连接、收发消息、断线重连。断线重连是最容易翻车的地方,后面避坑章节会细讲。这里先记住一条原则:WebSocket 只负责实时消息推送,登录、历史记录、敏感词审核这些操作仍然走普通 HTTPS 接口,不要让所有业务都挤在 WebSocket 里,那样调试起来会非常痛苦。
2.3 数据库设计:消息表、角色表和咨询场景的四个独特约束
聊天室的消息表结构看起来简单,但咨询场景会多出几个普通聊天室没有的字段。先看我常用的表结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| msg_id | varchar(64) | 客户端生成的消息唯一 ID,用于幂等去重 |
| room_id | varchar(32) | 房间 ID,一次咨询会话对应一个房间 |
| sender_role | tinyint | 1 学生来访者,2 值班导师,3 系统通知 |
| sender_name | varchar(32) | 匿名昵称或导师工号 |
| content | text | 消息内容 |
| msg_type | tinyint | 1 文本,2 图片,3 系统提示 |
| is_audited | tinyint | 敏感词审核状态:0 待审,1 通过,2 触发预警 |
| create_time | datetime | 消息时间 |
对比普通聊天室,这里多了三个咨询场景特有的设计。一是msg_id必须由客户端生成而不是依赖数据库自增,这样弱网重发时服务端才能识别重复消息;二是sender_role把来访者和导师分开,前端渲染气泡样式、后端做权限控制都靠它;三是is_audited字段,咨询场景下消息不能完全实时直达,涉及自伤、自残等敏感词的消息要先触发预警流程。
除此之外还需要两张辅助表:一张是值班导师表,记录导师的在线状态和当前服务房间号,用来实现「导师忙,请稍候」的排队提示;另一张是敏感词表,存触发预警的词汇和对应的紧急程度。预约功能如果要做,再单独加一张预约表,这个后面说。
服务端我常用的是 Node.js + WebSocket 库 + MySQL,轻量、好演示、代码量适中。用 Java Spring Boot 也可以,但对毕设来说偏重,而且启动和部署的演示环节容易出岔子。
3. 小程序端从零跑通:登录、聊天页与业务规则落地
3.1 登录与角色分配:匿名来访者如何不撞名、不泄露身份
心理咨询的第一条规则是匿名。小程序端不能用真实微信号直接当昵称展示,也不能要求用户填写姓名学号。常见做法是用户进入时选角色:来访者或导师。来访者由后端生成一个随机匿名代号,比如「同学 A017」,导师则绑定自己的工号。这样页面展示的永远只有代号,但数据库里能对应到具体会话记录。
小程序端的登录代码长这样:
// pages/login/login.js const app = getApp() Page({ data: { role: 1, // 默认来访者 }, // 选择角色后调用微信登录,拿 code 换后端登录态 onRoleTap(e) { const role = Number(e.currentTarget.dataset.role) this.setData({ role }) }, onLoginTap() { wx.login({ success: async (res) => { if (!res.code) { wx.showToast({ title: '获取登录凭证失败', icon: 'none' }) return } // 把微信登录 code 和角色一起发给后端 const loginRes = await app.request({ url: '/api/login', method: 'POST', data: { code: res.code, role: this.data.role, }, }) if (loginRes.code === 0) { // 后端返回 token 和匿名昵称,存入本地缓存 wx.setStorageSync('token', loginRes.data.token) wx.setStorageSync('nickname', loginRes.data.nickname) wx.setStorageSync('role', loginRes.data.role) wx.redirectTo({ url: '/pages/room-list/room-list' }) } }, }) }, })这段代码里要注意三个点。第一,wx.login拿到的code有效期只有 5 分钟,而且只能使用一次,所以拿到后要立刻发给后端换取登录态,后端用这个code调微信接口换openid,前端不接触openid,避免在客户端暴露用户身份。第二,角色选择必须在登录请求之前完成,因为后端要根据角色决定返回的是匿名代号还是导师身份。第三,登录成功后前端只保存后端返回的token和nickname,不要额外存储任何真实身份信息,这是心理咨询项目的隐私底线。
后端登录接口的伪代码逻辑是:收到code后调微信接口换openid,查询数据库看该openid是否已存在,不存在就按角色创建新用户,来访者生成随机代号,导师查找已有的导师账号;存在则直接返回已有身份。这里有一个常见的坑:同一个用户在不同时间进入,会被识别成不同匿名代号,如果项目要求「老用户回来能继续看历史记录」,就要靠openid映射到同一个匿名身份,但不能把这个映射关系暴露到前端。
3.2 聊天页核心代码:消息发送、渲染与滚动定位
聊天页是这个项目工作量最大的页面,核心功能是消息列表的渲染、发送消息、滚动到底部。很多同学在这里第一次遇到「明明 setData 了数据,页面却不滚动」的困惑,所以我把关键实现完整贴出来。
先看 WXML 结构:
<!-- pages/chat/chat.wxml --> <view class="chat-page"> <scroll-view class="msg-list" scroll-y scroll-into-view="{{scrollIntoId}}" scroll-with-animation bindscrolltoupper="onLoadMore"> <view wx:for="{{msgList}}" wx:key="msgId" id="msg-{{item.msgId}}"> <view class="msg-row {{item.senderRole === 1 ? 'left' : 'right'}}"> <view class="msg-nickname">{{item.senderName}}</view> <view class="msg-bubble">{{item.content}}</view> </view> </view> </scroll-view> <view class="input-area"> <input value="{{draft}}" bindinput="onInput" bindconfirm="onSend" confirm-type="send" placeholder="输入内容,回车发送" /> <button size="mini" bindtap="onSend">发送</button> </view> </view>对应的 JS 逻辑:
// pages/chat/chat.js const app = getApp() Page({ data: { msgList: [], draft: '', scrollIntoId: '', page: 1, hasMore: true, }, // 页面加载时拉历史消息并建立 WebSocket onLoad(options) { this.roomId = options.roomId this.initRoom() }, async initRoom() { // 1. 先拉历史消息,保证进房间能看到之前的内容 const historyRes = await app.request({ url: '/api/room/history', data: { roomId: this.roomId, page: this.data.page, }, }) this.setData({ msgList: historyRes.data.list, scrollIntoId: `msg-${historyRes.data.list[historyRes.data.list.length - 1].msgId}`, }) // 2. 建立 WebSocket 连接,token 带在 URL 上做鉴权 const token = wx.getStorageSync('token') this.ws = wx.connectSocket({ url: `ws://localhost:3000/ws?token=${token}&roomId=${this.roomId}`, }) this.ws.onMessage((res) => { const data = JSON.parse(res.data) if (data.type === 'new_msg') { // 收到新消息,追加到列表 const newMsg = data.data this.setData({ msgList: [...this.data.msgList, newMsg], scrollIntoId: `msg-${newMsg.msgId}`, }) } }) }, onInput(e) { this.setData({ draft: e.detail.value }) }, onSend() { const content = this.data.draft.trim() if (!content) return // 生成客户端消息 ID,格式:时间戳 + 随机数 const msgId = `${Date.now()}_${Math.floor(Math.random() * 10000)}` const tempMsg = { msgId, senderRole: wx.getStorageSync('role'), senderName: wx.getStorageSync('nickname'), content, } // 先本地追加,再通过 WebSocket 发给后端 this.setData({ msgList: [...this.data.msgList, tempMsg], draft: '', scrollIntoId: `msg-${msgId}`, }) this.ws.send({ data: JSON.stringify({ type: 'send_msg', data: { msgId, content }, }), }) }, onLoadMore() { if (!this.data.hasMore) return // 分页加载更早的消息,逻辑略 }, })这段代码里最关键的设计是「先本地追加,再发 WebSocket」,也就是乐观更新。用户点发送后消息立刻出现在列表里,不用等服务端回包,体感上流畅很多。但乐观更新有个副作用:如果消息发送失败,本地已经显示了,需要在收到服务端失败回执时把这条消息标记为发送失败,并允许用户点击重发。这个失败重发逻辑会牵扯到幂等,后面避坑章节专门说。
scroll-into-view是滚动定位的关键,它的值必须是一个 DOM 元素的id。消息列表里每条消息都用id="msg-{{item.msgId}}"包了一层,新消息到达时把scrollIntoId更新成最新消息的id,滚动视图就会自动滑到底部。这个方案在消息量少的时候没问题,但消息超过几十条后,每次都精确滚动还是会有一点点卡顿,优化手段是「接近底部才滚动,用户往上翻看历史时不强制拉到底部」。
还有一个新手经常犯的错:scroll-view必须设置明确高度,不能靠内容撑开。常见做法是给scroll-view加flex: 1并让父容器占满页面,或者直接写height: calc(100vh - 输入区域高度),否则scroll-into-view完全不生效。
3.3 敏感词与快捷回复:把咨询业务规则真正写进前端
普通聊天室到上一步已经能用了,但心理咨询聊天室必须有这两块业务功能:敏感词预警和导师快捷回复。敏感词不能只靠后端过滤,前端也要先拦一道,目的是在消息上屏之前就给出提示,避免敏感内容直接出现在聊天记录里。
前端过滤的实现很简单,维护一份敏感词数组,发送前遍历匹配:
// utils/sensitive.js const sensitiveWords = ['不想活', '自杀', '抑郁', '坚持不住'] // 示意数据 export function checkSensitive(content) { const hit = sensitiveWords.find((word) => content.includes(word)) if (hit) { return { level: 2, // 1 普通提醒,2 紧急预警 word: hit, tip: '你当前的内容触发了情绪预警,建议联系值班导师。', } } return null }在onSend里加上这个检查,命中时弹窗提示并让用户确认是否仍要发送,同时给消息打上「预警」标记,后端收到后也会做一个二次过滤并推送给导师端。注意前端过滤只是一种体验优化,不能替代后端过滤,因为前端代码可以被绕过,后端必须有一份完整的敏感词表和审核逻辑兜底。
快捷回复是给导师用的功能。导师同时接待多个来访者时,打字速度跟不上,所以聊天页预设几条常用回复,比如「我在的,你慢慢说」「能具体描述一下最近的情况吗」「听起来你最近压力很大,愿意多说一点吗」。实现上就是一个picker组件,选中后自动填入输入框。这个小功能代码量不大,但答辩时能体现出你对咨询业务的理解。
4. 后端服务与业务闭环:让聊天室从「能聊天」到「可交付」
4.1 后端骨架:HTTP 接口与 WebSocket 共用一个端口
后端服务我习惯用 Node.js 来实现,核心依赖只有两个:一个 HTTP 框架处理普通接口,一个 WebSocket 库处理长连接。这里有一个工程技巧:把 HTTP 服务和 WebSocket 服务挂到同一个端口上,这样小程序端只需要配一个域名或 IP,不用区分 HTTP 和 WS 两个地址,部署和答辩演示都省心。
核心骨架代码:
// server/app.js const http = require('http') const Koa = require('koa') const Router = require('@koa/router') const { WebSocketServer } = require('ws') const app = new Koa() const router = new Router() // 普通 HTTP 接口:登录、拉历史、敏感词审核 router.post('/api/login', async (ctx) => { // 登录逻辑:换 openid、创建或查找用户、返回 token ctx.body = { code: 0, data: { token: 'xxx', nickname: '同学A017', role: 1 } } }) router.get('/api/room/history', async (ctx) => { // 分页查询历史消息 }) router.post('/api/room/create', async (ctx) => { // 来访者发起新咨询,创建房间 }) // 创建 HTTP 服务 const server = http.createServer(app.callback()) // 把 WebSocket 服务挂到同一个 server 上 const wss = new WebSocketServer({ server, path: '/ws' }) wss.on('connection', (ws, req) => { // 从 URL 参数里拿 token 和 roomId,做鉴权和房间绑定 const url = new URL(req.url, 'http://localhost') const token = url.searchParams.get('token') const roomId = url.searchParams.get('roomId') // 校验 token,无效则直接关闭连接 }) server.listen(3000, () => { console.log('服务已启动: http://localhost:3000') })这个骨架有两个值得在答辩时讲清楚的设计点。第一,WebSocket 的握手请求和普通 HTTP 请求一样会带上 URL,所以 token 可以直接放在查询参数里,服务端在connection事件里做鉴权,鉴权失败就ws.close(),避免未登录用户建立长连接。第二,path: '/ws'让 WebSocket 只处理/ws路径的握手,普通接口走router,两者互不干扰。刚才小程序端连接地址是ws://localhost:3000/ws?token=xxx&roomId=xxx,和这里是对应的。
4.2 消息链路:先写库、再广播、后确认的一小时闭环
消息收发是聊天室后端最核心的链路,顺序错了就会出现「消息丢了」或「消息重复」。我采用的顺序是:收到消息 → 写数据库 → 广播给房间内所有连接 → 给发送者回执。先写库再广播,这样就算广播环节出问题,消息也已经落了库,重连后拉历史记录不会丢数据。
服务端消息处理代码:
// server/ws-handler.js const db = require('./db') async function handleSendMessage(ws, payload) { const { msgId, content } = payload.data const { roomId, userId } = ws.context // 1. 幂等检查:如果 msgId 已存在,直接返回成功,不重复入库 const exists = await db.query('SELECT msg_id FROM messages WHERE msg_id = ?', [msgId]) if (exists.length > 0) { ws.send(JSON.stringify({ type: 'send_ack', data: { msgId, status: 'duplicate' } })) return } // 2. 敏感词二次过滤,命中则标记预警状态 const auditResult = await checkSensitiveInDb(content) // 3. 写入数据库 await db.query( 'INSERT INTO messages (msg_id, room_id, sender_role, sender_name, content, is_audited) VALUES (?, ?, ?, ?, ?, ?)', [msgId, roomId, ws.context.senderRole, ws.context.nickname, content, auditResult ? 2 : 1] ) // 4. 广播给房间内所有在线连接 const msg = { msgId, roomId, senderRole: ws.context.senderRole, senderName: ws.context.nickname, content, isAudited: auditResult ? 2 : 1, createTime: new Date().toISOString(), } broadcastToRoom(roomId, JSON.stringify({ type: 'new_msg', data: msg })) // 5. 给发送者回执 ws.send(JSON.stringify({ type: 'send_ack', data: { msgId, status: 'ok' } })) }这里最容易被忽略的是第 1 步幂等检查。如果没有这一步,前端在弱网环境重发同一条消息(相同的msgId),后端会写入两条完全一样的内容,用户就会看到「消息重复出现」。有了msg_id唯一索引和先查后插的逻辑,重复消息会被拦截,这是聊天室类项目必须有的基本功。
另一个重点是消息顺序。WebSocket 消息到达服务端的顺序不等于用户发送的顺序,所以前端展示时不能依赖数组push的顺序。我的做法是:消息列表渲染前按createTime排序,同时每条消息在客户端生成时带上时间戳,服务端在入库时以服务器时间为准覆盖。如果两条消息同一秒到达,再按msgId的字典序排,保证顺序稳定。
4.3 预约与评价:两个给答辩加分的增量功能
聊天室做到这里已经是一个完整可交付的项目了,但如果想让答辩有更多亮点,我建议再加两个小功能:预约咨询和咨询评价。它们单独看都不复杂,但合在一起能让评审老师觉得你想到了咨询业务的完整闭环。
预约功能的实现思路是:来访者在房间列表页或单独的预约页选择一个时间段,提交预约;值班导师端能看到待确认的预约,确认后系统创建一个咨询房间;到时间后双方从小程序进入房间开始聊天。数据库需要一个预约表,字段包括预约 ID、来访者 ID、导师 ID、预约时间、状态(待确认/已确认/已完成)。
评价功能更简单,咨询结束后来访者对本次咨询打分并写一段文字反馈,存评价表;导师端可以看到自己的历史评价,但看不到是谁评价的——匿名性在评价环节也要保持。这两个功能加起来大约多写两个页面、三个接口、两张表,工作量可控,但项目的故事线就完整了:发现需求 → 预约 → 匿名咨询 → 结束评价,这是学校心理咨询真实的服务流程。答辩时你把这条链路讲清楚,老师很难再问出「你这个和普通聊天室有什么区别」这种问题。
预约功能还有一个小坑:如果导师不在线,预约谁来确认?我的处理是预约提交后进入待确认状态,同时给导师端发一条订阅消息提醒,导师下次打开小程序时能看到待办列表。订阅消息是微信小程序的一次性模板能力,用户授权一次才能推一次,不能当成常驻通知用,这个限制后面避坑章节会再提到。
5. 避坑指南:毕业设计聊天室最常见的五种翻车现场
5.1 真机显示白屏,开发者工具里却一切正常
现象:在小程序开发者工具里预览、调试都正常,一拿手机扫码预览,页面直接白屏,控制台也没有明显报错。
原因:最常见的是基础库版本不一致。开发者工具默认使用较高版本的基础库,而手机上的微信如果版本较旧,可能不支持你在代码里使用的某些新 API 或语法特性。另一个常见原因是代码里使用了 ES6+ 语法,而「增强编译」选项没有打开,导致真机环境无法解析。
解决:打开开发者工具的「详情 → 本地设置」,勾选「将 JS 编译成 ES5」;在项目配置里把基础库最低版本设低一些;真机调试时先看手机端 vConsole 的报错信息,不要只看开发者工具的控制台。经验是,先在真机预览一遍再继续写后面的功能,不要攒到最后一次性测,那样翻车了都不知道是哪一步引入的问题。
5.2 聊天记录超过 100 条后明显卡顿
现象:刚进房间时滑动流畅,聊天记录越拉越多,滑动开始掉帧,输入文字也有延迟感。
原因:这是setData的典型问题。小程序每次setData都会把数据从逻辑层传到渲染层,数据量越大传输越慢。聊天页如果每次追加消息都把整个msgList数组传一遍,消息一多自然卡。
解决:两个手段配合。第一,历史消息分页加载,每次只加载 20 条,scroll-view滚动到顶部时触发加载上一页,而不是一次性把所有历史消息塞进数组。第二,新消息到达时做局部更新,setData只传新增的那一条,不传全量数组。如果消息列表实在很长,还可以考虑把超过一定数量的旧消息从数组里摘除,只保留最近 200 条在内存中,需要查看更早的再从服务端拉取。聊天记录越积越多的项目,这条是必踩的坑。
5.3 切后台再回来,WebSocket 静默断开收不到消息
现象:用户正聊着天,切到别的应用或锁屏,过几分钟回到小程序,发现消息收不到了,界面看起来还停在原地,发消息也没反应。
原因:微信小程序在进入后台一段时间后,WebSocket 连接会被系统主动断开,这是平台限制。更麻烦的是,这个断开没有明确的事件通知,前端还认为连接是通的,继续发消息就一直失败。
解决:方案是「心跳探测 + 断线重连 + 补偿拉取」。前端每 30 秒发一个ping,服务端回pong;如果前端连续两次没有收到pong,就主动销毁连接并重新建立。重新连接成功后,不能只靠 WebSocket 补消息,还要调一次/api/room/history接口,带上本地最后一条消息的时间戳,把离线期间漏掉的消息一次性拉回来。这个「断开后拉补偿」的链路,答辩时被追问的概率极高,建议重点准备。
5.4 弱网环境消息重复入库,聊天记录出现两条相同内容
现象:网络信号不好时点击发送,界面提示发送失败,用户重试了一次,结果聊天记录里出现两条一模一样的内容。
原因:前端在发送失败后重试时,为每条重试消息生成了新的msgId,后端的幂等判断失效,两条内容都入库了。本质是重试机制没有复用原始消息 ID。
解决:重试时复用原来那条消息的msgId,服务端的幂等检查才能生效。我一般在onSend时就生成msgId并保存在消息对象里,发送失败后重试直接取原msgId,不重新生成。后端再加一层保险:messages表的msg_id字段建唯一索引,就算前端写错了,数据库也会拦下重复插入。这一条不仅聊天室适用,所有涉及「发送 + 确认 + 重试」的功能都该这么做。
5.5 导师端收不到新消息提醒,来访者白等半天
现象:来访者在聊天室发了一条消息,导师手机锁屏或小程序在后台,没有任何提醒,来访者等了几分钟没回应,体验很糟糕。
原因:小程序退到后台后 WebSocket 连接已断开,消息推送根本到不了导师端。想用订阅消息解决,但微信规定订阅消息是「一次性」的,用户授权一次只能推一条,不能像 APP 推送那样持续收到,所以订阅消息只能做「触发式提醒」,不能做常驻推送。
解决:在导师端做一层「在线状态 + 轮询兜底」。导师打开小程序时建立 WebSocket 并上报在线;如果导师长时间没有交互,后端在收到新消息时给导师端发一条订阅消息提醒「有新的咨询消息,请打开小程序查看」。订阅消息的授权时机很关键,要在导师首次使用 App 时用明确的弹窗请用户同意授权,这个弹窗在毕设演示时要提前准备好,免得现场演示时被系统弹窗打断流程。这套方案不算完美,但在微信小程序的能力范围内已经是能落地的最优解。
6. 演示录像怎么录:让答辩老师三分钟看懂你的源码价值
演示录像这个交付物,很多同学都是随手拿手机对着屏幕录一遍操作就交了,这是最亏的做法。录像其实是你的第二份「答辩陈述」,录得好能大幅降低老师追问的密度。我的录法是三段式脚本,每段都有明确目的,总时长控制在 6 到 8 分钟。
第一段是功能演示,控制在 3 分钟以内。按真实用户路径走一遍:来访者登录拿到匿名代号 → 进入房间发起咨询 → 导师端收到提醒进入房间 → 双方对话 → 触发一条敏感词看预警效果 → 结束咨询后评价。这里不要只演示「能聊天」,要把咨询场景特有的流程演示到,特别是匿名代号和敏感词预警,这两个是普通聊天室没有的差异点。
第二段是代码导读,控制在 2 分钟。不用逐行讲,用编辑器的文件树展示项目结构,然后打开三个关键文件:聊天页的 JS(讲乐观更新和scroll-into-view)、后端消息处理(讲先写库再广播和幂等检查)、数据库表结构(讲msg_id和is_audited的设计意图)。每处停留 20 秒左右,告诉老师「这个文件里我做了一个 xx 处理,解决的是 xx 问题」,比对着屏幕滚动代码强十倍。
第三段是异常演示,控制在 1 分钟。把开发者工具的网络面板打开,模拟离线状态,发一条消息触发重试,恢复网络后展示消息不重复、不丢失。再演示一次切后台再回来的断线重连补偿拉取。这两段是「加分演示」,不是所有同学都会录,录了就能让老师看到你是真的在真机环境下调试过,而不是只在模拟器里点了两下。
录制工具我用的是系统录屏,录像分辨率调成 1080p,帧率 30,文件不要压缩得太狠,保证文字清楚。再加上一条个人经验:录像前先完整走一遍脚本,把鼠标移动、键盘输入这些操作练顺,录的时候不小心点错了一步就重录一遍,一条 8 分钟的成品录像耗时小半天很正常,写好脚本能少折腾很多次。我当时就是把脚本写在备忘录里,每段干完打个勾,整个录制过程不慌不忙。
这套录法配合前面几章的代码结构,你的毕设材料就齐了:能跑通的小程序、结构清晰的后端、讲得清楚的设计决策和数据库字段,外加一段一眼能看到亮点的演示录像。做这个项目最大的心得是,聊天室的价值不在聊天本身,而在于那些「为了让聊天在咨询场景下真正可信」而补上的细节——匿名、审核、幂等、断线补偿,你能把它们讲成自己的故事,这个毕设就算立住了。希望帮到你。
本文还有配套的精品资源,点击获取