SpringBoot實(shí)現(xiàn)實(shí)時彈幕的示例代碼
實(shí)時彈幕系統(tǒng)已成為現(xiàn)代視頻網(wǎng)站和直播平臺的標(biāo)準(zhǔn)功能,它讓觀眾可以在觀看視頻時發(fā)送即時評論,這些評論會以橫向滾動的方式顯示在視頻畫面上,增強(qiáng)了用戶的互動體驗(yàn)和社區(qū)參與感。
本文將介紹如何使用SpringBoot構(gòu)建一個實(shí)時彈幕系統(tǒng)。
效果展示

一、實(shí)時彈幕系統(tǒng)概述
1.1 什么是彈幕系統(tǒng)
彈幕系統(tǒng)允許用戶發(fā)送的評論直接顯示在視頻畫面上,這些評論會從右向左橫向滾動。
1.2 彈幕系統(tǒng)特點(diǎn)
實(shí)時性:用戶發(fā)送的彈幕幾乎立即顯示在所有觀看者的屏幕上
互動性:觀眾可以直接"看到"其他人的反應(yīng),形成集體觀看體驗(yàn)
時間關(guān)聯(lián)性:彈幕通常與視頻的特定時間點(diǎn)關(guān)聯(lián)
視覺沖擊力:大量彈幕同時出現(xiàn)會形成獨(dú)特的視覺效果
二、技術(shù)設(shè)計
2.1 整體架構(gòu)
我們將構(gòu)建的彈幕系統(tǒng)包括以下主要組件:
1. 前端播放器:負(fù)責(zé)視頻播放和彈幕展示
2. WebSocket服務(wù):處理實(shí)時彈幕消息的傳遞
3. 彈幕存儲:保存歷史彈幕記錄
4. 內(nèi)容過濾組件:過濾不良內(nèi)容
2.2 通信協(xié)議選擇
實(shí)現(xiàn)實(shí)時彈幕系統(tǒng),我們需要選擇一個適合的通信協(xié)議。主要選項(xiàng)包括:
| 協(xié)議 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場景 |
|---|---|---|---|
| WebSocket | 全雙工通信,低延遲,廣泛支持 | 需要服務(wù)器保持連接,資源消耗較大 | 實(shí)時性要求高的場景 |
| SSE (Server-Sent Events) | 服務(wù)器推送,簡單實(shí)現(xiàn) | 只支持服務(wù)器到客戶端的單向通信 | 服務(wù)器推送更新場景 |
| 長輪詢 (Long Polling) | 兼容性好,實(shí)現(xiàn)簡單 | 效率低,延遲高 | 兼容性要求高的場景 |
此處選擇WebSocket進(jìn)行實(shí)現(xiàn)。
三、使用SpringBoot實(shí)現(xiàn)WebSocket服務(wù)
3.1 添加依賴
首先,在pom.xml中添加相關(guān)依賴:
<?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配置
創(chuàng)建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");
// 設(shè)置應(yīng)用程序前綴
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 注冊STOMP端點(diǎn),客戶端通過這個端點(diǎn)連接到WebSocket服務(wù)器
registry.addEndpoint("/ws-danmaku")
.setAllowedOriginPatterns("*")
.withSockJS(); // 啟用SockJS fallback選項(xiàng)
}
}3.3 定義彈幕消息模型
使用MyBatis-Plus實(shí)體定義:
@Data
@TableName("danmaku")
public class Danmaku {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "content", strategy = FieldStrategy.NOT_EMPTY)
private String content; // 彈幕內(nèi)容
@TableField("color")
private String color; // 彈幕顏色
@TableField("font_size")
private Integer fontSize; // 字體大小
@TableField("time")
private Double time; // 視頻時間點(diǎn)
@TableField("video_id")
private String videoId; // 關(guān)聯(lián)的視頻ID
@TableField("user_id")
private String userId; // 發(fā)送用戶ID
@TableField("username")
private String username; // 用戶名
@TableField("created_at")
private LocalDateTime createdAt; // 創(chuàng)建時間
}3.4 彈幕消息傳輸對象
@Data
public class DanmakuDTO {
private String content;
private String color = "#ffffff"; // 默認(rèn)白色
private Integer fontSize = 24; // 默認(rèn)字體大小
private Double time;
private String videoId;
private String userId;
private String username;
}3.5 定義Mapper接口
@Mapper
public interface DanmakuMapper extends BaseMapper<Danmaku> {
/**
* 根據(jù)視頻ID查詢所有彈幕,按時間排序
*/
@Select("SELECT * FROM danmaku WHERE video_id = #{videoId} ORDER BY time ASC")
List<Danmaku> findByVideoIdOrderByTimeAsc(@Param("videoId") String videoId);
/**
* 根據(jù)視頻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 彈幕服務(wù)
@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;
}
/**
* 保存并發(fā)送彈幕
*/
public Danmaku saveDanmaku(DanmakuDTO danmakuDTO) {
// 內(nèi)容過濾(簡單示例)
String filteredContent = filterContent(danmakuDTO.getContent());
// 創(chuàng)建彈幕實(shí)體
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());
// 保存到數(shù)據(jù)庫
danmakuMapper.insert(danmaku);
// 通過WebSocket發(fā)送到客戶端
messagingTemplate.convertAndSend("/topic/video/" + danmaku.getVideoId(), danmaku);
return danmaku;
}
/**
* 獲取視頻的所有彈幕
*/
public List<Danmaku> getDanmakusByVideoId(String videoId) {
return danmakuMapper.findByVideoIdOrderByTimeAsc(videoId);
}
/**
* 獲取指定時間范圍內(nèi)的彈幕
*/
public List<Danmaku> getDanmakusByVideoIdAndTimeRange(
String videoId, Double startTime, Double endTime) {
return danmakuMapper.findByVideoIdAndTimeBetween(videoId, startTime, endTime);
}
/**
* 簡單的內(nèi)容過濾實(shí)現(xiàn)
*/
private String filterContent(String content) {
// 實(shí)際應(yīng)用中這里可能會有更復(fù)雜的過濾邏輯
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;
}
/**
* 發(fā)送彈幕
*/
@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);
}
/**
* 獲取指定時間范圍內(nèi)的彈幕(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);
}
}四、前端實(shí)現(xiàn)
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;
transform: translateY( 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* 允許點(diǎn)擊穿透到視頻 */
}
.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="發(fā)送彈幕...">
<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">發(fā)送</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實(shí)現(xiàn)
// 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(實(shí)際應(yīng)用中可能從URL或其他地方獲?。?
const videoId = 'video123';
// 用戶信息(實(shí)際應(yīng)用中可能從登錄系統(tǒng)獲取)
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);
// 訂閱當(dāng)前視頻的彈幕頻道
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 => {
// 記錄歷史彈幕,用于播放到相應(yīng)時間點(diǎn)時顯示
window.historyDanmakus = danmakus;
console.log(`已加載${danmakus.length}條歷史彈幕`);
})
.catch(error => console.error('獲取歷史彈幕失敗:', error));
}
// 發(fā)送彈幕
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) {
// 創(chuàng)建彈幕元素
const danmakuElement = document.createElement('div');
danmakuElement.className = 'danmaku';
danmakuElement.textContent = danmaku.content;
danmakuElement.style.color = danmaku.color;
danmakuElement.style.fontSize = danmaku.fontSize + 'px';
// 隨機(jī)分配軌道(垂直位置)
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';
// 計算動畫持續(xù)時間(基于容器寬度)
const duration = 8 + Math.random() * 4; // 8-12秒
danmakuElement.style.animationDuration = duration + 's';
// 添加到容器
danmakuContainer.appendChild(danmakuElement);
// 動畫結(jié)束后移除元素
setTimeout(() => {
danmakuContainer.removeChild(danmakuElement);
}, duration * 1000);
}
// 視頻時間更新時,顯示對應(yīng)時間點(diǎn)的歷史彈幕
videoPlayer.addEventListener('timeupdate', function() {
const currentTime = videoPlayer.currentTime;
// 如果歷史彈幕已加載
if (window.historyDanmakus && window.lastCheckedTime !== Math.floor(currentTime)) {
window.lastCheckedTime = Math.floor(currentTime);
// 檢查是否有需要在當(dāng)前時間點(diǎn)顯示的彈幕
window.historyDanmakus.forEach(danmaku => {
// 如果彈幕時間點(diǎn)在當(dāng)前時間的±0.5秒內(nèi)且尚未顯示
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);
}
});
}
});
// 視頻跳轉(zhuǎn)時重置已顯示彈幕記錄
videoPlayer.addEventListener('seeking', function() {
window.displayedDanmakus = [];
});
// 綁定發(fā)送按鈕點(diǎn)擊事件
sendBtn.addEventListener('click', sendDanmaku);
// 綁定輸入框回車事件
danmakuInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendDanmaku();
}
});
// 連接WebSocket
connect();
});五、性能優(yōu)化與擴(kuò)展
5.1 性能優(yōu)化策略
1. 消息壓縮:對WebSocket消息進(jìn)行壓縮,減少網(wǎng)絡(luò)傳輸量
@Configuration
public class WebSocketMessageConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
// 啟用消息壓縮
registry.setMessageSizeLimit(128 * 1024) // 消息大小限制,防止大量彈幕導(dǎo)致的內(nèi)存問題
.setSendBufferSizeLimit(512 * 1024) // 發(fā)送緩沖區(qū)大小限制
.setSendTimeLimit(15 * 1000); // 發(fā)送超時限制
}
}2. 彈幕分頁加載:對于長視頻,分段獲取彈幕數(shù)據(jù)
@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. 前端渲染優(yōu)化:控制同時顯示的彈幕數(shù)量
// 在前端控制最大顯示彈幕數(shù)
const MAX_DANMAKU_COUNT = 100;
// 在showDanmaku函數(shù)中添加限制
function showDanmaku(danmaku) {
// 檢查當(dāng)前彈幕數(shù)量
const currentDanmakuCount = document.querySelectorAll('.danmaku').length;
if (currentDanmakuCount >= MAX_DANMAKU_COUNT) {
// 如果超過最大數(shù)量,移除最早的彈幕
const oldestDanmaku = document.querySelector('.danmaku');
if (oldestDanmaku) {
oldestDanmaku.remove();
}
}
// 原有彈幕顯示邏輯...
}5.2 彈幕過濾增強(qiáng)
對于敏感內(nèi)容過濾,可以實(shí)現(xiàn)更復(fù)雜的過濾系統(tǒng):
@Service
public class ContentFilterService {
private Set<String> sensitiveWords;
@PostConstruct
public void init() {
// 從配置文件或數(shù)據(jù)庫加載敏感詞
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);
}
}六、完整單機(jī)演示
6.1 項(xiàng)目結(jié)構(gòu)
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 應(yīng)用配置
# application.properties server.port=8080 # H2數(shù)據(jù)庫配置 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 數(shù)據(jù)庫初始化腳本
-- 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
);
-- 添加一些測試數(shù)據(jù)
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 主應(yīng)用類
@SpringBootApplication
@MapperScan("com.example.danmaku.mapper")
public class DanmakuApplication {
public static void main(String[] args) {
SpringApplication.run(DanmakuApplication.class, args);
}
}6.5 運(yùn)行與測試
1. 啟動SpringBoot應(yīng)用:
mvn spring-boot:run
2. 訪問應(yīng)用:
http://localhost:8080/index.html
3. 查看H2數(shù)據(jù)庫控制臺:
參考application.properties中的數(shù)據(jù)庫配置屬性
http://localhost:8080/h2-console
以上就是SpringBoot實(shí)現(xiàn)實(shí)時彈幕的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot實(shí)時彈幕的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
tio-boot?jfinal-plugins框架整合redis示例詳解
這篇文章主要為大家介紹了tio-boot?jfinal-plugins框架整合redis示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
IDEA下因Lombok插件產(chǎn)生的Library source does not match the bytecode報
這篇文章主要介紹了IDEA下因Lombok插件產(chǎn)生的Library source does not match the bytecode報錯問題及解決方法,親測試過好用,需要的朋友可以參考下2020-04-04
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(23)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07
超細(xì)講解Java調(diào)用python文件的幾種方式
有時候我們在寫java的時候需要調(diào)用python文件,下面這篇文章主要給大家介紹了關(guān)于Java調(diào)用python文件的幾種方式,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12

