尧图精选

微信小程序聊天交友系统:鉴权、WebSocket长连接与匹配算法答辩指南

🕒 发布时间:2026/9/17 15:53:28 📁 来源:尧图网络
简介面向高校计算机相关专业毕业设计答辩场景的演示文稿主题为基于微信小程序的大学生线上聊天交友系统适合正在准备答辩陈述、需要梳理讲解逻辑的本科与专科学生参考。压缩包内共1个文件即单份pptx体积约315KB可直接在Office或WPS中打开编辑便于按需替换校名与个人信息。文稿围绕选题背景与意义、系统可行性及需求分析展开涵盖B/S架构与MySQL数据存储的技术选型、SpringBoot后端框架与Java面向对象特性的应用说明并给出管理员与用户两大模块的权限划分动态信息、私信、申请好友、通知信息等功能均有对应页面示意研究内容、结论收尾与关键词提炼同样齐备读者可据此快速搭建讲解框架、补充技术细节问答。目前已有60人学习下载。1. 微信小程序做聊天交友系统答辩真正要讲的是三条链路微信小程序 大学生线上聊天交友系统答辩现场被问得最多的从来不是页面做得漂不漂亮而是「两个陌生同学是怎么被匹配到一起的」「消息从发送到对方手机响中间走了几跳」。这两问答不上来答辩 PPT 做得再花也撑不住。整套系统真正要讲的技术骨架只有三块小程序端负责登录、聊天与匹配入口业务后端负责鉴权、会话与消息落库长连接服务负责实时下发。适合正在做同类毕业设计的同学也适合想复刻一套完整聊天链路的初中级开发者。下面按登录鉴权、数据表与匹配、PPT 组织、现场演示四条线把能直接抄的部分写清楚重点放在评委真正会追问的参数和链路上。2. 登录鉴权与实时消息链路从 wx.login 到 wx.connectSocket2.1 用 wx.login 换 openidappsecret 必须留在后端小程序端拿不到用户身份只能拿到一个一次性的临时凭证 code。这个 code 有效期 5 分钟且只能用一次换 openid 的动作必须在服务端完成因为换取的请求需要 appsecret。常见误用是有人把 appsecret 写进小程序代码里反编译一下就能扒出来答辩时被问到这一点基本会被扣分。// pages/login/login.js Page({ onLoad() { // 1. 拿临时凭证 code一次有效5 分钟内必须换掉 wx.login({ success: (res) { if (!res.code) { console.error(wx.login 未返回 code, res); return; } // 2. code 只能交给自己的后端前端换不到 openid wx.request({ url: https://api.example.com/api/auth/login, method: POST, data: { code: res.code, nickname: this.data.nickname }, success: (r) { // 3. 拿到业务 token后续每个请求放 header wx.setStorageSync(token, r.data.token); wx.setStorageSync(uid, r.data.uid); }, fail: (err) console.error(登录接口失败, err) }); } }); } });服务端的换取逻辑同样短但要注意超时和错误码判断。微信侧返回 errcode 不为 0 时说明 code 已过期或被复用直接把 401 抛给前端让用户重进登录页不要在这里做重试重试用的还是同一个失效 code。// server/routes/auth.js const axios require(axios); const jwt require(jsonwebtoken); router.post(/login, async (req, res) { const { code } req.body; // appid / secret 从环境变量读取不落代码库 const { data } await axios.get(https://api.weixin.qq.com/sns/jscode2session, { params: { appid: process.env.WX_APPID, secret: process.env.WX_SECRET, js_code: code, grant_type: authorization_code }, timeout: 5000 // 微信侧偶发慢5 秒不返回直接判失败 }); if (data.errcode) return res.status(401).json({ msg: data.errmsg }); // openid 是用户在当前小程序内的唯一标识用它做账号主键 let user await db.User.findOne({ where: { openid: data.openid } }); if (!user) { user await db.User.create({ openid: data.openid, nickname: 同学 (Date.now() % 10000) }); } // 业务 token 有效期 7 天和 session_key 解耦 const token jwt.sign({ uid: user.id }, process.env.JWT_SECRET, { expiresIn: 7d }); res.json({ token, uid: user.id }); });这里的关键参数有三个grant_type固定为authorization_codetimeout给到 5000 毫秒而不是默认无限制业务 token 的expiresIn取 7 天和微信侧 session_key 的失效周期解耦避免 session_key 过期把用户踢下线。2.2 消息实时下发wx.connectSocket 的最小可用实现聊天系统的核心体验是「对方发来消息我这边要立刻出现」。轮询在几十人同时在线时就已经很难看长连接才是正解。三种方案的差别可以直接在答辩 PPT 里列成一张表。方案首次延迟服务端压力小程序端实现成本适用规模短轮询3s 一次平均 1.5s高大量空请求最低一个 setInterval演示用不推荐长轮询接近实时中连接占住不放中需要处理超时重发中小规模WebSocket接近实时低连接常驻中需要心跳和重连本课题推荐小程序端的实现要点是心跳和退避重连缺了这两个弱网下会表现为「消息时有时无」。// utils/socket.js let socketTask null; let heartbeatTimer null; let retry 0; function connect(uid, token) { socketTask wx.connectSocket({ url: wss://api.example.com/ws?uid${uid}token${token}, timeout: 8000 // 首包超时弱网下别用默认值 }); socketTask.onOpen(() { retry 0; // 每 25 秒一次心跳服务端 60 秒收不到就判定掉线 heartbeatTimer setInterval(() { socketTask.send({ data: JSON.stringify({ type: ping, ts: Date.now() }) }); }, 25000); }); socketTask.onMessage((res) { const msg JSON.parse(res.data); if (msg.type pong) return; // 心跳回包不当作业务消息 // 落本地缓存页面通过全局事件总线刷新避免整页 setData const key chat_${msg.sessionId}; const list wx.getStorageSync(key) || []; list.push(msg); wx.setStorageSync(key, list); getApp().globalData.bus.emit(new-message, msg); }); socketTask.onClose(() { clearInterval(heartbeatTimer); // 指数退避最多退到 16 秒防止弱网下疯狂重连 const delay Math.min(2000 * Math.pow(2, retry), 16000); setTimeout(() connect(uid, token), delay); }); socketTask.onError((err) console.error(socket error, err)); } module.exports { connect };心跳间隔 25 秒配合服务端 60 秒超时是留了一到两次丢包的余量退避上限 16 秒是为了在教室 WiFi 抖动时既不断连也不轰炸服务端。重连成功后必须补拉一次离线消息否则断连期间的消息会永久丢失这一步很多简化实现都漏了。2.3 真机调试请求到不了后端优先查这四个点开发者工具里跑得好好的一上真机就请求失败是这套系统最常见的卡点。按顺序排查基本能定位小程序管理后台的 request 合法域名是否配了当前后端域名且必须是 HTTPS真机不做任何例外放行。证书链是否完整。部分安卓机型不认缺少中间证书的配置用openssl s_client -connect api.example.com:443 -showcerts看返回的证书链是不是从站点证书一路到根证书。后端是否监听在0.0.0.0而不是127.0.0.1容器部署时这一点尤其容易被忽略。基础库版本过低导致某些 API 行为不一致在开发者工具详情面板里把基础库调到较新的稳定版本再对比真机表现。提示真机调试面板里的 Network 只显示部分请求长连接的握手过程看不到排查 WebSocket 问题要在服务端打印握手日志。3. 会话、消息与交友匹配的表结构怎么落地3.1 三张核心表的字段与索引设计聊天系统的表不用多用户、会话、消息三张就够撑起全部功能。会话表里用「小 id 在前、大 id 在后」的唯一键能保证一对用户之间永远只有一行会话避免重复会话这种脏数据。-- 用户表openid 唯一兴趣标签用 JSON 存匹配时直接读 CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL UNIQUE, nickname VARCHAR(32) NOT NULL, avatar VARCHAR(255), gender TINYINT DEFAULT 0, college VARCHAR(64), grade VARCHAR(16), tags JSON, last_active DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 会话表a_id b_id 的唯一键保证一对用户只有一行 CREATE TABLE t_session ( id BIGINT PRIMARY KEY AUTO_INCREMENT, a_id BIGINT NOT NULL, b_id BIGINT NOT NULL, last_msg_id BIGINT, last_msg_at DATETIME, UNIQUE KEY uk_pair (a_id, b_id), KEY idx_a (a_id, last_msg_at), KEY idx_b (b_id, last_msg_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 消息表查历史永远带 session_id联合索引按会话 自增 id 走 CREATE TABLE t_message ( id BIGINT PRIMARY KEY AUTO_INCREMENT, session_id BIGINT NOT NULL, from_id BIGINT NOT NULL, to_id BIGINT NOT NULL, content VARCHAR(1000), msg_type TINYINT DEFAULT 0, is_read TINYINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_session (session_id, id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段取舍上有两处值得在答辩里主动讲t_user.tags用 JSON 而不是标签关联表是因为毕业设计的匹配逻辑只需要「求交集」用 JSON 能少两次 join代价是不方便做标签维度的统计规模上来后再拆表即可t_message没有做水平分表靠idx_session支撑按会话翻页单会话几千条消息完全够用。表名职责关键约束预估数据量级t_user账号与交友资料openid 唯一千级t_session会话与最后一条消息指针(a_id, b_id) 唯一万级t_message消息正文与已读状态(session_id, id) 索引十万级3.2 会话列表的未读数与最后一条消息一条 SQL 拿全会话列表页要同时展示对方昵称头像、最后一条消息、未读红点分三条 SQL 查再拼装是最容易写的也是最容易在答辩现场被问「这样查几次数据库」的写法。合并成一条用子查询算未读数即可。SELECT s.id AS session_id, u.nickname, u.avatar, m.content AS last_content, m.created_at AS last_time, (SELECT COUNT(*) FROM t_message x WHERE x.session_id s.id AND x.to_id ? AND x.is_read 0) AS unread FROM t_session s JOIN t_user u ON u.id IF(s.a_id ?, s.b_id, s.a_id) LEFT JOIN t_message m ON m.id s.last_msg_id WHERE s.a_id ? OR s.b_id ? ORDER BY s.last_msg_at DESC LIMIT 20;三个?传的都是当前登录用户的 uid。IF(s.a_id ?, s.b_id, s.a_id)用来取会话里的另一方这样不用区分当前用户是 a 还是 b。last_msg_id是写入消息时同步更新的指针避免用MAX(created_at)反查消息表。3.3 匹配打分可解释的标签加权比协同过滤更适合答辩匹配算法最容易踩的坑是直接上协同过滤结果数据量只有几百条召回质量还不如随机答辩时也讲不清楚为什么推荐这个人。用可解释的加权打分更稳共同兴趣权重最高同院系同年级次之再加上活跃度衰减每一项都能对应到一条产品逻辑。// server/service/match.js function score(me, other) { const myTags new Set(me.tags || []); const common (other.tags || []).filter(t myTags.has(t)); let s common.length * 10; // 共同兴趣权重最高 if (me.college other.college) s 5; // 同院系线下见面成本低 if (me.grade other.grade) s 3; // 同年级作息接近 // 活跃度衰减最近 7 天内活跃才有加分避免推僵尸号 const days (Date.now() - new Date(other.last_active).getTime()) / 86400000; s Math.max(0, 7 - days); if (other.id me.id) return -1; // 过滤自己 return s; } // 冷启动兜底候选不足 10 人时用同院系 最近活跃补位 function candidates(me, pool) { const scored pool.map(u ({ u, s: score(me, u) })) .filter(x x.s 0) .sort((a, b) b.s - a.s); if (scored.length 10) { const extra pool.filter(u u.college me.college u.id ! me.id); return [...new Set([...scored.map(x x.u), ...extra])].slice(0, 10); } return scored.slice(0, 10).map(x x.u); }权重的取值不需要精确重要的是能解释10 / 5 / 3 对应「兴趣 院系 年级」的优先级活跃度贡献最高只有 7 分不会盖过共同兴趣。如果答辩时被问「权重怎么调的」答「先按产品假设定初值再拿 50 个测试账号做人工评估微调」比答「跑网格搜索」更可信。3.4 长列表渲染用局部路径 setData别整个数组重刷小程序里最常见的性能问题就是聊天页每来一条消息就this.setData({ messages: list })列表上百条后输入框会明显卡顿因为整个数组都要走一遍数据传输和 diff。改成只更新变化的那一条路径写法如下。// 只改数组里某一条index 是该消息在列表中的下标 const key messages[${index}]; this.setData({ [key]: Object.assign({}, this.data.messages[index], { status: sent }) }); // 新消息追加用 concat 到末尾路径同样只动一项 this.setData({ [messages[${this.data.messages.length}]]: newMsg });路径里的messages[3]这种写法是小程序支持的局部更新语法数据量只传一条。配合wx:key用消息 id 而不是下标能避免插入新消息时整列错位。真机上还有一类问题聊天页在 iOS 上滚动不跟手通常是把整个列表放在scroll-view里又用了固定高度换成scroll-view加scroll-into-view并给一个明确的高度就会正常。4. 答辩 PPT 的技术页怎么排架构、时序与参数口径4.1 一页架构图放五层每层都要有选型理由架构图不是画得越复杂越好五层足够覆盖且每层都能接住一个追问。把这张表直接搬到 PPT 备注里讲的时候不容易漏。层次组件选型理由可能被追问客户端微信小程序原生免安装、登录态天然可用为什么不用 uni-app接入层HTTPS WSS复用同一域名与证书证书链怎么配业务服务Node.js Express与 WebSocket 同进程部署简单并发量多少长连接ws / socket.io心跳与房间管理现成断线怎么补消息存储MySQL Redis消息落库在线状态与未读计数放缓存缓存和库怎么一致答辩时最有杀伤力的一句是「同一域名同时承载 HTTPS 和 WSS端口复用 443省掉一次证书配置和域名备案」这能直接说明你理解部署成本。4.2 用 python-pptx 先把 PPT 骨架跑出来答辩 PPT 的页序基本是固定的与其在编辑器里一页页插占位符不如脚本生成骨架再回头填图和截图。这样改页序只要改一个列表。# build_ppt.py一次性生成答辩 PPT 骨架页序固定 from pptx import Presentation from pptx.util import Pt # (页标题, 该页要点列表) SLIDES [ (课题背景与选题意义, [校园社交场景痛点, 已有方案不足, 本课题目标]), (系统总体架构, [客户端 / 接入层 / 业务服务 / 长连接 / 存储, 选型理由]), (登录鉴权流程, [wx.login 拿 code, 后端 code2session 换 openid, 签发业务 token]), (实时消息链路, [WebSocket 长连接, 心跳 25s / 超时 60s, 离线消息补拉]), (数据库设计, [用户 / 会话 / 消息三张表, 索引与翻页策略]), (匹配算法, [标签加权打分, 冷启动兜底]), (功能演示, [登录 / 匹配 / 收发消息截图]), (测试与不足, [压测数据, 已知问题与改进方向]), ] prs Presentation() layout prs.slide_layouts[1] # 标题 内容版式 for title, points in SLIDES: slide prs.slides.add_slide(layout) slide.shapes.title.text title body slide.placeholders[1].text_frame body.text points[0] for p in points[1:]: para body.add_paragraph() para.text p para.font.size Pt(20) # 正文字号压到 20投影仪上不挤 prs.save(defense.pptx)slide_layouts[1]是默认模板里的标题加内容版式换成自己的模板时下标要跟着改。字号统一压到 20 磅是因为教室投影普遍偏暗行数多的页面字号一大就放不下。生成后需要手工补的是架构图、时序图和截图文字部分基本不用再动。4.3 时序图别画满两条链路各六个节点就够答辩时间有限画五六条链路的时序图既画不完也讲不完。抓登录和发消息两条每条控制在六个节点以内。登录链路小程序 wx.login 取 code → 请求业务后端 → 后端请求微信接口换 openid → 建号或查号 → 签发 token → 小程序存 token。发消息链路小程序发消息 → 写入 t_message → 更新会话 last_msg 指针 → 查对方在线状态 → 在线则长连接推送离线则只落库 → 对方上线补拉。4.4 参数口径表把「能扛多少人」说成可验证的数被问性能时最忌讳说「理论支持上万并发」。给出自己实测的口径更站得住即使数字不大。指标实测值测量方式单条消息端到端延迟120 ms 以内打时间戳两端相减单机长连接数500 左右ws 压测脚本逐步加压会话列表接口 P9580 ms200 条会话数据下压测心跳超时判定60 s服务端定时清理注意压测数据要能复现脚本留在仓库里被追问时可以直接翻出来。5. 演示前的真机检查与三个高频追问的应答口径5.1 上台前 30 分钟的检查清单演示翻车八成不是代码问题而是环境和设备。下面这几项按顺序过一遍比临场救火高效得多。检查项具体动作失败表现账号体系准备两个测试微信号均已是小程序体验成员无法登录或看不到对方合法域名后台确认 request 与 socket 域名已配置真机全部请求失败基础库版本开发者工具与真机对齐到同一稳定版本部分 API 行为不一致网络手机连会场 WiFi 并实测收发一条长连接反复重连电量与提示关掉消息通知横幅避免演示被打断截图被弹窗遮挡两个测试号务必提前互加为好友关系或走完匹配流程否则现场演示「发消息」时找不到会话入口这个坑每年都有人踩。5.2 对方不在线怎么办用订阅消息补上提醒长连接只在对方小程序前台运行时有效对方退出后消息只能落库。想做到「锁屏也能收到提醒」需要接入订阅消息。它不是无条件的推送必须由用户主动授权所以授权时机要放在有明确收益的场景里比如匹配成功后弹一次。// 匹配成功页用户动作之后请求订阅授权 wx.requestSubscribeMessage({ // tmplIds 从公众号后台的订阅消息模板里取一次最多三个 tmplIds: [TEMPLATE_ID_1], success: (res) { if (res[TEMPLATE_ID_1] accept) { // 把授权结果上报服务端据此判断能否下发 wx.request({ url: https://api.example.com/api/subscribe, method: POST, header: { Authorization: wx.getStorageSync(token) }, data: { accepted: true } }); } }, fail: (err) console.warn(订阅授权失败, err) });服务端下发时有两条硬约束要在答辩里说清楚一次授权通常只能下发一条对应模板的消息不能反复使用用户拒收后不能再次弹窗打扰只能等下一次主动操作的时机再请求。把这两点讲出来比只说「用了订阅消息」显得更懂边界。5.3 三个必被追问的问题先把口径背熟第一个是「消息会不会丢」。口径是每条消息先落t_message再推送长连接推送失败不影响数据客户端重连后按本地最后一条消息 id 向服务端补拉增量因此断连期间的消息不会丢只会延迟。第二个是「两个人同时发消息会不会串」。口径是会话有唯一键约束消息表按session_id 自增 id保证时序客户端按服务端返回的 id 排序渲染不依赖本地时间。第三个是「为什么不用现成的即时通讯云服务」。这里不要贬低现成方案讲清楚毕业设计的取舍更有说服力自建链路是为了把鉴权、存储、长连接三段完整走一遍成本是可预期的运维量收益是每个环节的参数都能自己调。真要在生产环境落地把长连接层换成成熟方案、业务层保持不变是更合理的演进路径。被追问到没准备的问题时说「这一点我目前只验证了 X还没做 Y 的对比测试」比硬答稳得多。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →