FEATURED · 精选文章

SpringBoot会议管理系统:MySQL建模、冲突校验与权限控制

发布时间 / 2026/9/18 16:20:13
来源 / 创域科博编辑部
栏目 / 资讯中心
SpringBoot会议管理系统:MySQL建模、冲突校验与权限控制 简介这是一份面向计算机相关专业毕业生与Java Web开发初学者的毕业设计论文文档以「基于SpringBoot的会议管理系统」为选题可用于毕业设计参考、论文写作范例学习及课程项目选题借鉴。资源为1个doc格式文档压缩包约5.12MB内容围绕系统概述、需求分析、数据库设计、前后端编码实现与测试等章节展开行文完整、结构规范。目前已有142人学习下载适合需要搭建同类管理系统或撰写论文的读者参考。文档完整呈现了员工管理、公告管理、会议室管理、会议资料管理、会议投票与意见收集等模块的设计思路数据库层面涉及用户表、会议表、公告表、会议室表等实体字段规划技术选型涵盖Java、SpringBoot、MySQL与HTML前端并包含单元测试、集成测试与系统测试的验证过程。读者可据此了解一个Java Web管理系统的需求梳理、库表设计与开发测试全流程也可借鉴其论文目录组织与章节写法为自身选题提供可复用的框架与思路。1. 一次会议排期引发的返工这套系统到底在解决什么公司规模到了几十人往上会议室排期靠群里喊话和 Excel 共享问题就暴露得特别快。有人约了 3 楼大会议室另一个部门同时在纸上写了同一时段两边人到门口才发现撞车会议资料散在个人网盘会后想找回某版决议得翻聊天记录投票表决只能靠举手表决再人工统计。这些场景的共同点不是缺个表单而是缺一张能约束时间资源、能把资料和结论沉淀下来的数据层。基于 SpringBoot 的会议管理系统要处理的正是这类问题。它把员工、会议室、会议、会议资料、投票、意见收集这几类对象放进 MySQL通过 SpringBoot 提供的 REST 接口做增删改查管理员维护基础数据和公告员工端浏览会议室、查看资料、参与投票、提交意见。系统边界很清楚不做视频会议不做即时通讯只做会议组织过程中的信息流转与状态跟踪。适合谁读拿这个题目做计算机毕业设计的同学能直接对照论文里的功能模块抄出可运行的后端结构需要给中小团队搭内部预约工具的在职开发也能从里面的冲突校验 SQL 和权限切分方式里挑走能用的部分。下面从工程骨架讲到数据库建模再落到冲突校验和接口排错最后补测试与验收演示的实操细节。2. SpringBoot 工程初始化与会议管理领域的数据建模很多同学在第一步就卡住不是不会写代码而是被版本组合搞崩了。搞清楚选型再动手能省掉后面一半的调试时间。2.1 版本组合与工程骨架生成当前 Spring Initializr 网页默认给的是 SpringBoot 3.x而 3.x 强制要求 JDK 17 起步。学校机房或者自己电脑上装的还是 JDK 1.8导入项目后启动直接报Unsupported class file major version这就是热搜里版本太高想回退到 1.8的典型现场。毕设场景稳妥的组合是 SpringBoot 2.7.18 JDK 1.8 Maven理由是 2.7 是 2.x 最后一个长期维护版本网上资料最多MyBatis 生态也最成熟。生成方式有两种。网页端在 Initializr 上把 Boot 版本手动切到 2.7.xJava 选 8勾选 Spring Web、MyBatis Framework、MySQL Driver、Lombok。想要命令行版本可以直接拼# 生成 SpringBoot 2.7.18 骨架JDK 8含 Web/MyBatis/MySQL curl https://start.spring.io/starter.zip \ -d typemaven-project \ -d languagejava \ -d bootVersion2.7.18 \ -d javaVersion8 \ -d groupIdcom.example \ -d artifactIdmeeting \ -d namemeeting \ -d dependenciesweb,mybatis,mysql,lombok \ -o meeting.zip unzip meeting.zip -d meeting cd meeting参数说明bootVersion决定 Spring 生态的主版本写成 2.7.18 就能绕过 3.x 的 JDK 门槛javaVersion8保证maven.compiler.source落在 1.8dependencies里 web 提供内嵌 Tomcat 和RestControllermybatis 提供 Mapper 扫描mysql 只是引入驱动实际连接串还得自己写。骨架跑起来后用mvn spring-boot:run验证看到 Tomcat 在 8080 端口启动即算通过——这一步能过后面的坑基本都是业务坑。提示如果 IDE 里创建项目时下拉框根本没有 2.7.x 选项说明本地 Initializr 插件版本太新直接用上面的 curl 或改pom.xml里的parent版本号更省事。2.2 员工、会议室、会议三张核心表论文里写了数据库 ER 图设计和数据库表设计但落到代码上真正撑住业务的是三张表加两张从表。设计原则是先定主键类型再定约束。下面是我在毕设里推荐的建表方式-- 员工表区分管理员与普通员工用 role 字段而非两张表 CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT, username VARCHAR(32) NOT NULL COMMENT 登录账号, password VARCHAR(64) NOT NULL COMMENT MD5加盐后的密文, real_name VARCHAR(32) NOT NULL, role TINYINT NOT NULL DEFAULT 0 COMMENT 0员工 1管理员, dept VARCHAR(64) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 会议室表容量和位置决定排期时的筛选条件 CREATE TABLE meeting_room ( id BIGINT NOT NULL AUTO_INCREMENT, room_name VARCHAR(64) NOT NULL, location VARCHAR(128) DEFAULT NULL, capacity INT NOT NULL DEFAULT 10, status TINYINT NOT NULL DEFAULT 1 COMMENT 1可用 0停用, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 会议表start_time/end_time 是冲突校验的核心字段 CREATE TABLE meeting ( id BIGINT NOT NULL AUTO_INCREMENT, title VARCHAR(128) NOT NULL, room_id BIGINT NOT NULL, creator_id BIGINT NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待开 1进行 2结束, PRIMARY KEY (id), KEY idx_room_time (room_id, start_time, end_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键点在最后一行索引idx_room_time。冲突校验的查询条件是同一会议室 时间区间重叠把room_id放最左、时间字段跟随能让这个查询走覆盖索引几十条会议数据看不出差别但答辩演示时如果准备了上千条模拟数据有没有这个索引就是秒回和转圈的区别。sys_user用单个role字段而不是拆成管理员表和员工表是因为两边的公共属性账号、姓名、部门完全一致拆表只会让登录接口多写一个分支。2.3 MyBatis 映射与 JSON 序列化的边界SpringBoot 论文里提到实现了 SQL 语句的分离这就是所谓的 MyBatis 框架这句话落到工程上就是resources/mapper/*.xml这个目录。实体类和表字段的映射有三条常见路线选错了后面改字段会很难受方案配置方式适用场景坑点驼峰自动映射mybatis.configuration.map-underscore-to-camel-casetrue字段命名规范统一real_name能映射realName但自定义别名失效resultMap手写XML 里逐字段声明有联表查询、字段别名字段多了冗长改表要同步改两处注解Results写在 Mapper 接口上单表简单查询复杂动态 SQL 不如 XML 直观我一般用第一种打底联表查询单独写resultMap。配置文件里补上# application.yml 关键片段 spring: datasource: url: jdbc:mysql://localhost:3306/meeting_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: trueserverTimezoneAsia/Shanghai这行建议都写上否则 MySQL 8 驱动在部分环境下会把DATETIME读成前一天表现为预约的是 5 月 1 日查出来是 4 月 30 日这种时间偏移排查起来非常费时。前端通过 JSON 拿数据时LocalDateTime默认序列化成2024-05-01T09:00:00这种带 T 的格式如果 Vue 端直接用字符串截取会出错稳妥做法是在字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)或全局配置ObjectMapper的日期格式。3. 会议室冲突校验、资料归档与投票模块的落地实现三张表建好之后真正决定系统好不好用的是业务规则。会议室不能重复占用、投票不能刷票、资料不能越权下载这三条规则写歪一条演示时就会被追问。3.1 时间段交集 SQL 与并发预约防重判断两个时间段是否重叠本质是四种情况取反新会议的开始时间早于已有会议的结束时间且新会议的结束时间晚于已有会议的开始时间。用 SQL 表达就是-- 校验目标会议室在 [newStart, newEnd) 区间是否已被占用 SELECT COUNT(1) FROM meeting WHERE room_id #{roomId} AND status ! 2 -- 已结束的会议不参与冲突计算 AND start_time #{newEnd} -- 已有会议早于新会议结束前开始 AND end_time #{newStart}; -- 已有会议晚于新会议开始后结束参数roomId是用户选中的会议室newStart/newEnd是前端传回的时间串。这段 SQL 在 Service 层调用返回值大于 0 就抛出业务异常提示该时段已被占用。注意这里用的是严格小于和严格大于也就是允许上一场 10:00 结束、下一场 10:00 开始这种背靠背排期如果业务上要求留出 15 分钟清场把条件改成start_time DATE_ADD(#{newEnd}, INTERVAL 15 MINUTE)即可。单机演示环境这样写够了但只要有两个请求同时进来先查后插之间仍然存在竞态窗口。要彻底堵住可以在meeting表上加一条唯一约束或改用悲观锁// Service 层把查重 插入放进同一个事务并对会议室行加锁 Transactional(rollbackFor Exception.class) public void createMeeting(MeetingDTO dto) { // 对会议室记录加行锁串行化同一会议室的预约请求 roomMapper.selectByIdForUpdate(dto.getRoomId()); int conflict meetingMapper.countConflict( dto.getRoomId(), dto.getStartTime(), dto.getEndTime()); if (conflict 0) { throw new BizException(该会议室在所选时段已被占用); } meetingMapper.insert(dto.toEntity()); }selectByIdForUpdate对应的 SQL 末尾要加FOR UPDATE这样两个并发请求会排队进入第二个拿到的锁一定是第一个事务提交之后的。代价是同一会议室的高并发场景下响应变慢不过会议预约这种低频操作完全能接受。Transactional里指定rollbackFor Exception.class是因为默认只对运行时异常回滚业务里抛的自定义BizException若不是继承RuntimeException事务不会回滚数据就脏了。3.2 会议投票的防重与事务边界投票模块的核心诉求有两个一是同一员工对同一议题只能投一次二是票数统计要准。防重最省事的办法不是写代码判断而是在表上加唯一索引让数据库兜底-- 投票记录表user_id topic_id 唯一从存储层杜绝重复投票 CREATE TABLE vote_record ( id BIGINT NOT NULL AUTO_INCREMENT, topic_id BIGINT NOT NULL COMMENT 关联会议投票议题, user_id BIGINT NOT NULL, option_id BIGINT NOT NULL COMMENT 所选选项, vote_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_topic_user (topic_id, user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;有了uk_topic_user重复投票在insert时就会抛DuplicateKeyException。业务层捕获它转成友好提示即可省掉一次先查有没有投过的查询也彻底避免并发下判断失效。统计票数时按option_id分组-- 统计某议题各选项得票含未选项展示需要前端自行补零 SELECT o.id AS optionId, o.option_text AS optionText, COUNT(v.id) AS voteCount FROM vote_option o LEFT JOIN vote_record v ON v.option_id o.id AND v.topic_id o.topic_id WHERE o.topic_id #{topicId} GROUP BY o.id, o.option_text ORDER BY o.id;用LEFT JOIN而不是INNER JOIN的原因很实际如果某个选项一票都没有内连接会让它整行消失前端图表里就少一根柱子看起来像 bug。用左连接保证每个选项都返回COUNT(v.id)因为只数非空值天然得到 0。参数topicId对应投票议题是一张单独的vote_topic表属于meeting的子表设计时别把议题字段直接塞进会议表否则一个会议想搞两个独立投票就得改表结构。3.3 会议资料上传与落盘路径策略会议资料管理在论文里是一整个模块实现上要回答两个问题文件存哪、谁能下。存哪这块毕设阶段最常见也最稳妥的做法是存本地磁盘数据库只存相对路径PostMapping(/material/upload) public Result? upload(RequestParam(file) MultipartFile file, RequestParam(meetingId) Long meetingId) throws IOException { // 按会议 ID 分目录避免单目录文件数过多 String dir uploadRoot File.separator meetingId; File dirFile new File(dir); if (!dirFile.exists() !dirFile.mkdirs()) { throw new BizException(创建上传目录失败); } // UUID 重命名规避中文名和同名覆盖问题 String origin file.getOriginalFilename(); String suffix origin.substring(origin.lastIndexOf(.)); String savedName UUID.randomUUID() suffix; file.transferTo(new File(dirFile, savedName)); MeetingMaterial m new MeetingMaterial(); m.setMeetingId(meetingId); m.setOriginName(origin); // 展示给用户看的原名 m.setStorePath(meetingId / savedName); // 实际落盘相对路径 materialMapper.insert(m); return Result.ok(); }uploadRoot来自配置项别硬编码否则换台机器演示就得改代码。suffix从原名里截取而不是用MultipartFile.getContentType()推断因为浏览器给的 MIME 类型并不可靠。最关键的一点在下载接口一定要在服务端校验当前登录用户是否有权访问该会议的资料不能只靠前端隐藏链接否则把 ID 一改就能拿到别人的会议文件。4. 员工端与管理端权限切分及接口联调排错论文把功能分成管理员和员工两套实际操作里这两套共用登录、共用一张用户表靠角色字段分流。分流写不对要么员工能删会议室要么管理员进不去后台。4.1 拦截器与角色字段的配合推荐用 Spring 的HandlerInterceptor在进入 Controller 之前做校验比在每个方法里写 if 判断干净得多public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception { // 1. 未登录直接拦截 Object user req.getSession().getAttribute(loginUser); if (user null) { resp.setStatus(401); return false; } // 2. 管理端接口要求 role 1 String uri req.getRequestURI(); if (uri.startsWith(/admin/)) { SysUser u (SysUser) user; if (u.getRole() null || u.getRole() ! 1) { resp.setStatus(403); return false; } } return true; } }注册的时候要注意排除登录、注册和静态资源路径否则用户连登录页都打不开。/admin/前缀是约定所有只有管理员能调的接口统一挂这个前缀拦截器只判断前缀就行不用维护一张长长的白名单。这种写法比用注解加反射的方案简单毕设规模完全够用如果后面接口多了想升级再换 Spring Security 也不迟。会话方案上毕设用HttpSession最省事登录成功把用户对象塞进 session前端每次请求带JSESSIONIDcookie。唯一要注意的是前后端分离部署时跨域 cookie 的携带问题如果浏览器控制台报SameSite相关警告就在拦截器返回时补resp.setHeader(Access-Control-Allow-Credentials, true)同时前端请求配置里打开withCredentials。4.2 接口报错的定位顺序联调阶段 90% 的问题集中在四类返回码上按下面顺序排查最快现象常见原因定位动作400 Bad Request前端 JSON 字段名和后端 DTO 不匹配打印请求体核对RequestBody字段401 Unauthorizedsession 过期或未携带 cookie看浏览器 Application 里有没有 JSESSIONID403 Forbidden角色不符合/admin/前缀要求查当前登录账号的role值500 Internal Server Error空指针、SQL 语法、类型转换看控制台完整堆栈重点看 Caused by排 500 的时候有个提速技巧在application.yml里打开 MyBatis 的 SQL 日志mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl这样控制台会把实际执行的 SQL 和参数打出来一眼就能看出是条件没传进去还是字段拼错。参数传null导致的空指针用RequestParam(required false)或者给 DTO 字段设默认值都能挡住但治本的办法还是在 Service 层入口做一次非空校验。另外一个高频坑是日期参数。前端日期选择器给的是2024-05-01字符串后端 DTO 用LocalDate接没问题但如果是LocalDateTime又没传时分秒会直接抛DateTimeParseException变成 400。统一约定前端传yyyy-MM-dd HH:mm:ss并在 DTO 字段上加DateTimeFormat就能对齐。5. 测试用例分层与答辩现场演示的两个技巧论文第六章写了测试实例和测试结论实际答辩时老师最爱问的就是你怎么保证冲突校验是对的。与其背结论不如现场跑两条边界用例。冲突校验的边界值至少覆盖五种完全重叠、部分重叠、首尾相接、完全不相交、跨天。前三种走接口能直接验证 SQL 条件写对了没有后两种最容易漏尤其是跨天——如果只按日期比较而不比较时间一场 23:00 到次日 01:00 的会议就会误判成不冲突。单元测试用 JUnit 5 加SpringBootTest即可重点测 Service 层Test Transactional // 测试完自动回滚不污染演示库 void shouldRejectOverlapMeeting() { MeetingDTO a buildDto(1L, 2024-05-01 09:00:00, 2024-05-01 10:00:00); meetingService.createMeeting(a); // 第一场正常 MeetingDTO b buildDto(1L, 2024-05-01 09:30:00, 2024-05-01 11:00:00); assertThrows(BizException.class, () - meetingService.createMeeting(b)); // 重叠必须被拒 }Transactional加在测试方法上让每次执行后自动回滚避免反复跑测试把演示数据搅乱。断言用assertThrows而不是捕获异常后手动fail()前者失败信息更直观输出里会直接告诉你期望的异常没抛出来。第二个技巧是关于演示数据。答辩现场最好预先灌入一批有故事的数据同一会议室相邻时段的两场会议、一个已经结束的会议、一个还有票没投完的投票、一份带中文名的会议资料。这样演示冲突校验、状态流转、投票统计、文件下载四个功能时都有现成的靶子不用现场手忙脚乱地新建。灌数据的脚本写成一个data.sql放在src/test/resources下配合spring.sql.init.modealways只在演示环境启用生产配置里关掉即可。答辩前把后端打个 jar用java -jar meeting-0.0.1-SNAPSHOT.jar --spring.profiles.activedemo一条命令启动比现场开 IDE 等 Maven 编译稳得多。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