diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..119545f
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ xyz.wbsite
+ test-websocket
+ 0.0.1-SNAPSHOT
+ test-websocket
+ Demo project for Spring Boot
+
+ 1.8
+
+
+
+
+ central
+ Central Repository
+ default
+ https://maven.aliyun.com/repository/public
+
+
+
+
+
+ central
+ Central Repository
+ https://maven.aliyun.com/repository/public
+ default
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-freemarker
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 3.6.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/src/main/java/xyz/wbsite/TestWebsocketApplication.java b/src/main/java/xyz/wbsite/TestWebsocketApplication.java
new file mode 100644
index 0000000..bd2ec45
--- /dev/null
+++ b/src/main/java/xyz/wbsite/TestWebsocketApplication.java
@@ -0,0 +1,15 @@
+package xyz.wbsite;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication
+@EnableScheduling
+public class TestWebsocketApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(TestWebsocketApplication.class, args);
+ }
+
+}
diff --git a/src/main/java/xyz/wbsite/config/WebSocketConfig.java b/src/main/java/xyz/wbsite/config/WebSocketConfig.java
new file mode 100644
index 0000000..4aa6520
--- /dev/null
+++ b/src/main/java/xyz/wbsite/config/WebSocketConfig.java
@@ -0,0 +1,14 @@
+package xyz.wbsite.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();
+ }
+}
diff --git a/src/main/java/xyz/wbsite/controller/IndexController.java b/src/main/java/xyz/wbsite/controller/IndexController.java
new file mode 100644
index 0000000..bdb329e
--- /dev/null
+++ b/src/main/java/xyz/wbsite/controller/IndexController.java
@@ -0,0 +1,20 @@
+package xyz.wbsite.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Controller
+public class IndexController {
+
+ @RequestMapping("/")
+ public String text(ModelMap map, HttpServletRequest request, HttpServletResponse response) {
+ map.put("name","张三");
+ return "index";
+ }
+}
diff --git a/src/main/java/xyz/wbsite/endpoint/WebSocketManager.java b/src/main/java/xyz/wbsite/endpoint/WebSocketManager.java
new file mode 100644
index 0000000..5478d84
--- /dev/null
+++ b/src/main/java/xyz/wbsite/endpoint/WebSocketManager.java
@@ -0,0 +1,115 @@
+package xyz.wbsite.endpoint;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Controller;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+
+@ServerEndpoint("/websocket/{userId}")
+@Controller
+public class WebSocketManager {
+
+ static Log log = LogFactory.getLog(WebSocketManager.class);
+
+ /**
+ * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
+ */
+ private static ConcurrentHashMap keySessionMap = new ConcurrentHashMap<>();
+ private static ConcurrentHashMap hashKeyMap = new ConcurrentHashMap<>();
+
+ /**
+ * 连接建立成功调用的方法
+ */
+ @OnOpen
+ public void onOpen(Session session, @PathParam("userId") String userId) {
+ // 移除并关闭旧会话
+ if (keySessionMap.containsKey(userId)) {
+ Session remove = keySessionMap.remove(userId);
+ hashKeyMap.remove(remove.hashCode());
+ try {
+ remove.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ keySessionMap.put(userId, session);
+ hashKeyMap.put(session.hashCode(), userId);
+
+ log.info("用户连接:" + userId + ",当前在线人数为:" + keySessionMap.size());
+
+ sendMessage(userId, "连接成功");
+ }
+
+ /**
+ * 连接关闭调用的方法
+ */
+ @OnClose
+ public void onClose(Session session) {
+ if (hashKeyMap.containsKey(session.hashCode())) {
+ String userId = hashKeyMap.remove(session.hashCode());
+ keySessionMap.remove(userId);
+ log.info("用户退出:" + userId);
+ }
+ }
+
+ /**
+ * 收到客户端消息后调用的方法
+ *
+ * @param message 客户端发送过来的消息
+ */
+ @OnMessage
+ public void onMessage(String message, Session session) {
+ log.info("收到客户端消息:" + message);
+ }
+
+ /**
+ * 发生错误移除会话
+ *
+ * @param session
+ * @param error
+ */
+ @OnError
+ public void onError(Session session, Throwable error) {
+ error.printStackTrace();
+ if (hashKeyMap.containsKey(session.hashCode())) {
+ String userId = hashKeyMap.remove(session.hashCode());
+ keySessionMap.remove(userId);
+ log.error("用户错误:" + userId + ",原因:" + error.getMessage());
+ }
+ }
+
+ /**
+ * 实现服务器主动推送
+ */
+ public void sendMessage(String userId, String message) {
+ Session session = keySessionMap.get(userId);
+ if (session == null) {
+ return;
+ }
+ try {
+ session.getBasicRemote().sendText(message);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Scheduled(fixedDelay = 3 * 1000)
+ public void sendTask() {
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ sendMessage("1", simpleDateFormat.format(new Date()));
+ }
+}
diff --git a/src/main/java/xyz/wbsite/test/Test.java b/src/main/java/xyz/wbsite/test/Test.java
new file mode 100644
index 0000000..25f4f7d
--- /dev/null
+++ b/src/main/java/xyz/wbsite/test/Test.java
@@ -0,0 +1,55 @@
+package xyz.wbsite.test;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+import okio.ByteString;
+
+public class Test {
+ public static void main(String[] args) {
+ final WebSocket[] socket = new WebSocket[1];
+ OkHttpClient mOkHttpClient = new OkHttpClient.Builder()
+ .retryOnConnectionFailure(true)
+ .build();
+
+ Request mRequest = new Request.Builder()
+ .url("ws://127.0.0.1:8080/websocket/1")
+ .build();
+
+ mOkHttpClient.newWebSocket(mRequest, new WebSocketListener() {
+ @Override
+ public void onOpen(WebSocket webSocket, Response response) {
+ socket[0] = webSocket;
+ System.out.println("连接已打开");
+ }
+
+ @Override
+ public void onMessage(WebSocket webSocket, String text) {
+ System.out.println("接收消息:" + text);
+ socket[0].send("收到消息");
+ }
+
+ @Override
+ public void onMessage(WebSocket webSocket, ByteString bytes) {
+ super.onMessage(webSocket, bytes);
+ }
+
+ @Override
+ public void onClosing(WebSocket webSocket, int code, String reason) {
+ System.out.println("连接关闭");
+ }
+
+ @Override
+ public void onClosed(WebSocket webSocket, int code, String reason) {
+ System.out.println("连接关闭");
+ }
+
+ @Override
+ public void onFailure(WebSocket webSocket, Throwable t, Response response) {
+ System.out.println("连接失败");
+ }
+ });
+ }
+}
diff --git a/src/main/java/xyz/wbsite/utils/StringUtils.java b/src/main/java/xyz/wbsite/utils/StringUtils.java
new file mode 100644
index 0000000..0181a77
--- /dev/null
+++ b/src/main/java/xyz/wbsite/utils/StringUtils.java
@@ -0,0 +1,24 @@
+package xyz.wbsite.utils;
+
+
+public class StringUtils {
+
+ public static boolean isNull(String str) {
+ return str == null;
+ }
+
+ public static boolean isNotNull(String str) {
+ return !isNull(str);
+ }
+
+ public static boolean isBlank(String str) {
+
+ return str != null && "".equals(str);
+ }
+
+
+ public static boolean isNotBlank(String str) {
+ return !isBlank(str);
+ }
+
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..ea229c3
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,18 @@
+spring.freemarker.suffix=.ftl
+spring.freemarker.enabled=true
+spring.freemarker.allow-request-override=false
+spring.freemarker.cache=true
+spring.freemarker.check-template-location=true
+spring.freemarker.charset=UTF-8
+spring.freemarker.content-type=text/html
+spring.freemarker.expose-request-attributes=false
+spring.freemarker.expose-session-attributes=false
+spring.freemarker.expose-spring-macro-helpers=false
+spring.freemarker.settings.template_update_delay=1
+spring.freemarker.settings.locale=zh_CN
+spring.freemarker.settings.datetime_format=yyyy-MM-dd HH:mm:ss
+spring.freemarker.settings.date_format=yyyy-MM-dd
+spring.freemarker.settings.number_format=#.##
+spring.freemarker.settings.classic_compatible=true
+spring.freemarker.settings.whitespace_stripping=true
+spring.freemarker.settings.url_escaping_charset=utf-8
\ No newline at end of file
diff --git a/src/main/resources/templates/index.ftl b/src/main/resources/templates/index.ftl
new file mode 100644
index 0000000..30c2aea
--- /dev/null
+++ b/src/main/resources/templates/index.ftl
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+