Socket
backend
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<!-- <exclusions>-->
<!-- <!– 排除 WebFlux 中不需要的 Spring MVC –>-->
<!-- <exclusion>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
</dependency>
WebSocket Gateway(Spring Boot 與 Nginx 的 WebSocket 配置)
Nginx 將 /ws 請求代理到 Spring Boot 的 WebSocket。
Spring Boot 使用 MessageController 處理消息映射和廣播。
package com.feddoubt.YT1.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) {
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS(); // 注册WebSocket端点
}
}
package com.feddoubt.YT1.controller.v1;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Slf4j
@Controller
public class MessageController {
@MessageMapping("/send") // 映射到 /app/send 的消息
String sendMessage(String message) {
return message;
}
@SendTo("/topic/convert") // 广播到 /topic/messages 的所有订阅者
public String sendMessageConvert(String message) {
return message;
}
@SendTo("/topic/embedUrl") // 广播到 /topic/messages 的所有订阅者
public String sendMessageEmbedUrl(String message) {
return message;
}
}
Spring Boot(服務端應用邏輯)
處理來自用戶和消息隊列的請求。
使用 NotificationService 將消息通過 WebSocket 發送給前端。
package com.feddoubt.YT1.service;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
private final SimpMessagingTemplate messagingTemplate;
public NotificationService(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
// 推送消息到指定主題
public void sendNotification(String topic, String message) {
messagingTemplate.convertAndSend(topic, message);
}
}
fronted
用戶端(前端 Vue.js 應用)
用戶操作按鈕,下載 MP3 或顯示嵌入的 YouTube 視頻。
通過 WebSocket 與後端進行即時通信,訂閱消息主題(/topic/convert 和 /topic/embedUrl)。
<button v-if="filename" @click="downloadMP3">Download {{ downloadformat.toUpperCase() }}</button>
<div v-if="embedUrl" class="embedUrl">
<iframe :src="embedUrl" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div>
const { createApp } = Vue;
createApp({
data() {
return {
filename: '',
embedUrl: '',
};
},
mounted() {
this.connectws();
},
methods: {
connectws() {
const socket = new SockJS('http://localhost:8801/ws'); // Nginx 或直接访问 Spring Boot 地址
const client = webstomp.over(socket);
client.connect(
{},
() => {
console.log("Connected to WebSocket");
// 订阅主题
client.subscribe("/topic/convert", (message) => {
this.filename = message.body;
this.successMessage = `You can download now! Please check your browser's download folder.`;
console.log("Received message: ", message.body);
});
client.subscribe("/topic/embedUrl", (message) => {
this.embedUrl = message.body;
console.log("Received message: ", message.body);
});
},
(error) => {
console.error("Connection error: ", error);
}
);
},
Nginx.conf
WebSocket 請求轉發到 Spring Boot。
靜態文件服務(如前端應用)。
user nginx;
#user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096; # 增加為更大的值
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
upstream ws{
server 10.0.2.2:64102;
}
server {
client_max_body_size 10M;
listen 8801;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /ws {
proxy_pass http://ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
# 支持 SockJS 的长轮询和其他请求
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}