使用人工智能写一个websocket聊天页面
最近在学习springboot 里面的定时任务和websocket模块,然后为了偷懒使用人工智能写了个页面,的确强大,直接看图片
网页配置chat.html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>WebSocket消息收发演示</title><style>* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {background: linear-gradient(135deg, #6e8efb, #a777e3);min-height: 100vh;display: flex;justify-content: center;align-items: center;padding: 20px;}.container {width: 100%;max-width: 600px;background-color: white;border-radius: 12px;box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);overflow: hidden;}.header {background-color: #4e73df;color: white;padding: 20px;text-align: center;}.status-bar {display: flex;justify-content: space-between;align-items: center;padding: 10px 20px;background-color: #f8f9fc;border-bottom: 1px solid #e3e6f0;}.status {display: flex;align-items: center;font-size: 14px;}.status-indicator {width: 10px;height: 10px;border-radius: 50%;margin-right: 8px;}.connected {background-color: #1cc88a;}.disconnected {background-color: #e74a3b;}.connecting {background-color: #f6c23e;}.control-panel {padding: 15px 20px;display: flex;gap: 10px;border-bottom: 1px solid #e3e6f0;}button {padding: 8px 16px;border: none;border-radius: 4px;cursor: pointer;font-weight: 500;transition: all 0.3s;}.connect-btn {background-color: #1cc88a;color: white;}.connect-btn:hover {background-color: #17a673;}.disconnect-btn {background-color: #e74a3b;color: white;}.disconnect-btn:hover {background-color: #c0352d;}.message-input {padding: 15px 20px;border-bottom: 1px solid #e3e6f0;}#message {width: 100%;padding: 12px 15px;border: 1px solid #d1d3e2;border-radius: 4px;font-size: 14px;}#send-btn {width: 100%;margin-top: 10px;padding: 10px;background-color: #4e73df;color: white;font-weight: 600;}#send-btn:hover {background-color: #2e59d9;}.messages-container {padding: 20px;height: 300px;overflow-y: auto;background-color: #f8f9fc;}.message {margin-bottom: 15px;padding: 12px 15px;border-radius: 6px;font-size: 14px;line-height: 1.5;}.received {background-color: #eaecf4;color: #333;}.sent {background-color: #4e73df;color: white;text-align: right;}.system {background-color: #f8f9fc;color: #858796;font-style: italic;text-align: center;border: 1px dashed #d1d3e2;}.error {background-color: #f8d7da;color: #721c24;border: 1px solid #f5c6cb;}.timestamp {font-size: 12px;color: #858796;margin-top: 5px;}.notification {position: fixed;top: 20px;right: 20px;padding: 15px 20px;border-radius: 6px;color: white;opacity: 0;transform: translateX(100%);transition: all 0.3s ease;z-index: 1000;}.notification.show {opacity: 1;transform: translateX(0);}.notification.success {background-color: #1cc88a;}.notification.error {background-color: #e74a3b;}.notification.info {background-color: #4e73df;}</style>
</head>
<body>
<div class="container"><div class="header"><h1>WebSocket消息收发演示</h1></div><div class="status-bar"><div class="status"><div class="status-indicator disconnected" id="status-indicator"></div><span id="status-text">未连接</span></div><div id="connection-info"></div></div><div class="control-panel"><button class="connect-btn" id="connect-btn">连接</button><button class="disconnect-btn" id="disconnect-btn" disabled>断开</button></div><div class="message-input"><input type="text" id="message" placeholder="输入要发送的消息..." disabled><button id="send-btn" disabled>发送消息</button></div><div class="messages-container" id="messages-container"><div class="message system">欢迎使用WebSocket演示。请先点击"连接"按钮建立连接。</div></div>
</div><div class="notification" id="notification"></div><script>// WebSocket连接let socket = null;var client_id=Math.random().toString(36).substring(2);const connectBtn = document.getElementById('connect-btn');const disconnectBtn = document.getElementById('disconnect-btn');const sendBtn = document.getElementById('send-btn');const messageInput = document.getElementById('message');const messagesContainer = document.getElementById('messages-container');const statusIndicator = document.getElementById('status-indicator');const statusText = document.getElementById('status-text');const connectionInfo = document.getElementById('connection-info');const notification = document.getElementById('notification');// 初始化function init() {// 添加事件监听器connectBtn.addEventListener('click', connectWebSocket);disconnectBtn.addEventListener('click', disconnectWebSocket);sendBtn.addEventListener('click', sendMessage);messageInput.addEventListener('keypress', (e) => {if (e.key === 'Enter') sendMessage();});}// 连接WebSocketfunction connectWebSocket() {// 使用公共的WebSocket测试服务// 注意:实际使用时可能需要替换为您的WebSocket服务器地址try {const baseUrl = window.location.origin;const contextPath = window.location.pathname.split('/')[1];const wsUrl = `${baseUrl.replace('http', 'ws')}/ws/${client_id}`;socket = new WebSocket(wsUrl);// socket = new WebSocket("ws://localhost:8080/ws/"+client_id);// 更新UI状态updateConnectionStatus('connecting', '连接中...');connectBtn.disabled = true;disconnectBtn.disabled = false;// WebSocket事件处理socket.onopen = function(event) {showNotification('连接成功!', 'success');updateConnectionStatus('connected', '已连接');messageInput.disabled = false;sendBtn.disabled = false;addSystemMessage('WebSocket连接已建立');};socket.onmessage = function(event) {addMessage(event.data, 'received');};socket.onerror = function(event) {showNotification('连接错误!', 'error');addSystemMessage('WebSocket连接错误', 'error');console.error('WebSocket错误:', event);};socket.onclose = function(event) {showNotification('连接已关闭', 'info');updateConnectionStatus('disconnected', '未连接');connectBtn.disabled = false;disconnectBtn.disabled = true;messageInput.disabled = true;sendBtn.disabled = true;addSystemMessage('WebSocket连接已关闭');};} catch (error) {showNotification('连接失败: ' + error.message, 'error');console.error('WebSocket连接失败:', error);updateConnectionStatus('disconnected', '连接失败');connectBtn.disabled = false;}}// 断开WebSocket连接function disconnectWebSocket() {if (socket && socket.readyState === WebSocket.OPEN) {socket.close();}}// 发送消息function sendMessage() {const message = messageInput.value.trim();if (!message) return;if (socket && socket.readyState === WebSocket.OPEN) {socket.send(message);addMessage(message, 'sent');messageInput.value = '';} else {showNotification('无法发送消息: 连接未建立', 'error');}}// 添加消息到消息容器function addMessage(text, type) {const messageElement = document.createElement('div');messageElement.classList.add('message', type);const messageContent = document.createElement('div');messageContent.textContent = text;const timestamp = document.createElement('div');timestamp.classList.add('timestamp');timestamp.textContent = new Date().toLocaleTimeString();messageElement.appendChild(messageContent);messageElement.appendChild(timestamp);messagesContainer.appendChild(messageElement);messagesContainer.scrollTop = messagesContainer.scrollHeight;}// 添加系统消息function addSystemMessage(text, type = 'system') {const messageElement = document.createElement('div');messageElement.classList.add('message', type);messageElement.textContent = text;messagesContainer.appendChild(messageElement);messagesContainer.scrollTop = messagesContainer.scrollHeight;}// 更新连接状态function updateConnectionStatus(status, text) {statusIndicator.className = 'status-indicator ' + status;statusText.textContent = text;// 更新连接信息if (status === 'connected') {const baseUrl = window.location.origin;const contextPath = window.location.pathname.split('/')[1];const wsUrl = `${baseUrl.replace('http', 'ws')}/ws/${client_id}`;// socket = new WebSocket(wsUrl);connectionInfo.textContent = '使用'+wsUrl;} else {connectionInfo.textContent = '';}}// 显示通知function showNotification(message, type) {notification.textContent = message;notification.className = 'notification ' + type;notification.classList.add('show');setTimeout(() => {notification.classList.remove('show');}, 3000);}// 页面加载完成后初始化window.onload = init;
</script>
</body>
</html>
java springboot后端项目
WebSocketConfig.java文件
package com.yestea.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
WebConfig.java文件
package com.yestea.config;import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.yestea.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.text.SimpleDateFormat;
import java.util.List;
import java.util.TimeZone;@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");registry.addResourceHandler("/template/**").addResourceLocations("classpath:/template/");registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**") // 拦截所有请求.excludePathPatterns("/login", "/error", "/regisger", "/doc.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs", "/v2/api-docs-ext", "/csrf","/template/**"); // 排除登录和测试接口}@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {// 创建消息转换器MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();// 获取ObjectMapperObjectMapper objectMapper = converter.getObjectMapper();// 设置日期格式SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");objectMapper.setDateFormat(dateFormat);// 设置时区objectMapper.setTimeZone(TimeZone.getDefault());// 设置序列化规则(可选)objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);// 将配置好的转换器添加到转换器列表的第一个位置,确保优先使用converters.add(0, converter);}
}
定时任务MyTask.java
package com.yestea.task;import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.yestea.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.Date;@Component
@EnableScheduling
@Slf4j
public class MyTask {@Scheduled(cron="0 * * * * ?")public void executeTask(){Date date=new Date();String format = DateUtil.format(date, "yyyy-MM-dd HH:mm:ss");log.info("定时任务开始执行:{}",format);}@Scheduled(cron="0 * * * * ?")public void sendAllMessage(){Date date=new Date();String format = DateUtil.format(date, "yyyy-MM-dd HH:mm:ss");log.info("websocket定时任务开始广播:{}",format);WebSocketServer.sendMessage("定时任务开始广播:"+format);}
}
WebSocketServer.java
package com.yestea.websocket;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;@Slf4j
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {private static Map<String,Session> sessionMap=new HashMap<>();@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {log.info("连接成功"+ sid);System.out.println("连接成功");sessionMap.put(sid,session);}@OnMessagepublic void onMessage(String message, @PathParam("sid") String sid) {log.info("收到消息"+ message);System.out.println("收到消息");for (String key : sessionMap.keySet()) {Session session = sessionMap.get(key);}}@OnClosepublic void onClose(@PathParam("sid") String sid) {log.info("连接关闭"+ sid);System.out.println("连接关闭");sessionMap.remove(sid);}public static void sendMessage(String message) {Collection<Session> values = sessionMap.values();for (Session session : values) {try {session.getAsyncRemote().sendText(message);} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);}}}
}
springboot启动日志
2025-08-27 13:44:00.003 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : websocket定时任务开始广播:2025-08-27 13:44:00
2025-08-27 13:44:00.004 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : 定时任务开始执行:2025-08-27 13:44:00
2025-08-27 13:45:00.013 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : 定时任务开始执行:2025-08-27 13:45:00
2025-08-27 13:45:00.013 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : websocket定时任务开始广播:2025-08-27 13:45:00
2025-08-27 13:46:00.010 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : websocket定时任务开始广播:2025-08-27 13:46:00
2025-08-27 13:46:00.010 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : 定时任务开始执行:2025-08-27 13:46:00
2025-08-27 13:47:00.008 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : websocket定时任务开始广播:2025-08-27 13:47:00
2025-08-27 13:47:00.008 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : 定时任务开始执行:2025-08-27 13:47:00
2025-08-27 13:48:00.004 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : 定时任务开始执行:2025-08-27 13:48:00
2025-08-27 13:48:00.005 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : websocket定时任务开始广播:2025-08-27 13:48:00
2025-08-27 13:49:00.015 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : websocket定时任务开始广播:2025-08-27 13:49:00
2025-08-27 13:49:00.015 INFO 16268 --- [ scheduling-1] com.yestea.task.MyTask : 定时任务开始执行:2025-08-27 13:49:00