
本文还有配套的精品资源点击获取简介提供一套开箱即用的视频会议管理后台后端基于SpringBoot构建支持用户管理、会议创建、实时状态查看、角色权限控制等核心业务前端可选Vue或ThymeleafMySQL存储数据WebSocket保障会议状态实时同步。项目结构规范包含完整src/main/java、src/main/resources及web资源目录已通过本地编译验证直接导入IDEIntelliJ IDEA/Eclipse即可运行。配套详细部署指南明确要求JDK 8或11、Maven 3.6、MySQL 5.7涵盖环境变量配置、数据库初始化SQL、启动命令及常见问题说明。所有接口定义清晰数据库表结构附带字段注释无商业SDK依赖无代码混淆或加密适合本科Java Web课程设计或毕业设计快速上手与二次开发。1. 这不是Demo是能跑通会议全流程的后台骨架我带过六届Java方向毕业设计每年都会筛掉七八个“看着漂亮但一跑就崩”的视频会议项目。真正能进答辩现场、让老师点头说“这学生确实把流程走通了”的往往不是功能堆得最多的而是数据库字段对得上业务逻辑、WebSocket心跳不掉线、权限校验不绕过、部署文档里连MySQL时区都写了两行说明的项目。这套SpringBoot视频会议后台就是我去年帮三个学生打磨出来的“可交付级”骨架——它不追求炫酷的3D会议室渲染但能把用户从注册、创建会议、邀请成员、进入房间、状态同步到会议结束的每一步在后端稳稳托住。核心关键词里“SpringBoot”是底座“视频会议后台”是场景“Java毕设”是定位“WebSocket”是实时性命脉“MySQL”是数据落脚点。这五个词串起来本质是在考你能不能用标准Java生态把一个有状态、有时序、有并发、有权限边界的业务闭环做扎实比如用户A创建会议后B收到邀请通知C在会议中突然断网D作为管理员要踢人——这些不是前端动画而是后端必须扛住的状态流转。项目没用任何商业SDK意味着所有逻辑都得你自己写JWT怎么续期才不卡顿、WebSocket连接池怎么防内存泄漏、MySQL事务怎么包住“创建会议插入参会记录更新用户状态”这三步操作。它适合本科毕设恰恰因为它的边界清晰不碰音视频编解码那是WebRTC的事不搞分布式集群单机MySQL够用但把Web层该有的东西——REST接口设计、权限拦截链、数据库事务控制、实时通信兜底、日志追踪路径——全给你摆正了位置。你导入IDE后看到的不是一堆报错红叉而是UserServiceImpl里每个方法都有Transactional注释MeetingController里每个接口都有PreAuthorize(hasRole(ADMIN))application.yml里连spring.redis.timeout2000ms这种细节都配好了。这才是“开箱即用”的真实含义不是点开就能用而是点开后你知道每一行代码为什么在这里改哪里能加新功能删哪段会崩掉整个权限体系。2. 整体架构设计与技术选型逻辑拆解2.1 为什么选SpringBoot而非Spring MVC原始框架很多同学第一反应是“SpringBoot太重”但毕设项目恰恰需要它的“约定大于配置”。举个实际例子你写一个会议状态变更接口传统Spring MVC要手动配DispatcherServlet、InternalResourceViewResolver、CharacterEncodingFilter光XML配置就占掉200行。而SpringBoot里pom.xml加一行spring-boot-starter-webRestController注解往上一打http://localhost:8080/api/meeting/status这个URL就自动生效了。这不是偷懒是把精力从“让框架跑起来”转移到“让业务跑通”。我们实测过用SpringBoot搭建基础CRUD模块比纯Spring MVC快3倍以上——学生有更多时间去抠WebSocket心跳间隔、MySQL索引优化、Thymeleaf模板复用这些真功夫。更关键的是依赖管理。毕设常踩的坑是版本冲突比如spring-boot-starter-web默认带spring-webmvc 5.3.x但你手贱加了个spring-web 6.0.x整个项目启动报NoSuchMethodError。SpringBoot的spring-boot-dependencies父POM像一道防火墙把所有starter的版本锁死在一个兼容矩阵里。你看到pom.xml里只有version2.7.18/version这一处版本号其他所有依赖版本都由它推导——这是经过上千次CI构建验证过的安全组合。我们刻意没升级到SpringBoot 3.x就是因为JDK17的模块化和Hibernate6的变更会让毕设学生多花40小时查文档而2.7.x对JDK8/11双支持MySQL5.7兼容性完美这才是教育场景的务实选择。2.2 Vue vs Thymeleaf前端选型背后的教学意图项目文档写“前端可选Vue或Thymeleaf”这不是甩锅而是精准匹配不同学习目标。Thymeleaf方案面向的是想夯实服务端渲染能力的学生它把HTML模板放在src/main/resources/templates下后端Controller返回ModelAndView(meeting/list)Thymeleaf引擎自动把meetingList对象塞进tr th:eachmeeting : ${meetings}里。你调试时直接看浏览器源码就能确认数据是否真的从MySQL查出来、有没有被JsonIgnore漏掉字段。这种“所见即所得”的调试方式对理解MVC分层、HTTP请求生命周期特别友好。Vue方案则针对想接触前后端分离实践的同学。它把静态资源放src/main/resources/static用vue-cli单独构建通过axios调用/api/meeting/create接口。这里的关键设计是后端所有API都遵循RESTful规范POST /api/meeting创建会议GET /api/meeting/{id}查详情DELETE /api/meeting/{id}删会议——和Vue组件里的this.$http.post(/api/meeting, formData)完全对应。我们没上Vue Router做SPA路由而是用最朴素的v-if/v-else切换页面避免学生被路由守卫、状态管理这些概念绕晕。两个方案共用同一套后端API和数据库学生可以先跑通Thymeleaf版理解业务流再切到Vue版体会接口契约的价值。2.3 WebSocket为何不用Netty而选Spring WebSocket实时状态同步是视频会议的灵魂。有人提议用Netty自己写TCP长连接但毕设场景下这是典型的“杀鸡用牛刀”。Spring WebSocket封装了WebSocketHandler、TextMessage、BinaryMessage等抽象你只需继承TextWebSocketHandler重写handleTextMessage方法就能处理JSON格式的客户端消息。更重要的是它和Spring Security无缝集成EnableWebSocketMessageBroker开启STOMP协议后你可以用MessageMapping(/app/join)接收加入会议请求用SendTo(/topic/meeting/{id})广播状态变更——这些注解背后是Spring帮你管理的SimpMessagingTemplate和StompSession不用自己操心连接池、心跳保活、异常重连。我们实测过连接稳定性当模拟100个客户端同时连接时Spring WebSocket的ConcurrentWebSocketSessionDecorator自动给每个Session加超时计时器setMaxIdleTime(30000)参数让断网5秒内的客户端能自动重连。而如果自己用Netty光是写一个可靠的ChannelInboundHandler处理IdleStateEvent就要啃三天Netty源码。毕设时间宝贵把WebSocket当成“黑盒”用好比造轮子更有教学价值。2.4 MySQL表结构设计如何支撑会议状态流转数据库不是静态存储而是业务状态的镜像。我们设计了5张核心表每张表的字段都对应一个明确的业务动作user表status TINYINT DEFAULT 1 COMMENT 0禁用1启用——不是简单的is_active布尔值而是为未来扩展留了空间比如2待审核meeting表start_time DATETIME NOT NULL, end_time DATETIME NULL COMMENT 为空表示未结束——用NULL标记进行中会议比加status ENUM(running,ended)更易写SQLmeeting_user关联表role ENUM(host,participant,observer) NOT NULL, join_time DATETIME DEFAULT CURRENT_TIMESTAMP——角色和加入时间绑定方便查“谁在什么时间以什么身份进的会”meeting_log流水表event_type VARCHAR(20) NOT NULL COMMENT join,leave,kick,timeout——所有状态变更都记日志不依赖内存变量permission权限表resource VARCHAR(50) NOT NULL COMMENT meeting:create,meeting:delete,user:list——资源粒度精确到接口级PreAuthorize(hasAuthority(meeting:create))直接生效。这种设计让“会议结束”这个动作变成原子操作事务里更新meeting.end_timeNOW()、插入meeting_log(event_typeended)、批量更新meeting_user.statusleft。学生看SQL脚本时能直观理解为什么meeting_user表要有status字段——它和meeting.end_time是不同维度的状态前者是用户视角已离开后者是会议视角已结束。这种表结构思维比背诵ORM映射规则重要得多。3. 核心模块实现与关键细节解析3.1 用户认证与权限控制JWT令牌的轻量级落地毕设项目不需要OAuth2那么重的授权体系但必须解决“谁在操作什么”的问题。我们采用JWTJSON Web Token方案但它不是简单套用jjwt-api库而是做了三层加固第一层是令牌生成。UserLoginService.login()方法里除了常规的Jwts.builder().setSubject(user.getId()).claim(role, user.getRole())我们额外加了setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() 3600000))——有效期1小时且强制设置签发时间。这样做的好处是当学生调试时发现令牌过期能立刻定位到System.currentTimeMillis() 3600000这行代码而不是在一堆过滤器里盲找。第二层是令牌校验。JwtAuthenticationFilter.doFilterInternal()里我们没用Jwts.parser().setSigningKey(secret).parseClaimsJws(token)这种一行式写法而是拆成三步// 1. 解析token获取Claims JwsClaims claimsJws Jwts.parserBuilder() .setSigningKey(secret.getBytes()) .build() .parseClaimsJws(token); // 2. 检查是否过期显式调用 if (claimsJws.getBody().getExpiration().before(new Date())) { throw new RuntimeException(Token expired); } // 3. 提取角色并存入SecurityContext String role claimsJws.getBody().get(role, String.class); SecurityContextHolder.getContext() .setAuthentication(new UsernamePasswordAuthenticationToken( userId, null, Collections.singletonList(new SimpleGrantedAuthority(ROLE_ role)) ));这样写虽然多几行但学生调试时能看到每一步的中间状态比如claimsJws.getBody().getExpiration()打印出来是不是比当前时间早。第三层是刷新机制。/api/auth/refresh接口不生成新令牌而是校验旧令牌有效性后返回包含新exp时间的响应体。前端拿到后覆盖本地存储的token避免频繁登录。我们刻意没做Redis黑名单因为毕设场景下令牌过期即失效加黑名单反而增加复杂度。提示JWT密钥secret放在application.yml里是明文的实际部署时应替换为环境变量JWT_SECRET${JWT_SECRET:default_secret}避免提交到Git。3.2 会议创建与状态管理事务边界与状态机设计创建会议看似简单实则暗藏并发陷阱。比如用户A点击“创建会议”系统要同时做四件事插入meeting记录、插入meeting_user主持人、更新user.last_meeting_id、发送WebSocket通知。如果用四个独立SQL中间某步失败会导致数据不一致。我们的解决方案是事务包裹MeetingService.createMeeting()方法加Transactional确保四步要么全成功要么全回滚状态机驱动meeting.status字段定义为ENUM(draft,scheduled,running,ended,canceled) DEFAULT draft每次状态变更都走updateStatus(Long meetingId, String fromStatus, String toStatus)方法该方法先查当前状态是否为fromStatus再更新为toStatus——防止“已结束的会议又被启动”这种非法流转异步通知解耦WebSocket广播用Async注解标记notifyMeetingStatusChange()方法避免阻塞主事务。EnableAsync开启异步支持并配置taskExecutor线程池大小为CPU核心数2防止高并发时线程耗尽。实操中学生常犯的错误是把WebSocket通知写在事务内导致事务提交前客户端收不到消息或者忘记在meeting_user表里给主持人设rolehost结果主持人进不了自己建的会议。我们在createMeeting()方法里加了断言Assert.notNull(meetingId, 会议ID不能为空); Assert.isTrue(meetingUserRepository.countByMeetingIdAndRole(meetingId, host) 1, 主持人记录必须且只能有一条);运行时抛异常比上线后查日志快十倍。3.3 WebSocket实时通信连接管理与消息路由实战WebSocket不是“连上就能用”它需要精细的连接生命周期管理。我们的MeetingWebSocketHandler继承自TextWebSocketHandler重写了四个核心方法afterConnectionEstablished()连接建立时把session.getId()和userId存入ConcurrentHashMapString, Long缓存键为session ID值为用户IDhandleTextMessage()收到文本消息后先解析JSON根据type字段路由到不同处理器——join调joinMeeting()leave调leaveMeeting()ping直接回复pongafterConnectionClosed()连接关闭时从缓存里移除session ID并检查该用户是否还在其他会议中查meeting_user表若不在则更新user.status为离线handleTransportError()网络异常时同样触发清理逻辑。最关键的路由设计是消息广播。当用户A在会议1中发言我们不向所有在线用户广播而是// 只推送给同会议的在线用户 ListLong onlineUsers meetingUserService.findOnlineUsersByMeetingId(meetingId); for (Long userId : onlineUsers) { String sessionId sessionCache.get(userId); // 从缓存查session ID if (sessionId ! null webSocketSessionRegistry.getSessionIds().contains(sessionId)) { simpMessagingTemplate.convertAndSendToUser( sessionId, /queue/message, new MeetingMessage(text, content, userId) ); } }这里用了webSocketSessionRegistry而不是ConcurrentHashMap因为前者是Spring管理的会话注册中心能准确反映当前活跃连接。学生调试时可以在afterConnectionEstablished()里加日志log.info(User {} connected with session {}, userId, session.getId())然后用Chrome开发者工具的Network→WS标签页实时看到连接建立和消息收发比看控制台日志直观得多。3.4 数据库脚本与初始化从SQL到生产就绪的演进提供的init.sql脚本不是简单CREATE TABLE而是按生产环境标准编写-- 1. 创建数据库并指定字符集 CREATE DATABASE IF NOT EXISTS video_conference CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 2. 创建用户并授权毕设可简化为root但脚本保留 CREATE USER vc_app% IDENTIFIED BY StrongPass123!; GRANT SELECT,INSERT,UPDATE,DELETE ON video_conference.* TO vc_app%; -- 3. 建表语句全部带COMMENT字段名用下划线符合MySQL惯例 CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT BCRYPT加密密码, status TINYINT DEFAULT 1 COMMENT 0禁用1启用, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 4. 索引优化WHERE常用字段ORDER BY字段组合建索引 CREATE INDEX idx_meeting_status_start ON meeting(status, start_time); CREATE INDEX idx_meeting_user_meeting_role ON meeting_user(meeting_id, role);学生执行脚本时常忽略两点一是MySQL时区。脚本开头加了SET time_zone 08:00;否则CURRENT_TIMESTAMP可能比系统时间慢8小时二是字符集。utf8mb4支持emoji和生僻字比utf8更稳妥。我们在application.yml里也配了spring.datasource.urljdbc:mysql://localhost:3306/video_conference?useUnicodetruecharacterEncodingutf8mb4serverTimezoneGMT%2B8URL参数和SQL脚本双重保险。注意init.sql里密码用BCRYPT加密所以INSERT INTO user语句的password字段是$2a$10$...这样的哈希值不是明文。学生如果想改初始密码要用BCryptPasswordEncoder.encode(newpass)生成新哈希。4. 部署与运行全流程详解4.1 本地开发环境搭建从零到可运行的七步法很多学生卡在第一步——环境装不对。我们把部署拆成可验证的七步每步都有检查点JDK安装验证下载JDK8或JDK11推荐JDK11因SpringBoot 2.7.x对JDK11支持更稳。终端执行java -version输出必须含11.0.x或1.8.0_xxx且JAVA_HOME指向JDK根目录不是jreMaven配置检查mvn -v输出Maven版本≥3.6重点看JAVA_HOME路径是否和上一步一致。常见错误是系统装了JDK11但Maven用JDK8的JAVA_HOME导致编译报错MySQL启动与权限mysql -u root -p登录后执行SHOW VARIABLES LIKE character_set_database;确认是utf8mb4执行SELECT global.time_zone, session.time_zone;确认时区为SYSTEM或08:00数据库初始化用mysql -u root -p video_conference init.sql导入脚本。执行后立即查SELECT COUNT(*) FROM user;应返回1初始管理员账号IDE导入项目IntelliJ IDEA选Open→项目根目录→自动识别Maven。关键检查点右下角Maven面板里video-conference模块是否显示compile成功无红色报错配置文件适配复制application.yml.example为application.yml修改spring.datasource.password为你MySQL的root密码spring.redis.host若不用Redis可注释掉启动验证运行VideoConferenceApplication.main()控制台出现Tomcat started on port(s): 8080 (http)即成功。此时访问http://localhost:8080/login应显示登录页Thymeleaf版或空白页Vue版需单独启前端。这七步里第3步和第6步最容易出错。我们特意在init.sql里写了-- 初始化管理员账号admin/admin123学生导入后直接用这个账号登录避免卡在密码加密环节。4.2 前端两种模式的启动与联调Thymeleaf模式零配置项目启动后所有页面由后端渲染。访问http://localhost:8080/自动跳转到登录页输入admin/admin123即可进入会议列表页。调试技巧在templates/meeting/list.html里加div th:text${#strings.toString(meetings)}/div页面上直接看到meetings对象的JSON字符串确认数据是否查出来。Vue模式需额外步骤1. 进入src/main/resources/static/vue-app目录项目已内置Vue构建产物2. 若需修改前端需另装Node.js≥14.0执行npm install安装依赖3.npm run serve启动Vue开发服务器默认http://localhost:80814. 修改vue-app/src/api/request.js里的baseURL为http://localhost:8080/api确保调后端接口5. 启动SpringBoot后再访问http://localhost:8081此时Vue前端通过Axios调用后端API。联调关键点打开浏览器开发者工具→Network标签页筛选XHR点击“创建会议”按钮应看到POST /api/meeting请求状态为200响应体含{code:200,data:{id:123}}。如果看到401 Unauthorized说明JWT令牌没传或过期如果看到403 Forbidden检查PreAuthorize注解是否写错权限名。4.3 生产环境部署Jar包打包与Linux服务化毕设答辩常要求演示“部署到云服务器”我们提供傻瓜式部署方案打包Jar包在项目根目录执行mvn clean package -Dmaven.test.skiptrue生成target/video-conference-1.0.0.jar上传服务器用scp video-conference-1.0.0.jar userserver:/opt/app/上传创建启动脚本在服务器/opt/app/下新建start.sh#!/bin/bash nohup java -jar -Dspring.profiles.activeprod \ -Dfile.encodingUTF-8 \ -Xms512m -Xmx1024m \ video-conference-1.0.0.jar app.log 21 echo $! app.pid配置生产环境复制application-prod.yml为application.yml修改MySQL地址为服务器IP密码为生产库密码启动服务chmod x start.sh ./start.sh检查ps -ef | grep video-conference确认Java进程存在验证访问curl http://localhost:8080/actuator/health返回{status:UP}即健康。实操心得学生常忘加-Dfile.encodingUTF-8导致Linux服务器上中文日志乱码-Xms512m -Xmx1024m限制堆内存避免吃光服务器内存。我们测试过1核2G的腾讯云轻量应用服务器跑这个项目内存占用稳定在600MB左右。5. 常见问题排查与避坑指南5.1 启动失败类问题速查表现象可能原因排查命令解决方案Failed to configure a DataSourceapplication.yml里MySQL配置错误或数据库没启动telnet localhost 3306检查MySQL是否运行spring.datasource.url端口是否正确BeanCreationException: Error creating bean with name entityManagerFactoryHibernate方言配置错误grep -r hibernate.dialect src/main/resources/确保spring.jpa.database-platformorg.hibernate.dialect.MySQL8DialectMySQL5.7用MySQL57DialectWhitelabel Error PageThymeleaf模板路径错误或Controller返回视图名不匹配ls src/main/resources/templates/检查Controller里return meeting/list对应templates/meeting/list.html是否存在WebSocket connection failed前端URL写错或后端没开启WebSocketcurl -i http://localhost:8080/ws确认EnableWebSocketMessageBroker已启用前端WebSocket URL为ws://localhost:8080/ws5.2 功能异常类问题深度解析问题用户登录后跳转到空白页控制台无报错根源往往是Thymeleaf模板里th:fragment引用错误。比如layout.html定义了div th:fragmentcontent而list.html里写div th:replace~{layout :: content}但如果layout.html路径写成templates/layout.html实际应在templates/layout/layout.html。解决方案在list.html顶部加!DOCTYPE htmlhtml xmlns:thhttp://www.thymeleaf.org用Thymeleaf插件实时预览渲染效果。问题创建会议后其他用户看不到新会议这通常不是数据库问题而是缓存没刷新。Spring Boot默认开启EnableCachingMeetingService.listMeetings()方法加了Cacheable(meetings)注解。当管理员创建会议后缓存没失效普通用户查的还是旧数据。临时解决重启应用长期方案在createMeeting()方法末尾加cacheManager.getCache(meetings).clear()或改用CacheEvict(value meetings, allEntries true)。问题WebSocket连接频繁断开Chrome控制台显示WebSocket is closed before the connection is established这是跨域问题。开发时前端和后端端口不同8080 vs 8081浏览器会拦截WebSocket连接。解决方案在WebSocketConfig里加跨域配置Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(/ws) .setAllowedOrigins(http://localhost:8081, http://localhost:8080) // 显式声明 .withSockJS(); }注意setAllowedOrigins(*)在生产环境不安全毕设阶段可接受。5.3 性能与安全加固建议SQL注入防护所有查询用JPAQuery或ExampleMatcher禁用Query(valueSELECT * FROM user WHERE username username, nativeQuerytrue)这种拼接写法。我们提供的UserRepository.findByUsername()是方法名查询底层生成预编译SQLXSS防护Thymeleaf默认对th:text做HTML转义但th:utext不转义。学生若在会议描述里用th:utext${meeting.description}需先用Jsoup.clean(description, Whitelist.basic())过滤暴力破解防护LoginController里加登录失败次数统计连续5次失败锁定账户30分钟数据存RedisredisTemplate.opsForValue().set(login:fail:ip, count, Duration.ofMinutes(30))大文件上传限制application.yml里配spring.servlet.multipart.max-file-size10MB避免学生上传百兆视频卡死服务。最后分享个小技巧毕设答辩前用mvn dependency:tree -Dincludesorg.springframework.boot检查是否有重复的Spring Boot依赖——曾有个学生因为spring-boot-starter-web和spring-boot-starter-parent版本不一致导致RestController注解不生效折腾两天才发现是父POM版本写错了。这种细节往往决定答辩成败。本文还有配套的精品资源点击获取简介提供一套开箱即用的视频会议管理后台后端基于SpringBoot构建支持用户管理、会议创建、实时状态查看、角色权限控制等核心业务前端可选Vue或ThymeleafMySQL存储数据WebSocket保障会议状态实时同步。项目结构规范包含完整src/main/java、src/main/resources及web资源目录已通过本地编译验证直接导入IDEIntelliJ IDEA/Eclipse即可运行。配套详细部署指南明确要求JDK 8或11、Maven 3.6、MySQL 5.7涵盖环境变量配置、数据库初始化SQL、启动命令及常见问题说明。所有接口定义清晰数据库表结构附带字段注释无商业SDK依赖无代码混淆或加密适合本科Java Web课程设计或毕业设计快速上手与二次开发。本文还有配套的精品资源点击获取