⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 风象南 「风象南」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

实时弹幕系统已成为现代视频网站和直播平台的标准功能,它让观众可以在观看视频时发送即时评论。

这些评论会以横向滚动的方式显示在视频画面上,增强了用户的互动体验和社区参与感。

本文将介绍如何使用SpringBoot构建一个实时弹幕系统。

效果展示

一、实时弹幕系统概述

1.1 什么是弹幕系统

弹幕系统允许用户发送的评论直接显示在视频画面上,这些评论会从右向左横向滚动。

1.2 弹幕系统特点

实时性:用户发送的弹幕几乎立即显示在所有观看者的屏幕上
互动性:观众可以直接”看到”其他人的反应,形成集体观看体验
时间关联性:弹幕通常与视频的特定时间点关联
视觉冲击力:大量弹幕同时出现会形成独特的视觉效果

二、技术设计

2.1 整体架构

我们将构建的弹幕系统包括以下主要组件:

1. 前端播放器:负责视频播放和弹幕展示
2. WebSocket服务:处理实时弹幕消息的传递
3. 弹幕存储:保存历史弹幕记录
4. 内容过滤组件:过滤不良内容

2.2 通信协议选择

实现实时弹幕系统,我们需要选择一个适合的通信协议。主要选项包括:

协议 优点 缺点 适用场景
WebSocket 全双工通信,低延迟,广泛支持 需要服务器保持连接,资源消耗较大 实时性要求高的场景
SSE (Server-Sent Events) 服务器推送,简单实现 只支持服务器到客户端的单向通信 服务器推送更新场景
长轮询 (Long Polling) 兼容性好,实现简单 效率低,延迟高 兼容性要求高的场景

此处选择WebSocket进行实现。

三、使用SpringBoot实现WebSocket服务

3.1 添加依赖

首先,在pom.xml中添加相关依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cm</groupId>
<artifactId>springboot-danmaku</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

3.2 WebSocket配置

创建WebSocket配置类:

package com.example.danmaku.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 启用简单的消息代理,用于将消息返回给客户端
config.enableSimpleBroker("/topic");
// 设置应用程序前缀
config.setApplicationDestinationPrefixes("/app");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注册STOMP端点,客户端通过这个端点连接到WebSocket服务器
registry.addEndpoint("/ws-danmaku")
.setAllowedOriginPatterns("*")
.withSockJS(); // 启用SockJS fallback选项
}
}

3.3 定义弹幕消息模型

使用MyBatis-Plus实体定义:

@Data
@TableName("danmaku")
public class Danmaku {

@TableId(type = IdType.AUTO)
private Long id;

@TableField(value = "content", strategy = FieldStrategy.NOT_EMPTY)
private String content; // 弹幕内容

@TableField("color")
private String color; // 弹幕颜色

@TableField("font_size")
private Integer fontSize; // 字体大小

@TableField("time")
private Double time; // 视频时间点

@TableField("video_id")
private String videoId; // 关联的视频ID

@TableField("user_id")
private String userId; // 发送用户ID

@TableField("username")
private String username; // 用户名

@TableField("created_at")
private LocalDateTime createdAt; // 创建时间
}

3.4 弹幕消息传输对象

@Data
public class DanmakuDTO {
private String content;
private String color = "#ffffff"; // 默认白色
private Integer fontSize = 24; // 默认字体大小
private Double time;
private String videoId;
private String userId;
private String username;
}

3.5 定义Mapper接口

@Mapper
public interface DanmakuMapper extends BaseMapper<Danmaku> {

/**
* 根据视频ID查询所有弹幕,按时间排序
*/
@Select("SELECT * FROM danmaku WHERE video_id = #{videoId} ORDER BY time ASC")
List<Danmaku> findByVideoIdOrderByTimeAsc(@Param("videoId") String videoId);

/**
* 根据视频ID和时间范围查询弹幕
*/
@Select("SELECT * FROM danmaku WHERE video_id = #{videoId} AND time BETWEEN #{startTime} AND #{endTime} ORDER BY time ASC")
List<Danmaku> findByVideoIdAndTimeBetween(
@Param("videoId") String videoId,
@Param("startTime") Double startTime,
@Param("endTime") Double endTime);
}

