微信小程序交友系统答辩PPT:WebSocket聊天闭环与消息模型

发布时间:2026/9/17 19:58:37
微信小程序交友系统答辩PPT:WebSocket聊天闭环与消息模型 简介面向高校计算机与软件工程专业学生的毕业设计答辩材料这份PPT围绕基于微信小程序的大学生线上聊天交友系统展开适合正在准备毕设答辩、需要梳理系统设计与技术选型思路的同学参考。内容覆盖微信小程序的应用定位、系统设计背景与目标、B/S架构与Mysql数据存储方案并逐层讲解Java面向对象三大特征、SpringBoot框架的简化配置优势以及管理员与用户双模块的权限划分动态信息、私信、申请好友、通知信息等功能模块均有对应说明。压缩包内仅1个pptx文件约315KB体量轻便可直接用于答辩演示或作为排版与结构参考。目前已有60人学习适合需要快速搭建答辩框架、理清功能模块与技术亮点表达逻辑的本科与专科毕业生。1. 用一份答辩PPT讲清微信小程序交友系统真正的难点是讲得出闭环答辩教室里最常见的翻车不是代码跑不起来而是评委问一句两个人的消息是怎么走到对方手机上的台上的人只答得出用了 WebSocket。基于微信小程序的大学生线上聊天交友系统技术栈其实不算复杂小程序端加一个 Web 后端MySQL 存用户和消息Redis 存会话与在线状态WebSocket 做实时下发。真正难的是把注册登录—资料完善—匹配推荐—聊天—举报这条链路压缩成十五分钟能听懂的故事并且每一页 PPT 都能对应到一段可回放的代码或一张可验证的数据表。这份答辩 PPT 的受众是计算机相关专业的毕业设计评委他们关心需求是否真实、数据怎么存、实时性怎么保证、异常怎么兜底而不是你用了多少个框架。所以 PPT 的叙事顺序应该和代码的调用顺序反着来先给业务闭环再给数据模型最后才落到具体接口。后面几章按小程序端骨架—聊天模块—PPT 生成—现场兜底推进代码、参数、排错点都写到能直接抄的程度。2. 大学生线上聊天交友系统的微信小程序端骨架与登录链路先把小程序端的页面拆出来这一步决定后面 PPT 的架构图长什么样。交友型和普通工具型小程序最大的区别是它有两条独立的主路径——找人和聊天两条路径共享用户资料和鉴权态但不能互相耦合。常见做法是把匹配、会话列表、聊天窗口拆成三个平级页面用 tabBar 挂前两个聊天窗口用navigateTo进入。2.1 页面划分与接口映射页面路径职责主要接口pages/login/login静默登录、拿业务 tokenPOST /api/auth/loginpages/profile/edit性别、年级、兴趣标签POST /api/user/profilepages/match/index推荐卡片、喜欢/跳过GET /api/match/candidatespages/chat/list会话列表、未读数GET /api/chat/sessionspages/chat/room单聊窗口WebSocket /wspages/report/report举报与拉黑POST /api/report这张表直接就是答辩 PPT 里系统功能模块图的底稿一页放得下评委一眼能看出你确实分过层。2.2 用 wx.login 的 code 换业务 token小程序端不存 openid也不存 session_key只存一个自己签发的 token。整个流程是wx.login拿 codecode 传给自己后端后端拿 code 去换 openid再用 openid 换 JWT。// pages/login/login.js Page({ onLoad() { wx.login({ success: async ({ code }) { // code 一次性5 分钟内有效只能换一次不要缓存复用 const res await wx.request({ url: https://api.example.com/api/auth/login, method: POST, data: { code }, header: { content-type: application/json } }) wx.setStorageSync(token, res.data.token) // isNew 为 true 说明是首次登录跳资料完善页 wx.redirectTo({ url: res.data.isNew ? /pages/profile/edit : /pages/match/index }) } }) } })服务端这一侧要盯住两个细节// server/routes/auth.js router.post(/api/auth/login, async (req, res) { const { code } req.body const url https://api.weixin.qq.com/sns/jscode2session?appid${APPID} secret${SECRET}js_code${code}grant_typeauthorization_code const { openid, session_key, errcode } await axios.get(url).then(r r.data) if (errcode) return res.status(401).json({ msg: code 无效或已使用 }) let user await User.findOne({ where: { openid } }) if (!user) user await User.create({ openid, nickname: 同学${suffix()} }) // session_key 只留在服务端用于解密手机号/敏感数据绝不下发给小程序 const token jwt.sign({ uid: user.id }, JWT_SECRET, { expiresIn: 7d }) res.json({ token, isNew: !user.profileDone }) })参数说明grant_type固定写authorization_codeerrcode40163表示 code 已被使用通常是小程序端 onLoad 被触发两次导致的加一个isLogging标志位就能压掉。expiresIn: 7d是权衡后的值太短会导致用户频繁重登太长则封号后无法即时失效。2.3 顶部导航栏高度与基础库版本这两个必调项自定义导航栏是答辩截图里最容易看出没调好的地方。导航栏高度不能写死要按胶囊按钮的位置反推// utils/nav.js const { statusBarHeight } wx.getWindowInfo() // 状态栏高度刘海屏会变大 const menu wx.getMenuButtonBoundingClientRect() // 胶囊按钮位置 export const navHeight (menu.top - statusBarHeight) * 2 menu.height逻辑说明胶囊按钮上下留白是等距的所以胶囊顶部减去状态栏高度就是单侧留白乘二再加胶囊高度就是完整导航栏高度。这个值在 iOS 和安卓上差别很大写死 44px 在安卓上会出现标题被胶囊盖住的情况。基础库版本分两处设置别混淆开发者工具里改的是本地调试版本路径是详情 → 本地设置 → 调试基础库真正影响线上用户的是小程序后台设置 → 基本设置 → 基础库最低版本。如果用了wx.getWindowInfo它替代了旧版wx.getSystemInfoSync最低版本就不能低于 2.20.1。提示真机调试时接口请求到不了后端九成是三个原因——域名不是 HTTPS、后台没配 request 合法域名、开发者工具的不校验合法域名只对模拟器生效。真机必须走正式域名。3. 聊天模块落地WebSocket 长连、消息模型与匹配过滤聊天是这套系统的技术核心也是答辩 PPT 里唯一值得画时序图的地方。选型上先给一个对比结论评委问为什么不用轮询时可以直接答。方案延迟服务端开销适用场景HTTP 轮询 3s0–3s连接数随用户数线性涨消息量极低的场景HTTP 长轮询接近实时每连接占用一个挂起请求兼容性优先的老系统WebSocket毫秒级单连接常驻内存可控一对一/群聊校园交友场景的并发不高但会话时长长WebSocket 的收益明显大于成本。3.1 消息表与会话表的字段设计会话 id 用两个 uid 排序后拼接避免同一对用户产生两条会话这个经典坑。CREATE TABLE t_message ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, conv_id BIGINT UNSIGNED NOT NULL COMMENT 由两个uid升序拼接: min_max, from_uid BIGINT UNSIGNED NOT NULL, to_uid BIGINT UNSIGNED NOT NULL, content VARCHAR(1000) NOT NULL, msg_type TINYINT NOT NULL DEFAULT 0 COMMENT 0文本 1图片 2语音, status TINYINT NOT NULL DEFAULT 0 COMMENT 0未读 1已读 2已撤回, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id), KEY idx_conv_time (conv_id, created_at), KEY idx_to_status (to_uid, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_conversation ( conv_id BIGINT UNSIGNED NOT NULL, last_msg_id BIGINT UNSIGNED NOT NULL DEFAULT 0, unread_a INT NOT NULL DEFAULT 0 COMMENT A方的未读数, unread_b INT NOT NULL DEFAULT 0, PRIMARY KEY (conv_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;时间字段用DATETIME(3)而不是TIMESTAMP因为毫秒精度能让同一秒内的多条消息排序稳定聊天窗口出现消息顺序错乱基本都是这里退化成秒精度导致的。idx_conv_time这个联合索引专门服务拉某会话最近 50 条这个高频查询。3.2 小程序端 WebSocket 封装与心跳直接裸用wx.connectSocket会在切后台、弱网、断线重连时出问题必须包一层。// utils/socket.js class ChatSocket { constructor(token) { this.token token; this.retry 0 } connect() { this.task wx.connectSocket({ url: wss://api.example.com/ws?token${this.token} }) this.task.onOpen(() { this.retry 0; this.startHeartbeat() }) this.task.onMessage(({ data }) this.handle(JSON.parse(data))) this.task.onClose(() this.reconnect()) } startHeartbeat() { clearInterval(this.timer) this.timer setInterval(() { if (this.task.readyState 1) { this.task.send({ data: JSON.stringify({ type: ping, t: Date.now() }) }) } }, 25000) // 25s小于网关常见的 30s 空闲断连阈值 } reconnect() { clearInterval(this.timer) const delay Math.min(1000 * 2 ** this.retry, 30000) // 指数退避30s 封顶 setTimeout(() this.connect(), delay) } }参数说明心跳间隔必须小于服务端/Nginx 的proxy_read_timeout默认 60s 的话设 25s 是安全的。退避系数用 2封顶 30s避免服务器刚重启就被大量客户端同时打满。另外记得在wx.onAppHide里主动close在wx.onAppShow里判断readyState决定是否重连——安卓上小程序被系统挂起后readyState可能还停留在 1只靠它判断会漏掉重连。3.3 离线消息补偿与匹配推荐的实现WebSocket 只保证在线时实时离线期间的消息必须靠补偿拉取。客户端本地存每个会话的最大id重连成功后主动补一次-- 拉取某个会话中比本地 lastMsgId 新的消息最多 100 条 SELECT id, from_uid, content, msg_type, created_at FROM t_message WHERE conv_id ? AND id ? ORDER BY id ASC LIMIT 100;用id lastMsgId而不是created_at lastTime是因为自增主键天然有序且没有时钟回拨问题。推荐匹配用兴趣标签重合度做基线不需要上协同过滤本科答辩够用且解释得清楚# match/rank.py def score(me, other): # 标签重合度占 60%同校同年级各占 20% common len(set(me.tags) set(other.tags)) tag_score common / max(len(me.tags), 1) school 0.2 if me.school other.school else 0 grade 0.2 if me.grade other.grade else 0 return round(tag_score * 0.6 school grade, 3)敏感词过滤用 DFA 前缀树一次扫描即可别用for word in words: if word in text这种写法词库上千条时性能会明显塌方class WordFilter: def __init__(self, words): self.root {} for w in words: node self.root for ch in w: node node.setdefault(ch, {}) node[end] True # 词尾标记 def check(self, text): hits, n [], len(text) for i in range(n): node self.root for j in range(i, n): if text[j] not in node: break node node[text[j]] if node.get(end): hits.append(text[i:j 1]) return hits注意过滤要在服务端做客户端过滤只能算体验优化。有些同学把词库打包进小程序结果被反编译拿到词库答辩时被问到会很难解释。4. 用 python-pptx 把源码变成可交付的答辩 pptx到这一步代码基本定型剩下的是交付物。手动拖 PPT 的最大问题是改了架构图要重排一页改了表结构要重贴一张表。用python-pptx生成的好处是数据从代码里直接读改一次跑一次版本永远对得上。4.1 十六页是答辩的安全篇幅页码内容建议时长1封面、题目、姓名学号20s2选题背景与真实场景60s3国内外现状与差异化60s4功能模块图90s5技术选型与架构图90s6数据库 E-R 与核心表90s7–9关键实现登录、匹配、聊天180s10实时通信时序图90s11–12真机运行截图90s13性能数据与压测60s14难点与解决方案60s15不足与改进方向40s16致谢10s超过 18 页在 10 分钟答辩里一定讲不完宁少勿多。4.2 生成封面、表格页与架构页用学校模板当底版不要在空白演示文稿上画否则字体、配色和封面页眉对不上。from pptx import Presentation from pptx.util import Inches, Pt from pptx.dml.color import RGBColor prs Presentation(school_template.pptx) prs.slide_width, prs.slide_height Inches(13.333), Inches(7.5) # 16:9 def add_table(slide, data, leftInches(0.9), topInches(1.9), widthInches(11.5), heightInches(3.6)): rows, cols len(data), len(data[0]) table slide.shapes.add_table(rows, cols, left, top, width, height).table for r, row in enumerate(data): for c, val in enumerate(row): cell table.cell(r, c) cell.text str(val) p cell.text_frame.paragraphs[0] p.font.size Pt(14) # 正文 14pt投影后仍可读 p.font.color.rgb RGBColor(0x33, 0x33, 0x33) return table参数说明add_table里的height是建议高度不是硬约束行内容多时会自动撑开实际占位会变大。经验值是一页最多 6 行、每格不超过 18 个字超了就拆页或者降到 12pt。font.size不要低于 12pt教室投影仪分辨率通常只有 1024×768字号小了后排评委直接看不清。架构图这类图片页统一用给宽度不给高度的写法保持比例def add_image_slide(prs, png_path, title): slide prs.slides.add_slide(prs.slide_layouts[5]) # 标题内容版式 slide.shapes.title.text title slide.shapes.add_picture(png_path, Inches(0.9), Inches(1.7), widthInches(11.5)) return slide4.3 数据从哪里来让 PPT 和代码同源最省事的做法是让 PPT 生成脚本直接查库或读 JSON而不是手敲数字。比如核心表结构页import json with open(schema_summary.json, encodingutf-8) as f: tables json.load(f) # [{name: t_message, cols: 8, desc: 消息表}] data [[表名, 字段数, 用途]] [[t[name], t[cols], t[desc]] for t in tables] add_table(prs.slides.add_slide(prs.slide_layouts[5]), data) prs.save(答辩-微信小程序交友系统.pptx)这样改了表结构重跑一次脚本 PPT 就同步了。压测数字同理从接口的自定义日志里导出再填进去。提示如果你是用 HBuilderX 或 uniapp 打包微信小程序构建流程那一页直接截HuilderX → 发行 → 小程序-微信的菜单界面比画流程图更有说服力评委能看出你真实跑通过。4.4 pptx 交付前的四个检查项第一字体。中文尽量用系统自带字体微软雅黑、思源黑体用了特殊字体但答辩电脑没装会整页变成宋体并错位。第二图片。所有 PNG 控制在 300KB 以内几十张高清截图塞进去文件会涨到 50MB 以上拷贝到答辩教室的电脑上打开会卡。第三文件格式另存为.pptx而不是.ppt老版本格式在 Office 365 上打开可能出现表格边框丢失。第四文件名别带空格和特殊符号用答辩-微信小程序交友系统.pptx这种形式避免在教室电脑上因为编码问题打不开。5. 答辩追问与演示兜底把跑不起来的概率压到最低演示环节是整场答辩唯一不可控的部分需要提前设计兜底。我的习惯是准备三层第一层是现场真机演示第二层是提前录好的 60 秒实屏录屏覆盖登录、匹配、发消息、收到回复四个动作第三层是本地跑起来的小程序开发者工具。真机演示时提前打开wx.setEnableDebug({ enableDebug: true })和 vConsole请求失败时能直接看到状态码不用猜。关于真机请求到不了后端这个问题按顺序排查四个点域名是否为 HTTPS 且证书链完整小程序后台开发管理 → 服务器域名里 request 和 socket 合法域名是否都配了WebSocket 要单独配 socket 域名服务端是否只监听了 127.0.0.1 而不是 0.0.0.0Nginx 是否把Upgrade和Connection头透传给了后端。第四点最隐蔽配置片段如下location /ws { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; # 少了这两行握手会直接失败 proxy_read_timeout 120s; # 要大于客户端 25s 心跳间隔 }评委的追问其实高度集中在几个问题上提前把答案写进备注页讲的时候不慌追问回答要点消息会丢吗在线走 WebSocket离线靠id lastMsgId补偿拉取ack 后本地删待发队列怎么防止骚扰敏感词 DFA 过滤 举报表 拉黑关系表拉黑后匹配池双向排除并发多少如实说单机压测的 QPS 和连接数不要编造数据隐私openid 不外发session_key 仅存服务端手机号走加密数据解密有两点要克制一是不要宣称支持百万并发校园项目说这种话反而减分二是被问到自己没实现的功能时直接说这部分在需求里当前版本没做改进方向是……比硬答安全得多。最后给一个能明显提升观感的小技巧PPT 的每一张真机截图右上角都保留状态栏的时间并且时间戳是递进的登录 14:02、匹配 14:03、聊天 14:05。这一条能让评委快速确认流程是真实跑通的比任何文字说明都管用。本文还有配套的精品资源点击获取