SpringBoot+Vue教育培训机构办公管理系统实战:架构设计与源码解析

发布时间:2026/10/6 14:02:31
SpringBoot+Vue教育培训机构办公管理系统实战:架构设计与源码解析 教育培训机构的数字化管理一直是中小机构最头疼的问题学员信息散落在Excel表格里课程排期靠微信群接龙报销审批抱着一堆纸质单据满楼跑。市面上的SaaS系统倒是省事但数据在别人手里功能还未必匹配自己机构的实际流程。我最近在反复打磨一套教育培训机构线上办公管理系统的完整源码技术栈是SpringBoot Vue MyBatis MySQL前后端分离覆盖招生线索、学员管理、课程排课、考勤审批、财务报表这条完整业务闭环。这篇博文把这套系统的架构设计、核心模块实现、数据库建模和从零部署的全过程拆开讲一遍正在做教育管理系统、或者打算给机构搭内部办公系统的朋友可以直接拿来当参考蓝本。先交代一下背景。这套系统不是实验室里交作业的demo是从一家连锁培训机构的真实业务里一点点磨出来的。机构里有市场、销售、教务、财务、教师这几类角色各有各的诉求市场要看线索转化率销售要跟进意向学员教务要排课防冲突教师要看课表签到财务要对课时消耗算收入。如果这些角色各用各的表格数据一定是越做越乱。这套系统要解决的就是把这些角色的工作流收拢到一个统一的管理后台里。1. 项目概述与整体设计思路1.1 系统定位三条业务线收拢到同一套数据模型教育培训机构的日常运营本质上就三条线在跑业务线市场线索 → 跟进回访 → 体验课 → 报名缴费 → 续费转介绍教学线课程设置 → 排课 → 上课签到 → 课时消耗 → 作业与评价后勤线员工考勤 → 请假审批 → 报销与采购 → 公告通知管理混乱往往不是某一件事没做好而是三条线的数据没打通。比如销售报了一个班教务不知道是哪个校区的班财务不知道这笔钱对应多少课时。这套系统在数据模型层面就把三条线串起来学员表引用线索ID报名单引用课程ID和排课ID课时流水引用报名单ID。这样一查课消就能从“哪个学员→哪个班→哪次课→消了多少课时”一路追踪到底不会出现账目对不上的情况。做实体设计的时候有一个细节值得拿出来说所有核心表都保留deleted逻辑删除标记和created_at、updated_at时间字段。管理系统的数据是资产删除记录和单据这种操作物理删表后想恢复比登天还难。逻辑删除虽然让查询SQL里多了一个deleted 0条件但换来的数据安全是完全值得的。1.2 技术选型为什么是SpringBoot Vue MyBatis MySQL管理类系统有个特点并发不高、逻辑不复杂但业务关系多、权限要求细。这就决定了架构选择的方向是稳定、成熟、好招人、好维护而不是一味追新。选这套组合我的理由如下技术组件定位选型理由SpringBoot后端基础框架自动化配置减掉大量样板代码内嵌Tomcat部署简单Spring Security做权限控制成熟Vue前端框架组件化开发适合后台多页面场景配合Element UI能快速搭建表单和表格MyBatis持久层框架SQL完全可控复杂统计查询直接写原生SQL优化比ORM框架更灵活MySQL数据库免费成熟运维资料遍地都是中小机构几十万行数据量轻松扛住有人会问为什么不用微服务教育办公系统的用户量级通常就是几十上百个员工同时在线单体应用完全够用微服务反而引入注册中心、配置中心、分布式事务一堆复杂度对一个小团队来说是灾难。也有人问为什么不用JPAJPA在简单CRUD上确实省事但一旦遇到课消报表这种七八张表关联的统计场景写JPQL或者Specification远不如原生SQL直观可控。MyBatis在复杂查询上的掌控感是这个项目选它最直接的原因。前端技术选型上这套工程用的Vue 2 Element UI。我知道Vue 3已经出来很久了但为什么教育信息化这个圈子里大量项目还在Vue 2原因很现实稳定组件生态、团队熟悉度、老项目维护成本。管理系统页面形态相对固定表格、表单、弹窗、树形控件就是全部主角Vue 2的响应式机制配合Element UI已经极其顺手。这不是技术落后是合适的工具用在合适的场景。1.3 源码目录结构与模块划分拿到源码第一件事别急着跑先把目录结构看明白。edu-office-system/ ├── backend/ │ ├── edu-admin/ # 后端主工程 │ │ ├── src/main/java/com/edu/office/ │ │ │ ├── controller/ # 接口层 │ │ │ ├── service/ # 业务逻辑层 │ │ │ ├── mapper/ # MyBatis Mapper接口 │ │ │ ├── entity/ # 实体类 │ │ │ ├── config/ # 配置类安全、跨域、拦截器 │ │ │ └── common/ # 统一返回体、异常处理、工具类 │ │ └── src/main/resources/ │ │ ├── mapper/ # MyBatis XML映射文件 │ │ └── application.yml # 核心配置文件 │ └── sql/ │ ├── edu_office_init.sql # 表结构与基础数据 │ └── edu_office_sample.sql # 模拟业务数据 ├── frontend/ │ ├── edu-web/ # Vue前端工程 │ │ ├── src/ │ │ │ ├── api/ # 接口请求封装 │ │ │ ├── router/ # 路由配置 │ │ │ ├── store/ # Vuex状态管理 │ │ │ ├── views/ # 页面组件 │ │ │ └── components/ # 通用组件 │ │ └── package.json └── README.mdbackend按com.edu.office分controller/service/mapper/entity/config/common几层是比较标准的单体分层controller只做参数接收和结果封装service承载业务逻辑mapper只管数据访问。common里放着统一返回体ResultT、全局异常处理器、分页工具类这些基础设施。这样分层的好处是职责单一新人接手时看代码位置就能猜出大概功能。frontend的src/permission.js是我最想让你先看的文件它在vue-router的全局前置守卫里做登录态校验和动态路由注入保证了不同角色登录后看到的菜单和能访问的页面完全不同。sql目录下两个文件分工明确init脚本是表结构加账号、角色、菜单等基础数据sample脚本是模拟业务数据。测试系统时先把sample导入就能看到完整的假数据效果。2. 核心技术栈深度解析2.1 SpringBoot核心配置版本匹配是第一道坎我见过太多人栽在版本上。SpringBoot 3.x已经要求JDK 17但很多机构服务器还跑着JDK 8字节码版本直接不兼容启动就报UnsupportedClassVersionError。这套源码基于SpringBoot 2.7.x JDK 8构建是目前生产环境最稳妥的组合之一。对应的MyBatis集成包用mybatis-spring-boot-starter2.3.xMySQL驱动用mysql-connector-java8.0.x三个版本互相兼容不会出现奇怪的依赖冲突。核心配置文件application.yml长这样server: port: 8080 servlet: context-path: /edu-api spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/edu_office?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: root jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.edu.office.entity configuration: map-underscore-to-camel-case: true cache-enabled: true logging: level: com.edu.office.mapper: debug几个细节必须解释清楚。spring.jackson.date-format写成yyyy-MM-dd HH:mm:ss是给Jackson全局定日期格式不然前端拿到的默认是ISO格式时间戳处理起来很麻烦。mybatis.configuration.map-underscore-to-camel-case必须开启这样数据库的create_time才能自动映射到实体的createTime否则查出来全是null。logging.level.com.edu.office.mapper: debug能把MyBatis打印的SQL和参数输出到控制台排查问题效率高一截——这个配置上线前记得改回info级别不然日志文件会被刷爆。再说一遍SpringBoot版本的事如果你本地电脑习惯用JDK 17或者SpringBoot 3.x不要直接把依赖升上去。3.x里javax包名改成了jakartaSpring Security的配置方式也变了整个MyBatis集成、拦截器、工具类都可能编译不过。用多版本JDK管理工具项目级指定JDK 8这是最省心的路线。2.2 Vue前端架构动态路由与权限控制的实现思路管理系统的前端难点从来不是页面多而是不同角色看到的系统不一样。销售不该看到财务的成本报表教师不该看到学员的缴费记录。这靠的不是把菜单藏起来而是路由层面的权限控制。这套源码在router目录下把路由拆成constantRoutes和asyncRoutes两份constantRoutes是登录页、404这类所有人可见的路由asyncRoutes是带meta.roles权限标记的业务路由。用户登录后前端根据用户角色动态过滤asyncRoutes再通过router.addRoutes注入到当前路由实例同时根据过滤结果动态生成左侧菜单。// src/permission.js - 全局前置守卫核心逻辑 router.beforeEach(async (to, from, next) { const token store.getters.token if (token) { if (to.path /login) { next({ path: / }) } else if (!store.getters.hasUserInfo) { const userInfo await store.dispatch(getUserInfo) const accessRoutes await store.dispatch(generateRoutes, userInfo.roles) router.addRoutes(accessRoutes) next({ ...to, replace: true }) } else { next() } } else { next(/login) } })这里有个非常隐蔽的坑动态路由注入后第一次导航可能命中不了新加的路由所以要在addRoutes之后用next({ ...to, replace: true })强制重新导航一次。不写这行刷新页面经常会白屏或者跳404排半天查不出原因。这也是vue-router动态路由被问得最多的一个问题面试和工作里都是高频。菜单权限的实现同样走这份过滤后的路由表Vuex里存一份sidebarRouters侧边栏组件递归渲染。每个路由的meta里带上title和icon二级路由自动做父子嵌套不用为每个页面单独写菜单配置。新增一个页面只要在asyncRoutes里加一条路由记录加上权限标记菜单和权限自动就有了。2.3 MyBatis持久层SQL可控与缓存策略MyBatis在这个项目里承担所有数据访问。XML映射文件放在resources/mapper下每张核心表对应一个XxxMapper.xml。为什么不全部用注解因为项目里有大量多表关联和动态条件查询XML里写动态SQL的if、where、foreach比注解里的字符串拼接直观得多而且改SQL不用重新编译Java代码。拿学员线索的分页条件查询举个例子状态、跟进人、时间范围都是可选项select idselectFollowList resultTypecom.edu.office.entity.StuStudent SELECT * FROM stu_student where if teststatus ! null AND status #{status} /if if testassignUserId ! null AND assign_user_id #{assignUserId} /if if teststartTime ! null AND created_at gt; #{startTime} /if if testendTime ! null AND created_at lt; #{endTime} /if /where ORDER BY next_follow_time ASC LIMIT #{offset}, #{pageSize} /selectwhere标签会自动处理第一个条件前面的AND不用手动拼“WHERE 11”这种丑陋写法。参数全部走#{}预编译不存在SQL注入问题。很多人喜欢用${}拼表名或排序字段那是安全红线能不用就不用。再说缓存。MyBatis一级缓存是SqlSession级别的默认开启同一个会话里多次查同一条SQL只查一次库二级缓存是namespace级别的默认关闭需要cache/标签显式开启。但这类频繁增删改的后台业务系统我强烈建议不要开二级缓存——教育办公系统数据实时性要求高今天排课明天教室冲突缓存稍微一脏就是重大事故。真要扛查询压力应该用Redis做更可控的缓存层而不是依赖MyBatis的二级缓存。所以application.yml里cache-enabled虽然是true实际上mapper XML里都没有加cache/二级缓存等于没启用这个开关保留只是为了兼容某些调试场景。课消报表这种多表统计MyBatis的优势就体现出来了。直接写原生SQL关联五张表数据库引擎做聚合比应用层循环快一两个数量级SELECT c.course_name, COUNT(DISTINCT s.id) AS student_cnt, SUM(cl.consume_hours) AS total_hours FROM crs_schedule sc JOIN crs_class c ON sc.class_id c.id JOIN stu_signup sg ON sg.class_id c.id JOIN stu_student s ON sg.student_id s.id LEFT JOIN crs_consume_log cl ON cl.signup_id sg.id WHERE sc.start_time BETWEEN #{startTime} AND #{endTime} GROUP BY c.id2.4 MySQL数据库设计核心表结构与字段约束这套系统的核心库edu_office一共30多张表按业务域可以分成四组用户权限sys_user、sys_role、sys_menu、sys_user_role、学员业务stu_student、stu_follow_log、stu_signup、教务教学crs_course、crs_class、crs_schedule、crs_consume_log、行政后勤oa_leave、oa_reimburse、oa_notice。拿学员表举例完整的建表语句是这样CREATE TABLE stu_student ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, student_no VARCHAR(32) NOT NULL UNIQUE COMMENT 学员编号, name VARCHAR(50) NOT NULL COMMENT 姓名, phone VARCHAR(20) NOT NULL COMMENT 手机号, source_channel VARCHAR(20) COMMENT 来源渠道转介绍/线上广告/地推, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1在读 2停课 3结业 4流失, assign_user_id BIGINT COMMENT 当前跟进销售ID, next_follow_time DATETIME COMMENT 下次跟进时间, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除0正常 1删除, KEY idx_phone (phone), KEY idx_status_assign (status, assign_user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学员表;设计要点学生编号设置唯一索引业务上方便线下对账phone建索引是因为按手机号查学员是高频操作组合索引(status, assign_user_id)覆盖“某个销售查看自己名下在读学员”这个高频查询场景。InnoDB是默认存储引擎支持事务和外键课消流水和财务对账必须依赖事务。utf8mb4字符集必须强调如果建表用了旧的utf8学员姓名里的生僻字就可能存不进去或者显示成乱码这是老MySQL版本最常见的坑。3. 核心业务模块设计与实现3.1 学员管理与招生线索状态驱动销售流程招生这块系统里最核心的是线索状态机。一条市场线索从进系统到成交要经历“新建 → 跟进中 → 已试听 → 已报名 → 已缴费”几个状态中间随时可能掉到“已流失”。状态流转不是随便改的后端service里做了校验比如已流失的线索不能直接改成已缴费必须重新走跟进流程。这个约束在业务上很重要否则销售为了业绩可以把流失线索直接复活数据就失真了。跟进功能的实现是写跟进日志。每条跟进记录存了下次跟进时间、跟进内容、跟进方式电话/微信/面谈销售首页会显示“今日待跟进”列表按next_follow_time排序提醒。跟进日志表stu_follow_log用student_id建索引列表查询毫秒级返回体感很流畅。这个模块还有一个容易被忽略的细节线索分配。新线索进系统时默认分配给当前负责该渠道的销售如果销售离职或者休假管理员可以批量转移线索归属。这个批量转移接口用了事务控制一次性更新几百条记录的assign_user_id中间任何一条失败就整体回滚避免出现线索被分配给两个人或者没人接手的中间状态。3.2 课程排课与课消流水冲突检测与课时扣减排课是教务模块里最容易写错逻辑的地方。排课表的基本字段是教室ID、教师ID、开始时间、结束时间、关联课程班级。新增排课时service层要做一个冲突校验同一教室、时间重叠不能排同一教师、时间重叠不能排。这个校验看起来简单写起来容易漏——一定要在SQL层面做重叠区间判断不能只查当天有没有课然后在前端判断。因为如果两个教务同时操作前端校验挡不住并发写入。判断区间重叠的SQL很经典SELECT COUNT(*) FROM crs_schedule WHERE teacher_id #{teacherId} AND #{startTime} end_time AND start_time #{endTime} AND deleted 0这条SQL的关键是重叠判定条件新课程的[start, end]与已有课程的[start, end]有交集等价于“新开始时间小于已有结束时间 且 新结束时间大于已有开始时间”。把这个条件记牢了排课冲突检测就不会写错。同理教室冲突检测只要把teacher_id换成room_id就行。课消逻辑是财务对账的基础学员报名课程后系统记录总课时每上一次课在crs_consume_log表插入一条消耗记录同时更新学员剩余课时。这一增一删必须在同一个事务里完成我用Transactional注解包住。如果事务边界没控制好就会出现课消明细有记录但剩余课时没减的问题月底财务对账哭都来不及。线上课堂这块如果机构开了直播课排课记录里可以挂一个直播地址。前端播放m3u8流媒体格式时直接引入hls.js就能在浏览器里播放不需要额外安装插件或者依赖特定播放器。这个方案对机构来说成本低运维也省事。3.3 审批流与办公协同状态机的通用设计行政办公模块的审批请假、报销、采购本质是同一套状态机草稿 → 提交 → 审批中 → 通过/驳回。这套源码没有为每种审批单独写一套代码而是抽象了oa_form通用表加类型字段表单内容用JSON存审批动作走统一的approve接口。这样新增一个审批类型不需要改表结构只加一条配置就行。这是一个非常值得借鉴的设计思路。CREATE TABLE oa_form ( id BIGINT PRIMARY KEY AUTO_INCREMENT, form_type VARCHAR(30) NOT NULL COMMENT 类型leave/reimburse/purchase, applicant_id BIGINT NOT NULL COMMENT 申请人, status TINYINT NOT NULL DEFAULT 0 COMMENT 0草稿 1审批中 2通过 3驳回, form_content JSON COMMENT 表单内容JSON, current_approver_id BIGINT COMMENT 当前审批人, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT审批单通用表;审批流权限上区分了普通员工和管理员普通员工提交申请后只能看到自己的单子部门负责人能看到本部门全部单子财务角色看报销单时能看到金额字段其他人隐藏。数据权限不是只在前端做菜单隐藏后端service查询时自动拼接部门过滤条件。用一个DataScopeAspect切面根据当前登录人的部门ID自动注入查询条件防止越权查看别的部门数据。管理系统里这种接口级的数据权限校验比单纯的前端控制要可靠得多。驳回和撤回是审批模块最容易忽略的边界。设计上规定审批中且当前审批人非本人不能撤回已驳回的单子重新编辑后走重新提交状态从草稿重新开始审批通过的单子不允许修改表单内容只能走红冲反审批流程。这些规则都是在状态机里写死的避免业务上扯皮。3.4 报表统计MySQL聚合查询的正确姿势管理系统到最后老板看的一定是报表。这套系统里报表模块有三张核心统计招生漏斗线索→试听→报名→缴费每一层的转化率、课消统计按校区/课程/月度分组统计消耗课时和剩余课时、财务汇总应收、实收、退款、课时费消耗收入。招生漏斗的数据用一条聚合SQL就能算出来SELECT SUM(CASE WHEN status 2 THEN 1 ELSE 0 END) AS followed_cnt, SUM(CASE WHEN status 3 THEN 1 ELSE 0 END) AS trial_cnt, SUM(CASE WHEN status 4 THEN 1 ELSE 0 END) AS signup_cnt, SUM(CASE WHEN status 5 THEN 1 ELSE 0 END) AS paid_cnt FROM stu_student WHERE source_channel online_ad AND created_at 2025-01-01报表SQL能用聚合函数就别在Java里做二次计算数据库引擎做count比应用层循环快一两个数量级。写报表查询的经验先在EXPLAIN里看有没有走索引再核对聚合字段的数据类型比如count返回的是BIGINTJava里接Long而不是Integer小了会溢出。报表模块的查询条件统一封装成ReportQuery对象时间范围、校区、课程类型、渠道来源都是可选参数后端用PageHelper做分页。报表数据量大时最有效的优化是“预处理”每天晚上定时任务把当天的课消和缴费数据按维度汇总到报表中间表白天老板看报表直接查中间表毫秒级返回不用每次都去扫流水明细表。4. 从零搭建到部署上线4.1 环境准备JDK、Maven、Node、MySQL版本怎么搭先列一套可以拍胸脯说跑得通的组合组件推荐版本说明JDK1.8生产环境兼容性最好的版本机构服务器普遍在用Maven3.6.x与SpringBoot 2.7.x配合稳定Node.js14.x / 16.x对应Vue CLI 4/5的依赖要求MySQL5.7或8.0两版都行驱动版本记得对应调整IDEA2022开发工具社区版也够用MySQL 5.7在Windows上的安装建议直接下载mysqld压缩包手动初始化别用安装版安装版经常在服务注册和权限初始化上出幺蛾子。手动初始化流程其实就三步解压、执行mysqld --initialize-insecure生成data目录和免密root、执行mysqld --install注册成Windows服务。整个过程五分钟搞定比安装版可控得多。MySQL 8.0的安装类似但驱动必须用com.mysql.cj.jdbc.Driver连接串里要加serverTimezone参数这块下面排查章节细说。Node版本这块同样有讲究。Vue CLI 5要求Node 12以上但Node 18、20这些新版本装依赖时经常报OpenSSL错误最省事的就是用nvm切到14或16装完依赖再切回来也不影响。4.2 导入数据库与修改配置把sql目录下两个脚本按顺序导入。先在MySQL里创建库再导数据避免脚本里没有CREATE DATABASE语句时傻眼mysql -uroot -p -e CREATE DATABASE edu_office DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p edu_office edu_office_init.sql mysql -uroot -p edu_office edu_office_sample.sql导入完成后验证一下select count(*) from sys_user;能查到初始账号select count(*) from stu_student;能查到示例学员说明导入成功。接下来改application.yml里的数据库账号密码。密码里如果包含特殊字符比如、#、$直接拼在JDBC连接串里会被解析成连接参数大概率连不上。正确做法是放进spring.datasource.password里让框架处理别往url里塞。4.3 后端启动IDEA里配端口和启动类用IDEA打开backend/edu-admin目录等Maven把依赖拉完。找到主启动类EduApplication右键Run。如果默认端口不是想要的不用改代码在IDEA的Run Configuration里加VM options-Dserver.port8081或者直接改application.yml。框架读取配置的优先级是VM options大于配置文件想知道当前生效的是哪个端口看启动日志里的Tomcat started on port(s): 8080那行最靠谱。启动过程常见报错端口被占用改端口或者杀掉占用进程数据库连接失败多半是密码错了或者MySQL服务没起来Mapper找不到检查target目录下有没有把XML复制出来Maven有时会漏打包resources里的mapper文件需要在pom.xml的build里加上resources resource directorysrc/main/resources/directory includes include**/*.xml/include /includes /resource /resources4.4 前端构建与跨域联调前端工程在frontend/edu-web命令npm install npm run serve默认跑在8080端口和后端8080冲突所以前端开发服务器一般是8081。开发模式下跨域问题通过vue.config.js里的devServer.proxy解决devServer: { port: 8081, proxy: { /edu-api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求/edu-api/xxx会自动转发到后端浏览器里看不到跨域报错。有个小坑changeOrigin必须设为true不然后端拿到的请求头里Host还是8081某些场景下会出现奇怪的会话问题。生产环境不需要这个proxy改成Nginx反代即可。4.5 部署上线两种方式选哪个方式一前后端分离部署jar包加Nginx。后端jar包用nohup java -jar edu-admin.jar 启动Nginx配置把/edu-api反代到8080端口/指向前端dist目录server { listen 80; server_name edu.example.com; root /data/www/edu-web/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /edu-api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }方式二Vue打包后塞进SpringBoot。npm run build生成dist把dist里全部文件拷到后端src/main/resources/static目录重新打包成jar。这样只跑一个进程、只占一个端口部署运维最省事适合机构内部用、服务器资源紧张的场景。缺点也明显前后端耦合了前端一更新就要重新打jar重启后端。我的建议是如果只是给机构内部二三十个人用方式二最省心如果后续要做线上对外业务老老实实上方式一。5. 常见问题与排查技巧实录5.1 数据库层的幺蛾子问题1启动报Failed to configure a DataSource。检查application.yml里spring.datasource的配置有没有生效最常见是IDE里启动时激活的profile不对配置写到了application-dev.yml但启动时没指定dev。SpringBoot的配置加载机制是约定大于配置没指定profile时只加载默认的application.yml。问题2MySQL 8.0连不上报Public Key Retrieval is not allowed。驱动是8.x时连接串里要加allowPublicKeyRetrievaltrueuseSSLfalse这是MySQL 8默认加密连接导致的。前面配置里已经写上了直接抄就行。问题3中文乱码。检查三处数据库表字符集是不是utf8mb4、JDBC连接串有没有characterEncodingutf8、操作系统文件编码是不是UTF-8。三处全对了基本不会乱。5.2 MyBatis层的经典坑坑1查出来全是null。90%是驼峰映射没开。mybatis.configuration.map-underscore-to-camel-case: true加上这行世界安静。坑2动态SQL的and多了或者少了。用where标签包住动态条件它会智能处理第一个条件前的AND/OR。if里也别写“and xxx 1”这种硬编码条件不成立时SQL最后会多个裸and直接报语法错误。坑3分页查询排序字段拼接。orderBy字段如果来自前端参数一定要做白名单校验只允许传表里真实存在的列名否则就有注入风险。教育系统里虽然不涉及核心机密但该防的还是要防这是基本功。5.3 前端联调与构建问题问题1登录后刷新页面404。动态路由注入后没做重新导航代码里加next({ ...to, replace: true })解决。另外Nginx部署时也要配try_files $uri $uri/ /index.html;否则history模式刷新就404这是前端路由部署的通病。问题2npm install卡死或者报ERESOLVE。多半是node版本太新或太旧Vue CLI项目建议nvm切到14/16再装。如果报ERESOLVE unable to resolve dependency tree可以试试npm install --legacy-peer-deps。问题3接口能访问但页面数据不出来。打开浏览器F12看Network面板如果响应是401/403检查请求头里Authorization带没带token本地调试可以看Vuex里token有没有正确写入。前后端联调九成问题是token和跨域这两大件排查完再看看数据权限配置。5.4 版本与性能的提醒最后提醒一个容易被忽视的点SpringBoot版本不要盲目升。这个源码是2.7.x如果你本地电脑装的是JDK 17或者习惯用SpringBoot 3.x直接把依赖升上去会出现组件不兼容。建议用多版本JDK管理工具项目级指定JDK 8别动框架版本。真要用3.x那意味着整个MyBatis集成、Spring Security配置、部分API都要跟着升级测试工程量不小。性能方面这种管理系统的瓶颈基本不在代码而在数据库索引。把慢查询日志打开看到执行时间超过1秒的SQL拉出来EXPLAIN一下缺索引补索引少写SELECT *分页查询加LIMIT性能基本就够了。几十个并发的管理后台优化手段不需要什么分布式缓存、读写分离那是杀鸡用牛刀。写到这里这套系统的代码逻辑和部署流程就都过了一遍。我个人做教育行业管理系统最大的体会是技术栈永远不是瓶颈难点在业务建模。你这张表把谁作为主键、状态流转谁允许谁不允许、课消和财务的勾稽关系怎么设计这些想清楚了写代码只是时间问题。这套源码里踩过的坑都写进去了希望看到这篇的朋友能少走几步弯路。如果照着跑起来遇到问题或者在自己机构场景里发现了新的边界情况可以拿出来交流。这类系统迭代到后面才是真正有意思的阶段比如加一个学员端小程序、接入在线支付、做自动排课算法这些都是很好的扩展方向。