3.6 弹幕服务

@Service
public class DanmakuService {

private final DanmakuMapper danmakuMapper;
private final SimpMessagingTemplate messagingTemplate;

@Autowired
public DanmakuService(DanmakuMapper danmakuMapper, SimpMessagingTemplate messagingTemplate) {
this.danmakuMapper = danmakuMapper;
this.messagingTemplate = messagingTemplate;
}

/**
* 保存并发送弹幕
*/
public Danmaku saveDanmaku(DanmakuDTO danmakuDTO) {
// 内容过滤(简单示例)
String filteredContent = filterContent(danmakuDTO.getContent());

// 创建弹幕实体
Danmaku danmaku = new Danmaku();
danmaku.setContent(filteredContent);
danmaku.setColor(danmakuDTO.getColor());
danmaku.setFontSize(danmakuDTO.getFontSize());
danmaku.setTime(danmakuDTO.getTime());
danmaku.setVideoId(danmakuDTO.getVideoId());
danmaku.setUserId(danmakuDTO.getUserId());
danmaku.setUsername(danmakuDTO.getUsername());
danmaku.setCreatedAt(LocalDateTime.now());

// 保存到数据库
danmakuMapper.insert(danmaku);

// 通过WebSocket发送到客户端
messagingTemplate.convertAndSend("/topic/video/" + danmaku.getVideoId(), danmaku);

return danmaku;
}

/**
* 获取视频的所有弹幕
*/
public List<Danmaku> getDanmakusByVideoId(String videoId) {
return danmakuMapper.findByVideoIdOrderByTimeAsc(videoId);
}

/**
* 获取指定时间范围内的弹幕
*/
public List<Danmaku> getDanmakusByVideoIdAndTimeRange(
String videoId, Double startTime, Double endTime) {
return danmakuMapper.findByVideoIdAndTimeBetween(videoId, startTime, endTime);
}

/**
* 简单的内容过滤实现
*/
private String filterContent(String content) {
// 实际应用中这里可能会有更复杂的过滤逻辑
String[] sensitiveWords = {"敏感词1", "敏感词2", "敏感词3"};
String filtered = content;

for (String word : sensitiveWords) {
filtered = filtered.replaceAll(word, "***");
}

return filtered;
}
}

3.7 弹幕控制器

package com.example.danmaku.controller;

import com.example.danmaku.dto.DanmakuDTO;
import com.example.danmaku.model.Danmaku;
import com.example.danmaku.service.DanmakuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/danmaku")
public class DanmakuController {

private final DanmakuService danmakuService;

@Autowired
public DanmakuController(DanmakuService danmakuService) {
this.danmakuService = danmakuService;
}

/**
* 发送弹幕
*/
@MessageMapping("/danmaku/send")
public Danmaku sendDanmaku(DanmakuDTO danmakuDTO) {
return danmakuService.saveDanmaku(danmakuDTO);
}

/**
* 获取视频的所有弹幕(REST API)
*/
@GetMapping("/video/{videoId}")
public ResponseEntity<List<Danmaku>> getDanmakusByVideoId(@PathVariable String videoId) {
List<Danmaku> danmakus = danmakuService.getDanmakusByVideoId(videoId);
return ResponseEntity.ok(danmakus);
}

/**
* 获取指定时间范围内的弹幕(REST API)
*/
@GetMapping("/video/{videoId}/timerange")
public ResponseEntity<List<Danmaku>> getDanmakusByTimeRange(
@PathVariable String videoId,
@RequestParam Double start,
@RequestParam Double end) {
List<Danmaku> danmakus = danmakuService.getDanmakusByVideoIdAndTimeRange(videoId, start, end);
return ResponseEntity.ok(danmakus);
}
}

四、前端实现

4.1 HTML和CSS

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>弹幕视频播放器</title>
<style>
#video-container {
position: relative;
width: 800px;
height: 450px;
margin: 0 auto;
background-color: #000;
overflow: hidden;
}

#video-player {
width: 100%;
height: 100%;
}

#danmaku-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* 允许点击穿透到视频 */
}

.danmaku {
position: absolute;
white-space: nowrap;
font-family: "Microsoft YaHei", sans-serif;
font-weight: bold;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
animation-name: danmaku-move;
animation-timing-function: linear;
animation-fill-mode: forwards;
}

