WEB通知和React Native之即时通讯(iOS Android)
一,需求分析
1.1,允许服务器主动发送信息给客户端,客户端能监听到并且能接收。
1.2,为了方便同一个系统内的用户可以指定某个用户可以私聊。
1.3,给指定用户或多个用户发送通知。
二,技术介绍
2.1.WebSocket介绍
1,WebSocket 是什么?
WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。
- WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
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 = document2 .querySelector('input[type="file"]')
3 .files[0];
4 ws.send(file);
(3)发送 ArrayBuffer 对象
1 // Sending canvas ImageData as ArrayBuffer2 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 实现有以下三种。
- µWebSockets
- Socket.IO
- WebSocket-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);//发送通知
以上是 WEB通知和React Native之即时通讯(iOS Android) 的全部内容, 来源链接: utcz.com/z/382057.html