资讯动态

微信小程序+SpringBoot竞赛管理系统实战指南

发布时间:2026/10/8 7:56:00 来源:尧图企业网站定制
简介本资源是一套面向计算机专业本科生的毕业设计实战项目聚焦大学生科技竞赛全流程数字化管理需求采用微信小程序Spring Boot前后端分离架构适用于Java全栈开发学习与课程设计参考。资源包共1338个文件涵盖131个Java后端核心类、141个Vue管理页面组件、187个小程序JS逻辑文件、319个PNG界面截图及90个WXML/WXSS视图文件辅以SQL脚本、YML配置、BAT部署脚本和完整论文文档整体77.3MB。目前已有132人学习下载体现较强的教学适配性与实践参考价值。用户可直接获取可运行的完整系统源码、MySQL建库脚本、含开题报告与论文的全套文档、环境工具包及相同框架项目的详细安装教程特别适合缺乏项目经验的学生快速理解竞赛报名、成绩发布、主办方协同、学生收藏查询等真实业务模块的实现逻辑与前后端交互细节。1. 为什么这个毕业设计选题能避开“查重雷区”又稳过答辩微信小程序 SpringBoot 的大学生科技竞赛管理系统不是套壳网页而是真跑在手机里的闭环系统你手头那份“基于微信小程序的大学生科技竞赛管理系统”如果只是把后台页面塞进 WebView、前端用 Vue 写完再套个小程序壳——那它本质还是个网页连微信登录都得跳转 H5用户授权弹窗卡顿、手机号获取失败率高、通知推送形同虚设。而真正能拿高分、让答辩老师点头的版本是小程序端只做轻量交互与状态展示所有业务逻辑、权限校验、数据聚合、文件上传、消息分发全部由 SpringBoot 后端兜底且前后端通信走标准 RESTful 接口 自定义 Token 鉴权 微信原生能力桥接。这不是“Java 写后端 小程序写前端”的简单拼接而是围绕“学生报名→团队组队→作品提交→评审打分→结果公示→证书生成”这一完整竞赛生命周期做的垂直建模。它不碰敏感词、不涉外部代理、不依赖非标 SDK纯国产技术栈SpringBoot 2.7.x MyBatis-Plus 3.5.x 微信基础库 2.28.0部署在阿里云轻量应用服务器或本地 Docker 即可跑通全流程。适合本科毕设、课程设计、实训项目尤其适合想用真实业务练手 Java Web 开发、理解小程序与服务端协同边界的学生——你写的不是 Demo是能被学院教务办真拿来用一届的轻量级 SaaS 工具。2. 从零搭起双端骨架小程序端用原生框架 SpringBoot 用模块化结构拒绝“一个 main 方法打天下”2.1 小程序端不用 UniApp不套 Vue就用微信原生开发规范起步很多同学一上来就用 UniApp 或 Taro结果调试时发现wx.login()返回 code 无效、getPhoneNumber按钮点击无响应、云开发环境和自建后端混用导致 token 校验失败。真实落地做法是用微信开发者工具新建“小程序-原生”项目禁用云开发所有网络请求走wx.request所有用户态数据存wx.setStorageSync所有 UI 组件用官方van-button/uni-datetime-picker注意这里用的是社区维护的轻量版非 uni-app 全家桶。目录结构严格按功能域划分├── pages/ │ ├── index/ // 首页竞赛列表 状态筛选 │ ├── apply/ // 报名页表单 团队邀请码输入 │ ├── upload/ // 作品上传支持图片/视频/PDF 进度条 │ └── score/ // 查分页加密成绩单 二维码下载证书 ├── components/ │ ├── auth-modal/ // 登录弹窗含手机号一键授权需配置 button open-typegetPhoneNumber │ └── team-card/ // 团队卡片显示成员头像 角色 状态徽标 ├── utils/ │ ├── request.js // 封装 wx.request自动注入 header.token、拦截 401 跳登录 │ └── crypto.js // 前端 AES 加密用于证书 PDF 文件名混淆防批量下载 └── app.js // 全局 App 实例onLaunch 中检查登录态并跳转提示小程序端不写任何数据库操作、不存敏感字段如手机号明文、不处理评分逻辑——这些全交给后端。前端唯一“计算”是时间格式化new Date().toLocaleString(zh-CN)和状态映射status: 0 → 未提交, 1 → 已提交, 2 → 评审中。2.2 SpringBoot 后端按领域拆 Module拒绝“一个 jar 包包打天下”Gradle 多模块结构比 Maven 更易管理依赖冲突尤其当你需要同时接入微信开放平台 SDK、MyBatis-Plus、Apache POI导出证书、MinIO存作品文件时// root build.gradle.kts plugins { kotlin(jvm) version 1.8.10 id(org.springframework.boot) version 2.7.18 // 注意别用 3.x小程序 JWT 兼容性差 id(io.spring.dependency-management) version 1.1.0 } subprojects { apply(plugin org.springframework.boot) apply(plugin io.spring.dependency-management) apply(plugin org.jetbrains.kotlin.jvm) dependencies { implementation(org.springframework.boot:spring-boot-starter-web) implementation(com.baomidou:mybatis-plus-boot-starter:3.5.3.1) // 3.5.x 是兼容 JDK8 的最后稳定版 implementation(com.github.wechatpay-open-project:wechatpay-apache-httpclient:0.4.10) // 官方微信支付/登录 SDK runtimeOnly(mysql:mysql-connector-java:8.0.33) } }模块划分如下每个 module 对应一个清晰职责Module 名职责关键依赖competition-apiController 层 DTO 全局异常处理器spring-boot-starter-web,validationcompetition-serviceService 接口 实现类 事务管理spring-boot-starter-aop,spring-txcompetition-repositoryMapper 接口 Entity MyBatis-Plus 配置mybatis-plus-boot-starter,pagehelper-spring-boot-startercompetition-common工具类AES 加解密、微信签名生成、Excel 导出模板commons-codec,poi-ooxmlcompetition-fileMinIO 文件上传/下载封装 签名 URL 生成io.minio:minio:8.5.7注意competition-api只暴露RestController不包含任何业务逻辑competition-service里所有方法加Transactionalcompetition-repository的MapperScan注解必须指定本 module 的 mapper 包路径否则启动报Cannot resolve symbol xxxMapper。3. 微信登录与手机号绑定不是调个 API 就完事而是要打通“code → session_key → 手机号 → 用户主键”四步链路3.1 小程序端button open-typegetPhoneNumber必须配合bindgetphonenumber事件 后端解密很多人卡在“点了按钮没反应”其实是没配对三个东西小程序后台「开发管理 → 开发者工具」里开通「获取手机号」接口权限button组件必须设置langzh_CN否则 iOS 下按钮文字不显示bindgetphonenumber回调里拿到的event.detail.code不是登录 code而是加密手机号的临时凭证必须传给后端解密。小程序代码示例pages/apply/index.wxmlbutton open-typegetPhoneNumber bindgetphonenumberonGetPhoneNumber langzh_CN classauth-btn 一键授权手机号 /button对应 JSpages/apply/index.jsonGetPhoneNumber(e) { if (e.detail.code) { // 注意e.detail.code 是加密凭证不是 login code wx.request({ url: https://your-api.com/api/v1/auth/bind-phone, method: POST, data: { encryptedData: e.detail.encryptedData, iv: e.detail.iv, code: e.detail.code }, header: { Authorization: wx.getStorageSync(token) || }, success: res { if (res.data.code 200) { wx.showToast({ title: 绑定成功, icon: success }) wx.navigateBack() } } }) } else { wx.showToast({ title: 授权失败, icon: none }) } }3.2 SpringBoot 端用WxPayService解密手机号再关联到用户主键微信官方 SDK 提供了WxPayService.decryptPhoneNo()方法但必须先用code换取session_key再用session_key encryptedData iv解密。关键点在于code有效期 5 分钟且每个code只能换一次session_key所以后端必须做幂等控制。Java 代码AuthController.javaPostMapping(/bind-phone) public Result? bindPhone(RequestBody PhoneBindRequest req, RequestHeader(value Authorization, required false) String token) { // 1. 校验 token 是否有效JWT 解析 Long userId jwtUtil.getUserId(token); if (userId null) return Result.fail(登录失效); // 2. 用 code 换 session_key调用微信接口 https://api.weixin.qq.com/sns/jscode2session String sessionKey wechatService.getSessionKey(req.getCode()); if (sessionKey null) return Result.fail(code 无效或已过期); // 3. 解密手机号微信 SDK 自带方法 String phone wechatService.decryptPhoneNo(sessionKey, req.getEncryptedData(), req.getIv()); if (phone null) return Result.fail(手机号解密失败); // 4. 绑定到用户表user_id → phone boolean bound userService.bindPhone(userId, phone); return bound ? Result.success() : Result.fail(绑定失败); }注意wechatService.getSessionKey()内部会缓存code → session_key映射Redis 5 分钟 TTL避免重复请求微信接口触发限流decryptPhoneNo()方法要求encryptedData和iv必须 Base64 解码后再传入否则抛IllegalBlockSizeException。4. 竞赛核心流程落地从“学生报名”到“证书生成”每一步都带事务与状态机校验4.1 报名环节团队邀请码 成员角色分配用乐观锁防重复加入学生 A 创建团队时生成 6 位数字邀请码如829317学生 B 输入该码申请加入。常见错误是直接INSERT INTO team_member结果出现同一人加入多个团队、或队长误删自己导致团队解散。正确做法是用状态机 乐观锁 唯一索引三重保障。数据库表设计关键字段CREATE TABLE team ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, invite_code CHAR(6) UNIQUE NOT NULL, -- 加唯一索引 status TINYINT DEFAULT 0 COMMENT 0-组建中,1-已提交,2-评审中,3-已结束 ); CREATE TABLE team_member ( id BIGINT PRIMARY KEY AUTO_INCREMENT, team_id BIGINT NOT NULL, user_id BIGINT NOT NULL, role TINYINT DEFAULT 0 COMMENT 0-成员,1-队长, status TINYINT DEFAULT 0 COMMENT 0-待审核,1-已加入,2-已退出, version INT DEFAULT 0, -- 乐观锁字段 UNIQUE KEY uk_team_user (team_id, user_id) );Service 层逻辑TeamService.javaTransactional public Result? applyToTeam(Long userId, String inviteCode) { // 1. 根据邀请码查团队且 status0仅允许组建中团队接受申请 Team team teamMapper.selectOne(new QueryWrapperTeam() .eq(invite_code, inviteCode) .eq(status, 0)); if (team null) return Result.fail(团队不存在或已关闭报名); // 2. 检查该用户是否已申请过防止重复点提交 Integer count teamMemberMapper.selectCount(new QueryWrapperTeamMember() .eq(team_id, team.getId()) .eq(user_id, userId)); if (count 0) return Result.fail(您已申请加入该团队); // 3. 插入申请记录乐观锁由 MyBatis-Plus 自动处理 version 字段 TeamMember member new TeamMember(); member.setTeamId(team.getId()); member.setUserId(userId); member.setRole(0); // 默认成员 member.setStatus(0); // 待审核 teamMemberMapper.insert(member); return Result.success(申请已提交请等待队长审核); }4.2 作品上传MinIO 存储 前端分片 后端合并支持 500MB 大文件小程序wx.uploadFile默认超时 60 秒传大文件必失败。真实方案是前端分片每片 5MB、后端接收分片、合并、生成访问 URL、更新数据库记录。流程要点小程序端用wx.getFileSystemManager().readFile读取文件二进制按Math.ceil(fileSize / 5242880)分片每片 POST 到/api/v1/file/upload-part携带fileIdUUID、partNumber、totalParts后端用 MinIOputObject存每个分片文件名格式{fileId}/{partNumber};最后一片上传完成后调用 MinIOcomposeObject合并所有分片生成最终文件fileId.ext返回https://minio.your-domain.com/bucket/{fileId}.pdf?Expires...带签名 URL1 小时有效。Java 合并逻辑FileService.javapublic String mergeParts(String fileId, int totalParts) { ListComposeSource sources new ArrayList(); for (int i 1; i totalParts; i) { sources.add(ComposeSource.builder() .bucket(competition-files) .object(String.format(%s/%d, fileId, i)) .build()); } String finalObjectName fileId .pdf; minioClient.composeObject(ComposeObjectArgs.builder() .bucket(competition-files) .object(finalObjectName) .sources(sources) .build()); // 生成预签名 URL1 小时 return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(competition-files) .object(finalObjectName) .expiry(3600) .build()); }提示MinIO 必须开启--console-address :9001并配置 Nginx 反向代理否则小程序无法直传composeObject要求所有分片在同一 bucket且不能跨 region。5. 避坑指南这 4 个血泪经验让 90% 的毕设同学少改三天代码5.1 现象小程序wx.request报错request:fail ssl hand shake error原因后端 SpringBoot 启动时未配置 HTTPS而微信强制要求https://协议即使是本地调试也必须用https://localhost:8080 信任自签名证书。解决本地开发用mkcert生成localhost.pem和localhost-key.pemSpringBootapplication.yml加server: port: 8080 ssl: key-store: classpath:localhost.p12 key-store-password: changeit key-store-type: PKCS12 key-alias: localhost小程序开发者工具 → 设置 → 项目设置 → “不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”必须关闭模拟真实环境。5.2 现象MyBatis-PlussaveBatch()插入 1000 条数据MySQL 报Packet too large原因MySQL 默认max_allowed_packet4MB批量插入 SQL 超长被截断。解决MySQL 配置文件my.cnf加max_allowed_packet 64MMyBatis-Plus 配置分批大小mybatis-plus.batch-size500在application.yml中更稳妥做法用LambdaQueryChainWrapper替代saveBatch()逐条插入牺牲性能保稳定性。5.3 现象微信登录js_code换session_key时返回{errcode:40029,errmsg:invalid code}原因code被前端多次使用或appid/secret配错或code已过期5 分钟。解决后端收到code后立即调用微信接口不缓存、不延迟在 Redis 中以code:为前缀存session_keyTTL 设为 300 秒日志打印原始code和请求 URL对比小程序wx.login()返回值是否一致注意code区分大小写且含特殊字符。5.4 现象证书 PDF 生成后中文乱码字体显示为方块原因Apache POI 默认字体不支持中文且未指定字体路径。解决下载simhei.ttf黑体放入src/main/resources/fonts/Java 代码中显式加载FontProvider fontProvider new FontProvider(); fontProvider.addFont(src/main/resources/fonts/simhei.ttf); Document pdfDoc new Document(PageSize.A4); pdfDoc.addFont(fontProvider.getFont(SimHei, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED));或更简单用iText7替代iText5其PdfFontFactory.createFont()支持Identity-H编码。6. 让答辩老师眼前一亮的三个细节技巧不只是能跑还要让人觉得“这学生真懂落地”6.1 在小程序首页加“实时报名人数”气泡背后是 Redis WebSocket 的轻量级实现很多同学首页只放静态列表而你可以加一个动态数字“当前已有237人报名 · 截止时间2024-06-15 23:59”实现不用复杂消息队列用 SpringBoot 内置WebSocketRedis Pub/Sub即可学生提交报名后后端redisTemplate.convertAndSend(competition:join, update)小程序建立 WebSocket 连接wss://your-api.com/ws监听competition:join频道每次收到消息触发wx.request获取最新统计数GET /api/v1/stat/total更新页面。关键代码WebSocketConfig.javaConfiguration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new StatWebSocketHandler(), /ws) .setAllowedOrigins(*); } }前端监听app.jsonLaunch() { this.ws wx.connectSocket({ url: wss://your-api.com/ws }); this.ws.onMessage(res { if (res.data update) { wx.request({ url: https://your-api.com/api/v1/stat/total, success: d { getApp().globalData.totalJoin d.data.data; }}); } }); }这个细节不增加核心功能但展示了你对“实时性”“轻量通信”“前后端协同”的理解答辩时老师会问“你怎么保证并发下数字准确”——你答“Redis INCR 每次上报后异步刷新误差容忍 1~2 人”。6.2 用Schema注解生成 Swagger 文档让接口描述像教科书一样清晰别只写ApiOperation(获取竞赛列表)要让每个参数、每个返回字段都有业务语义说明GetMapping(/list) Operation(summary 获取可报名竞赛列表按状态过滤) public ResultPageCompetitionVO list( Parameter(description 状态0-草稿,1-报名中,2-评审中,3-已结束, example 1) RequestParam(defaultValue 1) Integer status, Parameter(description 页码从 1 开始, example 1) RequestParam(defaultValue 1) Integer current, Parameter(description 每页数量, example 10) RequestParam(defaultValue 10) Integer size) { return competitionService.listByStatus(status, current, size); }生成的 Swagger 页面会自动带示例值、枚举说明、必填标识答辩时直接打开/swagger-ui.html指着接口说“老师您看这个分页参数我写了默认值避免空参报错状态字段给了枚举说明前端不用查文档就知道 1 是报名中”——这就是工程素养。6.3 在pom.xml里加profiles一键切换测试/生产环境配置别让答辩老师看到你application-prod.yml里硬编码数据库密码。用 Maven Profile Spring Profilesprofiles profile idprod/id properties spring.profiles.activeprod/spring.profiles.active /properties /profile profile iddev/id activation activeByDefaulttrue/activeByDefault /activation properties spring.profiles.activedev/spring.profiles.active /properties /profile /profiles然后application-dev.yml放 H2 内存数据库application-prod.yml用jdbc:mysql://prod-db:3306/comp?useSSLfalse打包时mvn clean package -Pprod即可生成生产包。答辩演示用-Pdev部署用-Pprod干净利落。我带过 12 届毕设最常被问的问题不是“你用了什么技术”而是“你遇到的最大问题是什么怎么解决的”——所以我在文档里专门留了一章《踩坑实录》把ssl handshake error、invalid code、PDF 乱码这些真实翻车现场写清楚不是为了炫技是告诉老师我知道这系统哪里会崩而且我有预案。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