@keyframes danmaku-move {
from {
transform: translateX(100%);
}
to {
transform: translateX(-100%);
}
}

#danmaku-form {
margin-top: 20px;
text-align: center;
}

#danmaku-input {
width: 60%;
padding: 8px;
border-radius: 4px;
border: 1px solid #ccc;
}

#color-picker {
margin: 0 10px;
}

#send-btn {
padding: 8px 16px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

#send-btn:hover {
background-color: #40a9ff;
}
</style>
</head>
<body>
<div id="video-container">
<video id="video-player" controls>
<source src="your-video-url.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<div id="danmaku-container"></div>
</div>

<div id="danmaku-form">
<input type="text" id="danmaku-input" placeholder="发送弹幕...">
<input type="color" id="color-picker" value="#ffffff">
<select id="font-size">
<option value="18">小</option>
<option value="24" selected>中</option>
<option value="30">大</option>
</select>
<button id="send-btn">发送</button>
</div>

<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.0/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
<script src="danmaku.js"></script>
</body>
</html>

4.2 JavaScript实现

// danmaku.js
document.addEventListener('DOMContentLoaded', function() {
// 获取DOM元素
const videoPlayer = document.getElementById('video-player');
const danmakuContainer = document.getElementById('danmaku-container');
const danmakuInput = document.getElementById('danmaku-input');
const colorPicker = document.getElementById('color-picker');
const fontSizeSelect = document.getElementById('font-size');
const sendBtn = document.getElementById('send-btn');

// 视频ID(实际应用中可能从URL或其他地方获取)
const videoId = 'video123';

// 用户信息(实际应用中可能从登录系统获取)
const userId = 'user' + Math.floor(Math.random() * 1000);
const username = '用户' + userId.substring(4);

// WebSocket连接
let stompClient = null;

// 连接WebSocket
function connect() {
const socket = new SockJS('/ws-danmaku');
stompClient = Stomp.over(socket);

stompClient.connect({}, function(frame) {
console.log('Connected to WebSocket: ' + frame);

// 订阅当前视频的弹幕频道
stompClient.subscribe('/topic/video/' + videoId, function(response) {
const danmaku = JSON.parse(response.body);
showDanmaku(danmaku);
});

// 获取历史弹幕
loadHistoryDanmaku();
}, function(error) {
console.error('WebSocket连接失败: ', error);
// 尝试重新连接
setTimeout(connect, 5000);
});
}

// 加载历史弹幕
function loadHistoryDanmaku() {
fetch(`/api/danmaku/video/${videoId}`)
.then(response => response.json())
.then(danmakus => {
// 记录历史弹幕,用于播放到相应时间点时显示
window.historyDanmakus = danmakus;
console.log(`已加载${danmakus.length}条历史弹幕`);
})
.catch(error => console.error('获取历史弹幕失败:', error));
}

// 发送弹幕
function sendDanmaku() {
const content = danmakuInput.value.trim();
if (!content) return;

const danmaku = {
content: content,
color: colorPicker.value,
fontSize: parseInt(fontSizeSelect.value),
time: videoPlayer.currentTime,
videoId: videoId,
userId: userId,
username: username
};

stompClient.send('/app/danmaku/send', {}, JSON.stringify(danmaku));

// 清空输入框
danmakuInput.value = '';
}

// 显示弹幕
function showDanmaku(danmaku) {
// 创建弹幕元素
const danmakuElement = document.createElement('div');
danmakuElement.className = 'danmaku';
danmakuElement.textContent = danmaku.content;
danmakuElement.style.color = danmaku.color;
danmakuElement.style.fontSize = danmaku.fontSize + 'px';

// 随机分配轨道(垂直位置)
const trackHeight = danmaku.fontSize + 5; // 轨道高度
const maxTrack = Math.floor(danmakuContainer.clientHeight / trackHeight);
const trackNumber = Math.floor(Math.random() * maxTrack);
danmakuElement.style.top = (trackNumber * trackHeight) + 'px';

// 计算动画持续时间(基于容器宽度)
const duration = 8 + Math.random() * 4; // 8-12秒
danmakuElement.style.animationDuration = duration + 's';

// 添加到容器
danmakuContainer.appendChild(danmakuElement);

// 动画结束后移除元素
setTimeout(() => {
danmakuContainer.removeChild(danmakuElement);
}, duration * 1000);
}

// 视频时间更新时,显示对应时间点的历史弹幕
videoPlayer.addEventListener('timeupdate', function() {
const currentTime = videoPlayer.currentTime;

// 如果历史弹幕已加载
if (window.historyDanmakus && window.lastCheckedTime !== Math.floor(currentTime)) {
window.lastCheckedTime = Math.floor(currentTime);

// 检查是否有需要在当前时间点显示的弹幕
window.historyDanmakus.forEach(danmaku => {
// 如果弹幕时间点在当前时间的±0.5秒内且尚未显示
if (Math.abs(danmaku.time - currentTime) <= 0.5 &&
(!window.displayedDanmakus || !window.displayedDanmakus.includes(danmaku.id))) {

// 记录已显示的弹幕ID
if (!window.displayedDanmakus) {
window.displayedDanmakus = [];
}
window.displayedDanmakus.push(danmaku.id);

// 显示弹幕
showDanmaku(danmaku);
}
});
}
});

// 视频跳转时重置已显示弹幕记录
videoPlayer.addEventListener('seeking', function() {
window.displayedDanmakus = [];
});

// 绑定发送按钮点击事件
sendBtn.addEventListener('click', sendDanmaku);

// 绑定输入框回车事件
danmakuInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendDanmaku();
}
});

