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

资讯详情

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

Spring Boot校园讲座管理系统:全流程设计与实现

Spring Boot校园讲座管理系统:全流程设计与实现 办一场校园讲座最麻烦的往往不是准备内容而是把通知发布—在线报名—现场签到—事后统计这条链路管明白。我在帮学校信息中心做内部工具时体会特别深讲座信息散落在各院系群和Excel表格里学生要么错过报名要么到场后排长队签到老师事后统计到场人数还得手工翻表格。后来我基于Spring Boot把校园讲座管理系统完整搭了一遍所有流程收口到一个后台讲座发布、审核、报名、签到、评价、数据统计全部打通源码也整理成了可运行的完整工程。如果你正在找Java课程设计、毕业设计参考或者想搞明白一个Spring Boot项目从零到能部署的完整套路这篇博文应该能帮你少走不少弯路。1. 项目整体设计与技术选型1.1 核心需求拆解一场讲座背后的完整业务链条表面上这是一个CRUD项目但真动手前我习惯先把业务链路拆清楚。一场讲座从发起到结束大约要经历七个环节讲座发起、内容审核、对外发布、学生报名、现场开讲、到场签到、事后评价与统计。每个环节对应不同角色的诉求主办方关心讲座有没有被审核通过、报名人数有没有满学生关心最近有什么讲座、还剩多少名额管理员关心哪个分类的讲座办得多、到场率怎么样。这个系统设计的第一原则是面向流程而不是面向表单。换句话说不能只做一张讲座表的增删改查而要让每条讲座数据都有清晰的状态。比如一个讲座可以是草稿、待审核、已发布、已结束这样前端才能根据状态去展示不同的按钮后端也才能在报名时判断这个讲座现在能不能报名。很多新手一做管理系统就陷入写死一堆接口的误区把精力全花在页面字段上忽略了数据流转结果项目看上去功能很多实际上经不起追问。所以我在需求阶段定了几个更具体的目标支持多角色登录管理员、教师、学生讲座信息维护有审核环节报名要能防重复和防超员签到要在报名基础上完成核销统计要能按分类和时间维度聚合。这些目标组合起来项目才真正有了管理系统的骨架。1.2 技术栈选型为什么是Spring Boot MyBatis Plus而不是其他组合技术选型这块我直接选了Spring Boot作为主框架没犹豫。原因是校园讲座管理系统这种体量的Java Web项目用Spring Boot最合适。它天然内置了Tomcat、Starter依赖、自动配置开发时不用像传统SSH那样花大量时间拼XML配置Maven中央仓库拉依赖也简单。更现实的一点是这个项目在课程设计和毕业设计里很常见Spring Boot体系的资料多、坑少后续扩展和答辩提问都更容易应对。持久层我用了MyBatis Plus。有人会问为什么不用Spring Data JPA我承认JPA在单表操作上更省事但校园管理类系统通常要写一些复杂的统计SQL比如按月份聚合报名人数、按分类统计讲座场次MyBatis Plus在SQL控制上更直接。它的BaseMapper和LambdaQueryWrapper能处理掉大约七成的简单CRUDComplexSQL自己写XML也不复杂既有省事的封装又保留了手写SQL的能力。认证方案选了Spring Security JWT。简单说Spring Security负责拦截请求和角色鉴权JWT负责在无状态接口之间传递登录身份。校园讲座系统是典型的前后端分离场景前端Vue或者简单页面调用后端接口如果沿用传统Session在各个服务器间同步麻烦且不适合横向扩展。用JWT后用户登录一次拿一个Token后续每次请求带上后端验签就行。Redis在这里我做了可选配置如果讲座列表经常被访问可以用Redis做热点缓存不加也不影响核心业务初期部署更简单。1.3 功能模块规划一个管理系统该有的完整闭环模块划分直接决定开发节奏。我把系统拆成七个功能模块每个模块之间保持低耦合用户与权限模块登录、注册管理员维护用户信息角色区分管理员、教师、学生讲座分类模块分类的增删改查讲座创建时必选分类讲座信息模块讲座的草稿、提交审核、审核通过/驳回、讲座详情维护报名管理模块学生在线报名、取消报名、防重复、防超员签到管理模块主办方确认到场记录签到状态评价反馈模块讲座结束后学生打分和留言数据统计模块讲座场次、报名趋势、分类占比、到场率核心指标这里想多说一句报名和签到我刻意拆成两个模块但数据上都挂在报名记录这一条链上。签到本质是报名资格的核销不需要单独建表在报名记录上加一个到场状态字段即可既省表又直观。很多实战项目都喜欢多建表结果查询时要join一堆维护成本陡增。能用字段表达的状态就别用表去表达。2. 核心细节解析与实操要点2.1 数据模型设计从字段到表关系的取舍数据库设计是这类系统最见基本功的地方。我梳理了一下核心就五张表用户表sys_user、讲座分类表lecture_category、讲座表lecture_info、报名记录表lecture_registration、评价表lecture_feedback。sys_user表字段不多id、username、password、real_name、role、phone、email、create_time。密码这里必须存BCrypt加密后的值千万别用明文。角色字段用字符串ADMIN、TEACHER、STUDENT表示简单可控不用为三个角色建复杂的RBAC表。lecture_info是重点表。核心字段是title、speaker、category_id、start_time、location、capacity、enrolled_count、status、audit_status、publisher_id。我在这里把status和audit_status分开设计status表示业务状态草稿0/已发布1/已结束2audit_status表示审核状态未提交0/待审核1/通过2/驳回3。为什么要分开因为审核通过和讲座已结束描述的不是同一个维度混在一个字段里会导致后续查询状态时逻辑混乱。这是我在真实项目里踩过坑之后总结出来的能用两个独立字段表达的状态不要合并成一个数字假装节省字段。lecture_registration表是整个系统防重和签到的关键。字段就是id、lecture_id、user_id、register_time、attend_status。这里有一个非常重要的细节要给(lecture_id, user_id)加联合唯一约束。这样即使前端并发点了两次报名数据库层也能兜住不会出现一条重复报名记录。签到用attend_status区分0未签到1已签到。lecture_category表就简单了id、name、sort最多再加个备注。评价表lecture_feedback记录lecture_id、user_id、rating、comment同样加唯一约束保证一个学生只能对同一场讲座评价一次。2.2 权限体系与接口设计规范权限设计如果不做控制学生调一个管理员接口就能删讲座项目直接崩盘。我按照角色划分了三层权限角色能做什么典型接口管理员审核讲座、管理用户、查看全部统计、管理分类/api/admin/**教师发布讲座、编辑自己的讲座、查看自己讲座报名名单、发起签到/api/lectures/**部分学生浏览讲座、报名/取消、签到、提交评价/api/student/**统一接口前缀的方式让我在后端配置Spring Security时非常轻松/api/auth/**路径放行登录注册/api/admin/**要求ADMIN角色其余接口需要登录即可。Spring Security的URL匹配顺序要注意更具体的规则写在前面比如/api/admin/lectures/**要先于/api/lectures/**匹配否则角色校验会被提前放行或者被错误拦截。统一返回结构是我做这个项目时给自己定的规范。所有接口一律返回{ code: 200, message: success, data: ... }这样的格式前端只需要封装一个request.js处理code即可。如果返回结构不统一前端处理每个接口都要单独判断混乱程度会直线上升。这个规范应该在写第一个接口前就定好写完后统一用枚举类定义错误码不要随手写数字。2.3 讲座状态机与报名防重最容易翻车的两个业务细节整个项目里真正的业务难点不在CRUD而在两个状态控制逻辑上。第一个是讲座状态机。讲座从创建到可报名要经历教师保存草稿status0→ 提交审核audit_status1→ 管理员审核通过audit_status2, status1→ 讲座结束status2。驳回时回到草稿态教师修改后可重新提交。这个状态流转用if-else去写极其容易漏条件我的经验是先把状态转换图在草稿纸上画出来再用枚举判断。报名接口必须检查audit_status 2 status 1二者缺一不可。你想想一个被驳回的讲座如果没检查审核状态就开放报名学生报了名主讲人还不知情那真是事故了。第二个是并发报名防重。校园讲座热门场次经常一两分钟就爆满学生同时点击报名后端如果是查询人数→判断是否超员→插入记录的三步走极有可能出现超卖。我采用的做法是两步先查讲座状态和当前报名数如果未满执行一条受保护的更新语句UPDATE lecture_info SET enrolled_count enrolled_count 1 WHERE id ? AND enrolled_count capacity只有当受影响行数为1时才向lecture_registration插入报名记录。这相当于用数据库层面的条件更新来充当乐观锁避免同时进来的请求都认为还有名额。容量修改也有一个小坑讲座发布后如果主办方要调低容量上限必须校验新容量大于当前已报名人数否则会出现数据库里报名人数大于容量的脏数据。这个校验写在后端service层接口层面统一拒绝非法修改。3. 核心环节实现与部署记录3.1 环境准备与项目初始化我实际搭建时的环境配置如下JDK 1.8当然用11也没问题、Maven 3.6、MySQL 5.7/8.0、IDEA 2022及以上、Navicat或DataGrip作为数据库客户端。Redis这版项目是可选项不开不影响核心流程。创建数据库时注意字符集推荐直接用utf8mb4避免将来要存emoji或者中文昵称时乱码。SQL脚本里预置管理员账号admin / 123456密码已用BCrypt加密以及几个测试学生账号。首次导入时我遇到过一个问题如果数据库是MySQL 8.0驱动要写成com.mysql.cj.jdbc.Driver并且连接串必须带上serverTimezoneAsia/Shanghai否则会报时区异常。关键的application.yml配置大致长这样server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/lecture_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 10MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto注意map-underscore-to-camel-case这个配置。数据库字段是start_timeJava属性是startTime不开启下划线转驼峰MyBatis Plus查询结果就映射不上查出来的对象全是null。这是非常经典的入门卡点。3.2 登录认证模块JWT的完整落地认证模块我拆成了三个部分JwtUtil工具类、JwtAuthenticationFilter过滤器和SecurityConfig配置。JwtUtil负责生成和解析Token核心代码如下public class JwtUtil { private static final String SECRET your-secret-key-change-in-production; private static final long EXPIRE 7 * 24 * 60 * 60 * 1000L; public static String generateToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }Token过期时间我设置为7天。校园讲座系统不是银行核心系统学生一周内反复重新登录会非常烦躁但如果做的是交易系统或者后台管理系统这个过期时间就必须缩短到半小时到两小时之间。安全策略永远要结合业务场景定不是越长越安全也不是越短越好。JwtAuthenticationFilter继承OncePerRequestFilter核心逻辑就是取Header里的Authorization、去掉Bearer前缀、解析Token、把userId和role塞进SecurityContext。这里有一个我栽过的坑过滤器里如果解析失败不能直接抛异常要捕获异常后清理SecurityContext否则后续接口会拿到脏认证信息。SecurityConfig配置做了这三件事放行登录注册接口、拦截其他接口、添加JWT过滤器。http.csrf().disable() .cors().and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/api/auth/**, /api/lectures/list).permitAll() .antMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() .and() .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);/api/lectures/list放行是因为讲座列表页不需要登录就能看到但报名必须要登录。cors()这里要提前写好CORS配置类开发时前端Vue默认端口往往和后端不一样不配置跨域页面请求全部失败。3.3 讲座发布与审核的实现讲座发布的入口是教师端的一个表单包含标题、主讲人、分类、时间、地点、容量、简介等字段。保存时区分两种操作存草稿和提交审核。PostMapping(/api/lectures) public Result createLecture(RequestBody LectureSaveDTO dto, AuthenticationPrincipal UserPrincipal user) { Lecture lecture new Lecture(); BeanUtils.copyProperties(dto, lecture); lecture.setPublisherId(user.getId()); lecture.setStatus(0); // 草稿 lecture.setAuditStatus(0); // 未提交 lecture.setEnrolledCount(0); lectureService.save(lecture); return Result.success(); }提交审核时把audit_status改成1管理员在/api/admin/lectures/audit接口处理。审核通过后audit_status2、status1讲座对外可见驳回时audit_status3并且必须写驳回原因。我在写驳回原因时发现一个体验细节如果只是改一个状态值学生端看到讲座不可用却不知道为什么主办方也很困惑。所以在lecture_info表加了一个audit_remark字段驳回原因存进去教师端打开列表就能看到修改后重新提交。这个字段虽小但审核流程的可用性立刻就上来了。3.4 报名、签到与统计功能的实现报名接口是整个系统最典型的业务代码我直接贴核心逻辑Transactional public Result register(Long lectureId, Long userId) { Lecture lecture lectureService.getById(lectureId); if (lecture null || lecture.getStatus() ! 1 || lecture.getAuditStatus() ! 2) { return Result.fail(讲座不存在或未开放报名); } int rows lectureMapper.increaseEnrolledCount(lectureId); if (rows 0) { return Result.fail(报名人数已满); } try { LectureRegistration registration new LectureRegistration(); registration.setLectureId(lectureId); registration.setUserId(userId); registration.setAttendStatus(0); registrationService.save(registration); } catch (DuplicateKeyException e) { throw new BusinessException(您已经报名过该讲座); } return Result.success(); }increaseEnrolledCount对应的SQL是UPDATE lecture_info SET enrolled_count enrolled_count 1 WHERE id ? AND enrolled_count capacity这一步保证并发安全。我特意在方法上加了Transactional避免人数增加了但报名记录插入失败导致的数据不一致。签到功能实现起来相对简单主办方在报名名单页面勾选签到后端校验该学生确实报名过然后把attend_status置为1。如果扩展到扫码签到实质也是把这个操作封装成一个带时效的Token接口。统计功能我用了三条SQL赚足大头-- 按分类统计讲座场次 SELECT category_id, COUNT(*) FROM lecture_info WHERE status 2 GROUP BY category_id; -- 按月统计报名人数趋势 SELECT DATE_FORMAT(register_time, %Y-%m) AS month, COUNT(*) FROM lecture_registration GROUP BY month; -- 讲座到场率 SELECT l.id, l.title, COUNT(r.id) AS registered_count, SUM(CASE WHEN r.attend_status 1 THEN 1 ELSE 0 END) AS attended_count FROM lecture_info l LEFT JOIN lecture_registration r ON l.id r.lecture_id WHERE l.status 2 GROUP BY l.id;聚合查询用MyBatis Plus的QueryWrapper也能写但这种多表LEFT JOIN统计我还是建议写到XML里或者用注解SQL语义更清楚也方便后续加索引优化。4. 常见问题排查与经验分享4.1 开发中踩过的六个坑逐个说明项目做完后我最想保留的是一份问题速查表因为这些坑基本覆盖了Spring Boot新手最容易翻车的几个位置。问题现象可能的根因解决方案前端请求接口报跨域错误后端未启用CORS或配置了allowCredentials(true)但allowedOrigins(*)冲突明确指定允许的来源域名不要用通配符credentials登录后调用接口仍返回401过滤器放行路径顺序不对或Token过期先确认路径匹配顺序再检查JWT过期时间查询出的实体字段全是null没开启map-underscore-to-camel-case属性名对不上在application.yml加配置或为字段加TableField后端返回时间比数据库少了8小时服务器、数据库、连接串时区不一致连接串显式加serverTimezoneAsia/Shanghai上传讲座封面图片失败Spring Boot默认上传大小限制1MB配置spring.servlet.multipart.max-file-size同一条数据逻辑删除后再次插入报唯一键冲突逻辑删除字段与联合唯一索引冲突把逻辑删除字段加入业务唯一判断或在删除时物理删除旧的报名记录第一个跨域坑困扰过我很久。CORS配置一旦出现allowedOrigins(*)配合allowCredentials(true)浏览器会直接拦截响应。正确做法是写成准确的Origin列表或者在开发环境单独配置一个允许的本地端口。第二个坑是JWT。我调试时发现Token明明生成成功了请求还是被拦截。后来发现是SecurityConfig里我把jwtFilter加到了UsernamePasswordAuthenticationFilter后面导致认证信息还没设置权限判断就开始执行。过滤器的挂载顺序一定要在UsernamePasswordAuthenticationFilter之前。第三个坑写出来可能有人觉得笨但真的非常常见。MyBatis Plus默认开启驼峰映射但前提是数据库字段是下划线风格。如果你的表字段本来就叫startTime而没有下划线那映射是正常的一旦混合命名一查一个不吱声。建议建表时全部用下划线Java属性全部用驼峰这是最省心的组合。4.2 安全与性能自查清单虽然是校内系统但安全底线不能丢。我给自己列了一个自查清单分享几个最关键的第一密码必须BCrypt加密。新增用户时用BCryptPasswordEncoder.encode()登录校验时用matches()。这个类Spring Security已经内置不要自己写MD5加盐逻辑BCrypt每次加密会生成随机盐安全性比MD5高一个量级。第二管理员接口的路径权限要配置到位。/api/admin/**必须要求ADMIN角色不能只靠前端隐藏按钮。前端隐藏只是体验优化后端拦截才是安全底线。测试方法是用一个学生Token直接请求管理员接口看是否返回403如果放行了说明配置有问题。第三列表查询必须分页。MyBatis Plus的Page对象用起来很简单但很多人在写讲座列表时图省事直接list()查全表数据量一上来页面就卡死。分页的同时在lecture_info表的start_time和category_id上建索引报名表在(lecture_id, user_id)上建联合索引即可。如果访问量比较大讲座详情这类热点接口可以考虑用Redis包一层Key设计为lecture:detail:{id}设置一小时过期时间在这个体量的系统里足以应付绝大多数压力。第四接口层尽量少的打印明文敏感数据。登录接口的请求日志不要完整打印密码审计日志保留操作人、操作时间和操作动作就够了这也是真实项目里常见的合规要求。4.3 后续扩展方向按实际优先级排序如果一个学期后学校提出要扩展我会按这个优先级来排首先做消息通知报名成功后用邮件或企业微信机器人推给学生省去主办方逐个通知然后做扫码签到生成一场讲座专属的二维码学生扫码后校验报名记录并签到把现场排队的体验彻底解决接着可以做讲座回放视频挂载和讲座详情页打通最后有条件的话把统计页面升级成可视化看板用ECharts把分类占比和到场率画成图表汇报时直接投屏。按这个顺序扩展每一期都有独立价值不会做成过度设计的玩具。这套系统的技术栈依然是主流通用方案。Spring Boot负责后台接口MyBatis Plus处理数据访问JWT无状态认证让前后端分离自然流畅。如果你也想动手做类似的项目我的建议是先从数据模型开始看把lecture_info的状态字段和lecture_registration的唯一约束理解透再往上叠加接口和页面。数据结构稳了功能扩展就是加接口的事数据结构乱了后面每一步都在还债。我个人在实际搭建中体会最深的一点是把报名和签到用同一张报名记录表承接用attend_status区分阶段这个设计在编码阶段看不出优越性但到了统计到场率和排查重复数据时省了不知道多少功夫。很多设计上的取舍要到运维和排查阶段才知道当初的选择到底香不香。
返回列表