
1. 项目概述与选题背景1.1 核心需求解析先把这个标题拆开来看面向企业用户的复合型活动基地公共会议管理系统加上一个加粗的会议室预订系统。说白了这就是一个给企业或园区用的会议管理平台负责会议室信息展示、在线预订、审批管理、使用记录统计这些事。放在复合型活动基地这个场景里就是那种既有办公区、又有路演厅、还有洽谈室和培训室的综合型场所会议室数量多、类型杂、使用频次高靠人工在纸上登记或者用Excel排期基本就是灾难现场。这个选题在Java毕设里属于常青树级别我每年都能看到好几个人做类似方向。为什么大家喜欢做这个因为它的复杂度卡得刚刚好——比“图书管理系统”那种纯CRUD高出一截又不至于像电商秒杀系统那样需要分布式事务、消息队列这些重型组件。它能完整覆盖一个真实项目该有的东西权限控制、复杂查询、时间冲突检测、审批流、统计报表恰好能把Spring Boot MyBatis Vue这套主流技术栈串起来。对于做毕设的同学来说选择Spring Boot而不是SSH或者Servlet原生的理由非常现实Spring Boot的自动配置机制能把大量样板配置直接省掉一个spring-boot-starter-web就搞定了Web环境spring-boot-starter-data-redis就接入缓存开发效率比SSH那套动不动就要写一堆XML配置的方式高出一个量级。而且现在企业的Java后端岗位问的就是Spring Boot 微服务这套东西选这个技术栈做毕设相当于提前把面试常见的技术场景演练了一遍。1.2 这类系统要解决的现实问题会议室管理听着简单真正跑起来你会遇到一堆真实的问题。我见过一个做园区运营的朋友他们那里8间会议室高峰期一天要排40多场会议原来用Excel排期经常出现两个部门抢同一间会议室或者会议开始半小时前才发现和另一场培训冲突。更麻烦的是有些会议室设备不一样——有的有视频会议系统有的有白板有的能坐20人有的只能坐6人人工排期根本记不住这些细节。所以这类系统真正要解决的核心问题有三个资源冲突问题同一间会议室在同一时间段不能被预订两次。这个听起来简单但判断冲突的逻辑有不少坑后面我会详细讲。设备与容量匹配问题用户订会议室之前得知道这间会议室能坐多少人、有什么设备、在几楼、有没有窗户。这些属性信息必须清晰地展示在预订页面上。审批与权限问题企业场景下会议室的预订通常不是“点一下就行”的订大会议室可能要部门经理审批订普通洽谈室可能只需要管理员确认。怎么把审批流做得既灵活又不复杂是个需要动脑子的设计点。这个系统把这三个核心问题都覆盖了所以它作为毕设选题的价值不只是“能交差”而是你在写它的过程中确实能接触到企业级应用开发的真实套路。2. 技术选型与架构设计思路2.1 后端框架选择Spring Boot为什么是标准答案Spring Boot在Java毕设里已经处于绝对统治地位这个不用争。但我要说的是它怎么就适合这个项目了。第一开发效率高。这个项目要涉及的角色有管理员、普通员工、审批人涉及的功能跨预约、审批、统计、用户管理、会议室管理如果你用传统的Spring MVC手动配一堆Bean和XML光配置文件就得写几百行。Spring Boot的自动配置把这些都吃了你只需要关注业务代码。第二生态完善。会议室预订系统天然需要这些组件MyBatis或MyBatis-Plus操作数据库、Spring Security或Sa-Token做权限控制、Redis做缓存比如监控当前谁在哪个会议室、WebSocket做会议室状态实时推送。这些在Spring Boot生态里都有成熟的starter直接引入依赖就能用。第三部署简单。毕设答辩的时候你大概率需要现场演示。Spring Boot应用打成一个可执行JAR包java -jar一行命令就能跑起来。你要是用传统的Tomcat部署WAR包还得先装Tomcat、配置数据源、调整JVM参数演示现场出问题的概率高得多。这个项目里我推荐的技术组合是后端Spring Boot 2.7.x稳定、资料多别追新版本ORMMyBatis-Plus比原生MyBatis少写大量XML分页插件好用权限Sa-Token比Spring Security上手简单太多代码侵入性低前端Vue 2 Element UIVue 3 Element Plus也行但Vue 2的教程和案例多毕设阶段更容易找到参考数据库MySQL 8.x别用5.7了8.0的窗口函数做统计报表方便缓存Spring Data Redis用来缓存用户会话和会议状态可选但加了能加分2.2 前后端分离还是服务端渲染这是个必须做选择的点。我见过不少毕设项目前端用Vue全家桶后端纯提供JSON接口但这种模式在答辩的时候有个尴尬的问题你现场演示的时候前端和后端要同时启动两个服务万一网络配置不对、端口冲突、跨域没配好演示就卡住了。我的建议是如果做纯后台管理系统前端用Vue Element UI 做前后端分离没问题但要提前把跨域问题解决干净如果求稳用Thymeleaf服务端渲染也行尤其是你要在演示的时候省心一点。不过说实话现在毕业设计的主流做法已经是前后端分离了企业里也这么干。这篇博文的代码结构我按前后端分离来设计——前端一套Vue工程后端一套Spring Boot工程中间通过RESTful API通信。工程结构大致是这样meeting-admin后端Spring Boot工程controller接收前端请求返回JSONservice业务逻辑层处理预订冲突检测、审批流等核心逻辑mapperMyBatis-Plus的数据访问层entity数据库实体类dto前端传参的封装对象config配置类跨域、拦截器、异常处理meeting-web前端Vue工程views页面组件登录、会议室列表、预订日历、审批中心、统计报表api封装Axios请求router前端路由storeVuex状态管理等一下有些同学听到“Vue工程”就头疼觉得自己前端不行。这里我多说一句如果时间紧你可以不用Vue CLI那套复杂工程化流程直接引入Vue 3的CDN Element Plus的CDN写单页HTML文件就能把页面做出来后端提供接口就行。这种方式在毕设里完全够用还省去node_modules的安装时间。2.3 数据库设计会议室预订的核心表结构数据库设计是这类系统的重头戏。表设计得好不好直接决定你后面的业务逻辑是写得顺还是写得痛苦。我直接把这套系统的核心表结构拆给你看。先明确我们有哪些实体用户表User、会议室表MeetingRoom、预订记录表Reservation、审批记录表ApprovalLog、通知表Notification、会议室设备表RoomDevice可选。用户表用户表不能只存个username和password你需要冗余一个部门字段因为在企业场景里会议室预订通常要区分部门而且后面做统计报表也要按部门维度统计。字段设计如下id主键自增username登录名唯一passwordBCrypt加密后的密码real_name真实姓名展示在预订记录里department所属部门用于按部门统计和权限控制role角色建议用整数表示1超级管理员2部门管理员3普通员工email/phone联系方式用于预订成功或冲突时的通知status账号状态0禁用1正常会议室表会议室表需要有足够的属性让用户在做预订决策时参考。字段设计如下id主键room_name会议室名称比如“一号路演厅”“三楼洽谈室”location具体位置比如“A栋2层206”capacity容纳人数整数room_type会议室类型可选项小型洽谈室、标准会议室、大型路演厅、培训室equipment设备清单用逗号分隔存储比如“投影仪,视频会议系统,白板”open_time/close_time开放时间段比如08:00-22:00status是否启用管理员可以停用某些正在装修的会议室description补充说明比如“窗户朝南采光好”这里有个设计细节要注意设备清单建议直接用一个文本字段存不要拆成单独的设备表。为什么因为设备信息对业务逻辑没有强约束你只需要在展示时列出来就行拆表反而增加复杂度。毕设项目要用“够用就好”的设计原则不要为了追求范式搞得自己写起来痛苦。预订记录表这是整个系统的核心表字段设计直接关系到冲突检测怎么写。字段如下id主键room_id外键关联会议室表user_id外键关联用户表预订人meeting_title会议主题meeting_date会议日期只存日期比如2025-06-15start_time开始时间比如09:00end_time结束时间比如11:30status预订状态1待审批2已批准3已拒绝4已取消5已完成create_time预订创建时间remark备注比如参会人数、是否需要特殊布置为什么日期和时间字段要分开存因为预订的冲突判断是“同一日期下同一会议室的时间段重叠”日期单独存查询当天所有预订记录只需要WHERE meeting_date ?索引效率高逻辑也清晰。审批记录表审批记录表用来追踪一条预订申请的审批链路字段如下idreservation_id关联预订记录表approver_id审批人IDapproval_comment审批意见approval_status审批结果1通过0拒绝approval_time审批时间很多毕设不做这张表审批状态直接写在预订表里。但我觉得加上会更好它让系统支持“多人审批”的扩展可能还能在详情页展示完整的审批历史答辩的时候老师问起来你能多说两句。关键索引设计预订记录表需要建复合索引(room_id, meeting_date, start_time, end_time)。这个索引能保证按天查某会议室预订记录的时候走索引速度会快很多。单用户的一天预订记录可以建(user_id, meeting_date)索引用于用户在个人中心查看“我的预订”。2.4 前后端接口设计规范前后端分离的开发模式接口设计必须一开始就定好规矩。我给自己定的规范是这样所有接口统一返回Result对象结构为{ code: 200, message: success, data: {} }分页接口返回{ total: 100, records: [] }时间参数统一用字符串格式yyyy-MM-dd HH:mm:ss避免前后端时区不一致的坑所有需要登录的接口在Header里带上tokenSa-Token默认机制写接口文档的话接口路径可以这样规划分类路径说明认证POST /api/auth/login登录返回token认证POST /api/auth/logout注销会议室GET /api/rooms会议室列表支持条件筛选会议室GET /api/rooms/{id}会议室详情预订POST /api/reservations提交预订申请预订GET /api/reservations/my我的预订列表预订GET /api/reservations/pending待审批列表审批POST /api/approvals通过或拒绝统计GET /api/stats/room-usage会议室使用率统计3. 核心功能模块设计与实现3.1 登录认证与权限控制Sa-Token的简单做法权限控制这块我强烈建议不要用Spring Security。不是说它不好而是对一个毕设规模的系统来说Spring Security的学习成本太高了——过滤器链、UserDetailsService、AuthenticationManager这些概念能把人绕晕而且配置写错了查错特别费劲。Sa-Token就简单得多。核心就三步# 引入依赖Maven坐标如下 cn.dev33:sa-token-spring-boot-starter:1.34.0登录的时候调用StpUtil.login(userId)框架会生成一个token返回给前端。前端存到localStorage每次请求在Header里加satoken: 这个token。后端在配置类里注册一个拦截器Configuration public class SaTokenConfigure implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor(handle - { // 登录验证 StpUtil.checkLogin(); })).addPathPatterns(/**) .excludePathPatterns(/api/auth/login, /api/rooms/list); } }这样就实现了“不登录不允许访问接口”的效果。角色权限控制用StpUtil.checkRole(admin)在需要管理员权限的接口上加一行就行。3.2 会议室列表展示与条件筛选会议室列表页几乎是所有用户的入口页面。这里要支持按room_type、capacity、equipment等条件筛选还要展示每个会议室的今日空闲时段。后端接口设计如下GetMapping(/api/rooms) public ResultPageResultRoomVO listRooms(RequestParam(required false) String roomType, RequestParam(required false) Integer minCapacity, RequestParam(required false) String date, RequestParam(required false) String equipment) { PageResultRoomVO page roomService.selectRoomPage( roomType, minCapacity, date, equipment); return Result.ok(page); }查询逻辑里有个关键点如果传了date参数查询时要LEFT JOIN预订记录表把当天已预订的时间段列表一并返回。前端拿到会议室数据后直接渲染一个卡片列表每张卡片上标注“今天空闲时间段09:00-11:0014:00-17:00”。在Service层做这个联查时我记得第一次写的时候踩了个坑如果某会议室当天没有任何预订记录LEFT JOIN会得到一条null记录前端拿到的occupiedTimeRanges是null而不是空数组。处理方式是在VO里把occupiedTimeRanges初始化成空列表或者用MyBatis-Plus的TableField(fill FieldFill.INSERT)自动填充但这些细节问题要提前处理好。3.3 时间冲突检测这套系统的核心难点会议室预订系统的灵魂就是时间冲突检测。很多第一次做这类项目的同学检测逻辑写得一团糟查出来当天所有预订然后一条条写if判断。这么做能出结果但边界情况容易漏而且代码看着就不专业。正确的检测思路是这样的判断两条预订是否冲突只有一种情况不冲突——新预订的开始时间 已有预订的结束时间或者新预订的结束时间 已有预订的开始时间。也就是说不冲突的条件是new_start existing_end 或 new_end existing_start那么冲突条件就是new_start existing_end 且 new_end existing_start等价于new_start existing_end AND new_end existing_start这个判断一定要在数据库查询里做不能把当天所有预订记录全查到内存里再循环判断。性能是一方面更重要的是代码优雅。用MyBatis-Plus写这个条件LambdaQueryWrapperReservation wrapper new LambdaQueryWrapper(); wrapper.eq(Reservation::getRoomId, roomId) .eq(Reservation::getMeetingDate, meetingDate) .in(Reservation::getStatus, Arrays.asList(1, 2)) // 待审批和已批准都算占用 .lt(Reservation::getStartTime, endTime) // 已有的开始时间 新结束时间 .gt(Reservation::getEndTime, startTime); // 已有的结束时间 新开始时间 Integer count reservationMapper.selectCount(wrapper); if (count 0) { throw new BizException(该时间段已被预订请更换时间或选择其他会议室); }这里注意一点status字段必须加限定把已取消和已拒绝的记录排除掉否则会影响正确性。表中字段start_time和end_time在数据库里我用的是TIME类型在Java实体里用LocalTime来接收。这样在做时间比较时MyBatis-Plus会自动生成start_time ?这样的SQL不会出字符串比较的坑。3.4 预订审批流设计不搞复杂工作流但要有状态机思维企业场景里的会议室预订一般不可能是纯自助。我设计了一套简单够用的审批流普通会议室提交预订后直接到管理员审批状态变为“待审批”大型会议室容量 20人需要两级审批先部门负责人审批再到管理员审批特殊时段非工作时间直接拒绝不进入审批流这个规则在Service层通过策略模式实现。定义接口public interface ApprovalStrategy { boolean support(ReservationRequest request); void process(ReservationRequest request); }然后实现三个策略类NormalRoomStrategy、LargeRoomStrategy、SpecialTimeStrategy。通过一个策略工厂根据请求参数选择合适的策略。这种设计的好处是如果你后面想加新规则比如“跨部门会议需要两个审批人”只需要新增一个策略类就行不需要改原有代码。这就是答辩的时候可以和老师讲的“扩展性”。审批操作本身就是一个状态流转要重点考虑并发场景。比如管理员A和管理员B同时处理同一条预订一个通过一个拒绝怎么办我的方案是在审批记录表插入一条记录之前用乐观锁检查当前预订状态Reservation reservation reservationMapper.selectById(id); if (reservation.getStatus() ! 1) { // 不是待审批状态 throw new BizException(该预订已被处理请勿重复操作); } reservationMapper.updateById(reservation); // MyBatis-Plus默认带乐观锁3.5 统计报表模块会议室使用率分析的SQL写法统计报表是答辩时很讨巧的加分项。图纸上“系统能统计每个会议室的使用率”这句话落在代码里其实就是几条SQL。主要做这几个统计会议室使用率按月统计口径某会议室当月已批准预订的总时长 / 会议室开放总工作时长。SELECT room_id, SUM(TIMESTAMPDIFF(MINUTE, start_time, end_time)) / 60 AS used_hours FROM reservation WHERE status 2 AND meeting_date BETWEEN 2025-06-01 AND 2025-06-30 GROUP BY room_id;会议室开放工作时长可以在会议室表里配一个字段work_hours_per_day比如每天8小时当月20个工作日就是160小时。然后用used_hours除以160就是使用率。部门预订排行统计哪个部门用的会议室最多这个SQL很简单按部门分组统计预订次数即可。会议平均时长SELECT AVG(TIMESTAMPDIFF(MINUTE, start_time, end_time)) AS avg_minutes FROM reservation WHERE status 2;统计报表的前端展示我用的是ECharts的饼图和柱状图展示效果挺好。后端返回JSON数据前端直接渲染不需要额外的图表库配置。4. 项目管理与开发交付4.1 项目初始化与目录规划开发开始之前先花半小时把工程结构搭好后面能少走很多弯路。Spring Boot工程的pom.xml里必装的依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot-starter/artifactId version1.34.0/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson2/artifactId version2.0.25/version /dependency /dependenciesapplication.yml里几个关键配置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/meeting_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 sa-token: token-name: satoken timeout: 86400 is-concurrent: true is-share: false注意serverTimezoneAsia/Shanghai必须加否则连MySQL会报时区错误。这是新手最容易踩的坑之一。4.2 核心代码实现后端Service层的完整示例我挑三个核心的Service方法完整贴出来这样你可以直接对照着抄。预订提交的完整逻辑Service public class ReservationServiceImpl implements ReservationService { Autowired private ReservationMapper reservationMapper; Autowired private MeetingRoomMapper meetingRoomMapper; Override Transactional(rollbackFor Exception.class) public Long createReservation(ReservationRequest request) { // 1. 校验会议室是否存在并启用 MeetingRoom room meetingRoomMapper.selectById(request.getRoomId()); if (room null || room.getStatus() ! 1) { throw new BizException(会议室不存在或已被停用); } // 2. 校验预订时间合法性 if (request.getStartTime().isAfter(request.getEndTime())) { throw new BizException(开始时间不能晚于结束时间); } LocalTime openTime room.getOpenTime(); LocalTime closeTime room.getCloseTime(); if (request.getStartTime().isBefore(openTime) || request.getEndTime().isAfter(closeTime)) { throw new BizException(预订时间不在会议室开放时段内); } // 3. 时间冲突检测 LambdaQueryWrapperReservation wrapper new LambdaQueryWrapper(); wrapper.eq(Reservation::getRoomId, request.getRoomId()) .eq(Reservation::getMeetingDate, request.getMeetingDate()) .in(Reservation::getStatus, Arrays.asList(1, 2)) .lt(Reservation::getStartTime, request.getEndTime()) .gt(Reservation::getEndTime, request.getStartTime()); Integer conflictCount reservationMapper.selectCount(wrapper); if (conflictCount 0) { throw new BizException(该时间段已被预订请更换时间或会议室); } // 4. 组装预订记录并入库 Reservation reservation new Reservation(); BeanUtils.copyProperties(request, reservation); reservation.setStatus(1); // 待审批 reservation.setUserId(StpUtil.getLoginIdAsLong()); reservationMapper.insert(reservation); return reservation.getId(); } }会议室占用时间的查询Override public ListTimeRangeVO getOccupiedRanges(Long roomId, LocalDate date) { LambdaQueryWrapperReservation wrapper new LambdaQueryWrapper(); wrapper.eq(Reservation::getRoomId, roomId) .eq(Reservation::getMeetingDate, date) .in(Reservation::getStatus, Arrays.asList(1, 2)) .orderByAsc(Reservation::getStartTime); ListReservation reservations reservationMapper.selectList(wrapper); return reservations.stream() .map(r - new TimeRangeVO(r.getStartTime(), r.getEndTime(), r.getMeetingTitle())) .collect(Collectors.toList()); }这两个方法基本覆盖了预订模块最重要的业务逻辑。4.3 前端页面实现会议室预订页面的核心交互前端这块不用把代码全贴出来我挑最关键的预订交互逻辑说。会议室列表页用Vue组件渲染每个会议室卡片上有一个“预订”按钮。点击预约后弹出对话框用户选择日期、开始时间、结束时间填写会议主题。选择日期后前端要立即请求GET /api/rooms/{id}/occupied?datexxx接口拿到当天已经被占用的时间段然后在前端做一次过滤把不可选的时间在时间选择器里禁用掉。Element Plus的el-date-picker组件可以通过disabled-hours和disabled-minutes属性来禁用具体时间点。前端拿到占用时间段后比如10:00-11:00被占了那就在10:00、10:30这些时间点上禁用用户就无法选择了。这是一种“前端提示后端校验”的双保险做法前端交互体验好后端兜底保证数据正确性。前端拦截的伪代码逻辑// 把占用时间段转换成不可选择的小时数组 const occupiedRanges this.occupiedTimeRanges; const disabledHours []; for (let hour 8; hour 21; hour) { const hh hour 10 ? 0 hour : String(hour); for (const range of occupiedRanges) { const startHour range.startTime.substring(0, 2); const endHour range.endTime.substring(0, 2); if (hour parseInt(startHour) hour parseInt(endHour)) { disabledHours.push(hour); break; } } }4.4 “完整交付物”包含什么论文、LW、调试定制标题里写了“完整前后端代码说明文档LW”这里我要给毕设的同学提个醒一套完整的毕设交付物远远不止源码通常包含以下四块源码与数据库脚本完整可运行的前后端工程代码、SQL初始化脚本包括建库建表语句和初始数据。如果你的环境有问题还得有配套的环境安装文档。说明文档包括项目介绍、技术选型、环境搭建步骤、功能演示说明。我见过不少同学的说明文档写得极简就两页纸答辩的时候老师问“你的系统怎么跑起来”他答不上来。把运行步骤写清楚是对自己负责。LW即论文论文结构通常包括选题背景和意义、国内外研究现状、需求分析、系统设计架构图、功能模块图、数据库ER图、系统实现截图代码描述、系统测试、总结与展望。这个结构是固定的主题不要偏把“复合型活动基地”、“面向企业用户”、“会议管理系统”这三个关键词在论文里反复强化。演示视频与答辩PPT录一段系统演示视频主要功能都操作一遍建议用OBS录屏画质清晰标注关键操作步骤。答辩PPT控制在10页左右不要贴大段代码多用架构图、功能截图和数据流图。调试定制这块我想多说一句你在实际做项目时会发现前后端联调是最大的时间黑洞。后端接口返回的数据结构和前端预期不一致、跨域没配好、日期格式解析失败……这些问题一调就是一下午。我的经验是先定好接口返回数据结构前后端并行开发定期联调。接口文档用Apifox或者Postman维护每个人改动接口都要同步更新文档。5. 常见问题与调试经验5.1 时间字段比较的坑这是会议室预订系统里最容易翻车的点。MySQL的TIME类型在Java里如果用String接收会出现一个经典问题09:00和9:00虽然语义一样但字符串比较结果不同。更危险的是如果你把TIME映射成了LocalTime在SQL里生成的条件是start_time 10:00MySQL会自动把字符串转成TIME不会出错。但有个场景会翻车如果你用String接收然后用compareTo做时间比较那你得到的结果基本是靠运气。解决方法是实体类中时间字段全部用LocalTime前端传参时用HH:mm格式JSON序列化时配置JsonFormat(pattern HH:mm)。5.2 跨域配置的正确姿势前后端分离的情况下跨域问题基本都会遇到。正确配置方式是写一个WebMvcConfigurer的CorsRegistryConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowCredentials(true)和allowedOriginPatterns(*)要配套使用如果只写了allowedOrigins(*)带凭证的请求会报错。如果你用了Sa-Token还要注意一个细节Sa-Token的拦截器会拦截OPTIONS请求导致跨域预检失败。解决办法是在拦截器配置时把OPTIONS请求放行registry.addInterceptor(new SaInterceptor(handle - { if (handle.getRequest().getMethod().equals(OPTIONS)) { return; } StpUtil.checkLogin(); })).addPathPatterns(/**);5.3 MyBatis-Plus逻辑删除导致的查询异常如果你启用了MyBatis-Plus的逻辑删除logic-delete-field: deleted那么所有查询都会自动带上deleted 0的条件。在写复合查询的时候如果用了自定义SQL比如Select注解逻辑删除的自动拼接可能不生效你要自己手动加上deleted 0。我踩过一次坑会议室列表一直查询出来是空的排查了半天才发现是自定义SQL没带逻辑删除条件。5.4 端口占用与启动失败java -jar启动的时候报端口被占用先执行netstat -ano | findstr 8080查看占用进程的PID然后在任务管理器里结束对应进程或者直接改application.yml里的server.port。这种情况经常发生在反复启动调试的时候不复杂但很烦人。服务器上部署时MySQL数据库可以连接不通。主要原因有两类一是MySQL没启动二是配置文件里的url写错。jdbc:mysql://localhost:3306/meeting_system这里的3306是MySQL默认端口如果你安装时改过端口务必同步修改。5.5 演示现场环境问题的应急方案毕设答辩和演示的时候最怕电脑出问题。我总结了三条建议提前一天用java -jar把后端启动浏览器打开前端页面完整走一遍主流程确认没问题数据库导出SQL脚本在U盘里备一份如果现场MySQL挂了可以快速重建数据库前端建议打包成静态文件放在后端的resources/static目录下这样启动一个服务就能访问完整系统。具体做法可以参考“vue打包放进springboot”的那个思路——把Vue工程的dist目录内容复制到Spring Boot的src/main/resources/static下后端启动后直接访问8080端口就能看到前端页面。这是最稳妥的演示方案。6. 可扩展方向与个人体会6.1 往这个系统上还能加什么功能如果你的时间有富余想把这个毕设做得更有亮点这几个方向值得考虑消息通知模块预订状态变化时通过邮件或者短信提醒用户。这个功能不需要写死先做个邮件通知就够了。用Spring Boot的spring-boot-starter-mail配置好邮箱SMTP代码就十几行。这是“锦上添花”的功能但答辩的时候说“我们提供了邮件自动通知服务”档次立马上来了。二维码签到给每场会议生成一个二维码参与人扫码签到。这个用Google ZXing库生成二维码存到数据库里前端展示扫码后把状态改成“已签到”。功能不复杂但能体现移动化思维。会议纪要模块每次会议结束后用户可以上传会议纪要文件PDF、Word关联到预订记录上。这个东西解决“会议结束后的资料归属”问题也是企业场景的真实需求。大屏展示如果基地有运营大屏可以做一版大屏可视化页面用ECharts展示当前各会议室使用状态、今日会议数量、本月使用率排行。大屏页面以炫酷为主多用一些深色背景发光边框的样式设计答辩效果很好。6.2 命名规范与代码习惯面试官会很看重的东西最后说一点可能听着老生常谈但实际很重要的东西。我审过不少Java毕设的代码很多项目功能做出来了但是代码风格惨不忍睹类名大饼一样拼在一起、方法名和参数名没有语义、Controller里塞了几百行SQL、Service方法又长又臭。哪怕功能全通论文写得也还行但代码这关一旦被细问就会暴露问题。几个基本要求你做到就算出彩了类名大驼峰方法名小驼峰常量全大写下划线分隔Controller别写业务逻辑只做参数接收和结果返回异常统一抛出BizException在全局异常处理器里统一捕获每一个Service方法写完以后回头读一遍看能不能用一句话说清楚它在干什么。说不清说明逻辑太复杂需要拆分我自己的习惯是写代码前先画一张“状态流转图”贴在显示器旁边比如预订状态从“待审批”到“已批准”到“已完成”哪些操作能触发状态变化画清楚再写代码效率高很多。6.3 写在最后的一些建议做这个项目下来我最大的体会是会议室预订系统看似简单但它是把企业真实业务的一个缩影——有资源管理、有权限控制、有状态流转、有冲突处理、有数据统计每块都不算难叠在一起就考验你的系统思维和代码组织能力了。如果你现在正在做这个选题我给你的行动建议是先把数据库的表建好再写后端接口接口都通了再写前端页面最后写论文。不要一上来就研究Vue的动画效果或者研究Sa-Token的高级玩法先把主流程跑通后面的优化才有意义。碰到调试不动的情况先看后端日志。Spring Boot的日志默认会打印SQL前提是你配了log-impl: StdOutImpl看到SQL就能定位问题一大半。别盯着浏览器控制台看前端报错很多时候只是表象真实原因在后端接口返回的数据里。我做过的类似系统里会议室预订这个需求我还扩展过空间管理把会议室、工位、停车位都纳入资源池和会议服务关联茶水、投影、速记服务一并下单的版本。如果以后你到企业里做类似的后台系统这套思路完全可以平移过去——核心都是“资源-时间-人”三者的关系处理。希望这篇写得够细你能从中找到可以抄作业的部分也希望能帮你避掉几个我当年踩过的坑。动手写起来吧代码这东西看十篇博文不如自己跑通一个接口来得实在。