资讯动态

Spring Boot弹幕视频网站完整源码:高并发写与WebSocket实时推送实战

发布时间:2026/8/29 21:10:52 来源:尧图企业网站定制
简介弹幕系统是理解高并发写入场景的经典工程模型其核心在于实时性500ms延迟、高吞吐百级QPS与数据一致性之间的平衡。技术原理上依赖WebSocket全双工通信实现低延迟广播结合Redis内存缓存应对突发写压再通过异步落库定时对账保障MySQL最终一致性。该架构规避了轮询的资源浪费和SSE的单向局限也比秒杀系统更聚焦于‘写多读少’的典型业务特征。在Java后端工程实践中它自然串联起Spring Boot的MVC分层、MyBatis-Plus批量操作、Redis缓存穿透防护及Nginx反向代理等关键技术点是毕业设计与新人进阶的理想闭环项目。1. 项目概述一个真实跑起来的弹幕视频网站到底长什么样你搜“JAVA毕业设计 springboot弹幕视频网站”页面刷出来一堆压缩包标题都差不多点开却常是空目录、报错堆栈、或者连登录页都打不开的半成品。我带过三届计算机专业毕设每年帮学生 debug 这类项目不下二十个——真正能从零部署、用户可注册、上传视频、发弹幕、后台能删内容、不崩不卡的完整闭环系统不到两成。这个标题里的“springboot完整源码说明”不是指打包了几个 Java 文件就叫完整而是指它必须覆盖用户端行为流注册→登录→浏览→播放→发弹幕→点赞、服务端数据流视频元信息入库→弹幕实时写入→Redis 缓存命中→MySQL 持久化→后台管理同步刷新、以及运维支撑流Nginx 静态资源代理→Spring Boot Actuator 健康检查→Logback 日志分级→Linux 下 service 启动脚本。它解决的不是“能不能跑”而是“能不能像一个真实小站那样稳住 50 个并发用户同时发弹幕不丢帧、不延迟、不炸库”。适合两类人一是大四学生赶毕设 deadline需要可演示、可答辩、能改能调的底座二是刚转 Java 的后端新人想用一个有血有肉的业务场景把 Spring Boot 的 Controller 层校验、Service 层事务、Mapper 层批量插入、WebSocket 弹幕推送、Redis 缓存穿透防护、MySQL 索引优化这些散知识点串成一条线。别被“弹幕”二字吓住——它本质是个高并发写场景的简化模型比电商秒杀门槛低但比博客系统更能暴露真实工程问题。2. 整体架构设计与技术选型逻辑为什么不用 VueSpring Cloud而选这套组合2.1 分层结构三层不是摆设是为毕设答辩留出解释空间这个项目的分层不是教科书照搬而是为应对答辩时老师那句“你这个弹幕是怎么实时推给所有人的”预留了清晰的解释路径。它采用经典MVC WebSocket 缓存双写架构表现层ViewThymeleaf 模板引擎 原生 JavaScript。没上 Vue/React因为毕设答辩时老师更关心“你怎么控制弹幕位置和速度”而不是“你怎么做组件通信”。Thymeleaf 能直接在 HTML 里写th:if${user ! null}答辩时打开浏览器开发者工具指着 DOM 树说“这里判断登录态”比解释 Vue 的响应式原理直观十倍。JS 部分只做两件事监听键盘回车触发弹幕发送、用 Canvas 渲染弹幕流——所有计算都在前端后端只管收和存降低服务器压力。控制层ControllerSpring MVC 标准写法但关键在接口粒度设计。比如/video/play/{id}不只是返回视频地址而是返回{videoUrl, title, duration, danmuList}四个字段。其中danmuList是从 Redis 缓存里取的最近 200 条弹幕不是查库避免播放页加载时拖慢首屏。而发弹幕接口/danmu/send是 POST接收{videoId, content, time}不做任何业务校验如敏感词过滤放 Service 层只做基础参数校验Valid注解让 Controller 保持轻量方便答辩时快速说明“这一层只做路由和参数检查”。服务层Service真正的业务中枢。这里藏着三个关键设计弹幕写入策略不是简单danmuMapper.insert(danmu)。而是先写 Redis ListLPUSH danmu:1001 {content:666,time:123.45}再异步写 MySQL用Async注解标记方法。为什么因为弹幕是典型的“写多读少”Redis 写入毫秒级MySQL 插入可能因索引、锁导致几十毫秒延迟。如果同步写库用户发完弹幕要等 200ms 才看到成功提示体验极差。异步写库还能扛住突发流量——Redis 队列当缓冲池MySQL 慢点写完就行。视频播放统计每次/video/play/{id}被访问Service 层会执行redis.incr(video:playcount: id)同时用EXPIRE设置 24 小时过期。这样首页热门榜直接ZINCRBY hot:videos 1 video:1001就能更新权重不用每秒查 MySQL 更新播放数避免热点视频引发的数据库连接池耗尽。后台管理联动管理员在/admin/video/delete?id1001删除视频时Service 层不仅删 MySQL 的video表记录还会DEL danmu:1001清 Redis 弹幕缓存并PUBLISH video:deleted 1001发布消息到 Redis Channel。前端管理页用SUBSCRIBE video:deleted监听收到消息立刻刷新视频列表——实现“删完即见效果”比轮询接口优雅得多。数据层DAOMyBatis-Plus 生成器 手动优化 SQL。自动生成的VideoMapper.xml只保留selectById和insert复杂查询如“按标签查视频统计弹幕数”必须手写select标签里面明确写LEFT JOIN danmu ON video.id danmu.video_id GROUP BY video.id并加SelectKey获取自增主键。为什么不用 JPA因为毕设答辩时老师问“你这个关联查询用了什么优化”你能指着 XML 里写的USE INDEX (idx_video_tag)说清楚比解释 Hibernate 的二级缓存机制实在。2.2 技术栈取舍为什么选 WebSocket 而不是 SSE 或轮询弹幕实时性要求高延迟 500ms但并发量中等单视频峰值 200 人所以必须在实现复杂度和实时性间找平衡点。有人用 Server-Sent EventsSSE但它单向服务端→客户端无法处理“用户发弹幕”的反向请求也有人用 Ajax 轮询但每秒一次请求对服务器是灾难——200 个用户就是 200 QPS光 HTTP 头部解析就压垮 Tomcat。WebSocket 是唯一合理选择但关键在如何集成进 Spring Boot。项目用的是spring-boot-starter-websocket而非 Netty 手写。理由很实际毕设时间紧Netty 需要自己处理心跳、断线重连、消息编解码调试成本太高。Spring 的MessageMapping注解能直接把 JSON 消息映射到 Java 对象比如前端发{type:send,videoId:1001,content:顶}后端MessageMapping(/app/danmu)方法参数直接接DanmuMessage messageSpring 自动完成 JSON → Object 转换。更关键的是它内置SimpMessagingTemplate支持向指定用户或频道广播。比如用户 A 发弹幕后端处理完存库立刻执行template.convertAndSend(/topic/danmu/1001, danmu)所有订阅/topic/danmu/1001的客户端即正在看视频 1001 的所有人都会收到消息。这个设计让答辩时能清晰画出“前端发→WebSocket 入口→Service 处理→Redis 写→WebSocket 广播→前端收”的全链路图比解释“我用 Redis Pub/Sub 自己实现”更有说服力。提示WebSocket 连接数限制是隐形坑。Tomcat 默认最大连接数 10000但 Linux 系统对单进程文件描述符限制通常是 1024。毕设演示时如果模拟 2000 人并发必现java.io.IOException: Too many open files。解决方案不是调大 ulimit答辩现场没权限而是在 application.yml 中显式配置server: tomcat: max-connections: 5000 accept-count: 100这样即使系统级限制未改Tomcat 也会在队列满时拒绝新连接返回 503 而非崩溃保证演示稳定性。2.3 为什么用 Redis 而不是 MySQL 存弹幕数据一致性怎么保弹幕数据有三大特征写频次极高每秒百条、读时效性强只读最近 200 条、持久化要求低丢了 10 条不影响体验。MySQL 为保证 ACID每次写都要落盘、写 redo log、更新索引 B 树单条插入 10ms 是常态。而 Redis 的LPUSH命令是纯内存操作平均 0.1ms。实测对比1000 条弹幕写入MySQL 耗时 8.2 秒Redis 耗时 0.03 秒。但问题来了Redis 数据断电就丢MySQL 才是唯一真相源。怎么保证“用户看到的弹幕”和“数据库存的弹幕”一致项目采用缓存双写 最终一致性策略写流程用户发弹幕 → 先LPUSH danmu:{videoId} {json}到 Redis → 再异步INSERT INTO danmu (...) VALUES (...)到 MySQL。读流程播放页加载 →LRANGE danmu:{videoId} 0 199取最近 200 条 → 如果 Redis 返回空首次加载或缓存被清则SELECT * FROM danmu WHERE video_id ? ORDER BY time DESC LIMIT 200查 MySQL并将结果RPUSH回 Redis。一致性兜底每天凌晨 2 点执行定时任务扫描 Redis 中所有danmu:*key对每个 key 执行LLEN获取当前长度再查 MySQL 对应video_id的弹幕总数。如果 Redis 长度 MySQL 总数的 95%则全量同步SELECT * FROM danmu WHERE video_id ? ORDER BY id DESC LIMIT 200然后DEL danmu:{videoId}再RPUSH新数据。这个任务在Scheduled(cron 0 0 2 * * ?)下运行不影响白天业务。这个设计答辩时很好讲“Redis 是高速缓存MySQL 是保险柜。用户看到的是缓存里的快照保险柜里永远有完整备份。我们用定时任务定期对账确保快照不偏离真相太远。”比强行追求强一致性如用分布式事务更符合毕设务实精神。3. 核心功能实现细节从发一条弹幕到它飞过屏幕的全过程3.1 弹幕发送与接收WebSocket 如何扛住并发写入前端发送弹幕的 JS 代码只有 7 行但背后是精心设计的防刷机制// 前端发送前本地限频 let lastSendTime 0; function sendDanmu() { const now Date.now(); if (now - lastSendTime 2000) { // 2秒内只能发1条 alert(发送太频繁请稍后再试); return; } lastSendTime now; stompClient.send(/app/danmu, {}, JSON.stringify({ videoId: currentVideoId, content: document.getElementById(danmuInput).value, time: getCurrentVideoTime() // 当前播放进度单位秒 })); }后端接收端点MessageMapping(/app/danmu)的方法签名是MessageMapping(/app/danmu) SendTo(/topic/danmu/{videoId}) // 动态订阅主题 public DanmuVO handleDanmu(DestinationVariable Long videoId, Valid DanmuDTO dto, SimpMessageHeaderAccessor header) { // 1. 从 header 获取用户IDWebSocket 连接时已认证 String username (String) header.getSessionAttributes().get(username); if (username null) throw new RuntimeException(未登录); // 2. 敏感词过滤调用 Service 层 String filteredContent sensitiveWordService.filter(dto.getContent()); // 3. 构建弹幕实体 Danmu danmu new Danmu(); danmu.setVideoId(videoId); danmu.setContent(filteredContent); danmu.setTime(dto.getTime()); danmu.setUserId(getUserIdByUsername(username)); danmu.setCreateTime(LocalDateTime.now()); // 4. 写 Redis核心LPUSH EXPIRE redisTemplate.opsForList().leftPush(danmu: videoId, JSONObject.toJSONString(danmu)); redisTemplate.expire(danmu: videoId, 7, TimeUnit.DAYS); // 7天过期 // 5. 异步写 MySQL danmuService.asyncSave(danmu); // 6. 返回 VO 给前端含用户头像URL用于前端显示 return new DanmuVO(danmu, getUserAvatarUrl(username)); }关键点在于DestinationVariable Long videoId—— 它从 WebSocket 路径/app/danmu/1001中自动提取videoId让SendTo(/topic/danmu/{videoId})能精准广播到该视频专属频道。这样 100 个视频就有 100 个独立频道互不干扰。实测 200 人同时发弹幕Tomcat 线程池http-nio-8080-exec-xx占用稳定在 15 个以内CPU 使用率 35%远低于警戒线。注意SimMessageHeaderAccessor是获取 WebSocket Session 属性的关键。很多毕设项目在这里翻车——把用户 ID 存在 HTTP Session 里但 WebSocket 连接是独立的HTTP Session 属性拿不到。正确做法是在建立 WebSocket 连接时EventListener监听SessionConnectedEvent把用户信息存入header.getSessionAttributes()后续所有消息都能通过header.getSessionAttributes().get(username)取到。3.2 弹幕渲染引擎Canvas 如何实现千条弹幕不卡顿前端弹幕渲染不用 CSS 动画animation: move 10s linear infinite因为 CSS 动画在 100 条弹幕时浏览器重排重绘压力巨大iPhone SE 上直接掉帧。项目用 Canvas 2D API 手写渲染循环核心思想是对象池复用 requestAnimationFrame 节流// 弹幕对象池避免频繁 new/delete const danmuPool []; function getDanmu() { return danmuPool.length 0 ? danmuPool.pop() : new DanmuObject(); } function recycleDanmu(d) { d.reset(); // 清空属性 danmuPool.push(d); } // 渲染主循环 let danmuList []; // 当前屏幕上的弹幕数组 function renderLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空画布 // 1. 更新所有弹幕X坐标根据速度和时间差 const now Date.now(); for (let i 0; i danmuList.length; i) { const d danmuList[i]; d.x - d.speed * (now - d.lastTime) / 16; // 16ms 一帧 d.lastTime now; // 2. 移除飞出屏幕的弹幕 if (d.x d.width 0) { recycleDanmu(d); danmuList.splice(i, 1); i--; // 数组变短索引要回退 } } // 3. 绘制剩余弹幕 for (let d of danmuList) { ctx.font ${d.fontSize}px sans-serif; ctx.fillStyle d.color; ctx.fillText(d.content, d.x, d.y); } requestAnimationFrame(renderLoop); }每条弹幕对象DanmuObject包含x,y,speed,fontSize,color,content等属性reset()方法将其全部置为默认值。这样创建 1000 条弹幕实际只 new 了 100 个对象池大小其余都是复用。Canvas 渲染帧率稳定在 60fps即使 iPhone 6s 也能流畅播放。答辩时打开 Chrome DevTools 的 Rendering 面板能看到“FPS Meter”始终绿条满格比展示 CSS 动画的掉帧截图更有说服力。3.3 视频上传与转码为什么用 FFmpeg 而不是纯 Java 实现毕设网站不可能让用户上传 2GB 的原始 MP4必须转码为 Web 友好格式H.264AAC分辨率 ≤ 1280x720。Java 生态没有成熟视频转码库硬编码效率低下。项目采用FFmpeg 命令行调用 异步队列方案用户上传文件 → Controller 接收MultipartFile→ 保存到临时目录/tmp/upload/xxx.mp4Service 层构建 FFmpeg 命令ffmpeg -i /tmp/upload/xxx.mp4 \ -c:v libx264 -preset fast -crf 23 -vf scale1280:720:force_original_aspect_ratiodecrease,pad1280:720:(ow-iw)/2:(oh-ih)/2 \ -c:a aac -b:a 128k \ -movflags faststart \ /data/video/1001.mp4关键参数解释-preset fast编码速度与质量平衡比ultrafast体积小 30%比medium快 2 倍-crf 23恒定质量模式23 是视觉无损的临界值再低体积暴增再高画质肉眼可见下降-vf视频滤镜链先缩放保持宽高比再加黑边填满 1280x720避免拉伸变形-movflags faststart把 moov atom 移到文件开头实现“边下边播”否则用户得等整个文件下载完才能开始播放。用ProcessBuilder执行命令并重定向 stdout/stderr 到日志文件转码完成后用Files.move()将输出文件移到正式目录更新 MySQL 中video.url字段。这个方案的好处是FFmpeg 是工业标准参数调优文档齐全答辩时能说出每个参数的作用。比用 JavaCV 封装 FFmpeg 更透明也比用云转码服务如阿里云媒体转码更可控——毕设演示不能依赖外部网络。4. 毕设落地关键步骤从解压源码到成功演示的全流程4.1 环境准备JDK、MySQL、Redis、Nginx 四件套怎么配这不是复制粘贴就能跑的项目环境配置是第一道坎。按顺序来JDK 1.8必须用 1.8因为 Spring Boot 2.7.x项目所用版本官方最低要求 JDK 8。java -version输出必须是1.8.0_3XX。如果装了 JDK 17mvn clean package会报Unsupported class file major version 61错误。Windows 用户注意环境变量JAVA_HOME指向C:\Program Files\Java\jdk1.8.0_3XXPATH加%JAVA_HOME%\bin别漏掉%符号。MySQL 5.7用 8.0 会遇到mysql-connector-java驱动兼容性问题。建库语句CREATE DATABASE danmu_video DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER danmu% IDENTIFIED BY Danmu123; GRANT ALL PRIVILEGES ON danmu_video.* TO danmu%; FLUSH PRIVILEGES;关键是utf8mb4字符集否则用户昵称里的 emoji如 存不进去变成??。application.yml中 JDBC URL 必须带?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai。Redis 6.2Windows 用户别用老版 Redis for Windows推荐用 WSL2 安装原生 Redis。redis.conf关键配置bind 0.0.0.0 # 允许远程连接毕设演示需外网访问 requirepass DanmuRedis2024 # 密码application.yml 中要匹配 maxmemory 512mb # 防止吃光内存 maxmemory-policy allkeys-lru # 内存满时淘汰最久未用keyNginx 1.20不是必须但强烈建议。作用有三静态资源代理location /video/ { alias /data/video/; }让视频直连 Nginx不走 Spring Boot减轻 JVM 压力WebSocket 反向代理location /ws/ { proxy_pass http://localhost:8080/ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; }解决 WebSocket 跨域和连接中断问题HTTPS 支持用 Lets Encrypt 免费证书演示时地址栏显示绿色小锁显得更专业。实操心得MySQL 和 Redis 的密码千万别用root或123456。答辩老师会用telnet your-server-ip 3306测试 MySQL 是否开放如果密码弱当场指出“安全性缺失”扣分。我建议密码用Danmu2024这种带年份和项目名的组合既易记又满足复杂度要求。4.2 源码编译与启动Maven 依赖冲突怎么破解压后进入danmu-video目录执行mvn clean package -Dmaven.test.skiptrue。常见失败原因及解法Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile说明 JDK 版本不对。检查pom.xml中java.version1.8/java.version再确认mvn -v输出的 Java 版本。Could not resolve dependencies for project ...:spring-boot-starter-websocket:jar:2.7.18Maven 中央仓库抽风。在pom.xml的repositories下加阿里云镜像repository idaliyunmaven/id nameAliyun Maven/name urlhttps://maven.aliyun.com/repository/public/url /repositoryCaused by: java.lang.NoClassDefFoundError: org/apache/commons/io/FileUtilsMyBatis-Plus 依赖的 commons-io 版本冲突。在pom.xml中强制指定版本dependency groupIdcommons-io/groupId artifactIdcommons-io/artifactId version2.11.0/version /dependency打包成功后target/danmu-video-1.0.jar就是可执行文件。启动命令java -jar -Dspring.profiles.activeprod danmu-video-1.0.jar-Dspring.profiles.activeprod指定生产环境配置会读取application-prod.yml里面包含真实的 MySQL 和 Redis 地址。别用devprofile它默认 H2 内存数据库演示时数据全丢。4.3 首次访问与数据初始化如何快速获得可演示内容Jar 启动后浏览器访问http://your-server-ip:8080会看到登录页。但此时数据库是空的没视频、没用户。项目提供init.sql初始化脚本包含管理员账号admin / Admin123MD5 加密存储3 个测试视频/data/video/1001.mp4已转码好的 10MB 文件MySQL 中对应video表记录50 条测试弹幕INSERT INTO danmu (video_id, content, time, user_id, create_time) VALUES (1001, 这视频太棒了, 12.34, 1, 2024-01-01 10:00:00);。执行方式用 MySQL 客户端连接source /path/to/init.sql。注意路径要用绝对路径且init.sql中的视频文件路径/data/video/1001.mp4必须和你服务器上的实际路径一致。如果视频文件放在/home/user/videos/就要把 SQL 里的路径全替换成那个。踩过的坑init.sql里INSERT语句的create_time用2024-01-01 10:00:00但 MySQL 严格模式下会报错Incorrect datetime value。解决方案是改成NOW()或在my.cnf中关闭严格模式sql_modeSTRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION。4.4 演示话术设计答辩时如何讲清楚技术亮点毕设答辩不是代码朗诵而是讲故事。我帮学生设计的 5 分钟话术框架开场30秒“老师好我做的是一款支持实时弹幕互动的视频网站核心目标是验证 Spring Boot 在高并发写场景下的工程实践能力。系统已部署在腾讯云轻量应用服务器公网可访问。”架构亮点90秒“架构上采用分层设计。表现层用 Thymeleaf 保证答辩可解释性控制层用 RESTful 接口发弹幕接口/danmu/send做了参数校验服务层重点解决了弹幕高并发写问题——先写 Redis 缓存再异步写 MySQL实测 200 并发下延迟 200ms数据层用 MyBatis-Plus但复杂查询手写 SQL 并加索引优化。”技术难点90秒“最大的难点是弹幕实时性与稳定性的平衡。我选 WebSocket 而非轮询用SendTo(/topic/danmu/{videoId})实现精准广播为防刷前端加 2 秒限频后端用 Redis List 做缓冲为保数据设计缓存双写每日对账机制确保 Redis 和 MySQL 数据最终一致。”演示环节60秒“现在我演示登录管理员账号上传一个新视频点击‘上传视频’按钮选择本地文件等待转码完成然后用普通用户账号登录播放这个视频发送弹幕输入‘答辩顺利’回车可以看到弹幕实时飞过屏幕同时后台管理页的弹幕数立刻增加——证明 WebSocket 广播和数据同步生效。”总结30秒“通过这个项目我掌握了 Spring Boot 全栈开发流程特别是高并发场景下的缓存设计、异步处理和 WebSocket 集成。代码已开源文档齐全欢迎老师批评指正。”这套话术把技术点转化为问题-方案-效果的逻辑链比罗列“用了 Spring Boot、Redis、MySQL”有力得多。5. 常见问题排查与避坑指南那些让毕设挂掉的隐形炸弹5.1 启动报错java.lang.IllegalStateException: Failed to load ApplicationContext这是 Spring Boot 启动失败的万能错误根源在ApplicationContext初始化失败。按优先级排查现象原因解决方案Caused by: java.net.ConnectException: Connection refused (Connection refused)MySQL 或 Redis 服务没启动或application.yml中 host/port 写错systemctl status mysqld和redis-cli -h 127.0.0.1 -p 6379 -a DanmuRedis2024 ping测试连通性检查application-prod.yml中spring.redis.host是否为127.0.0.1不是localhost某些 DNS 解析慢Caused by: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failureMySQL 连接超时或max_connections被占满在 MySQL 中执行SHOW VARIABLES LIKE max_connections;如果 200执行SET GLOBAL max_connections 500;检查application.yml中spring.datasource.hikari.connection-timeout是否设为3000030秒Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name sqlSessionFactoryMyBatis Mapper XML 文件路径错误或MapperScan注解没扫到包确认resources/mapper/目录下有VideoMapper.xml且pom.xml中build段有resources配置确保 XML 文件被打包进 jar检查MapperScan(com.danmu.mapper)的包路径是否和实际 mapper 接口路径一致注意application.yml中数据库密码如果含特殊字符如必须用单引号包裹password: Danmu2024。否则 YAML 解析器会把当作锚点导致密码截断。5.2 功能异常弹幕发不出、视频播不了、后台登不上问题排查路径关键命令/操作弹幕发不出控制台报WebSocket is already in CLOSING or CLOSED stateWebSocket 连接被意外关闭检查 Nginx 配置中proxy_read_timeout 300;是否设置默认 60 秒WebSocket 长连接需更长浏览器 F12 Network 标签页筛选WS看连接状态是否为Established视频播放页空白Network 显示404 Not FoundNginx 静态资源代理失效curl -I http://localhost/video/1001.mp4如果返回404检查 Nginxlocation /video/的alias路径是否指向/data/video/且该目录有1001.mp4文件权限为nginx用户可读chown -R nginx:nginx /data/video/管理员登录失败提示“用户名或密码错误”密码加密方式不匹配检查UserServiceImpl中密码加密是否用BCryptPasswordEncoder.encode(Admin123)对比数据库user.password字段值是否以$2a$10$开头BCrypt 标识如果不是手动执行UPDATE user SET password$2a$10$... WHERE usernameadmin;5.3 性能瓶颈演示时卡顿、响应慢、CPU 爆表现象根因分析优化措施播放页加载慢Chrome Network 显示video/1001.mp4请求耗时 5s视频文件太大或 Nginx 没启用 gzip在nginx.conf的http块中加gzip on;gzip_types application/javascript text/css text/xml;注意视频文件不 gzip但 HTML/CSS/JS 会压缩减少首屏加载时间发弹幕后其他用户 3 秒才看到WebSocket 广播延迟或 Redis 写入慢检查redis-cli monitor看LPUSH danmu:1001命令是否立即执行如果延迟可能是 Redis 持久化阻塞save 900 1触发 RDB改为appendonly yesappendfsync everysecTomcat 线程池http-nio-8080-exec-xx占满top显示 Java 进程 CPU 99%某个接口死循环或数据库慢查询jstack -l pid导出线程堆栈搜索RUNNABLE状态的线程看是否卡在DanmuService.save()或VideoMapper.selectById()用show processlist查 MySQL 是否有Sleep状态的长连接实操心得演示前必做三件事1.free -h看内存剩余 1G2.df -h看磁本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价