ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序在线考试系统源码解析:从题库设计到前后端实现

微信小程序在线考试系统源码解析:从题库设计到前后端实现 简介这是一份微信小程序在线考试系统的完整源码包面向希望快速搭建或学习在线考试功能的小程序开发者。资源涵盖前端页面、后端接口与数据库设计可实现用户微信授权登录、试题管理、随机组卷、在线答题、自动计分与成绩反馈等核心流程并支持WebSocket实时同步考试状态。压缩包共343个文件以js、json、wxml、wxss等前端代码文件为主辅以png图标、html管理端页面及Git版本控制相关文件整体大小约4.47MB目录结构清晰便于按模块阅读和二次开发。目前已有791人学习下载。对于正在做毕业设计、课程项目或想研究微信小程序完整业务实现的开发者而言这份源码既能提供可运行的参考实现也能帮助理解小程序前端与后台协作、权限认证、考试计时等关键细节。1. 为什么说微信小程序在线考试系统是轻量级在线教育的首选形态拿到这份微信小程序在线考试系统源码我第一反应不是去看页面效果而是先翻目录结构。config、source、docs、.git、package-lock.json这些标签基本勾勒出一个标准小程序项目的骨架前端页面、配置文件、文档和版本控制一应俱全。整个系统最核心的价值在于它绕开了传统考试系统必须下载App或访问PC端网页的路径用户在微信里扫码或搜索即可进入考试几秒内完成授权、答题、交卷、看分。这个体验对临时测试、企业内训、学校随堂测验这类场景非常致命——不需要安装不需要记住网址考完即走。源码的便捷性之外我更看好它作为小程序开发范本的参考价值。里面同时出现了admin.html和student.html说明这是一套前后端兼有的完整闭环不是只写了个答题界面。适合两类人一类是做教育产品、培训系统的开发者想快速拿一套可运行的考试模块做二次开发另一类是刚接触微信小程序想知道用户授权、试题管理、计分逻辑这些常见功能在真实项目中如何落地的学习者。下面我按从数据模型到前后端联调的顺序拆开这份源码里最值得抄的几段逻辑。2. 小程序端目录结构与题库数据模型设计2.1 从config到package-lock.json先看懂项目的骨架拿到源码后不要急着点开app.js先看配置文件。config目录一般存放全局配置比如环境变量、API基础地址、小程序AppID。style.8fc44bc6.css这种带哈希值的文件名说明是经过构建工具打包后的产物通常是weapp或uniapp编译出来的样式文件源码里对应的SCSS或LESS文件可能在source目录中。package-lock.json的作用经常被新手忽略。这份文件锁定了所有npm依赖的具体版本意味着你在一台新机器上执行npm install时装到的依赖版本和源码作者当时的环境完全一致。对于要二次开发的项目我一般先执行npm cinpm ci和npm install的区别在于前者严格按package-lock.json安装不会自动更新依赖版本能最大程度避免“在我电脑上能跑”的问题。如果你在源码里看到node_modules没有一起打包这也是正常的执行完npm ci后依赖目录会自动生成。exclude和HEAD字段在.git相关文件中出现通常表示项目已经用Git做版本管理HEAD指向当前分支。如果你打算在这份源码上二次开发建议保留.git目录这样可以随时git diff查看改动也能通过git log了解作者提交历史很多时候提交信息比注释更说明问题。2.2 题库表该怎么建从源码反推数据模型在线考试系统的地基是试题数据。这份源码的student.html和admin.html分别对应学生端和管理端管理端必然有试题录入、编辑、删除功能对应的后端数据库至少要有四张表学生表、试题表、考试记录表、答题明细表。我按最常见的实现方式补全一个标准建表语句你可以直接作为参考CREATE TABLE exam_question ( id int(11) NOT NULL AUTO_INCREMENT, type tinyint(4) NOT NULL COMMENT 1单选 2多选 3判断 4填空 5问答, content text NOT NULL COMMENT 题干内容, options text COMMENT 选项JSON单选多选用, answer text NOT NULL COMMENT 参考答案, score int(11) NOT NULL DEFAULT 5 COMMENT 每题分值, difficulty tinyint(4) NOT NULL DEFAULT 1 COMMENT 难度 1-5, category_id int(11) DEFAULT NULL COMMENT 所属科目分类, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_type (type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT试题表;表结构里几个关键参数值得说明type字段用tinyint而不是字符串是为了节省空间和索引效率options用text存储JSON串例如选择题的[A.选项1,B.选项2]这样新增题型不用改表结构answer也是文本不同类型题存的内容不一样单选题存A判断题存true/false问答题存参考答案全文。KEY idx_category和KEY idx_type是高频查询条件随机组卷和按科目筛选试题都会走到这两个索引。2.3 WXML模板与WXSS样式的组织方式微信小程序前端由WXML、WXSS、JS、JSON四种文件组成。这份源码里source目录大概率是按页面分模块的比如pages/index/、pages/exam/、pages/result/。每次新建页面要在app.json的pages数组里注册这是新手最容易漏掉导致白屏的一步。{ pages: [ pages/index/index, pages/exam/exam, pages/result/result ], window: { navigationBarTitleText: 在线考试, navigationBarBackgroundColor: #4A90D9, navigationBarTextStyle: white } }navigationBarTitleText决定顶部导航栏的标题注意这里最长显示约8个汉字超出的部分会被截断为省略号。如果你发现微信小程序顶部导航栏高度在不同机型上显示不一致常见做法是给自定义导航栏预留状态栏高度通过wx.getSystemInfoSync()获取状态栏高度然后在WXSS里动态设置padding-top。3. 从授权登录到随机组卷考试流程的关键代码实现3.1 微信OAuth2.0无感登录的完整链路用户打开小程序第一件事就是识别身份。摘要里提到利用微信OAuth2.0授权协议实现无感登录。微信小程序的登录流程和Web端OAuth不同不是直接跳转授权页而是通过wx.login获取临时code传到后端换取openid和session_key。// pages/login.js onLoad() { this.wxLogin(); } wxLogin() { wx.login({ success: async (res) { if (res.code) { const code res.code; // 临时凭证5分钟有效 const resp await wx.request({ url: https://api.example.com/auth/login, method: POST, data: { code } }); // 后端用 code 换取 openid并返回自定义登录态 token wx.setStorageSync(token, resp.data.token); wx.setStorageSync(userInfo, resp.data.userInfo); } else { console.error(登录失败, res.errMsg); } } }); }这段逻辑中wx.login拿到的code是临时凭证有效期约5分钟且只能使用一次。后端拿到code后调用jscode2session接口传入appid、secret和code才能得到openid。注意千万不要在小程序端直接用appid和secret否则你的secret一打包就被别人反编译拿走了。仓库里如果有config目录通常只存appidsecret应当放在后端环境变量中。3.2 随机组卷的两种实现与公平性参数随机组卷是保证考试公平性的核心。常见做法有两种一是后端从数据库随机抽取题目二是在小程序端通过Math.random()打乱。考虑到试题数量大、且要避免重复我推荐后端做组卷前端只负责渲染。// Node.js 后端示例按分类和题型随机抽取 async function generatePaper(categoryId, questionCount) { const query { categoryId, type: 1 }; // 先抽单选题 const questions await db.collection(exam_question) .aggregate() .match(query) .sample({ size: questionCount }) .toArray(); return questions; }.sample({ size: questionCount })是MongoDB提供的随机抽样算子底层基于$sample在题库量大的时候效率远高于ORDER BY RAND()。如果你用的是MySQL就得用SELECT * FROM exam_question WHERE category_id ? ORDER BY RAND() LIMIT 10;这里ORDER BY RAND()在数据量大于十万行时性能会明显下降因为它要对全表排序。替代方案是先用COUNT(*)拿到总数再用随机偏移量取一条循环取够题目数。题量不大时直接RAND()即可不用过度优化。3.3 倒计时与防切屏考试过程的本地状态管理考试过程中最怕两件事倒计时不准、切屏作弊。小程序是单页应用页面切到后台时JS定时器会被挂起我们不能再依赖setInterval每秒扣减而是要在进入考试页面时记录服务器时间戳然后用Date.now()实时计算剩余时间。// pages/exam/exam.js data: { remainSeconds: 1800, // 初始剩余秒数 endTimeStamp: 0 }, startExam() { // 从服务端获取考试截止时间戳 wx.request({ url: https://api.example.com/exam/start, success: (res) { this.setData({ endTimeStamp: res.data.endTimeStamp }); this.tick(); } }); }, tick() { this.timer setInterval(() { const diff Math.ceil((this.data.endTimeStamp - Date.now()) / 1000); if (diff 0) { clearInterval(this.timer); this.submitPaper(); // 时间到自动交卷 } else { this.setData({ remainSeconds: diff }); } }, 500); }这里用endTimeStamp而不是本地计时好处是即使用户锁屏或退出再进来剩余时间依然准确。setInterval间隔设为500毫秒而不是1000毫秒是为了补偿JS事件循环可能带来的延迟避免出现明明还剩1秒却被判定超时的问题。防切屏可以通过监听wx.onHide和wx.onShow来实现检测到onHide时记录离开时间累计超过阈值就自动交卷并标记作弊。4. 后台管理页与试题批量导入的实战操作4.1admin.html和student.html两套页面的前后端分离思路这份源码里同时出现admin.html和student.html意味着它采用了H5页面承载后台管理的方案。在微信小程序生态里管理端很少做成小程序页面因为微信对个人主体小程序的类目限制多而且管理功能通常需要使用PC端操作。常见架构是小程序端只做学生考试入口管理后台用Vue或React独立部署管理员通过浏览器访问admin.html。student.html则可能是一个让用户从外部浏览器进入考试页面的备用入口用于兼容不支持小程序的环境。如果你要复用这套源码需要关注admin.html里调用的后端API是否与小程序端共用同一套服务。我通常会在后端项目里将管理接口统一加/admin/前缀并在中间件里校验管理员身份防止学生端直接调用管理接口。4.2 试题批量导入从Excel到题库的解析逻辑管理后台一个重要功能是批量导入试题。题库量大了以后手工一条条录入不现实常见的做法是提供Excel模板下载管理员按模板填写后再上传。解析Excel可以用前端xlsx库也可以在后端用multipart/form-data接收文件后解析。// 后端 Node.js multer 接收上传的试题Excel const multer require(multer); const XLSX require(xlsx); const upload multer({ dest: uploads/ }); app.post(/admin/import, upload.single(file), (req, res) { const workbook XLSX.readFile(req.file.path); const sheet workbook.Sheets[workbook.SheetNames[0]]; const rows XLSX.utils.sheet_to_json(sheet); const questions rows.map((row, index) { if (!row[题干] || !row[答案]) { throw new Error(第${index 2}行数据不完整); } return { type: row[题型] 单选 ? 1 : 2, content: row[题干], options: JSON.stringify([row[选项A], row[选项B], row[选项C], row[选项D]].filter(Boolean)), answer: row[答案], score: parseInt(row[分值]) || 5 }; }); // 批量插入数据库 db.collection(exam_question).insertMany(questions) .then(() res.json({ code: 0, count: questions.length })) .catch(err res.status(500).json({ code: 1, msg: err.message })); });XLSX.utils.sheet_to_json会按表头自动映射字段这里要求Excel第一行必须是“题型、题干、选项A、选项B、选项C、选项D、答案、分值”。filter(Boolean)是为了过滤掉空选项防止单选题只填了三个选项后面补空导致解析报错。注意导入时一定要做事务处理如果第100条数据有问题不能前面99条都成功、只有第100条失败否则会造成部分导入的脏数据。MySQL使用START TRANSACTION和COMMITMongoDB 4.0以上支持事务也可以用批量插入前先全量校验。4.3 管理端权限校验不能只靠前端隐藏入口很多小型系统会在前端隐藏管理入口比如只在admin.html放一个登录框登录成功就跳转。这种做法不够安全因为admin.html本身可能被反编译或直接通过URL访问。正确姿势是后端每个管理接口都校验Authorization请求头里的token。# 请求管理接口时请求头示例 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9常见的坑是token在前端被随意存储。小程序里wx.setStorageSync存储的token是明文虽然小程序的本地存储不像Web端那样容易被XSS窃取但如果手机越狱或Root后数据文件是可以被拷出来的。对于考试系统token有效期不宜过长建议设置为2小时并在每次请求时刷新。也可以考虑将token放进wx.getStorageSync的sessionStorage域退出时主动清除。5. 上线前必须会改的三处配置与两个进阶技巧5.1 修改刚进入的加载页面源码默认的启动页是pages/index/index如果你想自定义启动页比如让用户一进来看到考试公告而不是直接进入答题修改app.json中的pages数组第一项即可。微信小程序加载页本身是app.json里的pages第一个页面同时可以配置window下的backgroundColor和backgroundTextStyle来控制下拉加载背景。{ pages: [pages/notice/notice, pages/index/index], window: { backgroundColor: #f5f5f5, backgroundTextStyle: dark } }backgroundColor是下拉刷新时露出的背景色backgroundTextStyle决定加载小圆圈的颜色仅支持dark和light。如果你发现自定义加载页白屏大概率是app.js的onLaunch里有异步请求阻塞了页面渲染检查是否有await操作或setTimeout。5.2 跳转链接weixin://dl/business的避坑指南考试系统经常需要从公众号菜单或外部网页跳转到小程序官方提供两种方式wx.openUrl和URL Scheme。weixin://dl/business这种协议是微信跳转小程序的通用框架但实际项目中很少直接用裸链接因为需要在微信开放平台配置生成的URL Scheme才有效。// 生成小程序跳转链接后端调用 // 开放平台接口https://api.weixin.qq.com/wxa/generatescheme?access_tokenACCESS_TOKEN const data { jump_wxa: { path: /pages/index/index, // 跳转到的页面路径 query: examId123 }, is_expire: true, expire_type: 1, expire_interval: 30 // 30分钟有效 };配置时注意expire_type为1表示按分钟过期expire_interval最大可设30天。生成的链接在微信内点开后直接跳转小程序但在外部浏览器打开时可能会提示“请在微信客户端打开”这是微信的机制限制无法绕过。5.3 用抓包快速定位小程序接口问题开发微信小程序时经常遇到页面数据对不上这时候抓包是最高效的定位手段。PC端微信小程序抓包不需要Root手机常见做法是开启微信开发者工具在工具栏“详情”里勾选“不校验合法域名”然后使用Charles或Fiddler设置代理。我需要强调的是抓包只能用于调试自己的项目不要尝试破解或篡改他人小程序。# 使用Charles抓包时确保手机和电脑在同一局域网 # 手机设置HTTP代理为电脑IP:8888 # 在Charles中开启SSL Proxying并添加微信小程序的域名抓包能看到小程序发出的每一个请求的URL、请求头、响应体。当你发现考试页提交答案后没有返回成绩先看网络请求是否到达了后端如果请求体里没有携带token那就是登录态失效的问题如果请求正常但返回了500则要看后端日志而不是在小程序端盲目排查。5.4 导出考试成绩为Excel的快速方案考试结束后学生和管理员都需要成绩单。小程序端导出Excel有两种思路一是后端生成Excel文件返回文件地址小程序端用wx.downloadFile下载后调用wx.openDocument预览二是后端以JSON格式返回成绩前端拼接成CSV通过wx.setClipboardData复制到剪贴板。注意小程序无法直接生成Excel文件并保存在用户手机上iOS和Android的存储路径不同。wx.downloadFile({ url: https://api.example.com/export/score?examId123, success(res) { const filePath res.tempFilePath; wx.openDocument({ filePath: filePath, fileType: xlsx, showMenu: true, // 右上角菜单可转发 success: () console.log(打开文档成功) }); } });后端生成Excel时要注意文件名不能用中文微信下载临时文件时会使用res.tempFilePath作为路径如果服务器没有设置Content-Disposition前端拿到的文件名可能是一串随机字符不影响打开但会影响用户保存时的识别。这份源码的核心价值不在某一个组件而在于把在线考试这个业务从数据模型到前后端交互都串起来了。拿到任何一个模块优先看它的数据结构接口和鉴权方式然后再去改页面这样就不会把考试流程写死。本文还有配套的精品资源点击获取
返回列表