ARTICLE DETAIL

资讯详情

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

Spring Boot家教管理系统全栈实战:数据库设计、JWT鉴权与部署

Spring Boot家教管理系统全栈实战:数据库设计、JWT鉴权与部署 春招那会儿我接过不少私活其中很大一类就是帮本科生把毕业设计从能跑打磨到能答辩。做得最多的就是Spring Boot管理系统什么图书借阅、宿舍管理、校园考勤换一层业务壳子骨架都大同小异。但说实话基于Spring Boot家教管理系统的设计与实现这类题目是我觉得最有业务味道、也最能体现设计能力的一个选题。它不像图书管理那样只对着CRUD猛怼里面既有用户角色划分、又有订单和课时结算这种完整流程还牵扯到权限控制和时间冲突校验。今天我就以这个题目为例子把我做这类项目的完整思路、表结构设计、后端落地、前端联调、以及那些文档里绝对不会写的坑一次性讲清楚。这篇文章适合谁适合正在做家教管理系统毕设的同学适合想系统地走一遍Spring Boot全栈项目的初学者也适合接单帮别人做毕设的开发同行。我会按照真实项目的推进顺序来写从选题理由到数据库设计从JWT鉴权到定时任务每一段都是实操后的总结不是那种只给截图不给代码的教程。1. 为什么我推荐用Spring Boot做家教管理系统选题与技术选型的真实考量1.1 家教业务的核心痛点信息不对称与流程管理先聊一个最基础的问题家教管理系统到底要解决什么如果你只是把它当成一个增删改查的练习那答辩的时候很容易被问住。家教行业本质上是一个撮合平台牵涉到的角色至少有四类家长、学生、家教老师、平台管理员。传统模式下大家靠微信群和电话沟通信息极度分散上课时间靠口头约定课时费算不清楚老师放鸽子了家长也没处投诉。所以一个合格的家教管理系统至少要管住三件事谁来教、谁来学、怎么结算。老师和学生的匹配关系、课程的时间安排、课后的评价与课时确认这三条线是最核心的。你去看很多高分毕设论文功能列表拉得很长但业务主线也就是这三条。想明白这一点你在做需求分析的时候就不会跑偏。这也是为什么我不建议把题目改成家教信息发布系统或者家教预约平台——管理这两个字意味着你要做的不仅仅是一个信息墙而是一套有状态流转、有角色权限、有业务流程的后台系统这恰恰是Spring Boot最擅长的领域。1.2 技术选型对比Spring Boot、SSM、SSH该怎么选很多同学在开题的时候会纠结学校老师让用SSMSpring SpringMVC MyBatis我看网上全是Spring Boot的教程到底选哪个我的建议很直接优先Spring Boot除非导师硬性规定SSM。理由有三个第一Spring Boot的自动配置机制帮你省掉了大量XML配置尤其是数据源、事务管理器、拦截器这些东西以前在SSM里要手写一堆配置文件Spring Boot通过starter依赖和application.yml几行配置全搞定了。这不是偷懒而是把精力腾出来放在业务逻辑上。第二招聘市场和开源社区早就倒向Spring Boot了你写简历的时候精通Spring Boot比熟悉SSM有说服力得多。哪怕你只是做毕设用Spring Boot也是给自己攒经验。第三Spring Boot 2.x和3.x选谁的问题。我的建议是选2.7.x。为什么因为3.x要求JDK 17起步而且很多第三方starter的兼容性还停留在2.x时代。你如果是为了稳定把毕设做完JDK 8 Spring Boot 2.7.x MyBatis-Plus这套组合最稳踩坑最少。当然如果你的导师要求新那Spring Boot 3.2 JDK 17也可以但你要做好某些老教程代码不能直接抄的心理准备。2. 需求边界画清楚后面写代码才不拧巴2.1 四类角色的权限划分管理系统的核心思想就是不同人看到不同的东西。家教系统里我通常设置四种角色权限从上到下是角色核心操作范围特殊能力管理员全站用户管理、科目管理、订单总览强制下架违规老师、处理投诉家长发布需求、搜索老师、发起预约对已完成的课时进行确认/评价家教老师发布可授课程、查看预约、提交课时上传资质证明、维护可授课时间学生查看课程、提交试听申请绑定在家长账号下这里有个细节很多人不注意学生和家长在业务上经常是分离的。小学生上课是家长付费、家长选老师但真正上课的是孩子。所以在数据模型里学生信息应该独立建表家长账号通过student_id关联到孩子。如果你偷懒只做一个用户表角色字段后面做家长查看孩子的上课记录这个功能时就会非常别扭。2.2 核心业务流从预约到授课到结算我建议你把业务流程先画出来不是画给答辩看是画给自己写代码时理清头绪。家教系统的核心链路通常长这样家长搜索老师 → 按科目、区域、价格筛选家长发起预约 → 提交期望的上课时间老师确认/拒绝 → 确认后生成课程订单实际上课完成 → 老师提交课时记录家长确认课时 → 系统自动累计已确认课时结算阶段 → 家长充值/支付老师申请提现这条链路最大的特点是存在状态机。预约订单的状态至少要有待确认、已确认、已拒绝、进行中、已完成、已取消。数据库里最好用一个status字段专门存这个状态而不是通过删记录来表示取消。别小看这个设计答辩老师最喜欢问的就是订单取消后数据怎么处理你在表里保留状态字段直接就能答上来。2.3 容易被忽视的非功能需求很多同学做毕设只盯着功能清单结果答辩的时候被一句你这个系统并发怎么样问懵。我不主张你为了毕设去搞微服务、消息队列这种重武器但有几个非功能需求你要心里有数参数校验手机号格式、价格区间、时间格式后端的Validated注解不能省别把校验全丢给前端。日志记录操作日志表要建至少记录谁在什么时间做了什么操作关键业务操作如取消订单必须留痕。异常处理全局异常处理器要写不然前端动不动就拿到一个带堆栈的500页面体验太差。这些不是加分项而是基本素养。答辩老师可能不会逐行看代码但如果你能主动说一句我用拦截器做了JWT登录校验用全局异常处理器统一了返回格式印象分直接上一个档次。3. 数据库设计家教系统的地基怎么打3.1 核心表结构用户、课程、订单、课时记录数据库设计这一个章节在论文里能写十几页但落到具体表结构我建议你抓住五张主表和几张辅助表user表用户表CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(255) NOT NULL COMMENT BCrypt加密后的密码, nickname varchar(50) DEFAULT NULL, phone varchar(20) DEFAULT NULL, role tinyint(4) NOT NULL COMMENT 1-管理员 2-家长 3-老师 4-学生, status tinyint(4) DEFAULT 1 COMMENT 1-正常 0-禁用, create_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;teacher_profile表老师信息扩展表因为老师有科目、教学年限、认证状态、课时费等维度单独拆一张表更清爽用user_id关联。course_order表预约/课程订单表CREATE TABLE course_order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, teacher_id bigint(20) NOT NULL, student_id bigint(20) NOT NULL, parent_id bigint(20) DEFAULT NULL, subject_id bigint(20) NOT NULL COMMENT 科目, start_time datetime NOT NULL COMMENT 上课开始时间, end_time datetime NOT NULL COMMENT 上课结束时间, duration int(11) DEFAULT 2 COMMENT 课时长(小时), unit_price decimal(10,2) NOT NULL COMMENT 每小时单价, total_amount decimal(10,2) NOT NULL COMMENT 总价, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0-待确认 1-已确认 2-已拒绝 3-进行中 4-已完成 5-已取消, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;lesson_record表课时记录表老师上完课之后提交家长确认后这个状态才变成已完成这个表是为结算服务的。这几张表之间的外键关系我不建议真的在数据库里加物理外键逻辑外键就够了。理由是MyBatis-Plus做联表查询的时候物理外键反而容易造成麻烦而且如果老师被删除、但订单还要保留历史记录物理外键会挡住这种逻辑删除的需求。3.2 字段设计上的几个细节决策这里分享几个容易踩坑的点第一价格永远用DECIMAL不要用FLOAT或DOUBLE。浮点数在计算机里是近似存储0.1 0.2可能等于0.30000000000000004。金额字段一旦出现精度问题结算那一步就全乱了。DECIMAL(10,2)足够应付单价和总价。第二时间字段建议直接存datetime不要存字符串。有人图方便把上课时间存成2025-06-01 14:00这种字符串后面如果要查某个时间段有哪些老师在教或者做时间冲突校验字符串比较会把你折腾疯。MySQL的datetime字段配合BETWEEN语句比字符串好用一百倍。第三逻辑删除比物理删除安全。用户误操作删除了一条课时记录如果物理删了数据就没了。我一般在每张业务表加一个deleted字段0/1查询的时候默认过滤掉deleted1的记录。MyBatis-Plus里用TableLogic注解就能自动实现所以这个成本极低。第四订单号要单独生成不要用自增ID当订单号。自增ID暴露了业务量而且用户看到订单号是58这种会有种不正规感。你可以用时间戳随机数生成订单号比如yyyyMMddHHmmss 两位随机数或者直接用IdWorker.getIdStr()MyBatis-Plus自带雪花算法实现一行代码搞定。4. Spring Boot后端落地骨架搭建与关键接口实现4.1 目录分层controller-service-mapper的职责边界后端工程的包结构我建议按模块化来分而不是按层级分。什么概念按层级分是controller包下放所有Controllerservice包下放所有Service按模块分是order包下同时有OrderController、OrderService、OrderMapper。模块化的好处是改动一个功能时你只需要在一个包结构内跳转代码阅读成本低很多。推荐包结构com.example.tutor ├── common // 通用类返回结果、异常处理、常量 │ ├── Result.java │ ├── GlobalExceptionHandler.java │ └── BusinessException.java ├── config // 配置类拦截器、跨域、密码加密器 ├── security // JWT相关TokenUtil、JwtInterceptor ├── user // 用户模块 │ ├── UserController.java │ ├── UserService.java │ ├── UserMapper.java │ └── entity/User.java ├── order // 订单模块 └── lesson // 课时模块这里一定要记住Controller只做参数接收和结果返回不写业务逻辑。我见过太多人把数据库更新语句直接写在Controller里当时写起来爽后面调试的时候想死。Service层是核心业务逻辑全部放Service里Controller保持薄薄一层。4.2 登录认证JWT方案在单体应用中的实现家教系统里没有任何一个接口可以裸奔放行除了登录和注册。我的做法是用JWT做无状态认证全套流程是用户登录后端校验用户名密码密码用BCryptPasswordEncoder校验。校验通过后生成Token把用户ID、角色封装进Token的claims里。前端把Token存到localStorage每次请求在Authorization头带上。后端写一个拦截器拦截非白名单路径解析Token并放行。核心代码大致是这样Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login, /api/auth/register, /api/subject/list); } }public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new BusinessException(未登录或登录已过期); } // 解析Token如果签名不对或过期会抛异常 Claims claims JwtUtil.parseToken(token.replace(Bearer , )); request.setAttribute(userId, claims.get(userId)); request.setAttribute(userRole, claims.get(role)); return true; } }这里有个容易踩的坑拦截器抛异常全局异常处理器接得住吗需要注意拦截器preHandle里抛出的异常如果你是注册在WebMvcConfigurer里全局异常处理器是能接到的但如果你用HandlerInterceptor实现类里的逻辑抛了异常而你的异常处理器没有配置对前端会收到乱码。解决办法是确保ControllerAdvice生效并且自定义异常类里别吞掉堆栈信息。Token如果要实现用户被禁用后立刻失效光靠JWT是做不到的因为JWT是无状态的。我提供两个方案一是把Token有效期设短一些比如2小时配合前端重新登录机制二是在user表加一个token_version字段每次登录1JWT里存这个版本号服务端校验时比对版本号。方案二在毕设里属于亮点功能答辩可以主动讲。4.3 业务接口设计的几个原则有几个接口设计的原则我写代码的时候一直在用原则一接口返回结构要统一。所有接口都返回ResultT里面包含code、message、data三个字段。成功时code200失败时code500或者业务码。前端拿到这个结构统一处理不需要每个接口单独写异常逻辑。原则二列表查询千万别只做一个findAll。分页查询是标配前端传pageNum和pageSize后端用MyBatis-Plus的Page对象一行代码就能搞定分页public PageCourseOrder getOrderPage(Long userId, int pageNum, int pageSize) { LambdaQueryWrapperCourseOrder wrapper new LambdaQueryWrapper(); wrapper.eq(userId ! null, CourseOrder::getParentId, userId) .orderByDesc(CourseOrder::getCreateTime); return orderMapper.selectPage(new Page(pageNum, pageSize), wrapper); }LambdaQueryWrapper这个类强烈推荐使用它用lambda表达式引用列名编译期就能查出字段名错误比写字符串parent_id安全得多。原则三修改和删除要加是本人操作校验。比如老师想取消一个订单你得先查出订单里的teacher_id是不是当前登录用户不是就拒绝。这个逻辑虽然简单但很多初学者会忘这是数据安全的基本底线。5. 前端联调与打包Vue Spring Boot的协同细节5.1 接口约定与联调环境家教管理系统的前端我一般用Vue 3 Element Plus理由不用多说组件成熟、文档丰富、招聘要求里也常出现。前后端分离开发时最大的问题是跨域。Spring Boot后端解决跨域很简单写一个配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8081) // 前端地址 .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }如果你用了Shiro或者Spring Security跨域配置要放行预检请求OPTIONS不然浏览器先发个预检请求被拦截你后面真正的请求压根发不出去。前后端接口字段的命名也要提前约定好。我习惯后端返回驼峰命名比如createTime前端直接用不做转换。MySQL里的create_time通过MyBatis-Plus的驼峰映射自动转成createTime所以Java实体类里也是驼峰三层命名一致省去很多烦恼。5.2 Vue打包放进Spring Boot的坑与处理很多同学做完前后端分离老师却说我要一个能直接跑的东西这时候就需要把Vue打包后放到Spring Boot里打成单jar运行。步骤很简单在Vue项目根目录执行npm run build生成dist目录。把dist里的文件复制到Spring Boot的src/main/resources/static目录下。重新打包Spring Bootjava -jar启动后直接访问http://localhost:8080/就能看到前端页面。但这里有几个坑几乎必踩坑一Vue路由的history模式会导致刷新404。如果你用history模式路由访问/order/list刷新页面时Spring Boot会把请求交给DispatcherServlet找不到对应的/order/list接口就会返回404。解决办法是在后端加一个路由转发规则Controller public class RouteController { RequestMapping(value /{path:[^\\.]*}) public String forward() { return forward:/index.html; } }这个正则[^\\.]*的意思是路径里不含点号就转发到index.html这样既能放行静态资源js/css带点号又能解决前端路由404。这个配置在毕设答辩里是一个很能加分的点因为大部分同学根本不会遇到这个问题。坑二前端请求的BaseURL要跟部署环境适配。开发时前端代理指向http://localhost:8080打包之后前端页面和后端同源了如果代码里还硬编码了http://localhost:8080部署到服务器上就废了。我的做法是在Vue里用环境变量区分// .env.development VITE_API_BASE_URL /api // .env.production VITE_API_BASE_URL /api开发时配置Vite代理转发生产时因为同源所以不需要代理。这样代码里就统一写axios.post(${baseUrl}/order/create)不会出现环境切换的麻烦。坑三文件上传路径别用相对路径。家教老师上传资质证明教师资格证照片很多同学会把文件保存到static/upload/目录下这在本地跑好好的部署到服务器上就出问题。更好的做法是配置一个绝对路径的上传目录通过application.yml里的file.upload-path配置然后用一个虚拟路径映射到它Configuration public class FileConfig implements WebMvcConfigurer { Value(${file.upload-path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }这样前端访问/upload/xxx.jpg就能拿到图片而文件本体存在服务器某个固定目录不会因为重新打包jar而丢失。6. 实测中踩过的坑与优化建议6.1 Spring Boot版本和依赖兼容性问题这几个月我帮人搭建环境时遇到最多的报错九成出在版本上。Spring Boot 2.7.x对应MyBatis-Plus的mybatis-plus-boot-starter要选3.5.x但如果你用Spring Boot 3.x要求MyBatis-Plus 3.5.3以上并且包名会变从com.baomidou变到com.github.pagehelper这种边边角角的兼容问题则更隐蔽网上老教程的代码直接复制过来多半跑不起来。我的建议是做毕设就锁死一套经过验证的组合别追求最新。推荐组合如下组件推荐版本JDK1.8或11Spring Boot2.7.18MyBatis-Plus3.5.3.1MySQL驱动8.0.33Hutool5.8.xJJWT0.11.5Hutool这个工具库我强烈推荐里面封装了日期处理、加密、文件上传、验证码生成等等常用方法能少做很多轮子。JJWT是标准的Java JWT库0.11.5版本对JDK 8友好别去用io.jsonwebtoken:jjwt的旧坐标那是另一个坑。6.2 定时任务的实现与注意事项家教系统里天然需要定时任务比如课时开始前2小时给老师发提醒订单超过24小时未确认自动取消月底生成账单。Spring Boot的定时任务用Scheduled注解就够Component public class OrderAutoCloseTask { Scheduled(cron 0 0 * * * ?) // 每小时执行一次 public void closeExpiredOrders() { // 查询状态为待确认、创建时间超过24小时的订单批量改为已取消 } }但凡是定时任务就避不开几个细节一是cron表达式别写错。Spring Boot的cron是6段有时7段秒 分 时 日 月 周网上抄的表达式多半是Quartz的7段跑不起来别慌把首位去掉试试。二是定时任务默认是单线程串行执行的。如果你有多个定时任务默认情况下它们是排队完成的如果某个任务执行时间过长会卡住后面的任务。要并发执行就配置一个TaskScheduler并设置线程池大小。这个点回答系统如何优化性能时可以作为一个小亮点。三是线上环境里定时任务要关注幂等性。比如自动取消超时订单这个任务如果执行到一半服务器重启了重启后任务会重新执行你怎么保证不会重复取消我的办法是任务执行前判断订单当前状态只有在待确认状态才执行取消已经取消的跳过天然幂等。6.3 上线前要做的安全加固毕设虽然很多只需要在本地演示但如果你想把项目放到服务器上给老师远程看以下安全措施一个都别省密码加密禁止明文存密码BCryptPasswordEncoder起步这个Spring Security自带不需要引入全套Security框架单独引入spring-security-crypto依赖即可。SQL注入防堵MyBatis-Plus的LambdaQueryWrapper参数绑定天然防注入但如果是手写XML里的${}传参就要小心能用#{}就别用${}。越权访问防护JWT里存了role但后端不能只看前端传的role每个角色接口的鉴权必须依赖拦截器里从Token解析出的角色不能信任前端请求体里的字段。限制敏感接口的请求频率比如发送短信验证码、登录接口这种加个简单的Redis计数器限制频率。如果项目还没引入Redis也可以先用一个ConcurrentHashMap手动计数兜底差距区别只在分布式环境上。7. 写在最后的项目落地个人体会这个家教管理系统从建表到全部功能跑通我前后花了大概四天。如果只是为自己做三天就够剩下一天全部消耗在环境和联调上了。给我的最大感受是写业务系统的核心能力不是把代码敲出来而是把业务关系理清楚——学生要不要单独建表订单状态怎么流转课时确认之后钱怎么算这些问题想清楚了Spring Boot只是一个顺手的工具代码写起来非常快。但如果你一上来就闷头建工程写CRUD后面改表的代价会教你做人。另一个实操建议是从中间表入手写代码不要从头表写起。我每次都是先做订单表和课时记录表因为这两张表关联了其他所有表把它们跑通了用户表和科目表怎么接都顺理成章。最后留一个可以当答辩加分项/扩展方向的小提示家教系统的推荐逻辑其实特别适合做简单化处理比如同科目优先、同区域优先、价格从低到高排序就可以用一句SQL完成如果你想再深入一步还可以引入用户评价分作为排序权重这就是一个轻量级协同过滤的雏形。这句话你写在论文结论部分比空谈未来可以接入大数据分析要具体得多也更能体现你真的想过系统演进的方向。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表