SpringBoot+Uniapp智慧幼儿园管理系统:从架构到源码的落地实践

SpringBoot+Uniapp智慧幼儿园管理系统:从架构到源码的落地实践 简介基于SpringBoot与Uniapp双端架构的幼教集团一站式智慧幼儿园管理系统源码面向需要搭建园务数字化平台的中高级Java开发者及幼教信息化项目团队覆盖保健工作、教务管理、智能考勤、财务管理、OA处理、招生管理、健康档案、校区管理等多类核心场景。系统内置教师、园长、保健医生、助理四种角色各自拥有独立的数据与操作权限家长端支持查看孩子出勤、健康与学习进度并通过园长信箱、班级活动等模块强化家园沟通。源码包共312个文件其中257个Java源文件承担核心业务逻辑23个XML与2个yml、2个properties组合负责配置管理21个PNG图片服务于界面展示压缩包仅7.75MB结构紧凑清晰。目前已有255人学习该资源对幼教行业SaaS化平台研发、园区管理系统二次开发或毕业设计选型均具参考价值。1. 一个幼教集团十几个园所数据却各自为政幼教集团的信息化痛点往往不在功能少而在数据散总部想看考勤报表各园Excel汇总家长交费还在转账后人工登记教师拍照上传食谱家长在三个群里各看一遍。题目里的智慧幼儿园管理系统本质是用 SpringBoot 做后端服务、Uniapp 做家长端和教师端的多端入口把考勤、收费、食谱、用药、巡检这些场景收敛到一套源码体系里。对从业者来说这套设计的价值不只是“能跑”而是它先立住了一个可复用的组织模型——集团、园所、班级、幼儿四级关系贯穿所有业务权限和数据统计天然绑定在组织树上。这篇博客会顺着源码设计的思路从架构选型、数据库与接口设计、Uniapp 多端适配讲到部署排错和状态机收敛给出一套可以直接落地的做法。2. SpringBoot 与 Uniapp 的选型逻辑和整体架构2.1 为什么是 SpringBoot 而不是微服务全家桶智慧幼儿园这类业务系统并发量远没有互联网C端那么大真正的复杂度在业务规则和多端一致性上。SpringBoot 的优势在于自动配置把数据源、Redis、消息推送这些基础设施的启动成本压到最低单体应用够用且调试方便。集团化部署时一个 SpringBoot 工程通过配置文件切换 MySQL 和 Redis 地址即可新增一个园区环境不需要引入 Spring Cloud 那套治理组件。面试题里常问的“SpringBoot 自动配置原理”——EnableAutoConfiguration配合spring.factories加载XXXAutoConfiguration在源码里依然是核心骨架新手理解到这一层就够了。源码设计上我一般会按spring-boot-starter-web、mybatis-plus-boot-starter、spring-boot-starter-data-redis、spring-boot-starter-validation四件套起步。MyBatis-Plus 的LambdaQueryWrapper在写多条件查询时比手写 XML 快很多尤其适合幼教这种字段多但条件组合不复杂的业务。注意一点实体类上尽量不直接用 MyBatis-Plus 的TableField做过多逻辑字段映射保持单纯复杂查询放到 Service 层组装。2.2 Uniapp 在“家长端教师端”场景里的优势家长端需要覆盖微信小程序、支付宝小程序、H5 嵌入公众号、安卓和 iOS App教师端主要跑在安卓平板上。Uniapp 一套 Vue 代码编译到五个端对于幼教集团这种预算有限的场景是现实选择。源码组织上pages按角色拆目录pages/parent放食谱、请假、缴费记录pages/teacher放考勤上报、班级相册、安全巡检pages/common放登录、消息中心。这样运维人员能快速定位问题页面不需要在几十个页面文件里翻。需要注意 Uniapp 的端差异在关键 API 上不可完全抹平。比如定位用uni.getLocation在小程序端需要permission配置在 App 端需要原生 SDK 权限扫码用uni.scanCodeH5 端必须退回plus.barcode或第三方 JSSDK。源码设计时要对这些差异做一层薄封装而不是散落在业务页面里。2.3 组织模型和数据流设计智慧幼儿园管理系统的核心数据流一句话可以概括家长提交需求教师处理园长审批或查看集团总部汇总分析。家长端(Uniapp) ---- API Gateway(SpringBoot) ---- 业务模块 教师端(Uniapp) ---- | | 记录考勤/食谱/账单 | Redis(0) 总部报表(PC后台) ---- 报表模块 ---- 多园数据汇总这套逻辑落到数据库设计时四张核心表必须优先定稿表名核心字段设计要点sys_orgorg_id, org_name, parent_id, org_typeparent_id 支持集团→园→年级→班级四级child_infochild_id, org_id, class_id, guardian_openid与家长绑定用小程序 openid 最可靠attendance_recordrecord_id, child_id, arrive_time, leave_time, statusstatus 整数枚举0未入园 1在园 2已离园 3异常fees_orderorder_id, child_id, fee_type, amount, pay_status对账以订单号为准不依赖支付回调org_id会贯穿每张业务表这不仅是查询需要更是数据权限控制的基础。集团总部的账号能看到所有园园长的账号只能看到自己园后端在 MyBatis 拦截器里自动拼条件org_id ?业务代码里不需要再手写权限判断这属于源码设计早期就要定下的规范。提示child_info表中不建议把家长手机号做主键。家长换手机号很常见用微信号 unionid 作为监护人关联键更稳定。Redis 在整个系统里的定位是三个验证码缓存、token 黑名单家长退出登录时用、考勤高峰期的写缓冲。幼儿早上入园集中在 7:30-8:30这时候如果每个幼儿到达都直写数据库MySQL 压力会出现明显尖峰。常见做法是教师在 Uniapp 端批量上报一次扫多个幼儿后端校验后先写 Redis 队列再异步落库客户端只等一个“已受理”的响应。3. 从数据库到接口核心模块的源码设计落地3.1 工程目录结构与启动骨架源码设计的起点是工程结构不然后面会越写越乱。我习惯这样组织 SpringBoot 后端edu-cloud/ ├── edu-admin // 后台管理端园长/集团用 ├── edu-api // 家长/教师端 API ├── edu-common // 公共模块统一返回、异常、工具类 ├── edu-system // 组织架构、用户、权限 ├── edu-business // 业务模块考勤、收费、伙食、卫生保健 └── edu-report // 报表与统计单模块还是多模块取决于团队规模。如果是两三个后端维护单模块反而更省事如果超过五人并行开发一定要拆模块。多模块的核心收益不是解耦是编译隔离——edu-common改动不会触发整个工程重新编译。启动类和配置没什么特别但有一份配置值得写清楚多环境切换。用application-dev.yml、application-prod.yml区分本地调试和服务器部署关键是数据库密码不落明文jasypt或者环境变量注入spring: datasource: url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: ${DB_USER} password: ${DB_PASSWORD} redis: host: ${REDIS_HOST} port: ${REDIS_PORT}这些${}环境变量在 IDEA 本地调试时用env.local.env文件注入在服务器上用 docker-compose 的environment字段注入。这样源码仓库里永远不会出现数据库密码团队协作也安全。3.2 考勤模块的接口实现考勤是智慧幼儿园里最核心、最重实时的模块。教师端上报场景家长带幼儿到园门口教师用平板扫幼儿手环或二维码小程序拿到结果后调用后端接口。后端接口设计RestController RequestMapping(/api/attendance) public class AttendanceController { PostMapping(/record) public RVoid record(RequestBody Valid AttendanceBatchDTO dto) { // dto 示例{orgId:1001,classId:101,records:[{childId:501,type:1,time:2025-05-12 08:02:31}]} attendanceService.saveBatch(dto); return R.ok(); } }AttendanceBatchDTO需要加校验records列表不能为空每个childId必须在当前教师权限范围内type只能是 1入园或 2离园。权限校验不能只依赖前端隐藏按钮我在AttendanceServiceImpl.saveBatch的前三段会做一次当前登录教师与幼儿所属班级的匹配Override public void saveBatch(AttendanceBatchDTO dto) { // 1. 拿到当前登录教师的班级权限范围 ListLong allowedClassIds authService.getClassIdsByTeacher(LoginHelper.getUserId()); // 2. 校验提交的班级是否在权限范围内 if (!allowedClassIds.contains(dto.getClassId())) { throw new ServiceException(无权操作该班级考勤); } // 3. 批量保存状态: 1入园, 2离园 attendanceMapper.insertBatch(dto.getRecords()); }参数说明LoginHelper.getUserId()是我们在edu-common里封装的 ThreadLocal 工具类拦截器解析 JWT 后把用户信息放进去Service 层直接取用。教师在一个班就只管一个班的幼儿刷卡这也是多园所数据权限的最小闭环。3.3 家长端查食谱与请假申请家长端最有粘性的功能不是缴费是每天看食谱和请假。食谱按周编排家长端要按日期查询当天三餐两点。接口路径设计GET /api/meal/plan?orgId1001date2025-05-12返回内容包含餐次名称、菜品列表、配料过敏原标注、营养分析。过敏原在源码里要单独一个字段因为幼儿园对过敏处理是安全红线。前端拿到数据后对包含过敏原的菜品展示红色标记并提示家长填写过敏原记录// 家长端 Uniapp 请求封装示例 export function getTodayMealPlan(orgId, date) { return request({ url: /api/meal/plan, method: GET, data: { orgId, date } }) }请假申请走的是另一个节奏家长提交 → 班主任审批 → 系统自动计算退餐费用。这里牵扯到“请假与退费联动”源码设计上不要在请假接口里直接改费用订单而是发起请假后向msg_queue表插入一条待处理事件由定时任务在当天 18:00 统一处理避免家长早上请假、系统立刻退款结果菜已经买了的尴尬。3.4 Uniapp 前端登录态与权限控制家长端和小程序端的登录不推荐用账号密码而是用微信授权登录。用户点击授权后前端拿到code传给后端后端调用微信接口换openid再查child_info表确认该 openid 绑定了哪几个幼儿前端 uni.login - 拿 code - POST /api/auth/wx-login - 后端换 openid - 查绑定幼儿 - 签发 JWT - 返回 token 与默认儿童IDJWT 载荷部分要包含orgId和roleType这样后端拦截器判断接口权限时不需要查库。家长端默认展示第一个幼儿的信息切换幼儿时不需要重新登录只需要前端携带childId参数范围的数据。教师端则相反一个教师可能带两个班后端返回可管理的班级列表前端用 picker 组件切换。4. 本地跑通与多端打包的实战参数4.1 十分钟启动后端的最小步骤拿到源码后本地启动的完整顺序# 1. 建库UTF-8mb4 必须幼儿名字可能有生僻字 CREATE DATABASE edu_cloud DEFAULT CHARACTER SET utf8mb4; # 2. 执行 sql 目录下的 init.sql 和 dev_data.sql mysql -uroot -p edu_cloud sql/init.sql # 3. 修改 application-dev.yml 里的数据库账号密码 # 4. 启动 mvn spring-boot:run -Dspring-boot.run.profilesdev容易踩的坑在 MySQL 版本上。SpringBoot 2.7 对应驱动 5.x连接串用driverClassNamecom.mysql.jdbc.Driver没问题SpringBoot 3.x 对应驱动 8.x必须用com.mysql.cj.jdbc.Driver。很多人启动报ClassNotFoundException基本是这个原因。另外 MyBatis-Plus 的TableName在代码里标了实际表名不要在数据库里人为加前缀否则启动时映射全错。提示dev_data.sql会初始化集团、园所、班级、教师、幼儿的测试数据。本地调试建议保留这个文件不然手动造数据非常费时间。4.2 Uniapp 开发环境和 API 代理配置Uniapp 工程在manifest.json里配置h5的devServer.proxy解决本地跨域仅 H5 开发期有效h5: { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true // 生产环境 H5 由 Nginx 反代到后端不需要这里小程序端没有跨域概念但有个细节request的url必须填完整域名且域名必须在微信公众平台后台配置到request合法域名。如果源码设计里已经规划了env控制环境切换// config/env.js const ENV { dev: { BASE_URL: http://localhost:8080 }, prod: { BASE_URL: https://api.edu-cloud.com } }uni.request里直接用${ENV.BASE_URL}拼接路径。打包时记得把env切到prod很多新人改了后端地址但这里没改导致线上请求全打到 localhost。4.3 安卓打包与 iOS 推送的关键参数安卓市场分发需要注意manifest.json里的app-plus.distribute.android.permissions权限用途不声明时ACCESS_FINE_LOCATION教师巡检定位定位回调失败CAMERA扫码、拍照uni.scanCode 无响应RECORD_AUDIO班级视频/语音通知录音白屏VIBRATE扫码成功振动无感iOS 打包在 HBuilderX 云打包时勾选UniPush但要注意UniPush 离线推送需要在前端代码里获取cid传给后端保存。这个cid与 JWT 里的用户 ID 绑定后端推送时按cid发而不是绑手机号——手机号段在不同运营商侧策略不同绑cid最稳。安卓离线推送在部分国产 ROM 上必须引导用户开启“自启动”权限否则锁屏后收不到。这是场景痛点源码里写清楚用户引导页怎么跳转比后端多做几次推送重试都管用。4.4 联调时最常见的几个报错报错信息原因处理401 UnauthorizedJWT 过期或未携带检查请求头 Authorization 和拦截器排除路径413 Request Entity Too Large家长上传幼儿照片太大Nginx client_max_body_size 64M后端 spring.servlet.multipart.max-file-size50MERR_CERT_AUTHORITY_INVALIDH5 没有配 SSL测试环境用 http IP正式上证书Invalid bound statement (not found)MyBatis XML 没被扫描yml 里配mybatis-plus.mapper-locations: classpath*:mapper/**/*.xml联调时多用 curl 直接验证接口不经过前端排干扰项。比如验证考勤接口先 curl POST json看清返回结果再去查前端传参格式与后端字段是否对得上。这条经验对多年后端也适用——前端报错信息往往把真正的接口问题遮住了。5. 把考勤状态收敛成状态机源码可维护性上一个台阶最后落一个具体技巧考勤模块的status字段不要用散落的 if-else 去判断显式建立一个状态机。幼儿一天的考勤状态流转0(未入园) --入园登记-- 1(在园) 1(在园) --离园登记-- 2(已离园) 1(在园) --超时未接-- 3(异常) 3(异常) --补录离园-- 2(已离园)如果这些流转散落在 Controller 里后续每加一个状态就要改动多处还会出现“从 0 直接变 2”这种脏数据。我在attendance包里固定放一个AttendanceStatusHandlerpublic class AttendanceStatusHandler { public static final MapInteger, ListInteger TRANSITIONS new HashMap(); static { TRANSITIONS.put(0, Collections.singletonList(1)); // 未入园 - 在园 TRANSITIONS.put(1, Arrays.asList(2, 3)); // 在园 - 已离园/异常 TRANSITIONS.put(3, Collections.singletonList(2)); // 异常 - 已离园 } public static boolean canTransit(int from, int to) { ListInteger allowed TRANSITIONS.get(from); return allowed ! null allowed.contains(to); } }保存考勤的 Service 在更新前先调用canTransit不允许的流转直接抛业务异常。这个状态机方案足够简单团队里任何一个人接手都能立刻看懂状态流向。异常状态要配合一个定时任务每天 17:30 扫描仍处于“在园”状态的幼儿记录自动置为异常并推送消息给家长和值班教师。数据库层面再加一层保护attendance_record在child_id, attendance_date上加唯一索引一个孩子一天只能有一条考勤全量记录改状态的接口用UPDATE ... WHERE status ?乐观锁并发下不可能出现状态覆盖。这套“源码设计”的核心经验就一句话把业务规则写成代码里可见的结构状态机、权限树、统一入口而不是靠每个人默契遵守文档。幼教集团系统功能可以慢慢加组织模型和状态流转一旦定好后面加保健、加校车、加库存模块都是在同一棵树上长新枝。本文还有配套的精品资源点击获取