From e6d32ae8aae377172a57549e8c0a0b411fbc1af7 Mon Sep 17 00:00:00 2001 From: xlui Date: Wed, 4 Apr 2018 17:10:20 +0800 Subject: [PATCH 1/4] Token Authorization Example --- .../.idea/caches/build_file_checksums.ser | Bin 0 -> 537 bytes AndroidClient/.idea/codeStyles/Project.xml | 29 +++++++++ .../.idea/codeStyles/codeStyleConfig.xml | 5 ++ AndroidClient/.idea/misc.xml | 7 ++- AndroidClient/.idea/vcs.xml | 6 ++ .../xlui/im/activities/BroadcastActivity.java | 22 ++++++- .../me/xlui/im/activities/ChatActivity.java | 2 +- .../me/xlui/im/activities/GroupActivity.java | 2 +- .../main/java/me/xlui/im/util/StompUtils.java | 4 +- LICENSE | 2 +- README.md | 59 +++++++++++++++++- README_zh.md | 59 +++++++++++++++++- .../me/xlui/im/web/WebSocketController.java | 9 ++- .../src/main/resources/static/sockjs.min.js | 31 ++------- .../main/resources/templates/broadcast.html | 8 ++- 15 files changed, 200 insertions(+), 45 deletions(-) create mode 100644 AndroidClient/.idea/caches/build_file_checksums.ser create mode 100644 AndroidClient/.idea/codeStyles/Project.xml create mode 100644 AndroidClient/.idea/codeStyles/codeStyleConfig.xml create mode 100644 AndroidClient/.idea/vcs.xml diff --git a/AndroidClient/.idea/caches/build_file_checksums.ser b/AndroidClient/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000000000000000000000000000000000000..8908cfdcde47ad9f85ed84736a50f8bd117c9030 GIT binary patch literal 537 zcmZ4UmVvdnh`~NNKUXg?FQq6yGexf?KR>5fFEb@IQ7^qHF(oHeub?PDD>b=9F91S2 zm1gFoxMk*~I%lLNXBU^|7Q2L-Ts|(GuF1r}Q#7BMhIJFWRF{)3GpKNESYUkZd*k^N}Jx@15Pb%E$@WK)X9uynk3i+hBTP~V*;hv&X zaGz_>zPUi>AhoC@Gqt!Bu9#s<nXmM&$aZFBTX<=pz)Y&n347noPbF-ge6U)m6&KYlh IIMr1E0QXkA{Qv*} literal 0 HcmV?d00001 diff --git a/AndroidClient/.idea/codeStyles/Project.xml b/AndroidClient/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..30aa626 --- /dev/null +++ b/AndroidClient/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AndroidClient/.idea/codeStyles/codeStyleConfig.xml b/AndroidClient/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/AndroidClient/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/AndroidClient/.idea/misc.xml b/AndroidClient/.idea/misc.xml index 75dac50..c0f68ed 100644 --- a/AndroidClient/.idea/misc.xml +++ b/AndroidClient/.idea/misc.xml @@ -5,11 +5,12 @@ diff --git a/AndroidClient/.idea/vcs.xml b/AndroidClient/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/AndroidClient/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AndroidClient/app/src/main/java/me/xlui/im/activities/BroadcastActivity.java b/AndroidClient/app/src/main/java/me/xlui/im/activities/BroadcastActivity.java index 2cde4f4..ff3aeec 100644 --- a/AndroidClient/app/src/main/java/me/xlui/im/activities/BroadcastActivity.java +++ b/AndroidClient/app/src/main/java/me/xlui/im/activities/BroadcastActivity.java @@ -1,5 +1,6 @@ package me.xlui.im.activities; +import android.annotation.SuppressLint; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; @@ -14,12 +15,17 @@ import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import java.util.Arrays; + import me.xlui.im.R; import me.xlui.im.conf.Const; import me.xlui.im.util.StompUtils; import okhttp3.WebSocket; import ua.naiksoftware.stomp.Stomp; +import ua.naiksoftware.stomp.StompHeader; import ua.naiksoftware.stomp.client.StompClient; +import ua.naiksoftware.stomp.client.StompCommand; +import ua.naiksoftware.stomp.client.StompMessage; public class BroadcastActivity extends AppCompatActivity { private Button broadcast; @@ -40,7 +46,8 @@ private void init() { result = findViewById(R.id.show); } - @Override + @SuppressLint("CheckResult") + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_broadcast); @@ -51,7 +58,7 @@ protected void onCreate(Bundle savedInstanceState) { // 连接服务器 stompClient.connect(); Toast.makeText(this, "开始连接", Toast.LENGTH_SHORT).show(); - StompUtils.connect(stompClient); + StompUtils.lifecycle(stompClient); // 订阅消息 stompClient.topic(Const.broadcastResponse).subscribe(stompMessage -> { @@ -73,7 +80,16 @@ protected void onCreate(Bundle savedInstanceState) { } catch (JSONException e) { e.printStackTrace(); } - stompClient.send(Const.broadcast, jsonObject.toString()).subscribe(new Subscriber() { + String token = "this is a token generated by your code!"; + StompHeader authorizationHeader = new StompHeader("authorization", token); + stompClient.send(new StompMessage( + // Stomp command + StompCommand.SEND, + // Stomp Headers, Send Headers with STOMP + // the first header is necessary, and the other can be customized by ourselves + Arrays.asList(new StompHeader(StompHeader.DESTINATION, Const.broadcast), authorizationHeader), + // Stomp payload + jsonObject.toString())).subscribe(new Subscriber() { @Override public void onSubscribe(Subscription s) { Log.i(Const.TAG, "onSubscribe: 订阅成功!"); diff --git a/AndroidClient/app/src/main/java/me/xlui/im/activities/ChatActivity.java b/AndroidClient/app/src/main/java/me/xlui/im/activities/ChatActivity.java index db7b97f..4c545c0 100644 --- a/AndroidClient/app/src/main/java/me/xlui/im/activities/ChatActivity.java +++ b/AndroidClient/app/src/main/java/me/xlui/im/activities/ChatActivity.java @@ -72,7 +72,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { StompClient stompClient = Stomp.over(WebSocket.class, Const.address); stompClient.connect(); Toast.makeText(this, "开始连接", Toast.LENGTH_SHORT).show(); - StompUtils.connect(stompClient); + StompUtils.lifecycle(stompClient); stompClient.topic(Const.chatResponse.replace(Const.placeholder, user_id)).subscribe(stompMessage -> { JSONObject jsonObject = new JSONObject(stompMessage.getPayload()); diff --git a/AndroidClient/app/src/main/java/me/xlui/im/activities/GroupActivity.java b/AndroidClient/app/src/main/java/me/xlui/im/activities/GroupActivity.java index 584ae62..2b3f0b6 100644 --- a/AndroidClient/app/src/main/java/me/xlui/im/activities/GroupActivity.java +++ b/AndroidClient/app/src/main/java/me/xlui/im/activities/GroupActivity.java @@ -64,7 +64,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { StompClient stompClient = Stomp.over(WebSocket.class, Const.address); stompClient.connect(); Toast.makeText(this, "开始连接", Toast.LENGTH_SHORT).show(); - StompUtils.connect(stompClient); + StompUtils.lifecycle(stompClient); groupId.addTextChangedListener(new TextWatcher() { @Override diff --git a/AndroidClient/app/src/main/java/me/xlui/im/util/StompUtils.java b/AndroidClient/app/src/main/java/me/xlui/im/util/StompUtils.java index 0e78117..bf2f7e3 100644 --- a/AndroidClient/app/src/main/java/me/xlui/im/util/StompUtils.java +++ b/AndroidClient/app/src/main/java/me/xlui/im/util/StompUtils.java @@ -1,12 +1,14 @@ package me.xlui.im.util; +import android.annotation.SuppressLint; import android.util.Log; import me.xlui.im.conf.Const; import ua.naiksoftware.stomp.client.StompClient; public class StompUtils { - public static void connect(StompClient stompClient) { + @SuppressLint("CheckResult") + public static void lifecycle(StompClient stompClient) { stompClient.lifecycle().subscribe(lifecycleEvent -> { switch (lifecycleEvent.getType()) { case OPENED: diff --git a/LICENSE b/LICENSE index 783cf1d..65a04d7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 xlui +Copyright (c) 2018 xlui Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a75cfeb..20fd023 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,62 @@ stompClient.subscribe('/user/' + 1 + '/msg', function (response) { So when Bob send a message to Alice, she will receive it correctly. -
+## Token-Authorization -Android client and Browser support all endpoints now! +Sometimes, we want our endpoint can only be accessed by authorized users. So we need check users' identify. + +And I have add an example of passing `token` through HTTP headers. + +For server, we need to receive the header in our endpoint controller method: + +```java +private String token = "this is a token generated by your code!"; + +@MessageMapping("/broadcast") +@SendTo("/b") +public Response say(Message message, @Header(value = "authorization") String authorizationToken) { + if (authorizationToken.equals(token)) { + System.out.println("Token check success!!!"); + } else { + System.out.println("Token check failed!!!"); + } + return new Response("Welcome, " + message.getName() + "!"); +} +``` + +When you send a HTTP header `authorization` with a STOMP `SEND` method, the authorization token will be received properly. + +For browser client, what we should do is add HTTP header in out `SEND` method: + +```js +stompClient.send( + '/broadcast', + { + "authorization": "this is a token generated by your code!" + }, + JSON.stringify({'name': name}) +); +``` + +So when we send a message to endpoint `/broadcast`, the header `authorization` will be sent as well. And the server will do some check to the `authorizationToken`. + +For android client, it is similar to the browser client: + +```java +String token = "this is a token generated by your code!"; +StompHeader authorizationHeader = new StompHeader("authorization", token); +stompClient.send(new StompMessage( + // Stomp command + StompCommand.SEND, + // Stomp Headers, Send Headers with STOMP + // the first header is necessary, and the other can be customized by ourselves + Arrays.asList(new StompHeader(StompHeader.DESTINATION, Const.broadcast), authorizationHeader), + // Stomp payload + jsonObject.toString()) + ).subscribe(...); +``` + +So, now we can generate token in server and send the token to users after the successful login. When user want to send message to endpoints we provide, a valid token is required! ## Server @@ -84,4 +137,4 @@ Android client and Browser support all endpoints now! ## LICENSE -MIT +[MIT](LICENSE) diff --git a/README_zh.md b/README_zh.md index 99e1c98..dc4ff33 100644 --- a/README_zh.md +++ b/README_zh.md @@ -50,9 +50,62 @@ stompClient.subscribe('/user/' + 1 + '/msg', function (response) { 这样,当 Bob 给 Alice 发送消息的时候,Alice 会成功收到。 -## 当前状态 +## Token 身份认证 -安卓端和浏览器端现在支持所有端点! +有时候,我们会希望我们的端点只供认证的用户使用,所以我们需要检查用户的身份。常用的方法是通过 HTTP headers 传递 Token。 + +下面是一个通过 HTTP header 传递 `token` 并验证的示例。 + +**服务器端**,我们需要在 controller 中端点方法中接收相应的 Header: + +```java +private String token = "this is a token generated by your code!"; + +@MessageMapping("/broadcast") +@SendTo("/b") +public Response say(Message message, @Header(value = "authorization") String authorizationToken) { + if (authorizationToken.equals(token)) { + System.out.println("Token check success!!!"); + } else { + System.out.println("Token check failed!!!"); + } + return new Response("Welcome, " + message.getName() + "!"); +} +``` + +当通过 STOMP 的 `SEND` 方法向服务器发送消息并附带 `authorization` header 的时候,authorization 的值(即 token)会被服务器成功获取。 + +**浏览器端**,我们需要做的是在 `SEND` 方法中附带 HTTP Header: + +```js +stompClient.send( + '/broadcast', + { + "authorization": "this is a token generated by your code!" + }, + JSON.stringify({'name': name}) +); +``` + +现在,当我们向 `/broadcast` 发送消息的时候,`authorization` 也会被发送给服务器。 + +**安卓端**,跟浏览器端大体类似: + +```java +String token = "this is a token generated by your code!"; +StompHeader authorizationHeader = new StompHeader("authorization", token); +stompClient.send(new StompMessage( + // STOMP 指令 + StompCommand.SEND, + // STOMP headers + // 第一个 header 是必须的,其他的我们可以自定义 + Arrays.asList(new StompHeader(StompHeader.DESTINATION, Const.broadcast), authorizationHeader), + // STOMP 荷载(即消息体) + jsonObject.toString()) + ).subscribe(...); +``` + +现在,我们可以在服务器生成 Token,并且在用户成功登录的时候发送给用户。当用户想要发送消息到端点时,需要先提供合法的 Token。 ## 服务器端构建 @@ -84,4 +137,4 @@ stompClient.subscribe('/user/' + 1 + '/msg', function (response) { ## LICENSE -MIT +[MIT](LICENSE) diff --git a/WebSocketServer/src/main/java/me/xlui/im/web/WebSocketController.java b/WebSocketServer/src/main/java/me/xlui/im/web/WebSocketController.java index 2c263d4..05fe104 100644 --- a/WebSocketServer/src/main/java/me/xlui/im/web/WebSocketController.java +++ b/WebSocketServer/src/main/java/me/xlui/im/web/WebSocketController.java @@ -5,6 +5,7 @@ import me.xlui.im.message.Response; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessagingTemplate; @@ -14,12 +15,18 @@ public class WebSocketController { @Autowired SimpMessagingTemplate simpMessagingTemplate; + private String token = "this is a token generated by your code!"; // 当客户端向服务器发送请求时,通过 `@MessageMapping` 映射 /broadcast 这个地址 @MessageMapping("/broadcast") // 当服务器有消息时,会对订阅了 @SendTo 中的路径的客户端发送消息 @SendTo("/b") - public Response say(Message message) { + public Response say(Message message, @Header(value = "authorization") String authorizationToken) { + if (authorizationToken.equals(token)) { + System.out.println("Token check success!!!"); + } else { + System.out.println("Token check failed!!!"); + } return new Response("Welcome, " + message.getName() + "!"); } diff --git a/WebSocketServer/src/main/resources/static/sockjs.min.js b/WebSocketServer/src/main/resources/static/sockjs.min.js index b901fd6..e086e13 100644 --- a/WebSocketServer/src/main/resources/static/sockjs.min.js +++ b/WebSocketServer/src/main/resources/static/sockjs.min.js @@ -1,27 +1,4 @@ -/* SockJS client, version 0.3.4, http://sockjs.org, MIT License - -Copyright (c) 2011-2012 VMware, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -/* JSON2 by Douglas Crockford (minified).*/ -var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c1?this._listeners[a]=d.slice(0,e).concat(d.slice(e+1)):delete this._listeners[a];return}return},d.prototype.dispatchEvent=function(a){var b=a.type,c=Array.prototype.slice.call(arguments,0);this["on"+b]&&this["on"+b].apply(this,c);if(this._listeners&&b in this._listeners)for(var d=0;d=3e3&&a<=4999},c.countRTO=function(a){var b;return a>100?b=3*a:b=a+200,b},c.log=function(){b.console&&console.log&&console.log.apply&&console.log.apply(console,arguments)},c.bind=function(a,b){return a.bind?a.bind(b):function(){return a.apply(b,arguments)}},c.flatUrl=function(a){return a.indexOf("?")===-1&&a.indexOf("#")===-1},c.amendUrl=function(b){var d=a.location;if(!b)throw new Error("Wrong url for SockJS");if(!c.flatUrl(b))throw new Error("Only basic urls are supported in SockJS");return b.indexOf("//")===0&&(b=d.protocol+b),b.indexOf("/")===0&&(b=d.protocol+"//"+d.host+b),b=b.replace(/[/]+$/,""),b},c.arrIndexOf=function(a,b){for(var c=0;c=0},c.delay=function(a,b){return typeof a=="function"&&(b=a,a=0),setTimeout(b,a)};var i=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,j={"\0":"\\u0000","\x01":"\\u0001","\x02":"\\u0002","\x03":"\\u0003","\x04":"\\u0004","\x05":"\\u0005","\x06":"\\u0006","\x07":"\\u0007","\b":"\\b","\t":"\\t","\n":"\\n","\x0b":"\\u000b","\f":"\\f","\r":"\\r","\x0e":"\\u000e","\x0f":"\\u000f","\x10":"\\u0010","\x11":"\\u0011","\x12":"\\u0012","\x13":"\\u0013","\x14":"\\u0014","\x15":"\\u0015","\x16":"\\u0016","\x17":"\\u0017","\x18":"\\u0018","\x19":"\\u0019","\x1a":"\\u001a","\x1b":"\\u001b","\x1c":"\\u001c","\x1d":"\\u001d","\x1e":"\\u001e","\x1f":"\\u001f",'"':'\\"',"\\":"\\\\","\x7f":"\\u007f","\x80":"\\u0080","\x81":"\\u0081","\x82":"\\u0082","\x83":"\\u0083","\x84":"\\u0084","\x85":"\\u0085","\x86":"\\u0086","\x87":"\\u0087","\x88":"\\u0088","\x89":"\\u0089","\x8a":"\\u008a","\x8b":"\\u008b","\x8c":"\\u008c","\x8d":"\\u008d","\x8e":"\\u008e","\x8f":"\\u008f","\x90":"\\u0090","\x91":"\\u0091","\x92":"\\u0092","\x93":"\\u0093","\x94":"\\u0094","\x95":"\\u0095","\x96":"\\u0096","\x97":"\\u0097","\x98":"\\u0098","\x99":"\\u0099","\x9a":"\\u009a","\x9b":"\\u009b","\x9c":"\\u009c","\x9d":"\\u009d","\x9e":"\\u009e","\x9f":"\\u009f","\xad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601","\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f","\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d","\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029","\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d","\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061","\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065","\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069","\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d","\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0","\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4","\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8","\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc","\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"},k=/[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g,l,m=JSON&&JSON.stringify||function(a){return i.lastIndex=0,i.test(a)&&(a=a.replace(i,function(a){return j[a]})),'"'+a+'"'},n=function(a){var b,c={},d=[];for(b=0;b<65536;b++)d.push(String.fromCharCode(b));return a.lastIndex=0,d.join("").replace(a,function(a){return c[a]="\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4),""}),a.lastIndex=0,c};c.quote=function(a){var b=m(a);return k.lastIndex=0,k.test(b)?(l||(l=n(k)),b.replace(k,function(a){return l[a]})):b};var o=["websocket","xdr-streaming","xhr-streaming","iframe-eventsource","iframe-htmlfile","xdr-polling","xhr-polling","iframe-xhr-polling","jsonp-polling"];c.probeProtocols=function(){var a={};for(var b=0;b0&&h(a)};return c.websocket!==!1&&h(["websocket"]),d["xhr-streaming"]&&!c.null_origin?e.push("xhr-streaming"):d["xdr-streaming"]&&!c.cookie_needed&&!c.null_origin?e.push("xdr-streaming"):h(["iframe-eventsource","iframe-htmlfile"]),d["xhr-polling"]&&!c.null_origin?e.push("xhr-polling"):d["xdr-polling"]&&!c.cookie_needed&&!c.null_origin?e.push("xdr-polling"):h(["iframe-xhr-polling","jsonp-polling"]),e};var p="_sockjs_global";c.createHook=function(){var a="a"+c.random_string(8);if(!(p in b)){var d={};b[p]=function(a){return a in d||(d[a]={id:a,del:function(){delete d[a]}}),d[a]}}return b[p](a)},c.attachMessage=function(a){c.attachEvent("message",a)},c.attachEvent=function(c,d){typeof b.addEventListener!="undefined"?b.addEventListener(c,d,!1):(a.attachEvent("on"+c,d),b.attachEvent("on"+c,d))},c.detachMessage=function(a){c.detachEvent("message",a)},c.detachEvent=function(c,d){typeof b.addEventListener!="undefined"?b.removeEventListener(c,d,!1):(a.detachEvent("on"+c,d),b.detachEvent("on"+c,d))};var q={},r=!1,s=function(){for(var a in q)q[a](),delete q[a]},t=function(){if(r)return;r=!0,s()};c.attachEvent("unload",t),c.unload_add=function(a){var b=c.random_string(8);return q[b]=a,r&&c.delay(s),b},c.unload_del=function(a){a in q&&delete q[a]},c.createIframe=function(b,d){var e=a.createElement("iframe"),f,g,h=function(){clearTimeout(f);try{e.onload=null}catch(a){}e.onerror=null},i=function(){e&&(h(),setTimeout(function(){e&&e.parentNode.removeChild(e),e=null},0),c.unload_del(g))},j=function(a){e&&(i(),d(a))},k=function(a,b){try{e&&e.contentWindow&&e.contentWindow.postMessage(a,b)}catch(c){}};return e.src=b,e.style.display="none",e.style.position="absolute",e.onerror=function(){j("onerror")},e.onload=function(){clearTimeout(f),f=setTimeout(function(){j("onload timeout")},2e3)},a.body.appendChild(e),f=setTimeout(function(){j("timeout")},15e3),g=c.unload_add(i),{post:k,cleanup:i,loaded:h}},c.createHtmlfile=function(a,d){var e=new ActiveXObject("htmlfile"),f,g,i,j=function(){clearTimeout(f)},k=function(){e&&(j(),c.unload_del(g),i.parentNode.removeChild(i),i=e=null,CollectGarbage())},l=function(a){e&&(k(),d(a))},m=function(a,b){try{i&&i.contentWindow&&i.contentWindow.postMessage(a,b)}catch(c){}};e.open(),e.write(' diff --git a/WebSocketServer/src/main/resources/templates/chat.html b/WebSocketServer/src/main/resources/templates/chat.html index 67574f7..1049514 100644 --- a/WebSocketServer/src/main/resources/templates/chat.html +++ b/WebSocketServer/src/main/resources/templates/chat.html @@ -2,18 +2,18 @@ - 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..76988ed 100644 --- a/WebSocketServer/src/main/resources/templates/group.html +++ b/WebSocketServer/src/main/resources/templates/group.html @@ -4,24 +4,24 @@ - Spring Boot WebSocket 组内广播 + Spring Boot WebSocket Group chat
- +
- - + +
- + - +

From 875f73cd2aac5132e0b9f1dc2c7cc53e5a95314e Mon Sep 17 00:00:00 2001 From: xlui Date: Sat, 28 Sep 2019 11:55:02 +0800 Subject: [PATCH 4/4] Update comment and ui language --- .../im/activities/BroadcastActivity.java | 51 +++++----- .../example/im/activities/ChatActivity.java | 94 +++++++++---------- .../example/im/activities/GroupActivity.java | 81 ++++++++-------- .../java/app/xlui/example/im/conf/Const.java | 7 +- .../app/xlui/example/im/util/StompUtils.java | 33 +++---- .../app/src/main/res/values/strings.xml | 20 ++-- .../im/config/HandshakeInterceptor.java | 16 ++-- .../xlui/example/im/config/WebMvcConfig.java | 4 +- .../example/im/config/WebSocketConfig.java | 16 +++- .../example/im/web/WebSocketController.java | 31 +++--- .../main/resources/templates/broadcast.html | 4 +- .../src/main/resources/templates/chat.html | 4 +- .../src/main/resources/templates/group.html | 4 +- 13 files changed, 185 insertions(+), 180 deletions(-) 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 46993f5..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, "Connect start", 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, "Subscribe broadcast"); + 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 fe0ace7..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, "Connect start", 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 ea09010..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, "Connect start", 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 9010be2..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,21 +8,18 @@ import java.util.Map; +@Slf4j public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor { - private static Logger logger = LoggerFactory.getLogger("xlui"); - /** * Before websocket handshake * You can put some data into {@code attributes} here, and get it in WebSocketHandler's session - * - * WebSocket 握手前 *

- * 可以设置数据到 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); } @@ -32,7 +28,7 @@ public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse res */ @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 693e4da..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,19 +2,19 @@ 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 { +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { /** * Register STOMP's endpoint - * We don't need to implement WebSocketHandler, STOMP has do the thins what it need to do. - * + * We don't need to implement WebSocketHandler, STOMP has done this thing. + *

* 注册 STOMP 协议的节点(Endpoint),并映射为指定的 URL * 我们使用 STOMP,所以不需要再实现 WebSocketHandler。 * 实现 WebSocketHandler 的目的是接收和处理消息,STOMP 已经为我们做了这些。 @@ -26,7 +26,13 @@ public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) 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 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 2dd6a3a..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,23 +24,25 @@ public WebSocketController(SimpMessagingTemplate simpMessagingTemplate) { } /** - * @MessageMapping defines the endpoint for receiving message, client will send websocket message - * to endpoints defined in this method. @SendTo defines the return value's target endpoint of some - * method, clients which subscribe to this endpoint will receive the return value of this method. This method will - * send received message to all clients that subscribe @SendTo endpoint, just like a broadcast + * @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!!!"); @@ -52,14 +55,14 @@ 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 method will be resent to any clients that subscribe endpoint {@code /g/}. + * 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) { @@ -69,9 +72,9 @@ public void group(@DestinationVariable int groupID, Message message) { } /** - * Send message to specify user depend on {@code ChatMessage}, every user will subscribe himself/herself's endpoint + * 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 5e24279..bcadbd2 100644 --- a/WebSocketServer/src/main/resources/templates/broadcast.html +++ b/WebSocketServer/src/main/resources/templates/broadcast.html @@ -1,12 +1,12 @@ - + Spring Boot WebSocket Broadcast

You browser does not support 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 1049514..d2950a3 100644 --- a/WebSocketServer/src/main/resources/templates/chat.html +++ b/WebSocketServer/src/main/resources/templates/chat.html @@ -1,12 +1,12 @@ - + 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 76988ed..6a6b035 100644 --- a/WebSocketServer/src/main/resources/templates/group.html +++ b/WebSocketServer/src/main/resources/templates/group.html @@ -1,5 +1,5 @@ - + @@ -8,7 +8,7 @@