// 连接WebSocket
connect();
});

五、性能优化与扩展

5.1 性能优化策略

1. 消息压缩:对WebSocket消息进行压缩,减少网络传输量

@Configuration
public class WebSocketMessageConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
// 启用消息压缩
registry.setMessageSizeLimit(128 * 1024) // 消息大小限制,防止大量弹幕导致的内存问题
.setSendBufferSizeLimit(512 * 1024) // 发送缓冲区大小限制
.setSendTimeLimit(15 * 1000); // 发送超时限制
}
}

2. 弹幕分页加载:对于长视频,分段获取弹幕数据

@GetMapping("/video/{videoId}/paged")
public ResponseEntity<IPage<Danmaku>> getPagedDanmakus(
@PathVariable String videoId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "100") int size) {

Page<Danmaku> pageParam = new Page<>(page, size);
QueryWrapper<Danmaku> queryWrapper = new QueryWrapper<Danmaku>()
.eq("video_id", videoId)
.orderByAsc("time");

IPage<Danmaku> danmakus = danmakuMapper.selectPage(pageParam, queryWrapper);
return ResponseEntity.ok(danmakus);
}

3. 前端渲染优化:控制同时显示的弹幕数量

// 在前端控制最大显示弹幕数
const MAX_DANMAKU_COUNT = 100;

// 在showDanmaku函数中添加限制
function showDanmaku(danmaku) {
// 检查当前弹幕数量
const currentDanmakuCount = document.querySelectorAll('.danmaku').length;
if (currentDanmakuCount >= MAX_DANMAKU_COUNT) {
// 如果超过最大数量,移除最早的弹幕
const oldestDanmaku = document.querySelector('.danmaku');
if (oldestDanmaku) {
oldestDanmaku.remove();
}
}

// 原有弹幕显示逻辑...
}

5.2 弹幕过滤增强

对于敏感内容过滤,可以实现更复杂的过滤系统:

@Service
public class ContentFilterService {

private Set<String> sensitiveWords;

@PostConstruct
public void init() {
// 从配置文件或数据库加载敏感词
sensitiveWords = new HashSet<>();
sensitiveWords.add("敏感词1");
sensitiveWords.add("敏感词2");
sensitiveWords.add("敏感词3");
// 可以从外部文件加载更多敏感词
}

public String filterContent(String content) {
if (content == null || content.isEmpty()) {
return content;
}

String filteredContent = content;
for (String word : sensitiveWords) {
filteredContent = filteredContent.replaceAll(word, "***");
}

return filteredContent;
}

// 添加敏感词
public void addSensitiveWord(String word) {
sensitiveWords.add(word);
}

// 移除敏感词
public void removeSensitiveWord(String word) {
sensitiveWords.remove(word);
}
}

