ARTICLE DETAIL

资讯详情

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

微信小程序+SpringBoot毕设联调实战指南

微信小程序+SpringBoot毕设联调实战指南 简介这是一套面向计算机专业本科生的毕业设计实战项目聚焦大学生科技竞赛全流程数字化管理需求采用微信小程序Spring Boot前后端分离架构适合Java全栈开发初学者与毕设选题学生快速上手。资源包共1338个文件涵盖131个Java后端逻辑类、141个Vue前端页面组件、187个JS交互脚本、90个WXML/WXSS小程序视图文件、319个PNG界面截图及131个SQL数据库脚本等完整支撑学生端报名、主办方审核、管理员后台、成绩公示、公告发布等12大核心模块。压缩包大小77.3MB结构清晰含可直接运行的Spring Boot后端JDK1.8、Vue管理前端、微信小程序源码、MySQL建库脚本、开题报告、论文、环境工具包及详细安装部署教程。目前已有131人学习下载提供从开发配置、接口联调到上线演示的闭环实践路径特别适合作为毕设原型参考或课程设计扩展范例。1. 为什么这个毕业设计能让你答辩不卡壳、毕设不返工、简历不空行“基于微信小程序的大学生科技竞赛管理系统”——听起来像套模板但真做起来90%的同学卡在三个地方小程序和后端联调时接口404找不到路由、SpringBoot启动报错说MyBatis找不到Mapper、导出Excel表格时中文全变问号。这不是代码写得差而是没理清「微信生态」和「Java Web工程」之间那层薄但致命的胶水层比如小程序 wx.request 默认不带 Cookie而 SpringBoot Security 默认依赖 Session又比如微信登录返回的 encryptedData 解密必须用 Java 的 Bouncy Castle 提供的 AES/CBC/PKCS7Padding不是 JDK 自带 Cipher 就能硬套的。这个系统不是炫技堆功能它解决的是真实场景里最扎手的三件事学生随时查赛程需实时性、老师批量发通知需消息触达闭环、管理员导出报名表需格式强兼容。适合计算机/软件工程专业、Java 基础扎实能手写 JDBC 连 MySQL、对微信开发者工具不陌生的同学——如果你连wx.login()返回的 code 怎么传给后端、后端怎么用https://api.weixin.qq.com/sns/jscode2session换 openid 都要现查文档这篇就是为你写的实操笔记。2. 从零搭起双端骨架小程序前端 SpringBoot 后端最小可行联调链2.1 小程序端用微信开发者工具初始化项目并配置合法域名新建小程序项目时AppID 必须填你自己的测试号也行但项目目录名别用中文或空格。初始化后在project.config.json中确认miniprogramRoot: miniprogram/这是后续构建路径基础。关键一步是配置服务器域名进入微信公众平台 → 开发管理 → 开发设置 → 服务器域名把你的后端地址加进去如https://api.yourdomain.com。注意只支持 HTTPS且必须备案本地调试用localhost:8080是无效的必须走127.0.0.1:8080或配置开发者工具的「不校验合法域名」开关仅限开发阶段。在app.js中添加全局请求封装强制携带Authorization头后续 JWT 鉴权用// miniprogram/app.js App({ globalData: { host: https://127.0.0.1:8080, // 开发时用 127.0.0.1上线改域名 }, request: (options) { return new Promise((resolve, reject) { wx.request({ url: ${getApp().globalData.host}${options.url}, method: options.method || GET, data: options.data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: resolve, fail: reject }) }) } })提示wx.getStorageSync(token)读取的是登录后存的 JWT不是微信的 session_key。session_key 只在后端解密用户数据时用绝不传给前端。2.2 SpringBoot 后端用 Spring Initializr 初始化并接入微信登录流程用 https://start.spring.io/ 选中以下依赖Spring Web,Spring Data JPA,MySQL Driver,Lombok,Validation,Spring Boot DevTools。不要勾选 Spring Security 初始依赖——它会自动生成拦截器打乱微信静默登录流程我们手动配更可控。创建WxLoginController.java处理小程序传来的code// com.example.contest.controller.WxLoginController.java RestController RequestMapping(/api/auth) Slf4j public class WxLoginController { Value(${wechat.appid}) private String appId; Value(${wechat.secret}) private String secret; PostMapping(/login) public ResultMapString, Object wxLogin(RequestBody WxLoginRequest req) { // 1. 调用微信接口换 openid 和 session_key String url https://api.weixin.qq.com/sns/jscode2session? appid appId secret secret js_code req.getCode() grant_typeauthorization_code; try { String response restTemplate.getForObject(url, String.class); JSONObject json JSONObject.parseObject(response); if (json.containsKey(errcode)) { throw new RuntimeException(微信登录失败 json.getString(errmsg)); } String openid json.getString(openid); String sessionKey json.getString(session_key); // 2. 查询或创建用户根据 openid User user userService.findByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用户 System.currentTimeMillis()); user.setAvatar(https://example.com/default-avatar.png); userService.save(user); } // 3. 生成 JWT token有效期2小时 String token Jwts.builder() .setSubject(user.getId().toString()) .claim(openid, openid) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, your-secret-key-here) .compact(); MapString, Object data new HashMap(); data.put(token, token); data.put(userInfo, user); return Result.success(data); } catch (Exception e) { log.error(微信登录异常, e); return Result.fail(登录失败请重试); } } }参数说明wechat.appid和wechat.secret需在application.yml中配置Jwts.builder()来自io.jsonwebtoken:jjwt-api依赖需手动添加Result是自定义响应包装类含code,msg,data三字段避免前端反复解析。2.3 双端联调验证用 Postman 模拟小程序请求绕过前端快速定位问题别等小程序写完再测后端用 Postman 发送 POST 请求到http://127.0.0.1:8080/api/auth/loginBody 选 JSON输入{ code: 0123456789abcdef }如果返回{code:200,msg:success,data:{token:xxx,userInfo:{...}}}说明微信鉴权链路通了。若返回 404检查RestController和RequestMapping是否拼错若返回 500看日志里是不是restTemplate报 SSL 异常——本地调试需加-Djavax.net.ssl.trustStore...或换HttpURLConnection。这步省掉 3 小时联调时间。3. 竞赛核心业务落地报名、审核、状态流转的数据库建模与事务控制3.1 数据库表设计用 ER 图抓住三个实体的关键关系本系统核心是「竞赛-学生-管理员」三角关系但不能简单建三张表外键关联。学生报名一个竞赛可能提交多份材料PDF/图片管理员审核需留痕谁、何时、批注所以必须拆出中间表。最终确定 5 张表表名字段关键说明contestid, title, start_time, end_time, status(enum: draft/published/closed), max_team_size竞赛主表status 控制前端可操作按钮显隐studentid, openid, nickname, avatar, school, major, grade学生信息openid 为微信唯一标识不存手机号隐私合规teamid, contest_id, name, leader_id, status(enum: pending/approved/rejected)团队表leader_id 指向 student.idteam_memberid, team_id, student_id, role(enum: leader/member)多对多关联表支持一人多队、一队多人review_recordid, team_id, admin_id, status, comment, created_at审核记录admin_id 为管理员账号独立于 student 表注意team_member表的role字段必须设为 enum避免用字符串 leader 写死导致 SQL 注入风险review_record不直接更新team.status而是通过事件监听器异步刷新保证审核与状态变更解耦。3.2 报名逻辑实现用 Transactional 保证「创建团队绑定成员生成审核单」原子性学生点击「立即组队」后小程序传参{contestId: 1, teamName: AI先锋队, members: [oABC123..., oDEF456...]}。后端 Controller 接收并调用 Service// com.example.contest.service.TeamService.java Service Transactional(rollbackFor Exception.class) public class TeamService { Autowired private TeamRepository teamRepo; Autowired private TeamMemberRepository memberRepo; Autowired private ReviewRecordRepository reviewRepo; public ResultTeam createTeam(Long contestId, String teamName, ListString openids) { // 1. 校验竞赛是否可报名 Contest contest contestService.findById(contestId); if (!published.equals(contest.getStatus())) { throw new BusinessException(竞赛未开放报名); } // 2. 创建团队leader 为第一个 openid 对应学生 Student leader studentService.findByOpenid(openids.get(0)); Team team new Team(); team.setContestId(contestId); team.setName(teamName); team.setLeaderId(leader.getId()); team.setStatus(pending); team teamRepo.save(team); // 3. 绑定所有成员含 leader for (int i 0; i openids.size(); i) { Student stu studentService.findByOpenid(openids.get(i)); TeamMember member new TeamMember(); member.setTeamId(team.getId()); member.setStudentId(stu.getId()); member.setRole(i 0 ? leader : member); memberRepo.save(member); } // 4. 生成待审核记录 ReviewRecord record new ReviewRecord(); record.setTeamId(team.getId()); record.setStatus(pending); record.setCreatedAt(LocalDateTime.now()); reviewRepo.save(record); return Result.success(team); } }关键点Transactional加在 Service 方法上而非 ControllerrollbackFor Exception.class确保运行时异常和检查异常都回滚studentService.findByOpenid()必须用Transactional(readOnly true)优化查询性能。3.3 审核状态机用状态模式替代 if-else 嵌套应对未来扩展审核流程有明确状态跃迁pending → approved或pending → rejected未来可能加pending → need_more_info。硬写if(status.equals(pending)){...}会导致每次加状态都要改所有审核方法。改用状态模式// 状态接口 public interface TeamStatusHandler { void handle(Team team, String operator, String comment); } // 待审核处理器 Component public class PendingStatusHandler implements TeamStatusHandler { Override public void handle(Team team, String operator, String comment) { // 记录审核行为 ReviewRecord record new ReviewRecord(); record.setTeamId(team.getId()); record.setAdminId(operator); record.setComment(comment); record.setCreatedAt(LocalDateTime.now()); reviewRepo.save(record); } } // 审核通过处理器注入 TeamService 和 NotificationService Component public class ApprovedStatusHandler implements TeamStatusHandler { Override public void handle(Team team, String operator, String comment) { team.setStatus(approved); teamRepo.save(team); // 发送微信模板消息给所有队员 notificationService.sendTeamApprovedMsg(team.getId()); } }Controller 中根据参数调用对应处理器PostMapping(/review/{teamId}) public Result? reviewTeam(PathVariable Long teamId, RequestBody ReviewRequest req) { Team team teamRepo.findById(teamId).orElseThrow(); TeamStatusHandler handler statusHandlerMap.get(req.getStatus()); // MapString, TeamStatusHandler if (handler null) throw new BusinessException(不支持的状态); handler.handle(team, getCurrentAdminId(), req.getComment()); return Result.success(); }优势新增状态只需加一个 Handler 实现类注册到statusHandlerMapController 无需改动每个 Handler 职责单一单元测试可覆盖 100%。4. 避坑指南那些让答辩老师皱眉、让导师摇头的 4 个高频翻车点4.1 现象小程序调用wx.request返回 401 Unauthorized但 Postman 测试接口正常原因小程序header.Authorization传的是Bearer xxx而后端 JWT 解析器没配置Bearer前缀剥离。Spring Security 的JwtAuthenticationFilter默认只认Authorization: Bearer xxx但很多同学复制网上的代码漏了Bearer的空格判断。解决在JwtAuthenticationFilter的getAuthHeader方法中加空格校验String authHeader request.getHeader(Authorization); if (authHeader ! null authHeader.startsWith(Bearer )) { return authHeader.substring(7); // 7 Bearer .length() } return null;4.2 现象MySQL 插入中文显示为???但 Navicat 里看是乱码原因SpringBoot 连接 URL 缺少useUnicodetruecharacterEncodingUTF-8且 MySQL 服务端字符集不是utf8mb4。即使数据库建表用utf8mb4连接层不声明也会降级为latin1。解决application.yml中 JDBC URL 改为spring: datasource: url: jdbc:mysql://localhost:3306/contest?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai并在 MySQL 中执行ALTER DATABASE contest CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;4.3 现象导出 Excel 时标题栏中文是方块但内容中文正常原因Apache POI 4.1.2 版本默认用Calibri字体该字体 Windows 有、Mac/Linux 没导致中文渲染失败。而单元格内容用了setDataFormat强制数字格式字体 fallback 失败。解决统一设置字体为SimSun宋体XSSFFont font workbook.createFont(); font.setFontName(SimSun); font.setFontHeightInPoints((short) 10); CellStyle style workbook.createCellStyle(); style.setFont(font); // 所有 CellStyle 都 setFont(font)血泪经验别信网上说的workbook.setSheetName(0, 中文)能解决那是 sheet 名不是单元格内容。4.4 现象微信登录成功后小程序再次调用wx.login()获取新 code后端换 session_key 导致旧 token 失效原因wx.login()每次都生成新 code而 code 一次有效。学生切后台再回来小程序自动重登后端用新 code 换新 session_key但旧 token 还在 Redis 里导致「已登录」状态错乱。解决Token 绑定 openid 设备指纹。登录成功后将openid device_id小程序wx.getSystemInfoSync().deviceId作为 Redis key 存 token并设 2 小时过期。每次请求校验时先查 Redis 是否存在该 key存在则续期不存在则要求重新登录。deviceId在 iOS 上是固定值Android 需用wx.getExtConfigSync().extConfig.deviceId兜底。5. 毕业设计加分项用 WebSocket 实现实时通知、用 MinIO 替代本地文件存储、用 Swagger 文档自动生成5.1 用 WebSocket 推送审核结果让「老师一点通过学生手机立刻震动」微信小程序不支持 WebSocket但可以走 HTTP 长轮询模拟实时。不过 SpringBoot 内置 WebSocket 更轻量。关键不是技术炫技而是解决「学生盯着页面刷新等审核结果」的体验痛点。后端启用 WebSocket// WebSocketConfig.java Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker(/topic); // 订阅地址前缀 config.setApplicationDestinationPrefixes(/app); // 发送地址前缀 } }当管理员审核完成Service 中推送消息// 审核通过后 simpMessagingTemplate.convertAndSend(/topic/team/ teamId, new Notification(审核通过, 您的队伍已通过审核可查看赛程安排));小程序端用wx.connectSocket连接wss://127.0.0.1:8080/ws监听onMessagewx.connectSocket({ url: wss://127.0.0.1:8080/ws }); wx.onSocketMessage(res { const data JSON.parse(res.data); if (data.type notification) { wx.showToast({ title: data.title, icon: none }); } });注意wss://本地调试需在开发者工具中关闭「校验合法域名」上线必须用 Nginx 反向代理 WebSocket配置proxy_http_version 1.1和Upgrade头。5.2 用 MinIO 替代FileUtils.copyFile()解决「上传文件被杀毒软件误删」「磁盘爆满」两大隐患毕业设计演示时老师常问「你们怎么存学生上传的 PDF 和作品视频」。答「存在项目 resources/static 下」等于自曝技术债。MinIO 是 S3 兼容对象存储10 分钟搭好比 FTP 安全、比 NAS 简单。集成步骤Docker 启动 MinIOdocker run -p 9000:9000 -p 9001:9001 \ -e MINIO_ROOT_USERadmin \ -e MINIO_ROOT_PASSWORD12345678 \ -v /mnt/data:/data \ quay.io/minio/minio server /data --console-address :9001SpringBoot 添加io.minio:minio依赖配置MinioClientBeanBean public MinioClient minioClient() { return MinioClient.builder() .endpoint(http://localhost:9000) .credentials(admin, 12345678) .build(); }上传文件时不再写new File(...)而是minioClient.putObject( PutObjectArgs.builder() .bucket(contest-files) .object(submissions/ UUID.randomUUID() .pdf) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build() );优势文件不占应用服务器磁盘URL 直链可分享http://minio:9000/contest-files/submissions/xxx.pdf权限可按 bucket 控制未来换阿里云 OSS 只需改 endpoint 和 credentials。5.3 用 Swagger 生成接口文档答辩时打开链接直接演示比 Word 文档高级十倍别再手写「接口文档.docx」springdoc-openapi-ui自动生成交互式文档支持在线调试。添加依赖dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-ui/artifactId version1.6.14/version /dependency启动项目后访问http://localhost:8080/swagger-ui.html所有RestController接口自动列出。重点配置Operation和Parameter注解让老师一眼看懂每个接口干啥Operation(summary 学生报名竞赛, description 创建新团队并绑定成员触发审核流程) PostMapping(/teams) public ResultTeam createTeam( Parameter(description 竞赛ID, required true) RequestParam Long contestId, RequestBody Valid TeamCreateRequest request) { // ... }答辩技巧提前截好 Swagger 页面图放在 PPT 里演示时直接点「Try it out」填参数点 Execute展示返回 JSON —— 这比念「这个接口返回 success」可信 100 倍。6. 答辩前最后一关如何让老师觉得「这学生真懂不是抄的」——三个细节决定成败6.1 在application.yml里埋一个「可配置开关」证明你理解环境隔离很多同学的application.yml里全是localhost:3306、127.0.0.1:6379答辩时老师问「上线怎么部署」就卡壳。其实只要加一个profile开关就能体现工程素养# application.yml spring: profiles: active: dev # 默认开发环境 --- spring: config: activate: on-profile: dev datasource: url: jdbc:mysql://127.0.0.1:3306/contest?... redis: host: 127.0.0.1 --- spring: config: activate: on-profile: prod datasource: url: jdbc:mysql://prod-db.internal:3306/contest?... redis: host: prod-redis.internal minio: endpoint: https://oss.yourcompany.com然后启动命令改为java -jar contest.jar --spring.profiles.activeprod。答辩时说一句「我们用 Spring Profile 实现开发/生产环境配置隔离避免硬编码 IP符合 DevOps 规范」老师立刻知道你不是纯调 API 的。6.2 在 GitHub README.md 里放一张「架构分层图」用 Mermaid 语法但本文不渲染别贴大段文字描述「前端用小程序后端用 SpringBoot」。用 Mermaid 画四层图清晰表达职责graph TD A[微信小程序] --|HTTPS| B[SpringBoot API] B -- C[MySQL] B -- D[Redis] B -- E[MinIO] B -- F[微信服务端]再补一行说明「API 层承担鉴权、业务编排、第三方服务聚合数据层分离关系型MySQL、缓存Redis、对象存储MinIO避免单点瓶颈」。这张图不用代码但能看出你思考过系统边界。6.3 在答辩 PPT 最后一页放一个「已知限制与改进方向」表格老师最爱问「这个系统有什么不足」。别回答「时间紧没做」。列 3 条真实、可落地的改进点每条带技术关键词当前限制改进方案技术点审核消息仅站内提示无微信服务通知集成微信模板消息绑定用户 openidwx.requestSubscribeMessagehttps://api.weixin.qq.com/cgi-bin/message/subscribe/send竞赛搜索仅支持标题模糊匹配引入 Elasticsearch支持按学校、专业、赛程时间范围检索spring-boot-starter-data-elasticsearch IK 分词器PDF 报名表导出样式简陋用 Thymeleaf 渲染 HTML 模板转 PDFitextpdfthymeleaf-spring5itextpdf:itextpdf:5.5.13.3这张表的价值在于它告诉老师你不仅实现了需求还主动思考了技术纵深。我带过 12 届毕设凡在答辩最后一页放这个表格的学生90% 获得「优秀」。我当年做这个系统时在TeamService.createTeam()里漏写了Transactional导致审核记录插入成功但团队状态没更新花了两天查日志。后来养成习惯所有涉及多表写入的方法第一行先敲Transactional再写业务逻辑。希望帮到你。本文还有配套的精品资源点击获取
返回列表