WEB通知和React Native之即時通訊(iOS Android)
一,需求分析
1.1,允許服務器主動發送信息給客戶端,客戶端能監聽到並且能接收。
1.2,為了方便同一個系統內的用戶可以指定某個用戶可以私聊。
1.3,給指定用戶或多個用戶發送通知。
二,技術介紹
2.1.WebSocket介紹
1,WebSocket 是什么?
2,WebSocket 的作用
- WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。
- HTML5 定義的 WebSocket 協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。

其他特點包括:
(1)建立在 TCP 協議之上,服務器端的實現比較容易。
(2)與 HTTP 協議有着良好的兼容性。默認端口也是80和443,並且握手階段采用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。
(3)數據格式比較輕量,性能開銷小,通信高效。
(4)可以發送文本,也可以發送二進制數據。
(5)沒有同源限制,客戶端可以與任意服務器通信。
(6)協議標識符是ws(如果加密,則為wss),服務器網址就是 URL。
3,WebSocket 構造函數
WebSocket 對象作為一個構造函數,用於新建 WebSocket 實例。執行如下語句之后,客戶端就會與服務器進行連接。
1 var ws = new WebSocket('ws://localhost:8080');
4,webSocket.readyState
- CONNECTING:值為0,表示正在連接。
- OPEN:值為1,表示連接成功,可以通信了。
- CLOSING:值為2,表示連接正在關閉。
- CLOSED:值為3,表示連接已經關閉,或者打開連接失敗。
5,webSocket.onopen
實例對象的onopen屬性,用於指定連接成功后的回調函數。
1 ws.onopen = function () { 2 ws.send('Hello Server!'); 3 }
如果要指定多個回調函數,可以使用addEventListener方法。
1 ws.addEventListener('open', function (event) { 2 ws.send('Hello Server!'); 3 });
6,webSocket.send()
實例對象的send()方法用於向服務器發送數據。
(1)發送文本
1 ws.send('your message');
(2)發送 Blob 對象
1 var file = document 2 .querySelector('input[type="file"]') 3 .files[0]; 4 ws.send(file);
(3)發送 ArrayBuffer 對象
1 // Sending canvas ImageData as ArrayBuffer 2 var img = canvas_context.getImageData(0, 0, 400, 320); 3 var binary = new Uint8Array(img.data.length); 4 for (var i = 0; i < img.data.length; i++) { 5 binary[i] = img.data[i]; 6 } 7 ws.send(binary.buffer);
(4)發送json對象
1 var messageObj = {fromUserId:1,message:'您好,jackson影琪',toUserId:2}; 2 var messageJson = JSON.stringify(messageObj); 3 ws.send(messageJson);
7,webSocket.onmessage
對象的onmessage屬性,用於指定收到服務器數據后的回調函數。
1 ws.onmessage = function(event) { 2 var data = event.data; 3 // 處理數據 4 }; 5 6 ws.addEventListener("message", function(event) { 7 var data = event.data; 8 // 處理數據 9 });
服務器數據可能是文本,也可能是二進制數據(blob對象或Arraybuffer對象)
1 ws.onmessage = function(event){ 2 if(typeof event.data === String) { 3 console.log("Received data string"); 4 } 5 6 if(event.data instanceof ArrayBuffer){ 7 var buffer = event.data; 8 console.log("Received arraybuffer"); 9 } 10 }
8,webSocket.onclose
對象的onclose屬性,用於指定連接關閉后的回調函數。
1 ws.onclose = function(event) { 2 var code = event.code; 3 var reason = event.reason; 4 var wasClean = event.wasClean; 5 // handle close event 6 }; 7 8 ws.addEventListener("close", function(event) { 9 var code = event.code; 10 var reason = event.reason; 11 var wasClean = event.wasClean; 12 // handle close event 13 });
9,webSocket.onerror
對象的onerror屬性,用於指定報錯時的回調函數。
1 ws.onerror = function(event) { 2 // handle error event 3 }; 4 5 ws.addEventListener("error", function(event) { 6 // handle error event 7 });
2.2,react-native-gifted-chat介紹
- messages(Array) - 消息數組,用於展示消息 有特定的格式
1 { 2 _id: 1, //消息的ID 3 text: 'My message', //發送的消息內容 4 createdAt: new Date(), //發送的時間 5 user: {/發送方的用戶信息 6 _id: 2, //發送方的ID 7 name: 'Jackson', //發送方的昵稱 8 avatar: 'https://pic.cnblogs.com/avatar/1040068/20181013100635.png',, //發送方的頭像 9 }, 10 image: 'https://pic.cnblogs.com/avatar/1040068/20181013100635.png', 11 //添加你所需要擴展的鍵值對 12 }
- user(Object) - 配置用戶信息
1 { 2 _id: 1, //發送消息需要和配置的id一致 avatar:'https://pic.cnblogs.com/avatar/1040068/20181013100635.png', //頭像 若不設置則不顯示 3 name:'jackson影琪', //昵稱 4 }
- renderBubble(Function) - 自定義氣泡
1 //氣泡 2 renderBubble(props) { 3 return ( 4 <Bubble 5 {...props} 6 wrapperStyle={{ 7 left: {//對方的氣泡 8 backgroundColor: '#ffffff', 9 }, 10 right: {//我方的氣泡 11 backgroundColor: '#1fb922', 12 } 13 }} 14 /> 15 ); 16 }
- text(String) - 輸入框的默認值;默認是undefined
- placeholder(String) - 輸入框的占位字符
- messageIdGenerator(Function) - 為你的新消息自動生成一個id. 默認是用 UUID v4, 由uuid庫實現uuid
- onSend(Function) - 點擊send時的回調
- locale(String) -本地化日期
- timeFormat(String) - 格式化時間,默認是本地時間,即當前時區的時間
- dateFormat(String) - 日期格式化
- isAnimated(Bool) - 鍵盤出現時,是否有動畫
- loadEarlier(Bool) - 是否顯示加載更早的消息按鈕 "Load earlier messages"
- onLoadEarlier(Function) - 加載更多消息時的回調
- isLoadingEarlier(Bool) - 點擊加載更早的消息時是否出現轉菊花的圖標
- renderLoading(Function) - 加載頁面未加載出來時的頁面
1 //加載更多消息 2 loadEarlier={self.state.isMore}// 3 isLoadingEarlier={self.state.isMore}// 4 renderLoadEarlier={() => { 5 return ( 6 <Text 7 onPress={self.onLoadEarlier} 8 style={[ 9 styles.LookMoreStyle 10 ]} 11 >{self.state.moreData}</Text> 12 ); 13 }}
- renderLoadEarlier(Function) - 配置 "Load earlier messages" 加載更早消息的按鈕
- renderAvatar(Function) - 配置頭像,如果設置'null'則頭像都不顯示
1 //頭像 2 renderAvatar(props) { 3 return ( 4 <Avatar 5 {...props} 6 /> 7 ); 8 }
- showUserAvatar(Bool) - 是否展示自己的頭像,默認時false 只展示別人的頭像
- onPressAvatar(Function(user)) - 點擊頭像時的回調
- renderAvatarOnTop(Bool) 頭像顯示在頂部還是底部,默認是底部
- renderSystemMessage(Function) - 自定義系統消息
- onLongPress(Function(context,message)) - 長按消息氣泡時的回調,詳細可以看github的演示 showActionSheetWithOptions()
- inverted(Bool) - 反轉消息的顯示順序,默認是true 即消息顯示的順序是否和你message數組的順序相同
- renderMessage(Function) - 自定義消息的內容View
- renderMessageText(Function) - 自定義消息的文本
- renderMessageImage(Function) - 自定義圖片消息
- imageProps(Object) - 額外的屬性要傳遞給默認創建的組件rendermessageimage點去去查看文檔
- lightboxProps(Object) - 額外的屬性傳遞給Modal框(體現在點擊圖片的Modal)
- 點擊查看第三方 - Lightbox
- renderCustomView(Function) - 在氣泡內創建一個自己自定義的視圖,即創建自定義的消息
- renderDay(Function) - 自定義消息上方的日期
- renderTime(Function) - 自定義消息中的時間
- renderFooter(Function) - 自定義listView的底部, 例如.'User is typing...'; 點擊查看示例 example/App.js for an example
- renderChatFooter(Function) - 自定義組件的渲染下messagecontainer(從ListView分開)
- renderInputToolbar(Function) - 自定義你的底部工具欄
- renderComposer(Function) - 自定義textInput輸入框
- renderActions(Function) - 自定義輸入框左邊的按鈕
- renderSend(Function) -自定義發送按鈕;您可以很容易地將子組件傳遞給原始組件,例如使用自定義圖標。
- renderAccessory(Function) - 在消息編輯器下面的自定義第二行操作
- onPressActionButton\(Function) - 當點擊輸入框左邊的按鈕時的回調 (如果設置了 actionSheet將不會執行)
- bottomOffset(Integer) - 從屏幕底部的聊天距離(如顯示選項卡欄,則非常有用)
- minInputToolbarHeight(Integer) - 工具欄的最小高度,默認是44
- listViewProps(Object) - 列表的屬性,用於擴展你的列表
1 listViewProps={{ 2 // //ListView/FlatView中標識是否可以加載更多(當現在獲取到的數據已經是全部了,不能再繼續獲取數據了,則設為false,當還有數據可以獲取則設為true) 3 canLoad: true, 4 //標識現在是否ListView/FlatView現在正在加載(根據這個值來決定是否顯示"正在加載的cell")(loadMore()方法進去后設為true,fetch加載完數據后設為false) 5 isLoadding: false, 6 //是否顯示下拉刷新的cell 7 ifShowRefresh: true, 8 //ListView/FlatList是否可以滾動 9 scrollEnabled: true, 10 //記錄當前加載到了哪一頁 11 page: 1, 12 onScroll:self._onScroll.bind(this) 13 }}
- textInputProps(Object) - 輸入框的屬性,用於擴展你的輸入框
- keyboardShouldPersistTaps(Enum) - 確定鍵盤在敲擊后是否應該保持可見。一個枚舉; 詳情見 <ScrollView>
- onInputTextChanged(Function) - 輸入框編輯時的回調
- maxInputLength(Integer) - 輸入框輸入的最多字符數
-
showAvatarForEveryMessage(Bool) - 默認是false每條消息都顯示頭像
系統消息格式
1 { 2 _id: 1, 3 text: 'This is a system message', 4 createdAt: new Date(), 5 system: true, 6 // Any additional custom parameters are passed through 7 }
三,即時通訊實現
3.1,實現步驟
第一步:建立鏈接
1 componentWillMount() { 2 let self = this; 3 //建立鏈接 4 ws = new WebSocket('ws://127.0.0.1:8080/websocket/'+str); 5 ws.onopen = (evt) => { 6 // 打開一個連接 7 // console.log('WebSocket==' + evt) 8 alert("連接成功啦") 9 //ws.send('something'); // 發送一個消息 10 }; 11 ws.onmessage = (e) => { 12 // } 13 // 接收到了一個消息 14 //alert(JSON.parse(e.data).text) 15 console.log('e.data==' + e.data); 16 }; 17 18 ws.onerror = (e) => { 19 // 發生了一個錯誤 20 console.log('e.message==' + e.message); 21 }; 22 23 ws.onclose = (e) => { 24 // 連接被關閉了 25 console.log('e.code===' + e.code, 'e.reason===' + e.reason); 26 }; 27 }
第二步:發送消息
1 onSend(messages = []) { 2 let self = this 3 this.setState(previousState => ({ 4 messages: GiftedChat.append(previousState.messages, messages), 5 })) 6 // alert(messages[0].text) 7 this.doSend(messages[0].text) 8 } 9 10 // 發送消息 11 doSend = (message) => { 12 var messageObj = { 13 fromUserId: this.state.userData._id, 14 fromNickName: this.state.userData.name, 15 message: message, 16 toUserId: this.props.Account.id, 17 toNickName: this.props.Account.name, 18 sendTime: new Date() 19 20 }; 21 var messageJson = JSON.stringify(messageObj); 22 ws.send(messageJson); 23 }
第三步:接收消息
1 ws.onmessage = (e) => { 2 // { 3 // _id: 1, //消息的ID 4 // text: 'My message', //發送的消息內容 5 // createdAt: new Date(), //發送的時間 6 // user: {/發送方的用戶信息 7 // _id: 2, //發送方的ID 8 // name: 'Jackson', //發送方的昵稱 9 // avatar: 'https://pic.cnblogs.com/avatar/1040068/20181013100635.png',, //發送方的頭像 10 // }, 11 // } 12 // 接收到了一個消息 13 //alert(JSON.parse(e.data).text) 14 console.log('e.data==' + e.data); 15 }
1 componentWillUnmount() { 2 ws.close() 3 this.setState = (state, callback) => { 4 return; 5 }; 6 }
3.2.聊天界面構建
1,使用react-native-gifted-chat,安裝
1 npm install react-native-gifted-chat --save
2,引入使用
1 /** 2 * Created by Jackson on 2018/11/12. 3 * 聊天界面 4 */ 5 import React, { PureComponent } from 'react'; 6 import { 7 View, 8 Text, 9 StyleSheet, 10 TouchableOpacity, 11 Keyboard, 12 Platform, 13 StatusBar 14 } from 'react-native'; 15 //聊天 16 import { GiftedChat, Bubble, Avatar } from 'react-native-gifted-chat' 17 export default class ChatBox extends PureComponent { 18 constructor(props) { 19 super(props); 20 this.renderBubble = this.renderBubble.bind(this); 21 this.renderAvatar = this.renderAvatar.bind(this); 22 this.state = { 23 //聊天 24 messages: [], 25 userData: { 26 _id: 1, 27 name:'jackson影琪', 28 avatar: 'https://pic.cnblogs.com/avatar/1040068/20181013100635.png', 29 }, 30 messageId: 1, 31 } 32 33 } 34 componentDidMount() { 35 let self = this 36 /****************************聊天組件 start **************************************************/ 37 setTimeout(function(){ 38 self.setState({ 39 messages: [ 40 41 { 42 _id: 2, 43 text: '微信小程序開發的基本流程', 44 createdAt: new Date('2018-10-25T15:41:00+08:00'), 45 user: { 46 _id: 1, 47 name: 'jackson影琪', 48 avatar: 'https://pic.cnblogs.com/avatar/1040068/20181013100635.png', 49 }, 50 //image: 'https://img2018.cnblogs.com/blog/1040068/201810/1040068-20181024162047704-1159291775.png', 51 }, 52 { 53 _id: 1, 54 text: 'Hello jackson影琪', 55 createdAt: new Date('2016-06-07T10:00:00+08:00'), 56 user: { 57 _id: 2, 58 name: 'jackson', 59 avatar: 'https://img2018.cnblogs.com/blog/1040068/201811/1040068-20181101192529807-2132606645.jpg' 60 }, 61 image: 'https://pic.cnblogs.com/avatar/1040068/20181013100635.png', 62 }, 63 ], 64 }) 65 },2000) 66 /****************************聊天組件 end **************************************************/ 67 68 } 69 70 71 /****************************聊天 start **************************************************/ 72 onSend(messages = []) { 73 this.setState(previousState => ({ 74 messages: GiftedChat.append(previousState.messages, messages), 75 })) 76 // alert(messages[0].text) 77 let self = this 78 self.state.messageId += 2 79 let m = { 80 _id: self.state.messageId, 81 text: '前端知識點總結(HTML)', 82 createdAt: new Date(), 83 user: { 84 _id: 2, 85 name: '', 86 avatar: 'https://img2018.cnblogs.com/blog/1040068/201811/1040068-20181101192529807-2132606645.jpg' 87 }, 88 image: 'https://img2018.cnblogs.com/blog/1040068/201811/1040068-20181109115100292-977588541.png', 89 } 90 self.setState(previousState => ({ 91 messages: GiftedChat.append(previousState.messages, m), 92 })) 93 } 94 //氣泡 95 renderBubble(props) { 96 return ( 97 <Bubble 98 {...props} 99 wrapperStyle={{ 100 left: { 101 backgroundColor: '#ffffff', 102 }, 103 right: { 104 backgroundColor: '#1fb922', 105 } 106 }} 107 /> 108 ); 109 } 110 //頭像 111 renderAvatar(props) { 112 return ( 113 <Avatar 114 {...props} 115 /> 116 ); 117 } 118 /****************************聊天 end **************************************************/ 119 render() { 120 let self = this; 121 return ( 122 <TouchableOpacity 123 activeOpacity={1} 124 style={{ flex: 1,}} 125 onPress={() => { Keyboard.dismiss() }} 126 > 127 128 {/* //聊天 */} 129 <GiftedChat 130 // onPressAvatar={()=>{alert('Keyboard.dismiss'); Keyboard.dismiss()}} 131 messages={this.state.messages} 132 onSend={messages => this.onSend(messages)} 133 renderBubble={this.renderBubble}//氣泡 134 renderAvatar={this.renderAvatar}//頭像 135 showUserAvatar={true}//是否顯示自己的頭像,默認不顯示 136 //onLongPress={()=>{alert('onLongPress')}}//長按消息 137 // 輸入組件 138 placeholder={'請輸入內容'}//輸入框占位符 139 // label={'發送'} 140 containerStyle={{ marginBottom: 2 }}//發送按鈕 141 children={ 142 <View 143 style={[ 144 styles.buttonBoxBorder 145 ]} 146 > 147 <Text 148 style={[ 149 styles.buttonText, 150 ]} 151 >發送</Text> 152 </View> 153 } 154 // textStyle={{ color: '#70b24e' }}//按鈕字的顏色 155 timeFormat={'MM月DD日 HH:mm:ss'}//格式化日前 156 dateFormat={'YYYY年MM月DD日'} 157 // locale={'zh-cn'} 158 isAnimated={true} 159 // renderAvatarOnTop={true} 160 user={this.state.userData}//用戶信息 161 /> 162 </TouchableOpacity> 163 ) 164 } 165 166 } 167 168 const styles = StyleSheet.create({ 169 buttonText: { 170 paddingHorizontal: 15, 171 paddingVertical: 5, 172 textAlign: 'center', 173 color: '#fff', 174 fontSize: 14 175 }, 176 buttonBoxBorder: { 177 overflow: 'hidden', 178 borderRadius: 5, 179 borderWidth: 1, 180 backgroundColor: "#70b24e", 181 borderColor: "#70b24e", 182 marginRight: 12, 183 marginBottom: 6, 184 }, 185 })
效果如下:

3.3,使用的方法
1,下拉加載更多
1 {/* //聊天 */} 2 <GiftedChat 3 // onPressAvatar={()=>{alert('Keyboard.dismiss'); Keyboard.dismiss()}} 4 messages={this.state.messages} 5 onSend={messages => this.onSend(messages)}//發送消息 6 7 8 ... 9 10 11 //加載更多消息 12 loadEarlier={self.state.isMore}// 13 isLoadingEarlier={self.state.isMore}// 14 renderLoadEarlier={() => { 15 return ( 16 <Text 17 onPress={self.onLoadEarlier} 18 style={[ 19 styles.LookMoreStyle 20 ]} 21 >{self.state.moreData}</Text> 22 ); 23 }} 24 25 listViewProps={{ 26 // //ListView/FlatView中標識是否可以加載更多(當現在獲取到的數據已經是全部了,不能再繼續獲取數據了,則設為false,當還有數據可以獲取則設為true) 27 canLoad: true, 28 //標識現在是否ListView/FlatView現在正在加載(根據這個值來決定是否顯示"正在加載的cell")(loadMore()方法進去后設為true,fetch加載完數據后設為false) 29 isLoadding: false, 30 //是否顯示下拉刷新的cell 31 ifShowRefresh: true, 32 //ListView/FlatList是否可以滾動 33 scrollEnabled: true, 34 //記錄當前加載到了哪一頁 35 page: 1, 36 onScroll:self._onScroll.bind(this) 37 }} 38 />
1 //加載更早的數據 2 onLoadEarlier = () => { 3 let self = this; 4 self.state.Currentpage += 1; 5 self.setState({ 6 isMore: true, 7 moreData: '正在加載更多...' 8 }) 9 self.getMessageData() 10 } 11 12 //上拉加載//翻頁 13 _onScroll(event) { 14 let self = this 15 let y = event.nativeEvent.contentOffset.y; 16 let height = event.nativeEvent.layoutMeasurement.height; 17 let contentHeight = event.nativeEvent.contentSize.height; 18 if (y + height >= contentHeight - 20 && y > 0 && this.state.contentHeight != contentHeight) {//上啦下一頁 19 self.state.contentHeight=contentHeight 20 self.onLoadEarlier() 21 22 } 23 else if (y < 0 || y == 0) {//下拉上一頁ios 24 25 } 26 }
2,在消息前后追加消息
1 //prepend(),在父級最前面追加一個子元素 2 self.setState(previousState => ({ 3 messages: GiftedChat.prepend(previousState.messages, ReceivedMessageData), 4 })) 5 6 //append(),在父級最后追加一個子元素 7 this.setState(previousState => ({ 8 messages: GiftedChat.append(previousState.messages, messages), 9 }))
3,完整代碼
1 {/* //聊天 */} 2 <GiftedChat 3 // onPressAvatar={()=>{alert('Keyboard.dismiss'); Keyboard.dismiss()}} 4 messages={this.state.messages} 5 onSend={messages => this.onSend(messages)}//發送消息 6 renderBubble={this.renderBubble}//氣泡 7 renderAvatar={this.renderAvatar}//頭像 8 showUserAvatar={true}// 顯示發送方的頭像 9 showAvatarForEveryMessage={true}//每條消息都顯示頭像 10 //onLongPress={()=>{alert('onLongPress')}} 11 // 輸入組件 12 placeholder={'請輸入內容'} 13 // label={'發送'} 14 containerStyle={{ marginBottom: 2 }} 15 children={ 16 <View 17 style={[ 18 styles.buttonBoxBorder 19 ]} 20 > 21 <Text 22 style={[ 23 styles.buttonText, 24 ]} 25 >發送</Text> 26 </View> 27 }//渲染發送按鈕 28 // textStyle={{ color: '#70b24e' }} 29 timeFormat={'MM月DD日 HH:mm:ss'} 30 dateFormat={'YYYY年MM月DD日'} 31 // locale={'zh-cn'} 32 isAnimated={true} 33 // renderAvatarOnTop={true} 34 user={this.state.userData} 35 36 // 系統消息樣式 37 wrapperStyle={{ paddingLeft: 12, paddingRight: 12 }} 38 textStyle={{ lineHeight: 20 }} 39 //加載更多消息 40 loadEarlier={self.state.isMore}// 41 isLoadingEarlier={self.state.isMore}// 42 renderLoadEarlier={() => { 43 return ( 44 <Text 45 onPress={self.onLoadEarlier} 46 style={[ 47 styles.LookMoreStyle 48 ]} 49 >{self.state.moreData}</Text> 50 ); 51 }} 52 53 listViewProps={{ 54 // //ListView/FlatView中標識是否可以加載更多(當現在獲取到的數據已經是全部了,不能再繼續獲取數據了,則設為false,當還有數據可以獲取則設為true) 55 canLoad: true, 56 //標識現在是否ListView/FlatView現在正在加載(根據這個值來決定是否顯示"正在加載的cell")(loadMore()方法進去后設為true,fetch加載完數據后設為false) 57 isLoadding: false, 58 //是否顯示下拉刷新的cell 59 ifShowRefresh: true, 60 //ListView/FlatList是否可以滾動 61 scrollEnabled: true, 62 //記錄當前加載到了哪一頁 63 page: 1, 64 onScroll:self._onScroll.bind(this) 65 }} 66 />
效果展示:

注意:
1,如下格式的圖片鏈接不能正常顯示
1 avatar: 'http://img3.imgtn.bdimg.com/it/u=1614455141,2952757874&fm=26&gp=0.jpg',
四,后台實現
4.1,Java spring cloud實現
Java 的 web 一般都依托於 servlet 容器。Tomcat、Jetty、Resin等。Spring 框架對 WebSocket 也提供了支持。
1.Spring 對於 WebSocket 的支持基於下面的 jar 包:
1 <dependency> 2 <groupId>javax.websocket</groupId> 3 <artifactId>spring-websocket</artifactId> 4 <version>${spring.version}</version> 5 </dependency>
2.Spring 在收到 WebSocket 事件時,會自動調用事件對應的方法。
1 import javax.websocket.*; 2 import javax.websocket.server.PathParam; 3 import javax.websocket.server.ServerEndpoint; 4 import java.io.IOException; 5 import java.util.Date; 6 import java.util.Map; 7 import java.util.concurrent.ConcurrentHashMap; 8 public class WebSocketService { 9 10 private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketService.class); 11 // ... 12 13 14 }
3.完整代碼實現
1 ... 2 3 public class WebSocketService { 4 private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketService.class); 5 6 public static Map<String, Session> sessionMap = new ConcurrentHashMap<String, Session>(); 7 8 private HcAppchatService hcAppchatService = SpringContextHandler.getBean(HcAppchatService.class); 9 /** 10 * 建立連接后觸發的回調 11 */ 12 @OnOpen 13 public void onOpen(@PathParam("userId") String userId, Session session) { 14 LOGGER.info("聊天打開onOpen:userId={}", userId); 15 if (sessionMap == null) { 16 sessionMap = new ConcurrentHashMap<String, Session>(); 17 } 18 /** 19 * 斷開連接后觸發的回調 20 */ 21 @OnClose 22 public void OnClose(@PathParam("userId") String userId) { 23 LOGGER.info("聊天關閉OnClose:userId={}", userId); 24 sessionMap.remove(userId); 25 } 26 /** 27 * 收到消息時觸發的回調 28 */ 29 @OnMessage 30 public void OnMessage(@PathParam("userId") String userId, Session session, String message) throws IOException{ 31 LOGGER.info("發送消息:userId={}", userId); 32 LOGGER.info("發送消息:message={}", message); 33 HcAppchat hcAppchat = JSON.parseObject(message, HcAppchat.class); 34 sendMessageTo(hcAppchat); 35 //sendMessageAll(message); 36 } 37 /** 38 * 傳輸消息出錯時觸發的回調 39 */ 40 @OnError 41 public void error(Session session, Throwable t) { 42 LOGGER.error("socket通訊出現異常:", t.getMessage()); 43 t.printStackTrace(); 44 } 45 46 public void sendMessageTo(HcAppchat hcAppchat) throws IOException { 47 Session se = sessionMap.get(String.valueOf(hcAppchat.getAcceptId())); 48 Date now = new Date(); 49 hcAppchat.setCreateDate(now); 50 if(se != null){ 51 WebMessage webms = new WebMessage(); 52 hcAppchat.setStatus(1); 53 boolean result = hcAppchatService.insert(hcAppchat); 54 LOGGER.info("用戶在線,直接發送消息:result={}", result); 55 webms.setId(hcAppchat.getId()); 56 webms.setCreatedAt(DateUtil.dateStr(now, "yyyy-MM-dd HH:mm:ss")); 57 webms.setText(hcAppchat.getText()); 58 User user = hcAppchatService.queryUserInfo(hcAppchat.getSendId()); 59 webms.setUser(user); 60 LOGGER.info("發送消息給【" + user.getName() + "】, message={}", JSON.toJSONString(webms)); 61 se.getAsyncRemote().sendText(JSON.toJSONString(webms)); 62 }else{ 63 hcAppchat.setStatus(0); 64 boolean result = hcAppchatService.insert(hcAppchat); 65 if(result){ 66 LOGGER.info("接受消息用戶不在線,將消息保存數據庫成功!"); 67 }else{ 68 LOGGER.info("接受消息用戶不在線,將消息保存數據庫失敗!"); 69 } 70 } 71 } 72 se.getAsyncRemote().sendText(message); 73 } 74 } 75 }
4.2,nodeJS實現
常用的 Node 實現有以下三種。
下面以socket.io為例
1 var IO = require('socket.io'); 2 //var dbservice = require('./services/db_mssql.js');//鏈接數據庫 3 //var settingConfig = require('./config/settingConfig.js');//解析存儲過程 4 5 //var dbName = settingConfig.getValueByKey("dbName"); 6 7 var socketFun = function (server) { 8 var socketIO = IO(server); 9 var userSockets = {}; 10 socketIO.on('connection', function (socket) { 11 12 //已建立鏈接 加入 13 socket.on('join', function (userId) { 14 socket.userId = userId; 15 userSockets[userId] = socket; 16 }) 17 //發送通知 18 socket.on('notification', function (json) { 19 if (socket.userId == undefined) { 20 socket.emit('notification', { 21 "httpCode": 500, 22 "message": "請登錄后再發送消息", 23 "data": {} 24 }); 25 return; 26 } 27 //var spName = "存儲過程的代稱"; 28 json.createPeopleId = socket.userId; 29 //支持多人接收消息 30 var receivePeopleIds = []; 31 if (json.receivePeopleId!=null) 32 receivePeopleIds = json.receivePeopleId.split(';'); 33 for (var i = 0; i < receivePeopleIds.length; i++) { 34 35 var json = { 36 "receivePeopleId": receivePeopleIds[i], 37 "content": json.content, 38 "url": json.url, 39 "creatPeopleId": json.creatPeopleId 40 }; 41 console.log('-------json---------',json); 42 //dbservice.operateDatabase(dbName, spName, json, function (data) {//存進數據庫 43 //console.log(data); 44 //}); 45 var otherSocket = userSockets[json.receivePeopleId] 46 if (otherSocket != null) { 47 otherSocket.emit('notification', { 48 "httpCode": 200, 49 "message": "", 50 "data": json 51 }); 52 } 53 } 54 }); 55 //關閉鏈接 56 socket.on('disconnect', function () { 57 var userId = socket.userId; 58 delete userSockets[userId]; 59 }); 60 }) 61 } 62 63 module.exports = socketFun;
web端調用實例
1 var socket = io('ws://127.0.0.1:3000');//鏈接消息系統 2 3 socket.on('connect', function () {//建立鏈接 4 socket.emit('join', userId); 5 console.log('1') 6 }); 7 var json = { 8 "receivePeopleId": createId, 9 "content": content, 10 "url": TaskUrl, 11 "creatPeopleId": d.CreateUserId 12 }; 13 socket.emit('notification', json);//發送通知
