尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

微信小程序+SpringBoot学生管理系统实战指南

微信小程序+SpringBoot学生管理系统实战指南 简介本资源是一套基于微信小程序与SpringBoot/SSM技术栈开发的学生管理系统完整源码包面向计算机专业本科生、毕业设计开发者及Java全栈初学者解决高校场景下学生信息、课程、成绩等轻量级教务管理的落地实践需求。压缩包共762个文件涵盖101个Java后端业务逻辑文件、123个Vue组件用于管理后台前端、123个SVG图标资源、72个JS交互脚本、30个WXML/WXSS小程序页面文件以及SQL建表脚本、Bat一键部署脚本、YML配置文件和功能说明文档等整体大小28.41MB结构清晰模块划分明确。已有2698人学习下载项目经严格调试可直接运行配套数据库脚本与多环境启动脚本install/run/build.bat显著降低部署门槛源码注释完整含管理员、教师、学生三端权限控制逻辑适合作为毕设原型或SpringBoot小程序融合开发的实战范例。1. 这不是“微信小程序SpringBoot”的简单拼接而是一套可落地的校园轻量级管理闭环你拿到一个名为【weixin9198】基于微信小程序的学生管理系统springboot.zip的压缩包解压后看到springboot后端模块和miniprogram前端目录——但直接mvn spring-boot:run启动后端、用微信开发者工具打开小程序大概率会卡在「登录失败」或「数据为空」。这不是代码写错了而是这类项目天然存在三重断层微信身份体系与 SpringBoot Session 的隔离、小程序端请求签名与后端 JWT 校验的参数错位、学生数据模型在前后端字段语义上的隐性不一致。它面向的是高校教务员、辅导员或毕业设计指导教师解决的是「无需部署独立App、不依赖校内统一认证平台、3天内能上线试用」的真实场景。核心价值不在功能多全而在「微信扫码即用、学生信息增删改查响应在1.2秒内、导出Excel不丢学号前导零」这些细节的稳定交付。如果你正被毕设答辩倒逼、或需快速给实训班搭个演示系统这篇就是按真实调试日志还原的通关路径。2. 拆解微信小程序与 SpringBoot 的通信契约从 wx.login 到 JWT Token 的完整链路2.1 微信小程序端必须完成的三步身份初始化微信小程序无法直接使用传统 Cookie-Session 认证必须依赖微信提供的wx.logincode2Session机制生成唯一用户标识。常见错误是开发者在app.js中只调用wx.login()却未将返回的code传给后端导致后端无法换取openid。正确流程如下// app.js 中的 onLaunch 钩子关键 onLaunch: function () { wx.login({ success: (res) { // 1. 获取临时登录凭证 code const code res.code; // 2. 将 code 发送给 SpringBoot 后端 /api/auth/login 接口 wx.request({ url: https://your-domain.com/api/auth/login, method: POST, data: { code: code }, success: (loginRes) { // 3. 后端返回 JWT token存入 storage 供后续请求携带 if (loginRes.data.code 200) { wx.setStorageSync(token, loginRes.data.data.token); } } }); } }); }提示wx.login()返回的code有效期仅5分钟且同一用户连续调用会刷新code。务必在success回调中立即发起网络请求避免在fail或complete中处理。2.2 SpringBoot 后端实现 code2Session 的安全校验逻辑后端接收code后需向微信服务器发起 HTTPS 请求换取openid和session_key。注意两点不能硬编码 AppID/AppSecret、不能明文存储 session_key。标准实现应使用RestTemplate封装请求并通过Value注入配置// application.yml 中配置微信参数严禁写死 wechat: appid: wx1234567890abcdef secret: 9876543210fedcba9876543210fedcba jscode2session-url: https://api.weixin.qq.com/sns/jscode2session // AuthService.java 中的登录方法 public AuthResponse login(String code) { String url String.format( %s?appid%ssecret%sjs_code%sgrant_typeauthorization_code, wechatProperties.getJscode2sessionUrl(), wechatProperties.getAppid(), wechatProperties.getSecret(), code ); RestTemplate restTemplate new RestTemplate(); ResponseEntityString response restTemplate.getForEntity(url, String.class); // 解析微信返回的 JSON示例{openid:o6_bm1U..., session_key:...} JSONObject json new JSONObject(response.getBody()); String openid json.optString(openid); if (StringUtils.isBlank(openid)) { throw new RuntimeException(微信登录失败 json.optString(errmsg)); } // 生成 JWT Token使用 HS256 算法有效期24小时 String token Jwts.builder() .setSubject(openid) .setExpiration(new Date(System.currentTimeMillis() 24 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, your-secret-key-here) .compact(); return new AuthResponse(200, 登录成功, new AuthData(token)); }2.2.1 关键参数说明与安全边界参数说明安全要求appid/secret微信公众平台分配的凭证必须通过ConfigurationProperties绑定禁止写入 Controller 层js_code小程序端wx.login()返回的临时码单次有效后端校验后立即丢弃不存库session_key微信返回的密钥用于解密敏感数据绝不返回给前端仅用于服务端解密如手机号JWTsecret签名密钥生产环境必须使用 32 字节以上随机字符串避免硬编码2.3 前后端字段映射的隐形陷阱学生表结构一致性验证学生管理系统的核心是student表但小程序端常把studentId学号作为字符串输入而后端实体类可能定义为Long类型。当学号为20230001时Long.valueOf(20230001)会成功但202300001含前导零会被截断为20230001。必须统一使用String类型// Student.java 实体类关键 Entity Table(name t_student) public class Student { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; // 主键自增 Column(name student_id, length 16, nullable false) // 学号字段明确设为 String private String studentId; // 不是 Long避免前导零丢失 Column(name real_name, length 20, nullable false) private String realName; Column(name class_name, length 32) private String className; // getter/setter 省略 }注意MySQL 中student_id字段类型必须为VARCHAR(16)而非BIGINT。若已有表结构为数字类型需执行ALTER TABLE t_student MODIFY COLUMN student_id VARCHAR(16);并清理历史数据中的科学计数法表示如2.023e7。3. 实现学生数据的增删改查SpringBoot REST API 与小程序页面的精准对接3.1 后端接口设计RESTful 风格与微信小程序请求头适配微信小程序wx.request默认不发送Content-Type: application/json若后端 Controller 使用RequestBody接收 JSON必须显式设置请求头否则 SpringBoot 会报415 Unsupported Media Type。解决方案有两种方案一推荐在小程序端主动设置 header// pages/student/add/add.js 中提交学生信息 submitForm: function(e) { const formData e.detail.value; wx.request({ url: https://your-domain.com/api/student, method: POST, header: { Content-Type: application/json }, // 关键 data: JSON.stringify(formData), success: (res) { if (res.data.code 200) { wx.showToast({ title: 添加成功 }); wx.navigateBack(); // 返回列表页 } } }); }方案二后端兼容application/x-www-form-urlencoded// StudentController.java PostMapping(/student) public ResponseResult addStudent( RequestParam String studentId, RequestParam String realName, RequestParam String className) { Student student new Student(); student.setStudentId(studentId); // 直接接收字符串 student.setRealName(realName); student.setClassName(className); studentService.save(student); return ResponseResult.success(); }3.2 小程序端分页列表渲染避免wx:for性能瓶颈学生列表页若直接wx:for{{students}}渲染数百条数据会导致页面卡顿。必须启用分页并配合wx:for-index优化!-- pages/student/list/list.wxml -- view classstudent-list block wx:for{{students}} wx:keystudentId navigator url/pages/student/detail/detail?id{{item.studentId}} classstudent-item view classstudent-id学号{{item.studentId}}/view view classstudent-name姓名{{item.realName}}/view view classstudent-class班级{{item.className}}/view /navigator /block !-- 底部加载更多 -- view classload-more bindtaploadMore wx:if{{!isLastPage}} {{loadingText}} /view /view// pages/student/list/list.js Page({ data: { students: [], page: 1, pageSize: 10, isLastPage: false, loadingText: 上拉加载更多 }, onLoad() { this.loadStudents(); }, loadStudents() { wx.request({ url: https://your-domain.com/api/student?page${this.data.page}size${this.data.pageSize}, method: GET, header: { Authorization: Bearer wx.getStorageSync(token) }, success: (res) { const newData res.data.data.list || []; this.setData({ students: this.data.students.concat(newData), isLastPage: newData.length this.data.pageSize }); } }); }, loadMore() { if (this.data.isLastPage) return; this.setData({ page: this.data.page 1 }); this.loadStudents(); } });3.2.1 SpringBoot 分页接口实现MyBatis-Plus// StudentController.java GetMapping(/student) public ResponseResult listStudents( RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { PageStudent pageObj new Page(page, size); PageStudent result studentService.page(pageObj); return ResponseResult.success() .data(list, result.getRecords()) .data(total, result.getTotal()) .data(pages, result.getPages()); }提示MyBatis-Plus 的Page对象会自动注入COUNT查询无需手动写分页 SQL。若发现查询慢检查student_id字段是否已建索引CREATE INDEX idx_student_id ON t_student(student_id);3.3 导出 Excel 功能解决微信小程序端文件下载限制微信小程序不支持直接下载.xlsx文件必须由后端生成文件流并返回base64编码前端用wx.downloadFile触发保存// StudentController.java GetMapping(/student/export) public void exportStudents(HttpServletResponse response) throws IOException { ListStudent students studentService.list(); // 获取全部学生 // 使用 Apache POI 生成 Excel简化版 Workbook workbook new XSSFWorkbook(); Sheet sheet workbook.createSheet(学生名单); // 表头 Row headerRow sheet.createRow(0); String[] headers {学号, 姓名, 班级}; for (int i 0; i headers.length; i) { Cell cell headerRow.createCell(i); cell.setCellValue(headers[i]); } // 数据行 int rowNum 1; for (Student s : students) { Row row sheet.createRow(rowNum); row.createCell(0).setCellValue(s.getStudentId()); // 保持字符串格式 row.createCell(1).setCellValue(s.getRealName()); row.createCell(2).setCellValue(s.getClassName()); } // 写入响应流 response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenamestudents.xlsx); try (OutputStream out response.getOutputStream()) { workbook.write(out); } workbook.close(); }// 小程序端调用 exportExcel: function() { wx.downloadFile({ url: https://your-domain.com/api/student/export, header: { Authorization: Bearer wx.getStorageSync(token) }, success: (res) { if (res.statusCode 200) { wx.saveFile({ tempFilePath: res.tempFilePath, success: (saveRes) { wx.openDocument({ filePath: saveRes.savedFilePath, show: true }); } }); } } }); }4. 跨域、Token 拦截与微信域名白名单生产环境必调的三项配置4.1 SpringBoot 后端跨域配置CORS的精确控制微信小程序wx.request默认携带Origin头若后端未配置 CORS会触发浏览器预检OPTIONS 请求失败。必须在WebMvcConfigurer中显式放行Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) // 仅放行 /api 开头的路径 .allowedOrigins(https://servicewechat.com, https://developers.weixin.qq.com) // 微信官方域名 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) // 明确列出方法 .allowCredentials(true) // 允许携带 cookieJWT 场景下通常不用 .maxAge(3600); } }注意allowedOrigins不能写*否则allowCredentialstrue会失效。微信开发者工具调试时Origin为https://servicewechat.com真机测试时为https://developers.weixin.qq.com。4.2 JWT Token 拦截器提取 token 并注入 SecurityContext所有/api/**接口必须校验 JWT但login接口本身要放行。使用OncePerRequestFilter实现Component public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token extractToken(request); if (token ! null validateToken(token)) { String openid parseOpenid(token); UsernamePasswordAuthenticationToken auth new UsernamePasswordAuthenticationToken(openid, null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(auth); } filterChain.doFilter(request, response); } private String extractToken(HttpServletRequest request) { String authHeader request.getHeader(Authorization); if (authHeader ! null authHeader.startsWith(Bearer )) { return authHeader.substring(7); // 去掉 Bearer 前缀 } return null; } private boolean validateToken(String token) { try { Jwts.parser().setSigningKey(your-secret-key-here).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } }// SecurityConfig.java 中注册拦截器 Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(authz - authz .requestMatchers(/api/auth/login).permitAll() // 登录接口放行 .requestMatchers(/api/**).authenticated() // 其他接口需认证 ) .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }4.3 微信小程序后台域名配置三个必须填写的 URL在微信公众平台「开发管理 开发设置 服务器域名」中必须配置以下三项协议必须为https且域名需已备案配置项填写内容说明request 合法域名https://your-domain.com小程序wx.request只能访问此域名下的接口socket 合法域名留空本项目未使用 WebSocketdownload 合法域名https://your-domain.comwx.downloadFile下载 Excel 所需提示若使用 Nginx 反向代理需确保X-Forwarded-Proto: https头被正确传递否则 SpringBoot 的HttpServletRequest.isSecure()会返回false导致https重定向异常。5. 毕设级项目调试技巧快速定位「登录成功但查不到数据」的五类根因5.1 数据库连接池配置不当导致连接耗尽学生管理系统并发量低但若application.yml中 HikariCP 配置过大如maximum-pool-size: 100在 IDEA 本地调试时可能因端口冲突或内存不足导致连接超时。应改为保守值spring: datasource: hikari: maximum-pool-size: 5 minimum-idle: 2 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000验证方法启动后访问http://localhost:8080/actuator/hikaricp需引入spring-boot-starter-actuator查看active和idle连接数是否稳定。5.2 微信openid与数据库student表的权限错位常见误区是认为「登录成功就能查所有学生数据」实际应按微信openid绑定管理员角色。StudentController中需增加权限校验GetMapping(/student/{id}) public ResponseResult getStudent(PathVariable String id) { // 从 SecurityContext 获取当前 openid String currentOpenid SecurityContextHolder.getContext() .getAuthentication().getName(); // 查询该 openid 是否有权限查看此学生例如管理员 openid 写死或查 role 表 if (!admin-openid-here.equals(currentOpenid)) { // 普通用户只能查自己的数据假设 student 表有 openid 字段 Student student studentService.getOne( new QueryWrapperStudent().eq(student_id, id).eq(openid, currentOpenid) ); return ResponseResult.success().data(student, student); } // 管理员可查全部 Student student studentService.getById(id); return ResponseResult.success().data(student, student); }5.3 小程序setData异步更新导致的 UI 不同步在学生详情页中若直接this.setData({ student: res.data.data })后立即调用this.selectComponent可能因setData未完成而获取不到组件实例。必须使用回调// pages/student/detail/detail.js getStudentDetail() { wx.request({ url: https://your-domain.com/api/student/${this.data.id}, success: (res) { this.setData({ student: res.data.data }, () { // setData 完成后的回调中操作组件 const comp this.selectComponent(#student-form); if (comp) comp.initData(res.data.data); }); } }); }5.4 SpringBoot 版本与 JDK 兼容性问题针对weixin9198项目标题中weixin9198暗示项目创建于 2019-2020 年大概率使用 SpringBoot 2.1.x ~ 2.3.x。若用 JDK 17 启动会报java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter。解决方案!-- pom.xml 中添加 JAXB 依赖JDK 11 必须 -- dependency groupIdjavax.xml.bind/groupId artifactIdjaxb-api/artifactId version2.3.1/version /dependency dependency groupIdorg.glassfish.jaxb/groupId artifactIdjaxb-runtime/artifactId version2.3.1/version /dependency5.5 微信开发者工具「调试基础库版本」引发的 API 兼容问题小程序端若使用wx.getSystemInfoSync().SDKVersion判断基础库版本需注意weixin9198项目大概率基于 2.10.4 以下版本开发而新版基础库已废弃wx.showActionSheet的itemList参数改用items。检查project.config.json中的libVersion{ description: 项目配置文件, setting: { libVersion: 2.10.4 } }若需兼容新旧版本封装适配函数showActionSheet(items) { const version wx.getSystemInfoSync().SDKVersion; if (this.compareVersion(version, 2.10.4) 0) { wx.showActionSheet({ items }); // 新版 } else { wx.showActionSheet({ itemList: items }); // 旧版 } }, compareVersion(v1, v2) { const v1s v1.split(.).map(Number); const v2s v2.split(.).map(Number); for (let i 0; i Math.max(v1s.length, v2s.length); i) { const num1 v1s[i] || 0; const num2 v2s[i] || 0; if (num1 num2) return 1; if (num1 num2) return -1; } return 0; }本文还有配套的精品资源点击获取
返回列表