ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

SpringBoot+微信小程序:小学生托管管理系统实战全流程

SpringBoot+微信小程序:小学生托管管理系统实战全流程 又到了一年两度的毕业设计选题季托管管理系统这个题目每年都有一大批人做但真正能把前后端串起来、把业务流程讲清楚的并不多。很多同学卡在最基础的地方微信小程序怎么和后端通信登录状态怎么维持接送记录和签到记录怎么设计才不会乱这篇文章就围绕一个基于SpringBoot和微信小程序的小学生托管管理系统把从业务拆解、数据库设计、核心接口实现到小程序端联调、部署运行的完整链路捋一遍。内容偏实战适合正在做类似课题、或者想快速入门SpringBoot全栈开发的同学参考。我会把当时设计时踩过的坑和后来复盘觉得最该注意的点也都写出来。1. 托管业务拆解先理清需求再谈技术选型1.1 托管班的日常运转到底有哪些事做系统之前我专门跑了两家托管机构把他们的日常流程捋了一遍。所谓托管其实就是放学后把孩子接到机构里看着写作业、管顿饭、等家长来接。这里面有四个核心角色家长、学生、老师、机构管理员。日常动作看起来简单但拆成系统需求后其实不少早晨或放学后学生到达托管班需要记录入托时间学生离开时需要记录离托时间并且要确认是哪个家长接走的家长需要实时看到孩子到没到、走没走、作业完成状态每月收费、缴费记录、欠费提醒孩子生病或有事不能来家长需要请假机构发通知比如放假安排、活动通知管理员要能按班级、按时间查看考勤统计。这些需求单独拎出来每一个都不难但要在同一个系统里串起来并且保证数据准确、流程可追溯就需要在设计和实现上下一番功夫。1.2 技术选型SpringBoot与微信小程序为什么是绝配后端选SpringBoot理由很实在生态成熟、资料多、答辩好讲。SpringBoot的自动配置机制让项目初期搭建成本极低配合MyBatis-Plus单表CRUD基本不用写SQL开发效率很高。Java语言的强类型特性也让多人协作时更容易保持代码稳定。前端选微信小程序核心原因是家长端的使用门槛为零。托管班的家长群体里很多是爷爷奶奶辈让他们下载一个独立App根本不现实但在微信里打开小程序、点一下就能看到孩子状态几乎不需要学习成本。从技术角度说小程序提供了完整的登录体系、模板消息订阅能力而且开发调试工具很成熟比H5在微信里的体验更可控。这里我也对比过另一条路线只做一个web管理后台家长端用微信群人工沟通。结果是老师每天要拍几十张照片发群家长信息淹没在聊天记录里出了纠纷都找不到依据。所以最终方案还是管理后台家长小程序双端结构。1.3 功能模块划分双端各司其职整个系统的功能模块划分大致如下这个划分也直接决定了后面的表结构和接口设计。模块核心功能面向角色实现端学生管理学生信息增删改查、分班、状态管理老师/管理员管理后台小程序班级管理班级信息、师生绑定、容量设置管理员管理后台小程序考勤签到入托签到、离托签退、记录查询老师/家长双端接送管理接送人白名单、接送记录确认老师/家长双端费用管理月费生成、缴费登记、欠费统计管理员/家长双端请假管理请假申请、审批、缺勤同步家长/老师双端公告通知发布公告、消息触达管理员/家长双端每个模块的功能都不复杂难点在于模块之间的数据联动。比如请假审批通过后当天的考勤状态要自动标记为请假不能算缺勤缴费完成后家长端的费用状态要实时刷新。2. 核心表结构设计从业务反推数据库而不是先画ER图2.1 主表设计学生、班级、老师、家长四张核心表很多新手做表结构时喜欢一上来就画ER图结果画出来一堆关系落库的时候却不知道怎么处理。我的做法正好反过来把业务里的每一个功能点列出来反推需要哪些表、哪些字段。这个系统的核心主表有六张student学生信息表字段包括学生姓名、性别、出生日期、班级ID、入学日期、状态。特别要注意的字段是status我用了tinyint类型1代表在读0代表退托这样退托学生不会影响班级考勤统计。class_info托管班级表包括班级名称、所属年级、负责老师ID、容量上限。这里有个业务细节一个托管班通常对应一个主带老师所以直接冗余了teacher_id查询班级列表时少一次join。teacher教师表包括登录账号、密码BCrypt加密、姓名、手机号、角色。角色字段我用的是普通字符串虽然不够优雅但胜在直观项目里判断权限时直接用ADMIN、TEACHER比较。parent_user家长表核心字段是openid这是微信小程序登录后拿到的唯一标识。第一次通过微信授权登录时系统会自动创建这条记录。student_parent_rel学生家长关联表这个表单独说。sign_record签到记录表后面详细说。2.2 学生与家长的多对多关系为什么必须建关联表学生和家长之间不是简单的一对一。一个家庭可能有两个孩子都在同一个托管班一个孩子也可能同时绑定父亲、母亲、爷爷奶奶四个监护人。如果只在student表里加一个parent_id字段遇到多孩子或多家长的情况就直接崩了。所以这里必须建关联表student_parent_rel字段包括自增ID、student_id、parent_id、关系类型爸爸/妈妈/爷爷/奶奶/其他、是否接送授权人。其中是否接送授权人这个字段是我后来加上的因为在接送场景里不是所有绑定家长都有权接走孩子得有一个明确的标记。查询设计上一个典型的接口逻辑是家长登录后先查关联表拿到所有student_id再查student表列表。这里我用了一条SQL解决SELECT s.* FROM student s INNER JOIN student_parent_rel r ON s.id r.student_id WHERE r.parent_id #{parentId} AND s.status 12.3 签到记录与接送记录两个动作不能混在一张表里入托签到和离托接送是两个完全不同的业务动作虽然它们都发生在学生进出托管班这个时间点上但记录的内容和用途差别很大。签到记录侧重的是考勤谁、几点到、几点走、当天状态正常/迟到/请假/缺勤。它面向的是老师和管理员用于统计出勤率。接送记录侧重的是安全与责任归属谁来接的、和学生的关系是什么、哪个老师确认放行的。它面向的更多是安全事故追溯出了问题能查清楚哪一环有漏洞。我把这两张表分开设计字段对比如下字段sign_record签到记录pickup_record接送记录学生IDstudent_idstudent_id动作类型sign_type1入托/2离托无本身就是接送行为时间sign_timepickup_time操作人operator_id老师IDconfirm_teacher_id确认老师ID关联人员无picker_name、picker_relation接送人姓名与关系备注remarkremark之所以要拆还有一个现实原因签到记录一天只有两条入托离托但接送记录可能一天有多条比如中午家长临时接走一小时内送回这种特殊场景如果是单表design记录会非常混乱。3. 后端接口与小程序端的交互设计登录、鉴权、请求封装3.1 微信登录完整流程code换openid再换业务token微信小程序登录和传统账密登录最大的区别是它没有密码这个概念。整个流程基于微信的开放能力小程序端调用wx.login()拿到一个临时凭证code把code传给自己的后端接口/api/auth/login后端用code调用微信的jscode2session接口换回用户的openid和session_key后端拿着openid查parent_user表存在则直接生成业务token返回不存在则自动创建新用户前端把token存到wx.setStorageSync里后续所有请求都带这个token。这里有个安全误区必须提醒openid的换取必须放在后端完成。有些同学图方便在小程序端直接请求微信接口换openid这意味着你不得不在小程序代码里写死AppSecret而小程序代码是半公开的AppSecret一旦泄露任何人都能伪造身份。后端核心代码大致是这样RestController RequestMapping(/api/auth) public class AuthController { Resource private ParentUserService parentUserService; GetMapping(/login) public Result login(RequestParam String code) { // 1. 调用微信接口换取openid WxLoginResult wxResult wxService.code2Session(code); if (wxResult.getErrcode() ! 0) { return Result.error(微信登录失败 wxResult.getErrmsg()); } // 2. 查询或创建家长用户 String openid wxResult.getOpenid(); ParentUser parent parentUserService.findByOpenid(openid); if (parent null) { parent parentUserService.createByOpenid(openid); } // 3. 签发业务token String token JwtUtil.createToken(parent.getId(), parent.getRole()); return Result.ok(token); } }3.2 拦截器鉴权与三种角色的权限控制系统里一共三类使用者管理员、老师、家长。接口不能谁都能调比如家长不能调用签到接口老师不能修改收费标准。我的做法是后端统一加一个AuthInterceptor拦截器所有/api/开头的请求都会先经过它。拦截器从请求头里拿token校验签名和有效期解析出用户ID和角色放ThreadLocal里供后续Controller直接取用。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { throw new BizException(401, 未登录); } // 解析token若过期或非法则抛异常 LoginUser user JwtUtil.parseToken(token); UserContext.set(user); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); // 防止线程池复用导致用户串号 } }角色控制我用了Spring拦截器的路径匹配来实现简洁够用不需要引入Spring Security那么重的框架。比如/api/teacher/**的请求必须角色为TEACHER或ADMIN/api/admin/**必须为ADMIN。这种配置对一个毕设级别、访客量不大的系统是合适的真要做大型生产系统再换Security做细粒度权限。3.3 小程序端请求封装统一域名、token注入与错误处理小程序端的请求不能每次都写一遍wx.request必须封装。我习惯在utils/request.js里统一管理。const BASE_URL http://192.168.1.100:8080/api const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method || GET, data: data || {}, header: { Authorization: wx.getStorageSync(token) }, success: (res) { if (res.statusCode 200 res.data.code 200) { resolve(res.data.data) } else if (res.data.code 401) { wx.navigateTo({ url: /pages/login/login }) reject(res.data) } else { wx.showToast({ title: res.data.msg, icon: none }) reject(res.data) } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }) reject(err) } }) }) } module.exports { request, BASE_URL }有个细节真机预览时BASE_URL绝对不能写localhost。手机访问localhost指向的是手机自身不是你开发用的电脑。要连电脑上起的后端必须把BASE_URL改成电脑在局域网里的IP地址比如http://192.168.1.100:8080/api。开发模式下微信开发者工具可以勾选不校验合法域名但真机预览需要在详情设置里打开调试模式否则请求会被拦掉。4. 几个容易写歪的业务功能接送授权、缴费流水、请假状态机4.1 接送人白名单安全红线怎么用代码表达接送场景的业务痛点在于托管机构必须保证孩子不会被陌生人接走。所以设计上我做了接送人白名单机制。家长在小程序端维护接送白名单也就是student_parent_rel表里is_authorized_pickup1的那些记录。每新增一个接送授权人系统会要求填写姓名、关系、手机号。老师在小程序端执行离托操作时需要在接送人列表里选择此刻来接的是谁并可以调起摄像头拍一张接送人的照片作为留存证据。这个流程用后端接口表达如下POST /api/pickup/confirm 参数studentId, pickerId, photoUrl, remark 逻辑 1. 校验pickerId是否在studentId的白名单里 2. 校验学生当前状态是否为在托 3. 写入pickup_record表 4. 将学生状态置为已离托。第2步的校验特别重要。如果学生已经离托是不能再次执行离托操作的否则会出现一个学生被接走两次的数据脏账。我当时用一个整型状态字段current_status来标记学生当下是在托还是离托1表示在托0表示离托签到时置为1离托时置为0并且通过事务保证这两步同时成功或同时失败。4.2 费用流水表不做余额字段只做变动记录费用模块一开始我犯过一个设计错误想了直接在student表里加一个balance余额字段每次缴费就加每次计费就减。后来发现这会产生严重的数据一致性问题——退费、调价、优惠这些操作很难追溯而且并发时容易算错。后来我改成了纯粹的流水账设计一张fee_record表只记录每一笔费用的变动不冗余任何当前余额。需要看某个学生的缴费情况时就查这张表按时间排序。费用记录表字段如下字段说明id自增主键student_id学生IDfee_type费用类型1月托费/2延时费/3餐费/4退费amount变动金额正数表示应收负数表示退费status状态0待支付/1已支付/2已取消create_time生成时间pay_time支付时间remark备注比如2024年3月托费为什么不做余额字段因为托管班的收费场景相对简单一个月就一笔托费不像商城那样有充值、优惠、积分等复杂场景流水账完全能支持所有查询需求而且没有任何一致性问题。如果你发现项目要求里出现了账户余额这种概念那才需要单独考虑钱包表在此之前流水账够了。4.3 请假流程一个简单的状态机却有明确的业务闭环请假功能看似只是家长填个表单但它牵涉到考勤统计的联动。我把它设计成三态状态机申请中家长提交后状态为PENDING已通过老师审核同意后状态为APPROVED同时自动生成一条当天的考勤标记状态为请假不记入缺勤已驳回老师审核拒绝后状态为REJECTED此时家长可以修改日期后重新提交。这个状态机的核心在于已通过时对考勤记录的操作。如果请假审核通过后考勤表里没有同步标记月底统计出勤率时就会把请假当缺勤家长一投诉就是事故。所以这个联动必须放在请假审批通过的同一个事务里不能靠定时任务去补。请假表的表结构里还有一个隐藏字段leave_date记录的是具体请假日期。因为托管班的考勤按天算一个请假申请只对应某一天不做多日连请。如果家长想请三天假就分别提交三条申请这样后端逻辑简单老师审批也清楚。5. 项目跑通的全流程与典型报错从零环境到双端联调5.1 环境准备清单这个项目后端依赖的核心技术栈是JDK 8或17看SpringBoot版本、Maven 3.6、MySQL 5.7或8.0前端需要微信开发者工具。JDK版本这块特别提醒一下SpringBoot 2.x建议用JDK 8SpringBoot 3.x必须用JDK 17。买的毕设代码如果用的是SpringBoot 2.7.x但本机装了JDK 17大概率启动时没问题但一旦项目里用了javax开头的包就会报ClassNotFoundException。先把项目里的pom.xml打开看一下spring-boot-starter-parent版本再决定装哪个JDK别拿到就急着去改环境变量。MySQL方面建议直接用8.0字符集建库时指定utf8mb4避免学生姓名里有生僻字时出现乱码。建库语句CREATE DATABASE tuition_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;5.2 后端启动三步走第一步定基础环境。JDK配置好JAVA_HOMEMaven配置好settings.xml里的阿里云镜像不然拉依赖能把人急死。Maven镜像配置如下这是每个Java后端开发者必会的操作mirror idaliyunmaven/id mirrorOfcentral/mirrorOf namealiyun maven mirror/name urlhttps://maven.aliyun.com/repository/public/url /mirror第二步建库和执行SQL。项目源码里一般会带一个sql目录里面是完整的建表脚本和初始数据。用Navicat直接执行就好。如果源码没带SQL文件那要么是作者忘了打包要么是用了MyBatis-Plus的自动建表插件启动时自动生成但这种情况反而是隐患。第三步改配置文件。打开src/main/resources/application.yml把数据库账号密码、微信小程序AppID和AppSecret改掉。这里微信配置如果没填登录接口就会报所以你得先去微信公众平台注册一个小程序账号拿到AppID。个人主体的注册就能拿到AppID不需要认证。启动成功后控制台会出现SpringBoot的启动日志访问http://localhost:8080/一般会返回404这其实说明启动成功了只是没有配根路径的Controller而已。5.3 小程序前端配置与真机调试打开微信开发者工具导入小程序前端目录时需要填写自己的AppID这一点不要用测试号。测试号虽然能编译但很多接口权限受限。小程序端要改的位置通常只有一个请求封装文件里的BASE_URL。调试阶段建议使用局域网IP加后端端口。比如电脑IP是192.168.1.100那就把BASE_URL设为http://192.168.1.100:8080/api。在开发者工具的详情-本地设置里勾选不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书。这个选项是开发调试的生命线不勾选的话请求直接返回fail而且错误信息藏在控制台里不够显眼很容易让人误判成后端问题。真机预览时手机和电脑必须连同一个局域网Wi-Fi。如果手机能打开网页但小程序请求还是失败检查一下电脑防火墙是不是拦了8080端口临时关掉Windows防火墙再试一次成功率会大幅提升。5.4 我踩过的三个坑以及对应的排查思路第一个坑LocalDateTime序列化格式问题前端显示签到时间时突然变成了平平整整的数字字符串比如2024-03-01T10:30:00和预期格式差很远。这本质上是SpringBoot默认的Jackson序列化器对LocalDateTime的输出格式不符合我们的展示需求。处理方式有两种一是字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)二是全局配置。我推荐在application.yml里统一配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss但这只对java.util.Date生效对LocalDateTime不生效。LocalDateTime还是要加上JsonFormat注解或者引入jackson-datatype-jsr310模块并自定义序列化配置。最省事的做法是实体类字段上直接标注。第二个坑MyBatis-Plus逻辑删除与唯一索引的冲突MyBatis-Plus的逻辑删除是加了一个deleted字段查询时自动拼上WHERE deleted 0。但如果某张表的某个字段设置了唯一索引当过一年的学生退托被逻辑删除后第二年重新录入一个同名同号的转学生就会触发唯一索引冲突因为逻辑上删掉的记录其实还在表里。解决思路是在业务上绕开问题比如对学生的唯一索引设计为student_no加class_id组合或使用唯一索引加版本字段。如果毕设代码已经踩到这个坑最简单的处理方式是把逻辑删除改成物理删除毕竟学生数据恢复场景在托管系统里很少出现。第三个坑微信code2Session的errcode尤其40029微信登录第一次联调时一直报错code换取openid失败错误码是40029也就是code无效。这个坑的原因是后端拿到的code已经被用过了微信的code是一次性的。常见的触发场景有两个前端登录逻辑写了重试或者后端接口被重复调用。比如页面onLoad时执行wx.loginonShow时又执行了一次wx.login后一次拿到的code就会让前一次的失效。排查时可以直接在微信开发者工具的Network面板里看请求次数一般立即就能发现问题。最后再说几句大实话做这类全栈管理系统最耗时间的永远不是某个单一功能而是数据流转与功能联动的设计。表结构没想清楚就动手写接口后期一定会在联调返工上付出成倍的代价。先把业务场景走一遍把所有需要记录的状态和关系列出来再落数据库这步做得越扎实后面写代码越快。如果你拿到的就是一套像这样带前后端的毕设源码我建议不要急着直接跑起来看效果先用Navicat把每个表都点开看一眼字段再用微信开发者工具的调试器逐行追一遍登录流程。把这套系统真正弄懂比单纯跑通拿到一个结果有用得多答辩时老师随便提个问题你也能接住。有个小技巧送给你给老师演示系统前把一些典型数据做齐——比如一个学生完整的签到离托记录、一笔缴费流水、一条请假审批记录——截好图放在PPT里。老师不一定有时间看你现场操作但截图能直观展示系统的业务完整性这是你在答辩时最能拿分的部分。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表