六、完整单机演示

6.1 项目结构

src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── danmaku/
│ │ ├── DanmakuApplication.java
│ │ ├── config/
│ │ │ └── WebSocketConfig.java
│ │ ├── controller/
│ │ │ └── DanmakuController.java
│ │ ├── model/
│ │ │ └── Danmaku.java
│ │ ├── dto/
│ │ │ └── DanmakuDTO.java
│ │ ├── mapper/
│ │ │ └── DanmakuMapper.java
│ │ ├── service/
│ │ │ ├── DanmakuService.java
│ │ │ └── ContentFilterService.java
│ └── resources/
│ ├── application.properties
│ ├── schema.sql
│ └── static/
│ ├── index.html
│ └── danmaku.js

6.2 应用配置

# application.properties
server.port=8080

# H2数据库配置
spring.datasource.url=jdbc:h2:mem:danmakudb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# MyBatis-Plus配置
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.type-aliases-package=com.example.danmaku.model
mybatis-plus.global-config.db-config.id-type=auto

# WebSocket配置
spring.websocket.max-text-message-size=8192
spring.websocket.max-binary-message-size=8192

6.3 数据库初始化脚本

-- schema.sql
CREATE TABLE IF NOT EXISTS danmaku (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
content VARCHAR(255) NOT NULL,
color VARCHAR(20) DEFAULT '#ffffff',
font_size INT DEFAULT 24,
time DOUBLE NOT NULL,
video_id VARCHAR(50) NOT NULL,
user_id VARCHAR(50) NOT NULL,
username VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 添加一些测试数据
INSERT INTO danmaku (content, color, font_size, time, video_id, user_id, username, created_at)
VALUES
('这是第一条测试弹幕', '#ffffff', 24, 1.0, 'video123', 'user1', '测试用户1', CURRENT_TIMESTAMP),
('这是第二条测试弹幕', '#ff0000', 24, 3.0, 'video123', 'user2', '测试用户2', CURRENT_TIMESTAMP),
('这是第三条测试弹幕', '#00ff00', 24, 5.0, 'video123', 'user3', '测试用户3', CURRENT_TIMESTAMP),
('这是第四条测试弹幕', '#0000ff', 24, 7.0, 'video123', 'user4', '测试用户4', CURRENT_TIMESTAMP);

6.4 主应用类

@SpringBootApplication
@MapperScan("com.example.danmaku.mapper")
public class DanmakuApplication {

public static void main(String[] args) {
SpringApplication.run(DanmakuApplication.class, args);
}
}

6.5 运行与测试

  1. 启动SpringBoot应用:
mvn spring-boot:run
  1. 访问应用:
http://localhost:8080/index.html
  1. 查看H2数据库控制台:

参考application.properties中的数据库配置属性

http://localhost:8080/h2-console
文章目录
  1. 1. 一、实时弹幕系统概述
    1. 1.0.1. 1.1 什么是弹幕系统
    2. 1.0.2. 1.2 弹幕系统特点
  • 2. 二、技术设计
    1. 2.0.1. 2.1 整体架构
    2. 2.0.2. 2.2 通信协议选择
  • 3. 三、使用SpringBoot实现WebSocket服务
    1. 3.0.1. 3.1 添加依赖
    2. 3.0.2. 3.2 WebSocket配置
    3. 3.0.3. 3.3 定义弹幕消息模型
    4. 3.0.4. 3.4 弹幕消息传输对象
    5. 3.0.5. 3.5 定义Mapper接口
    6. 3.0.6. 3.6 弹幕服务
    7. 3.0.7. 3.7 弹幕控制器
  • 4. 四、前端实现
    1. 4.0.1. 4.1 HTML和CSS
    2. 4.0.2. 4.2 JavaScript实现
  • 5. 五、性能优化与扩展
    1. 5.0.1. 5.1 性能优化策略
    2. 5.0.2. 5.2 弹幕过滤增强
  • 6. 六、完整单机演示
    1. 6.0.1. 6.1 项目结构
    2. 6.0.2. 6.2 应用配置
    3. 6.0.3. 6.3 数据库初始化脚本
    4. 6.0.4. 6.4 主应用类
    5. 6.0.5. 6.5 运行与测试