最近在学习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();
});
}
// 连接WebSocket
function 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 {
@Bean
public 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 {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public 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/");
}
@Override
public 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/**"); // 排除登录和测试接口
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 创建消息转换器
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 获取ObjectMapper
ObjectMapper 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<>();
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
log.info("连接成功"+ sid);
System.out.println("连接成功");
sessionMap.put(sid,session);
}
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
log.info("收到消息"+ message);
System.out.println("收到消息");
for (String key : sessionMap.keySet()) {
Session session = sessionMap.get(key);
}
}
@OnClose
public 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