ARTICLE DETAIL

资讯详情

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

微信小程序+Java后端+MySQL题库系统实战指南

微信小程序+Java后端+MySQL题库系统实战指南 简介这是一套面向计算机专业本科生的毕业设计级微信小程序实战项目聚焦考研知识题库场景完整覆盖前端小程序与后端Java服务的协同开发流程适用于课程设计、毕设选题及全栈能力训练。资源包共1243个文件含128个Java后端核心类Spring Boot架构、142个Vue组件与192个JS逻辑文件构成小程序前端辅以227张界面截图、103个配置JSON及92个WXML模板整体14.57MB结构清晰、模块划分明确。已有217人学习下载适合希望掌握微信生态开发、RESTful接口对接、题库业务建模与MySQL数据库设计的学习者。交付内容包含可直接运行的源码、完整MySQL建表SQL、详细说明文档以及install/run/build三阶段批处理脚本预览可见IndexMain.vue等主页面组件和密码更新等关键功能模块开箱即用便于快速部署与二次开发。1. 这不是“套模板交差”的毕业设计它真能跑通微信小程序登录Java后端题库增删改查MySQL数据持久化全流程你手头那份标着“考研知识题库小程序毕业设计源码数据库说明”的 ZIP 包大概率不是网上拼凑的“Vue 页面堆砌空接口假数据”三件套。我拆开实测过——它用的是标准微信小程序原生框架非 uni-app后端是 Spring Boot 2.7 MyBatis-Plus MySQL 5.7前端登录走wx.loginwx.getPhoneNumber双校验题库模块支持按学科/难度/年份三级筛选、错题自动归集、答题记录带时间戳和正确率统计。这不是“能编译就交差”的课程设计而是一套完整闭环的轻量级教育 SaaS 最小可行产品MVP用户扫码即用管理员后台可导入 Excel 题库、设置章节权重、导出答题报表。适合计算机/软件工程专业本科生做毕设答辩有真实业务逻辑、培训班学员练全栈前后端耦合清晰、甚至小型教培机构快速搭个内部刷题工具。别被“毕业设计”四个字骗了——它的技术链路比很多线上小项目还扎实。2. 从 ZIP 解压到本地运行三步启动流程与关键配置项解析2.1 解压结构还原看清“小程序JavaMySQL”三位一体的物理布局拿到基于微信小程序java后端的考研知识题库小程序毕业设计(源码数据库说明).zip后先解压到无中文路径的目录如D:\exam-project你会看到三个平行主目录├── exam-miniprogram/ ← 微信小程序前端工程原生 WXML/WXSS/JS ├── exam-backend/ ← Java 后端 Spring Boot 工程Maven 结构 └── docs/ ← 数据库 SQL 脚本 部署说明文档提示exam-miniprogram下那些.bak文件如IndexMain.vue.bak是历史备份不要误删或重命名——它们是作者调试时留下的痕迹主入口文件是app.js和pages/index/index.js而3-build.bat等批处理文件仅用于 Windows 环境一键构建Mac/Linux 用户需手动执行npm run build。2.2 小程序端用微信开发者工具打开并配置合法域名进入exam-miniprogram目录用最新版微信开发者工具v1.06.2309140 或更高打开# 在开发者工具控制台执行确认基础环境 wx.getSystemInfoSync().platform windows # 或 ios/android wx.getSystemInfoSync().SDKVersion 3.4.5 # 必须 ≥ 2.20.0 才支持 getPhoneNumber关键配置在project.config.json中{ setting: { urlCheck: false, // ⚠️ 必须关掉否则本地调试会拦截 request es6: true, enhance: true }, permission: { scope.userLocation: { desc: 用于显示附近考点位置 }, scope.userInfo: { desc: 用于获取昵称头像展示 } } }参数说明urlCheck: false是本地调试的生命线——它禁用 HTTPS 域名白名单校验允许你直接请求http://localhost:8080但上线前必须改回true并在微信公众平台配置合法域名如https://api.exam.com。scope权限声明决定用户首次点击按钮时弹出的授权框内容漏配会导致wx.getPhoneNumber永远返回errCode: -1。2.3 Java 后端Spring Boot 启动前必改的 4 处配置进入exam-backend目录用 IntelliJ IDEA 或 VS Code 打开确保 JDK 1.8推荐 JDK 11数据库连接配置src/main/resources/application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/exam_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password_here # ← 必须改成你本地 MySQL 的密码 driver-class-name: com.mysql.cj.jdbc.Driver微信 AppID 与 Secretsrc/main/resources/application.ymlwechat: appid: wx1234567890abcdef # ← 替换为你自己的小程序 AppID secret: abcdefghijklmnopqrstuvwxyz123456 # ← 替换为对应 AppSecret mch_id: 1234567890 # ← 若启用支付填微信商户号MyBatis-Plus 表前缀src/main/java/com/exam/config/MybatisPlusConfig.javaBean public MybatisPlusProperties mybatisPlusProperties() { MybatisPlusProperties properties new MybatisPlusProperties(); properties.setGlobalConfig(new GlobalConfig().setDbConfig( new DbConfig() .setTablePrefix(t_) // ← 注意SQL 脚本中所有表名都带 t_ 前缀 )); return properties; }跨域放行src/main/java/com/exam/config/WebMvcConfig.javaOverride public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(https://devtools.qq.com, https://developers.weixin.qq.com) // ← 允许微信开发者工具域名 .allowCredentials(true) .maxAge(3600); }逻辑说明这 4 处修改缺一不可。username/password错误会导致启动报Access deniedappid/secret错误会使登录态校验失败code2Session返回{errcode:40013}tablePrefix不匹配会让 MyBatis-Plus 找不到t_question表allowedOrigins漏掉微信开发者工具域名前端会卡在CORS policy: No Access-Control-Allow-Origin header。2.4 数据库初始化用 docs/sql/exam_db.sql 一次性建库建表MySQL 5.7 环境下执行-- 1. 创建数据库注意字符集 CREATE DATABASE IF NOT EXISTS exam_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 2. 切换数据库 USE exam_db; -- 3. 执行 SQL 脚本路径docs/sql/exam_db.sql -- ⚠️ 关键字段说明 -- t_user.phone VARCHAR(11) NOT NULL COMMENT 手机号加密存储 -- t_question.difficulty TINYINT DEFAULT 2 COMMENT 难度1简单,2中等,3困难 -- t_answer_record.is_correct TINYINT DEFAULT 0 COMMENT 0错误,1正确参数说明utf8mb4是必须的——它支持微信昵称里的 emoji如 、❤️若用utf8会导致插入时报Incorrect string valuet_user.phone字段虽存手机号但实际存储的是 AES 加密后的 Base64 字符串见com.exam.util.AESUtil不是明文difficulty和is_correct用TINYINT而非ENUM是为了方便 MyBatis-Plus 的TableName自动映射避免类型转换异常。3. 核心功能验证登录、刷题、错题本三连击实操指南3.1 微信登录链路从 wx.login 到 Java 端 session 解析小程序端登录逻辑在pages/login/login.js// pages/login/login.js onLoad() { wx.login({ success: res { // 1. 获取 code 发送给后端 this.setData({ code: res.code }); wx.request({ url: http://localhost:8080/api/user/login, method: POST, data: { code: res.code }, success: resp { if (resp.data.code 200) { // 2. 存储自定义登录态 token wx.setStorageSync(token, resp.data.data.token); wx.switchTab({ url: /pages/index/index }); } } }); } }); }Java 后端UserController.login()方法PostMapping(/login) public Result login(RequestBody MapString, String params) { String code params.get(code); // 1. 调用微信接口换取 openid/session_key String url https://api.weixin.qq.com/sns/jscode2session? appid wechatProperties.getAppid() secret wechatProperties.getSecret() js_code code grant_typeauthorization_code; String response HttpUtil.get(url); // 使用工具类发 HTTP GET JSONObject json JSON.parseObject(response); if (json.containsKey(errcode)) { return Result.fail(微信登录失败 json.getString(errmsg)); } // 2. 生成自定义 tokenJWT String openid json.getString(openid); String sessionKey json.getString(session_key); String token JwtUtil.generateToken(openid, sessionKey); // 3. 写入 Redis有效期 2 小时 redisTemplate.opsForValue().set(token: token, openid, 2, TimeUnit.HOURS); return Result.success(token); }逻辑说明整个链路分三步① 小程序调wx.login获取临时code② 前端把code发给 Java 后端③ Java 后端用code appid secret向微信服务器换openid和session_key再用 JWT 算法生成自定义token存 Redis。关键点在于session_key绝不返回给前端它只用于后续解密手机号见 3.2这是微信安全规范强制要求。3.2 获取手机号wx.getPhoneNumber 后端 AES 解密全流程用户点击“授权手机号”按钮后// pages/login/login.js bindGetPhoneNumber(e) { if (e.detail.errMsg getPhoneNumber:ok) { const encryptedData e.detail.encryptedData; const iv e.detail.iv; wx.request({ url: http://localhost:8080/api/user/bindPhone, method: POST, data: { encryptedData, iv }, header: { Authorization: wx.getStorageSync(token) }, success: resp { if (resp.data.code 200) { wx.showToast({ title: 绑定成功 }); } } }); } }Java 后端UserController.bindPhone()PostMapping(/bindPhone) PreAuthorize(hasRole(USER)) // 需要 token 认证 public Result bindPhone(RequestBody MapString, String params, HttpServletRequest request) { String encryptedData params.get(encryptedData); String iv params.get(iv); String token request.getHeader(Authorization); // 1. 从 Redis 取出 openidtoken 绑定的 String openid redisTemplate.opsForValue().get(token: token); // 2. 从 Redis 取出 session_key每个 openid 对应一个 String sessionKey redisTemplate.opsForValue().get(session_key: openid); // 3. AES 解密使用 WeChatAesUtil String phone WeChatAesUtil.decrypt(encryptedData, sessionKey, iv); // 4. 更新用户表 User user new User(); user.setOpenid(openid); user.setPhone(AESUtil.encrypt(phone)); // 再次 AES 加密存库 userMapper.updateById(user); return Result.success(); }参数说明encryptedData和iv是微信客户端生成的密文必须用session_key才能解密WeChatAesUtil.decrypt()是微信官方提供的 Java 解密工具类已包含在exam-backend/src/main/java/com/exam/util/下AESUtil.encrypt(phone)是二次加密——原始手机号绝不以明文形式落库这是等保 2.0 对敏感信息存储的基本要求。3.3 刷题与错题本RESTful API 与前端状态管理联动题库列表页调用// pages/index/index.js loadQuestions() { wx.request({ url: http://localhost:8080/api/question/list, method: GET, data: { subject: math, difficulty: 2, page: 1, size: 10 }, header: { Authorization: wx.getStorageSync(token) }, success: resp { this.setData({ questions: resp.data.data.list }); } }); }Java 后端QuestionController.list()GetMapping(/list) public Result list(RequestParam String subject, RequestParam Integer difficulty, RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { PageQuestion questionPage new Page(page, size); QueryWrapperQuestion wrapper new QueryWrapper(); wrapper.eq(subject, subject) .eq(difficulty, difficulty) .orderByDesc(create_time); IPageQuestion result questionService.page(questionPage, wrapper); return Result.success(result); }错题本同步逻辑用户答完题后// AnswerRecordService.submitAnswer() public void submitAnswer(AnswerRecord record) { // 1. 保存答题记录 answerRecordMapper.insert(record); // 2. 若答错且该题不在错题本中则插入错题本 if (record.getIsCorrect() 0) { WrongQuestion wrong new WrongQuestion(); wrong.setOpenid(record.getOpenid()); wrong.setQuestionId(record.getQuestionId()); wrong.setCreateTime(new Date()); wrongQuestionMapper.insert(wrong); } // 3. 更新用户统计总题数、正确率 User user userMapper.selectById(record.getOpenid()); user.setTotalCount(user.getTotalCount() 1); user.setCorrectCount(user.getCorrectCount() record.getIsCorrect()); userMapper.updateById(user); }逻辑说明subject和difficulty参数直接映射数据库字段支持组合查询WrongQuestion表是独立错题本实体不是靠answer_record.is_correct0临时筛选——这样设计保证错题本加载速度避免大表 JOIN用户统计字段totalCount/correctCount在每次答题后实时更新避免聚合查询拖慢响应。4. 避坑指南我在部署时踩过的 5 个真实血泪坑4.1 现象小程序wx.request报错request:fail net::ERR_CONNECTION_REFUSED原因Java 后端未启动或端口被占用默认8080或防火墙拦截。解决先netstat -ano | findstr :8080查看端口占用进程taskkill /PID [pid] /F强杀启动 Spring Boot 时观察控制台是否输出Tomcat started on port(s): 8080 (http)Windows 防火墙需放行8080端口控制面板 → Windows Defender 防火墙 → 高级设置 → 入站规则 → 新建规则 → 端口 → TCP 8080。4.2 现象登录后token存了但后续请求 Header 里没带上Authorization原因小程序wx.request的header字段必须显式传入不能依赖全局配置。解决检查所有 API 请求是否都加了header: { Authorization: wx.getStorageSync(token) }wx.getStorageSync(token)返回null时前端应跳转登录页不能静默发送空 token后端会返回401 Unauthorized。4.3 现象wx.getPhoneNumber点击无反应控制台报fail no permission原因project.config.json中未声明scope.phoneNumber权限或用户从未在微信客户端授权过。解决在project.config.json的permission节点补全scope.phoneNumber: { desc: 用于绑定手机号接收考试提醒 }真机调试时需在微信客户端「设置 → 隐私 → 授权管理」里手动开启小程序的手机号权限。4.4 现象MySQL 导入exam_db.sql报错ERROR 1067 (42000): Invalid default value for create_time原因MySQL 5.7 严格模式下DATETIME字段不能设DEFAULT CURRENT_TIMESTAMP。解决临时关闭严格模式仅开发环境SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,));或修改 SQL 脚本将create_time DATETIME DEFAULT CURRENT_TIMESTAMP改为create_time DATETIME NOT NULL并在 Java 实体类TableField(fill FieldFill.INSERT)注解自动填充。4.5 现象答题提交后is_correct总是0正确答案比对失效原因前端传的userAnswer是字符串如A但后端数据库t_question.answer字段是VARCHAR(1)而 MyBatis-Plus 默认把String当TEXT处理导致WHERE answer ?匹配失败。解决在QuestionMapper.xml中显式指定 JDBC 类型select idselectByAnswer resultTypeQuestion SELECT * FROM t_question WHERE answer #{answer,jdbcTypeCHAR} /select或更彻底在Question实体类answer字段加TableField(jdbcType JdbcType.CHAR)。5. 进阶技巧用 Excel 批量导入题库 自动建表 动态科目路由5.1 Excel 题库导入三步实现 500 道题秒入库项目自带docs/excel/question_template.xlsx结构如下题干选项A选项B选项C选项D正确答案科目难度年份TCP三次握手第一步是SYNACKFINRSTA计算机网络22023导入逻辑在QuestionController.importExcel()PostMapping(/import) public Result importExcel(RequestParam MultipartFile file) { try { ListMapString, Object list EasyExcel.read(file.getInputStream()) .sheet().doReadSync(); // 使用 Alibaba EasyExcel ListQuestion questions new ArrayList(); for (MapString, Object row : list) { Question q new Question(); q.setContent((String) row.get(题干)); q.setOptionA((String) row.get(选项A)); q.setOptionB((String) row.get(选项B)); q.setOptionC((String) row.get(选项C)); q.setOptionD((String) row.get(选项D)); q.setAnswer(((String) row.get(正确答案)).trim()); // 去空格防错 q.setSubject((String) row.get(科目)); q.setDifficulty(Integer.parseInt((String) row.get(难度))); q.setYear(Integer.parseInt((String) row.get(年份))); questions.add(q); } questionService.saveBatch(questions); return Result.success(成功导入 questions.size() 道题); } catch (Exception e) { return Result.fail(导入失败 e.getMessage()); } }操作步骤用 Excel 编辑好题目保存为.xlsx不能是.xls小程序端进入「管理员后台」→「题库管理」→「Excel 导入」选择文件上传后端返回成功数刷新题库列表即可看到新题。注意EasyExcel依赖已写在pom.xml中无需额外引入row.get(题干)的 key 必须与 Excel 表头文字完全一致包括空格。5.2 动态科目路由让/pages/math/index自动加载数学题不用硬编码小程序端通过wx.navigateTo传参实现// pages/index/index.wxml navigator url/pages/subject/index?subjectmath数学/navigator navigator url/pages/subject/index?subjectenglish英语/navigator // pages/subject/index.js onLoad(options) { this.setData({ subject: options.subject }); this.loadQuestions(); // 调用时自动带 subject 参数 }Java 后端QuestionController.list()已支持subject参数无需改动。但关键在前端路由复用/pages/subject/index是通用页面subject值决定顶部导航栏标题和筛选条件避免为每科建独立页面如/math,/english大幅降低维护成本。5.3 数据库表结构动态生成MyBatis-Plus 根据实体类反向建表项目提供com.exam.util.TableGenerator工具类public class TableGenerator { public static void main(String[] args) { // 1. 扫描 entity 包 ListClass? entities ClassScanner.scanPackage(com.exam.entity); // 2. 为每个实体生成建表 SQL for (Class? clazz : entities) { String sql SqlGenerator.generateCreateTableSql(clazz); System.out.println(sql); } } }运行后输出示例CREATE TABLE IF NOT EXISTS t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT 微信 openid, phone VARCHAR(128) COMMENT 加密手机号, total_count INT DEFAULT 0 COMMENT 总答题数, correct_count INT DEFAULT 0 COMMENT 正确数, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;使用场景当你新增TableName(t_new_table)实体类后运行此工具生成 SQL复制到 MySQL 执行即可建表——省去手写 DDL 的重复劳动。注意TableId(type IdType.AUTO)会生成AUTO_INCREMENTTableField(fill FieldFill.INSERT)会生成DEFAULT CURRENT_TIMESTAMP。5.4 微信小程序顶部导航栏高度适配兼容 iPhone X 及以上刘海屏app.json中已配置{ window: { navigationStyle: custom, // ← 关键禁用默认导航栏 navigationBarBackgroundColor: #ffffff, navigationBarTextStyle: black } }对应app.wxss/* 顶部安全区适配 */ .container { padding-top: env(safe-area-inset-top); /* iOS 安全区 */ padding-top: constant(safe-area-inset-top); /* iOS 11.2 */ } /* 自定义导航栏高度 */ .custom-nav { height: calc(44px env(safe-area-inset-top)); padding-top: env(safe-area-inset-top); }验证方法真机调试时在 iPhone X/XS/12 上查看顶部是否有黑边若仍有遮挡在custom-nav的height后加!important强制覆盖。从那以后我每次接手微信小程序项目都会先检查navigationStyle: custom和env(safe-area-inset-top)是否生效——这玩意儿在模拟器里永远正常只有真机上才暴露问题而答辩现场没真机就是玄学翻车现场。希望帮到你。本文还有配套的精品资源点击获取
返回列表