在现代 Web 应用中,实时通信功能变得越来越重要。本文将带您一步一步地使用 Spring Boot 和 WebSocket 构建一个带有用户认证和消息存储功能的实时聊天应用。
项目结构
我们将构建以下功能模块:
- WebSocket 聊天功能
- 用户认证与授权
- 消息存储
- 基础前端界面
依赖配置
首先,在 pom.xml
中添加必要的依赖项:
xml复制代码<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database (for simplicity) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Jackson (for JSON processing) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
WebSocket 配置
创建一个 WebSocket 配置类来注册 WebSocket 端点:
java复制代码import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ChatHandler(), "/chat")
.setAllowedOrigins("*");
}
}
WebSocket 处理器
编写一个处理 WebSocket 消息的处理器:
java复制代码import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class ChatHandler extends TextWebSocketHandler {
private final Map<String, WebSocketSession> sessions = Collections.synchronizedMap(new HashMap<>());
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String userId = session.getUri().getQuery().split("=")[1];
sessions.put(userId, session);
broadcastOnlineUsers();
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
ChatMessage chatMessage = objectMapper.readValue(message.getPayload(), ChatMessage.class);
WebSocketSession targetSession = sessions.get(chatMessage.getTargetUserId());
if (targetSession != null) {
targetSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(chatMessage)));
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String userId = session.getUri().getQuery().split("=")[1];
sessions.remove(userId);
broadcastOnlineUsers();
}
private void broadcastOnlineUsers() throws Exception {
OnlineUsersMessage onlineUsersMessage = new OnlineUsersMessage(sessions.keySet());
String message = objectMapper.writeValueAsString(onlineUsersMessage);
for (WebSocketSession session : sessions.values()) {
session.sendMessage(new TextMessage(message));
}
}
}
WebSocket 消息类
java复制代码public class ChatMessage {
private String senderUserId;
private String targetUserId;
private String message;
// Getters and setters
}
public class OnlineUsersMessage {
private Set<String> onlineUsers;
public OnlineUsersMessage(Set<String> onlineUsers) {
this.onlineUsers = onlineUsers;
}
// Getters and setters
}
用户认证与授权
使用 Spring Security 进行用户认证和授权:
安全配置类
java复制代码import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/chat").authenticated()
.and()
.httpBasic();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
消息存储
使用 JPA 将聊天消息存储到数据库中:
消息实体类
java复制代码import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String senderUserId;
private String targetUserId;
private String message;
// Getters and setters
}
消息存储库接口
java复制代码import org.springframework.data.jpa.repository.JpaRepository;
public interface MessageRepository extends JpaRepository<Message, Long> {
}
消息服务类
java复制代码import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MessageService {
@Autowired
private MessageRepository messageRepository;
public Message saveMessage(Message message) {
return messageRepository.save(message);
}
}
消息控制器
java复制代码import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
@Autowired
private MessageService messageService;
@PostMapping("/saveMessage")
public Message saveMessage(@RequestBody Message message) {
return messageService.saveMessage(message);
}
}
前端实现
使用简单的 HTML 和 JavaScript 创建一个基本的聊天界面:
html复制代码<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat Application</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css">
<style>
/* 添加必要的CSS样式 */
</style>
</head>
<body>
<div id="userList" class="p-3"></div>
<div id="chatPopup" class="p-3" style="display: none;">
<div id="chatPopupHeader" class="d-flex justify-content-between align-items-center">
<div id="chatPopupHeaderContent"></div>
<button id="closeChatPopupButton">×</button>
</div>
<div id="chatPopupBody" class="overflow-auto p-2" style="height: 400px;"></div>
<div id="chatPopupFooter" class="d-flex p-2">
<input type="text" id="chatInput" class="form-control me-2" placeholder="Type a message...">
<button id="sendMessageButton" class="btn btn-primary">Send</button>
</div>
</div>
<script>
const senderUserId = "user1"; // 假设这是当前用户ID
let recipientId = ""; // 接收消息的用户ID
const ws = new WebSocket('ws://localhost:8080/chat'); // 你的 WebSocket URL
ws.onopen = function() {
console.log('Connected to WebSocket');
};
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
displayMessage(message.message, 'received');
};
document.getElementById("sendMessageButton").addEventListener("click", sendMessage);
function sendMessage() {
const messageInput = document.getElementById("chatInput").value;
if (messageInput.trim() !== '') {
const message = {
senderUserId: senderUserId,
message: messageInput,
targetUserId: recipientId.trim() !== '' ? recipientId : null
};
ws.send(JSON.stringify(message));
displayMessage(messageInput, 'sent');
document.getElementById("chatInput").value = '';
}
}
function displayMessage(message, type) {
const messageDiv = document.createElement("div");
messageDiv.textContent = message;
messageDiv.className = `col-12 message ${type}`;
document.getElementById("chatPopupBody").appendChild(messageDiv);
}
// 获取在线用户并渲染
function getOnlineUsers() {
// 使用 AJAX 或 Fetch API 调用后端获取在线用户列表
}
// 渲染用户列表
function renderUserList(users) {
const userList = document.getElementById("userList");
userList.innerHTML = '';
users.forEach(user => {
const userDiv = document.createElement("div");
userDiv.className = `user ${user.online ? '' : 'offline'}`;
userDiv.textContent = user.name;
userDiv.addEventListener('click', () => {
recipientId = user.id;
document.getElementById("chatPopup").style.display = 'block';
});
userList.appendChild(userDiv);
});
}
// 示例调用
getOnlineUsers();
</script>
</body>
</html>
总结
本文详细介绍了如何使用 Spring Boot 和 WebSocket 构建一个带有用户认证和消息存储功能的实时聊天应用。通过这些步骤,您可以轻松地创建一个功能齐全且安全的聊天应用,并根据自己的需求进行扩展和优化。