diff --git a/AndroidClient/app/src/main/AndroidManifest.xml b/AndroidClient/app/src/main/AndroidManifest.xml index e9107e4..6cbe748 100644 --- a/AndroidClient/app/src/main/AndroidManifest.xml +++ b/AndroidClient/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:usesCleartextTraffic="true" android:theme="@style/AppTheme"> diff --git a/AndroidClient/app/src/main/java/app/xlui/example/im/activities/BroadcastActivity.java b/AndroidClient/app/src/main/java/app/xlui/example/im/activities/BroadcastActivity.java index b2f5911..153f6e4 100644 --- a/AndroidClient/app/src/main/java/app/xlui/example/im/activities/BroadcastActivity.java +++ b/AndroidClient/app/src/main/java/app/xlui/example/im/activities/BroadcastActivity.java @@ -1,6 +1,5 @@ package app.xlui.example.im.activities; -import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; @@ -24,27 +23,26 @@ import ua.naiksoftware.stomp.dto.StompHeader; import ua.naiksoftware.stomp.dto.StompMessage; +@SuppressWarnings({"FieldCanBeLocal", "ResultOfMethodCallIgnored", "CheckResult"}) public class BroadcastActivity extends AppCompatActivity { - private Button broadcast; - private Button groups; - private Button chat; + private Button broadcastButton; + private Button groupButton; + private Button chatButton; - private EditText name; - private Button send; - private TextView result; + private EditText nameText; + private Button sendButton; + private TextView resultText; private void init() { - broadcast = findViewById(R.id.broadcast); - broadcast.setEnabled(false); - groups = findViewById(R.id.groups); - chat = findViewById(R.id.chat); - name = findViewById(R.id.name); - send = findViewById(R.id.send); - result = findViewById(R.id.show); + broadcastButton = findViewById(R.id.broadcast); + broadcastButton.setEnabled(false); + groupButton = findViewById(R.id.groups); + chatButton = findViewById(R.id.chat); + nameText = findViewById(R.id.name); + sendButton = findViewById(R.id.send); + resultText = findViewById(R.id.show); } - @SuppressLint("CheckResult") - @SuppressWarnings("ResultOfMethodCallIgnored") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -53,29 +51,29 @@ protected void onCreate(Bundle savedInstanceState) { this.init(); StompClient stompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, Const.address); - // 连接服务器 - stompClient.connect(); - Toast.makeText(this, "开始连接", Toast.LENGTH_SHORT).show(); StompUtils.lifecycle(stompClient); + Toast.makeText(this, "Start connecting to server", Toast.LENGTH_SHORT).show(); + // Connect to WebSocket server + stompClient.connect(); // 订阅消息 - Log.i(Const.TAG, "订阅广播"); + Log.i(Const.TAG, "Subscribe broadcast endpoint to receive response"); stompClient.topic(Const.broadcastResponse).subscribe(stompMessage -> { JSONObject jsonObject = new JSONObject(stompMessage.getPayload()); Log.i(Const.TAG, "Receive: " + stompMessage.getPayload()); runOnUiThread(() -> { try { - result.append(jsonObject.getString("response") + "\n"); + resultText.append(jsonObject.getString("response") + "\n"); } catch (JSONException e) { e.printStackTrace(); } }); }); - send.setOnClickListener(v -> { + sendButton.setOnClickListener(v -> { JSONObject jsonObject = new JSONObject(); try { - jsonObject.put("name", name.getText()); + jsonObject.put("name", nameText.getText()); } catch (JSONException e) { e.printStackTrace(); } @@ -84,7 +82,7 @@ protected void onCreate(Bundle savedInstanceState) { // Stomp command StompCommand.SEND, // Stomp Headers, Send Headers with STOMP - // the first header is necessary, and the other can be customized by ourselves + // the first header is required, and the other can be customized by ourselves Arrays.asList( new StompHeader(StompHeader.DESTINATION, Const.broadcast), new StompHeader("authorization", "this is a token generated by your code!") @@ -92,15 +90,16 @@ protected void onCreate(Bundle savedInstanceState) { // Stomp payload jsonObject.toString()) ).subscribe(); + nameText.setText(""); }); - groups.setOnClickListener(v -> { + groupButton.setOnClickListener(v -> { Intent intent = new Intent(); intent.setClass(BroadcastActivity.this, GroupActivity.class); startActivity(intent); this.finish(); }); - chat.setOnClickListener(v -> { + chatButton.setOnClickListener(v -> { Intent intent = new Intent(); intent.setClass(BroadcastActivity.this, ChatActivity.class); startActivity(intent); diff --git a/AndroidClient/app/src/main/java/app/xlui/example/im/activities/ChatActivity.java b/AndroidClient/app/src/main/java/app/xlui/example/im/activities/ChatActivity.java index f1271dc..5f2040e 100644 --- a/AndroidClient/app/src/main/java/app/xlui/example/im/activities/ChatActivity.java +++ b/AndroidClient/app/src/main/java/app/xlui/example/im/activities/ChatActivity.java @@ -1,6 +1,5 @@ package app.xlui.example.im.activities; -import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; @@ -24,44 +23,43 @@ import ua.naiksoftware.stomp.Stomp; import ua.naiksoftware.stomp.StompClient; +@SuppressWarnings({"FieldCanBeLocal", "ResultOfMethodCallIgnored", "CheckResult"}) public class ChatActivity extends AppCompatActivity { - private Button broadcast; - private Button groups; - private Button chat; + private Button broadcastButton; + private Button groupButton; + private Button chatButton; - private TextView userId; - private EditText chatUserId; - private Button submit; - private EditText chatMessage; - private Button send; - private TextView show; + private TextView userIdText; + private EditText chatUserIdText; + private Button submitButton; + private EditText chatMessageText; + private Button sendButton; + private TextView showText; - private String user_id; - private String chat_user_id; + private String userId; + private String chatUserId; private void init() { - broadcast = findViewById(R.id.broadcast); - groups = findViewById(R.id.groups); - chat = findViewById(R.id.chat); - chat.setEnabled(false); + broadcastButton = findViewById(R.id.broadcast); + groupButton = findViewById(R.id.groups); + chatButton = findViewById(R.id.chat); + chatButton.setEnabled(false); - userId = findViewById(R.id.id); - user_id = String.valueOf(new Random().nextInt(100)); - userId.setText(user_id); + userIdText = findViewById(R.id.id); + userId = String.valueOf(new Random().nextInt(100)); + userIdText.setText(userId); - chatUserId = findViewById(R.id.chat_user_id); - submit = findViewById(R.id.submit); - submit.setEnabled(false); + chatUserIdText = findViewById(R.id.chat_user_id); + submitButton = findViewById(R.id.submit); + submitButton.setEnabled(false); - chatMessage = findViewById(R.id.chat_message); - send = findViewById(R.id.send); - send.setEnabled(false); + chatMessageText = findViewById(R.id.chat_message); + sendButton = findViewById(R.id.send); + sendButton.setEnabled(false); - show = findViewById(R.id.show); + showText = findViewById(R.id.show); } - @SuppressWarnings("ResultOfMethodCallIgnored") - @SuppressLint("CheckResult") @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -70,23 +68,24 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { this.init(); StompClient stompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, Const.address); + Toast.makeText(this, "Start connecting to server", Toast.LENGTH_SHORT).show(); stompClient.connect(); - Toast.makeText(this, "开始连接", Toast.LENGTH_SHORT).show(); StompUtils.lifecycle(stompClient); - stompClient.topic(Const.chatResponse.replace(Const.placeholder, user_id)).subscribe(stompMessage -> { + Log.i(Const.TAG, "Subscribe chat endpoint to receive response"); + stompClient.topic(Const.chatResponse.replace(Const.placeholder, userId)).subscribe(stompMessage -> { JSONObject jsonObject = new JSONObject(stompMessage.getPayload()); Log.i(Const.TAG, "Receive: " + jsonObject.toString()); runOnUiThread(() -> { try { - show.append(jsonObject.getString("response") + "\n"); + showText.append(jsonObject.getString("response") + "\n"); } catch (JSONException e) { e.printStackTrace(); } }); }); - chatUserId.addTextChangedListener(new TextWatcher() { + chatUserIdText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -99,42 +98,43 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { @Override public void afterTextChanged(Editable s) { - if (!submit.isEnabled()) - submit.setEnabled(true); + if (!submitButton.isEnabled()) + submitButton.setEnabled(true); } }); - submit.setOnClickListener(v -> { - chat_user_id = chatUserId.getText().toString(); - if (chat_user_id.length() == 0) { + submitButton.setOnClickListener(v -> { + chatUserId = chatUserIdText.getText().toString(); + if (chatUserId.length() == 0) { return; } - submit.setEnabled(false); - send.setEnabled(true); + submitButton.setEnabled(false); + sendButton.setEnabled(true); }); - send.setOnClickListener(v -> { + sendButton.setOnClickListener(v -> { JSONObject jsonObject = new JSONObject(); try { - jsonObject.put("userID", chat_user_id); - jsonObject.put("fromUserID", userId.getText().toString()); - jsonObject.put("message", chatMessage.getText()); + jsonObject.put("userID", chatUserId); + jsonObject.put("fromUserID", userIdText.getText().toString()); + jsonObject.put("message", chatMessageText.getText()); } catch (JSONException e) { e.printStackTrace(); } - if (chat_user_id == null || chat_user_id.length() == 0) { - chat_user_id = chatUserId.getText().toString(); + if (chatUserId == null || chatUserId.length() == 0) { + chatUserId = chatUserIdText.getText().toString(); } stompClient.send(Const.chat, jsonObject.toString()).subscribe(); + chatMessageText.setText(""); }); - broadcast.setOnClickListener(v -> { + broadcastButton.setOnClickListener(v -> { Intent intent = new Intent(); intent.setClass(ChatActivity.this, BroadcastActivity.class); startActivity(intent); this.finish(); }); - groups.setOnClickListener(v -> { + groupButton.setOnClickListener(v -> { Intent intent = new Intent(); intent.setClass(ChatActivity.this, GroupActivity.class); startActivity(intent); diff --git a/AndroidClient/app/src/main/java/app/xlui/example/im/activities/GroupActivity.java b/AndroidClient/app/src/main/java/app/xlui/example/im/activities/GroupActivity.java index 19c7d22..13af9bb 100644 --- a/AndroidClient/app/src/main/java/app/xlui/example/im/activities/GroupActivity.java +++ b/AndroidClient/app/src/main/java/app/xlui/example/im/activities/GroupActivity.java @@ -1,6 +1,5 @@ package app.xlui.example.im.activities; -import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; @@ -22,38 +21,37 @@ import ua.naiksoftware.stomp.Stomp; import ua.naiksoftware.stomp.StompClient; +@SuppressWarnings({"FieldCanBeLocal", "ResultOfMethodCallIgnored", "CheckResult"}) public class GroupActivity extends AppCompatActivity { - private Button broadcast; - private Button groups; - private Button chat; + private Button broadcastButton; + private Button groupButton; + private Button chatButton; - private EditText groupId; - private Button submit; - private EditText name; - private Button send; - private TextView show; + private EditText groupIdText; + private Button submitButton; + private EditText nameText; + private Button sendButton; + private TextView showText; - private String group_id; + private String groupId; private void init() { - broadcast = findViewById(R.id.broadcast); - groups = findViewById(R.id.groups); - groups.setEnabled(false); - chat = findViewById(R.id.chat); + broadcastButton = findViewById(R.id.broadcast); + groupButton = findViewById(R.id.groups); + groupButton.setEnabled(false); + chatButton = findViewById(R.id.chat); - groupId = findViewById(R.id.group_id); - submit = findViewById(R.id.submit); - submit.setEnabled(false); + groupIdText = findViewById(R.id.group_id); + submitButton = findViewById(R.id.submit); + submitButton.setEnabled(false); - name = findViewById(R.id.name); - send = findViewById(R.id.send); - send.setEnabled(false); + nameText = findViewById(R.id.name); + sendButton = findViewById(R.id.send); + sendButton.setEnabled(false); - show = findViewById(R.id.show); + showText = findViewById(R.id.show); } - @SuppressWarnings("ResultOfMethodCallIgnored") - @SuppressLint("CheckResult") @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -62,11 +60,11 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { this.init(); StompClient stompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, Const.address); + Toast.makeText(this, "Start connecting to server", Toast.LENGTH_SHORT).show(); stompClient.connect(); - Toast.makeText(this, "开始连接", Toast.LENGTH_SHORT).show(); StompUtils.lifecycle(stompClient); - groupId.addTextChangedListener(new TextWatcher() { + groupIdText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -79,51 +77,52 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { @Override public void afterTextChanged(Editable s) { - if (!submit.isEnabled()) - submit.setEnabled(true); + if (!submitButton.isEnabled()) + submitButton.setEnabled(true); } }); - submit.setOnClickListener(v -> { - group_id = groupId.getText().toString(); - if (group_id.length() == 0) { + submitButton.setOnClickListener(v -> { + groupId = groupIdText.getText().toString(); + if (groupId.length() == 0) { return; } - stompClient.topic(Const.groupResponse.replace("placeholder", group_id)).subscribe(stompMessage -> { + stompClient.topic(Const.groupResponse.replace(Const.placeholder, groupId)).subscribe(stompMessage -> { JSONObject jsonObject = new JSONObject(stompMessage.getPayload()); Log.i(Const.TAG, "Receive: " + stompMessage.getPayload()); runOnUiThread(() -> { try { - show.append(jsonObject.getString("response") + "\n"); + showText.append(jsonObject.getString("response") + "\n"); } catch (JSONException e) { e.printStackTrace(); } }); }); - submit.setEnabled(false); - send.setEnabled(true); + submitButton.setEnabled(false); + sendButton.setEnabled(true); }); - send.setOnClickListener(v -> { + sendButton.setOnClickListener(v -> { JSONObject jsonObject = new JSONObject(); try { - jsonObject.put("name", name.getText().toString()); + jsonObject.put("name", nameText.getText().toString()); } catch (JSONException e) { e.printStackTrace(); } - if (group_id == null || group_id.length() == 0) { - group_id = groupId.getText().toString(); + if (groupId == null || groupId.length() == 0) { + groupId = groupIdText.getText().toString(); } - stompClient.send(Const.group.replace("placeholder", group_id), jsonObject.toString()).subscribe(); + stompClient.send(Const.group.replace(Const.placeholder, groupId), jsonObject.toString()).subscribe(); + nameText.setText(""); }); - broadcast.setOnClickListener(v -> { + broadcastButton.setOnClickListener(v -> { Intent intent = new Intent(); intent.setClass(GroupActivity.this, BroadcastActivity.class); startActivity(intent); this.finish(); }); - chat.setOnClickListener(v -> { + chatButton.setOnClickListener(v -> { Intent intent = new Intent(); intent.setClass(GroupActivity.this, ChatActivity.class); startActivity(intent); diff --git a/AndroidClient/app/src/main/java/app/xlui/example/im/conf/Const.java b/AndroidClient/app/src/main/java/app/xlui/example/im/conf/Const.java index e7dbca0..6bd3a37 100644 --- a/AndroidClient/app/src/main/java/app/xlui/example/im/conf/Const.java +++ b/AndroidClient/app/src/main/java/app/xlui/example/im/conf/Const.java @@ -5,9 +5,10 @@ public class Const { public static final String placeholder = "placeholder"; /** - * URL 中的 {@code im} 是在服务器中配置的 endpoint, - * 如果使用 Android Studio 自带的 AVD,地址应该是 10.0.2.2; - * 如果使用 Genymotion,地址应该是 10.0.3.2 + * im in address is the endpoint configured in server. + * If you are using AVD provided by Android Studio, you should uncomment the upper address. + * If you are using Genymotion, nothing else to do. + * If you are using your own phone, just change the server address and port. */ // private static final String address = "ws://10.0.2.2:8080/im/websocket"; public static final String address = "ws://10.0.3.2:8080/im/websocket"; diff --git a/AndroidClient/app/src/main/java/app/xlui/example/im/util/StompUtils.java b/AndroidClient/app/src/main/java/app/xlui/example/im/util/StompUtils.java index a322b4e..d037138 100644 --- a/AndroidClient/app/src/main/java/app/xlui/example/im/util/StompUtils.java +++ b/AndroidClient/app/src/main/java/app/xlui/example/im/util/StompUtils.java @@ -1,28 +1,29 @@ package app.xlui.example.im.util; -import android.annotation.SuppressLint; import android.util.Log; import app.xlui.example.im.conf.Const; import ua.naiksoftware.stomp.StompClient; +import static app.xlui.example.im.conf.Const.TAG; + public class StompUtils { - @SuppressLint("CheckResult") + @SuppressWarnings({"ResultOfMethodCallIgnored", "CheckResult"}) public static void lifecycle(StompClient stompClient) { - stompClient.lifecycle().subscribe(lifecycleEvent -> { - switch (lifecycleEvent.getType()) { - case OPENED: - Log.d(Const.TAG, "Stomp connection opened"); - break; + stompClient.lifecycle().subscribe(lifecycleEvent -> { + switch (lifecycleEvent.getType()) { + case OPENED: + Log.d(TAG, "Stomp connection opened"); + break; - case ERROR: - Log.e(Const.TAG, "Error", lifecycleEvent.getException()); - break; + case ERROR: + Log.e(TAG, "Error", lifecycleEvent.getException()); + break; - case CLOSED: - Log.d(Const.TAG, "Stomp connection closed"); - break; - } - }); - } + case CLOSED: + Log.d(TAG, "Stomp connection closed"); + break; + } + }); + } } diff --git a/AndroidClient/app/src/main/res/values/strings.xml b/AndroidClient/app/src/main/res/values/strings.xml index 6de869e..0bd4d6e 100644 --- a/AndroidClient/app/src/main/res/values/strings.xml +++ b/AndroidClient/app/src/main/res/values/strings.xml @@ -1,17 +1,17 @@ Im - 广播 - 动态群组 - 点对点 + Broadcast + Groups + Chat - 请输入你的名字: - 发送 + Please input you name: + Send - 请输入你要加入的群组: - 确定 + Please input the group id: + Confirm - 你的ID是: - 请输入聊天对象的ID: - 请输入聊天内容: + Please input you id: + Please input chat user id: + Please input chat message diff --git a/WebSocketServer/src/main/java/app/xlui/example/im/config/HandshakeInterceptor.java b/WebSocketServer/src/main/java/app/xlui/example/im/config/HandshakeInterceptor.java index ae273d4..3c35316 100644 --- a/WebSocketServer/src/main/java/app/xlui/example/im/config/HandshakeInterceptor.java +++ b/WebSocketServer/src/main/java/app/xlui/example/im/config/HandshakeInterceptor.java @@ -1,7 +1,6 @@ package app.xlui.example.im.config; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.socket.WebSocketHandler; @@ -9,25 +8,27 @@ import java.util.Map; +@Slf4j public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor { - private static Logger logger = LoggerFactory.getLogger("xlui"); - /** - * WebSocket 握手前 + * Before websocket handshake + * You can put some data into {@code attributes} here, and get it in WebSocketHandler's session *

- * 可以设置数据到 attributes 中,并在 WebSocketHandler 的 session 中获取 + * WebSocket 握手前 —— 可以设置数据到 attributes 中,并在 WebSocketHandler 的 session 中获取 */ @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { - logger.info("HandshakeInterceptor: beforeHandshake"); - logger.info("Attributes: " + attributes.toString()); + log.info("HandshakeInterceptor: beforeHandshake"); + log.info("Attributes: " + attributes.toString()); return super.beforeHandshake(request, response, wsHandler, attributes); } - // WebSocket 握手后 + /** + * After websocket handshake + */ @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { - logger.info("HandshakeInterceptor: afterHandshake"); + log.info("HandshakeInterceptor: afterHandshake"); super.afterHandshake(request, response, wsHandler, ex); } } diff --git a/WebSocketServer/src/main/java/app/xlui/example/im/config/WebMvcConfig.java b/WebSocketServer/src/main/java/app/xlui/example/im/config/WebMvcConfig.java index 0ffc94b..d76eac7 100644 --- a/WebSocketServer/src/main/java/app/xlui/example/im/config/WebMvcConfig.java +++ b/WebSocketServer/src/main/java/app/xlui/example/im/config/WebMvcConfig.java @@ -2,10 +2,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration -public class WebMvcConfig extends WebMvcConfigurerAdapter { +public class WebMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/broadcast").setViewName("/broadcast"); diff --git a/WebSocketServer/src/main/java/app/xlui/example/im/config/WebSocketConfig.java b/WebSocketServer/src/main/java/app/xlui/example/im/config/WebSocketConfig.java index c7264cb..0229f25 100644 --- a/WebSocketServer/src/main/java/app/xlui/example/im/config/WebSocketConfig.java +++ b/WebSocketServer/src/main/java/app/xlui/example/im/config/WebSocketConfig.java @@ -2,29 +2,44 @@ import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; 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 +// Enable WebSocket's message broker // 启用 Websocket 的消息代理 @EnableWebSocketMessageBroker -public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { - // 注册 STOMP 协议的节点(Endpoint),并映射为指定的 URL - // 我们使用 STOMP,所以不需要再实现 WebSocketHandler。 - // 实现 WebSocketHandler 的目的是接收和处理消息,STOMP 已经为我们做了这些。 +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + /** + * Register STOMP's endpoint + * We don't need to implement WebSocketHandler, STOMP has done this thing. + *

+ * 注册 STOMP 协议的节点(Endpoint),并映射为指定的 URL + * 我们使用 STOMP,所以不需要再实现 WebSocketHandler。 + * 实现 WebSocketHandler 的目的是接收和处理消息,STOMP 已经为我们做了这些。 + */ @Override public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) { + // Register the endpoint of STOMP, and use SockJS protocol // 注册 STOMP 协议的节点,并指定使用 SockJS 协议 stompEndpointRegistry.addEndpoint("/im").addInterceptors(new HandshakeInterceptor()).withSockJS(); } - // 配置使用消息代理 + /** + * Configure message broker + *

+ * 配置使用消息代理 + * + * @param registry message broker registry + */ @Override public void configureMessageBroker(MessageBrokerRegistry registry) { + // Configure global message broker, client will subscribe these message brokers to receive messages // 统一配置消息代理,消息代理即订阅点,客户端通过订阅消息代理点接受消息 registry.enableSimpleBroker("/b", "/g", "/user"); + // configure point to point message's prefix // 配置点对点消息的前缀 registry.setUserDestinationPrefix("/user"); } diff --git a/WebSocketServer/src/main/java/app/xlui/example/im/message/Message.java b/WebSocketServer/src/main/java/app/xlui/example/im/message/Message.java index 52b7fdd..6c59432 100644 --- a/WebSocketServer/src/main/java/app/xlui/example/im/message/Message.java +++ b/WebSocketServer/src/main/java/app/xlui/example/im/message/Message.java @@ -6,6 +6,8 @@ import lombok.Setter; /** + * Message sent to server by browser + * * 封装浏览器发送到服务器的消息 */ @Getter diff --git a/WebSocketServer/src/main/java/app/xlui/example/im/message/Response.java b/WebSocketServer/src/main/java/app/xlui/example/im/message/Response.java index 3ec7d70..98b199b 100644 --- a/WebSocketServer/src/main/java/app/xlui/example/im/message/Response.java +++ b/WebSocketServer/src/main/java/app/xlui/example/im/message/Response.java @@ -6,6 +6,8 @@ import lombok.Setter; /** + * Message sent to client + * * 封装发给客户端的消息 */ @Getter diff --git a/WebSocketServer/src/main/java/app/xlui/example/im/web/WebSocketController.java b/WebSocketServer/src/main/java/app/xlui/example/im/web/WebSocketController.java index aa1a0f4..72e6642 100644 --- a/WebSocketServer/src/main/java/app/xlui/example/im/web/WebSocketController.java +++ b/WebSocketServer/src/main/java/app/xlui/example/im/web/WebSocketController.java @@ -15,6 +15,7 @@ @Controller @Slf4j public class WebSocketController { + // You cannot integrate WebSocket with JWT for token validate. private static final String token = "this is a token generated by your code!"; private final SimpMessagingTemplate simpMessagingTemplate; @@ -23,18 +24,25 @@ public WebSocketController(SimpMessagingTemplate simpMessagingTemplate) { } /** + * @MessageMapping defines the endpoint for receiving messages, client will send websocket message + * to endpoints defined in this annotation. + * @SendTo defines the return value's target endpoint of this method, clients which subscribe to + * this endpoint will receive the return value of this method. + * This method will simply send messages received to all clients that subscribe to endpoint specified in + * @SendTo, just like a broadcast + * * @MessageMapping 定义接收消息的端点,客户端发送 WebSocket 消息到此端点。 * @SendTo 定义方法返回值发送的端点,订阅该端点的客户端可以收到服务器端的回复。 * 此端点默认将收到的消息发送到所有订阅了 @SendTo 端点的客户端,相当于广播。 * - * @param message 客户端消息 - * @param authorizationToken 自定义的请求校验 Token,后续也可以与 JWT 集成进行验证 - * @return 返回消息 + * @param message client message + * @param authorizationToken customize header, for token validate + * @return return client message to all clients that subscribe to /b */ // @MessageMapping("/broadcast") @SendTo("/b") - public Response say(Message message, @Header(value = "authorization") String authorizationToken) { + public Response broadcast(Message message, @Header(value = "authorization") String authorizationToken) { val response = new Response("Token check failed!"); if (authorizationToken.equals(token)) { log.info("Token check success!!!"); @@ -46,11 +54,15 @@ public Response say(Message message, @Header(value = "authorization") String aut } /** + * Add a placeholder in @MessageMapping to get the dynamic param in websocket url, for dynamic + * resending. Message sent to this endpoint will be resent to any clients that subscribe endpoint {@code /g/}. + * Just like a group chat. + *

* 通过在 @MessageMapping 中添加消息占位符来获取 url 内容,从而动态转发。 * 消息会发送到所有订阅了 {@code /g/} 的客户端,实现效果相当于群聊 * - * @param groupID 组ID - * @param message 发送的消息 + * @param groupID group id + * @param message client message */ @MessageMapping("/group/{groupID}") public void group(@DestinationVariable int groupID, Message message) { @@ -60,6 +72,9 @@ public void group(@DestinationVariable int groupID, Message message) { } /** + * Send message to specify user depend on {@link ChatMessage#getUserID}, each user will subscribe himself/herself's endpoint + * {@code /user//msg}, just like point to point chat. + *

* 依据 {@code ChatMessage} 中的内容发送消息给特定用户,每个用户都订阅自己接受消息的端点 * {@code /user//msg},实现效果类似点对点聊天 * diff --git a/WebSocketServer/src/main/resources/templates/broadcast.html b/WebSocketServer/src/main/resources/templates/broadcast.html index 152a970..bcadbd2 100644 --- a/WebSocketServer/src/main/resources/templates/broadcast.html +++ b/WebSocketServer/src/main/resources/templates/broadcast.html @@ -1,21 +1,21 @@ - + - Spring Boot WebSocket 广播式 + Spring Boot WebSocket Broadcast

貌似你的浏览器不支持 websocket

+

Your browser does not support websocket!

- - + +
- + - +

diff --git a/WebSocketServer/src/main/resources/templates/chat.html b/WebSocketServer/src/main/resources/templates/chat.html index 67574f7..d2950a3 100644 --- a/WebSocketServer/src/main/resources/templates/chat.html +++ b/WebSocketServer/src/main/resources/templates/chat.html @@ -1,19 +1,19 @@ - + - Spring Boot WebSocket 点对点 + Spring Boot WebSocket Point-to-Point chat

- +
diff --git a/WebSocketServer/src/main/resources/templates/group.html b/WebSocketServer/src/main/resources/templates/group.html index 4a5247e..6a6b035 100644 --- a/WebSocketServer/src/main/resources/templates/group.html +++ b/WebSocketServer/src/main/resources/templates/group.html @@ -1,27 +1,27 @@ - + - Spring Boot WebSocket 组内广播 + Spring Boot WebSocket Group chat
- +
- - + +
- + - +