实时通讯
- 篇幅较长,请配合目录观看
- 项目准备
- 1. 实时通讯-后端
- 2. 实时通讯-前端
- 3. 不同handler处理不同数据
- 4. 用户挤下线
- 4.1 修改模拟器断开62025再开一个模拟器
- 4.2 weixin-config-server新建application-redis.yml和application-rabbitmq.yml
- 4.3 weixin-user和weixin-netty导包
- 4.4 weixin-entty和weixin-user修改yml
- 4.5 weixin-entity定义ShutDownMsg和CharMsg
- 4.6 weixin-entty新建RabbitMQConfig
- 4.7 定义listener
- 4.8 weixin-user修改UserController的login方法发
- 4.9 修改login.html
- 4.10 修改index.html
- 4.11 Test
- 4.12 完善-修改WebSocketChannelHandler
- 4.13 完善-ConnHandler
- 4.13 完善-StartWebSocketServer
中国加油,武汉加油!
篇幅较长,请配合目录观看
项目准备
1. 实时通讯-后端
1.1 weixin-web新建服务weixin-netty(module-springboot)
1.2 导包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.48.Final</version>
</dependency>
1.3 编写yml
netty:
port: 8084
spring:
cloud:
config:
uri: http://localhost:8081
name: application
profile: netty,euclient
application:
name: weixin-netty
1.4 编写Handler
package com.wpj.netty.handler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
public class WebSocketChannelHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
String text = textWebSocketFrame.text();
System.out.println("客户端发送的数据:"+text);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("新客户端连接。");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端断开。");
}
}
1.5 编写服务端
package com.wpj.netty.server;
import com.wpj.netty.handler.WebSocketChannelHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class StartWebSocketServer implements CommandLineRunner {
@Value("${netty.port}")
private Integer port;
/**
* springboot初始化成功后调用
* @param args
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
try {
EventLoopGroup master = new NioEventLoopGroup();
EventLoopGroup slave= new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(master,slave);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec()); // 解码HttpRequest
pipeline.addLast(new HttpObjectAggregator(1024*10)); // 加密FullHttpRequest
// 添加WebSocket解编码
pipeline.addLast(new WebSocketServerProtocolHandler("/"));
// 客户端n秒后不发送信息自动断开
pipeline.addLast(new ReadTimeoutHandler(10, TimeUnit.SECONDS));
// 添加处理客户端的请求的处理器
pipeline.addLast(new WebSocketChannelHandler());
}
});
ChannelFuture channelFuture= bootstrap.bind(port);
channelFuture.sync();
System.out.println("服务端启动成功。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.6 编写ChannleGroup
package com.wpj.netty.channel;
import io.netty.channel.Channel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 保存所有的客户端的连接 设备id,channel
*/
public class ChannelGroup {
/**
* key:设备id
* Channel:设备id的连接对象
*/
public static Map<String,Channel> channelMap = new HashMap<>();
/**
* 添加channel到容器中
* @param did
* @param channel
*/
public static void addChannel(String did,Channel channel){
channelMap.put(did,channel);
}
/**
* 获取channel对象
* @param did
* @return
*/
public static Channel getChannel(String did){
return channelMap.get(did);
}
/**
* 删除channel对象
* @param did
*/
public static void removeChannel(String did){
channelMap.remove(did);
}
public static void removeChannel(Channel channel){
if(channelMap.containsValue(channel)){
Set<Map.Entry<String, Channel>> entries = channelMap.entrySet();
for(Map.Entry<String, Channel> ent:entries){
if(ent.getValue() == channel){
channelMap.remove(ent.getKey());
break;
}
}
};
}
}
2. 实时通讯-前端
2.1 优化登录
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link href="css/mui.css" rel="stylesheet" />
<script src="js/mui.js"></script>
<script src="js/weixin-utils.js"></script>
<script type="text/javascript">
mui.init()
// 类似jquery的$(function(){})
mui.plusReady(function () {
setTimeout(function(){
// 获取当前页面
var cpage = plus.webview.currentWebview();
var user = util.getUser();
if(user == null) {
// 跳转到登录页面
plus.webview.open("login.html","login.html");
} else {
// 跳转到index页面
plus.webview.open("index.html","index.html");
}
// 关闭页面
cpage.close();
}, 2000);
})
</script>
</head>
<body>
<img src="image/welcomebg.jpg" style="width: 100%;"/>
</body>
</html>
2.2 前端项目编写web-socket.js
var websocket;
window.ws = {
init:function(param){
initWebSocket(param);
}
}
function initWebSocket(param){
if(window.WebSocket){
websocket = new WebSocket("ws://192.168.91.1:8084/");
websocket.onopen = function(){
param.onopen();
};
websocket.onclose = function(){
param.onclose();
};
websocket.onmessage = function(resp){
var data = resp.data;
if(data == "heard"){
console.info(data);
clearTimeout(closeConnTime); // 清除定时关闭的连接
closeConn();
return;
}
param.onmessage(data);
};
}else{
alert("不支持WebSocket");
}
}
// 5s发送一个心跳
var sendHeardTime;
function sendHeard(){
sendHeardTime = setInterval(function(){
var param = {"type":2}; // 心跳
sendObj(param);
},5000);
}
// 关闭连接
var closeConnTime;
function closeConn(){
closeConnTime = setTimeout(function() {
websocket.close();
}, 10000);
}
// 重新连接
function reConn(){
console.info("重连。。");
setTimeout(function(){
init();
},5000);
}
// 发送对象
function sendObj(obj){
sendStr(JSON.stringify(obj));
}
// 发送一个字符串
function sendStr(str){
websocket.send(str);
}
2.3 index.html引入js
<script src="js/web-socket.js"></script>
2.4 修改index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
<script src="js/mui.min.js"></script>
<link href="css/mui.min.css" rel="stylesheet"/>
<script src="js/weixin-utils.js"></script>
<script src="js/web-socket.js"></script>
<script type="text/javascript" charset="utf-8">
mui.init();
mui.plusReady(function () {
plus.device.getInfo({
success:function(e){
console.info('getDeviceInfo success: ' + JSON.stringify(e))
ws.init({
onopen:function(){
var user = util.getUser();
var param = {"did":e.uuid, "type":1, "uid":user.id}
sendObj(param);
console.info("客户端连接成功");
},
onclose:function(){
console.info("客户端断开连接");
},
onmessage:function(data){
console.info("数据"+data);
}
})
},
fail:function(e){
console.info('getDeviceInfo faild: ' + JSON.stringify(e))
}
});
// 把4个页面放到一个数组里面
var pageArray = ["msg.html","friend.html","discovery.html","me.html"];
// 新页面的样式
var styles = {top:'0px',bottom:'50px'};
// 遍历数据创建页面
for(var i =0;i<pageArray.length;i++){
var page = pageArray[i];
// 创建一个页面
var newPage = plus.webview.create(page,page,styles);
// 给当前页面添加一个子界面
var cpage = plus.webview.currentWebview();
cpage.append(newPage);
// 隐藏其他页面
if(i != 0){
newPage.hide();
}
};
// 绑定点击事件
mui("nav").on('tap','a',function(){
// 获取点击节点的id
var id = this.getAttribute("id");
// 根据id获取页面
var pageId = pageArray[id];
// 根据页面id找到页面对象
plus.webview.getWebviewById(pageId).show();
})
})
</script>
</head>
<body>
<nav class="mui-bar mui-bar-tab">
<a class="mui-tab-item mui-active" id="0" style="touch-action: none;">
<span class="mui-icon mui-icon-weixin"></span>
<span class="mui-tab-label">消息</span>
</a>
<a class="mui-tab-item" id="1" style="touch-action: none;">
<span class="mui-icon mui-icon-contact" ></span>
<span class="mui-tab-label">好友</span>
</a>
<a class="mui-tab-item" id="2" style="touch-action: none;">
<span class="mui-icon mui-icon-navigate" ></span>
<span class="mui-tab-label">发现</span>
</a>
<a class="mui-tab-item" id="3" style="touch-action: none;">
<span class="mui-icon mui-icon-person"></span>
<span class="mui-tab-label">我的</span>
</a>
</nav>
</body>
</html>
3. 不同handler处理不同数据
3.1 weixin-netty导包
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>com.wpj</groupId>
<artifactId>weixin-entity</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
3.2 weixin-entity定义NettyMsg,ConnMsg
package com.wpj.entity.netty;
import lombok.Data;
import java.io.Serializable;
@Data
public class NettyMsg implements Serializable{
/**
* 1.新连接
* 2.心跳
* 3.单聊
* 4.正在输入
* 5.结束输入
* 6.挤下线
*/
private Integer type;
/**
* 客户端的设备id
*/
private String did;
}
package com.wpj.entity.netty;
import lombok.Data;
/**
* 连接对象
*/
@Data
public class ConnMsg extends NettyMsg{
private Integer uid;
}
3.3 定义ConnHandler
package com.wpj.netty.handler;
import com.wpj.entity.netty.ConnMsg;
import com.wpj.netty.channel.ChannelGroup;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class ConnHandler extends SimpleChannelInboundHandler<ConnMsg> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ConnMsg connMsg) throws Exception {
System.out.println("新的连接: " + connMsg);
ChannelGroup.addChannel(connMsg.getDid(), channelHandlerContext.channel());
}
}
3.4 修改handler
package com.wpj.netty.handler;
import com.google.gson.Gson;
import com.wpj.entity.netty.ConnMsg;
import com.wpj.entity.netty.NettyMsg;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
public class WebSocketChannelHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
String text = textWebSocketFrame.text();
System.out.println("客户端发送的数据:"+text);
Gson gson = new Gson();
NettyMsg nettyMsg = gson.fromJson(text, NettyMsg.class);
if (nettyMsg.getType() == 1) {
nettyMsg = gson.fromJson(text, ConnMsg.class);
}
// 往下传递
channelHandlerContext.fireChannelRead(nettyMsg);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("新客户端连接。");
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端断开。");
}
}
3.5 修改服务端
package com.wpj.netty.server;
import com.wpj.netty.handler.ConnHandler;
import com.wpj.netty.handler.WebSocketChannelHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class StartWebSocketServer implements CommandLineRunner {
@Value("${netty.port}")
private Integer port;
/**
* springboot初始化成功后调用
* @param args
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
try {
EventLoopGroup master = new NioEventLoopGroup();
EventLoopGroup slave= new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(master,slave);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec()); // 解码HttpRequest
pipeline.addLast(new HttpObjectAggregator(1024*10)); // 加密FullHttpRequest
// 添加WebSocket解编码
pipeline.addLast(new WebSocketServerProtocolHandler("/"));
// 客户端n秒后不发送信息自动断开
pipeline.addLast(new ReadTimeoutHandler(10, TimeUnit.SECONDS));
// 添加处理客户端的请求的处理器
pipeline.addLast(new WebSocketChannelHandler());
// 添加处理连接客户端请求的处理器
pipeline.addLast(new ConnHandler());
}
});
ChannelFuture channelFuture= bootstrap.bind(port);
channelFuture.sync();
System.out.println("服务端启动成功。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.6 定义HeardMsg和HeardHandler
package com.wpj.entity.netty;
/**
* 心跳对象
*/
public class HeardMsg extends NettyMsg{
}
package com.wpj.netty.handler;
import com.wpj.entity.netty.HeardMsg;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
public class HeardHandler extends SimpleChannelInboundHandler<HeardMsg> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HeardMsg heardMsg) throws Exception {
System.out.println("处理心跳"+heardMsg);
// 相应心跳
TextWebSocketFrame resp = new TextWebSocketFrame("heard");
channelHandlerContext.writeAndFlush(resp);
}
}
3.7 修改Handler
3.8 修改服务器
4. 用户挤下线
4.1 修改模拟器断开62025再开一个模拟器
4.2 weixin-config-server新建application-redis.yml和application-rabbitmq.yml
spring:
redis:
host: 192.168.59.100
password: admin
spring:
rabbitmq:
port: 5672
host: 192.168.59.100
virtual-host: /
username: guest
password: guest
4.3 weixin-user和weixin-netty导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
4.4 weixin-entty和weixin-user修改yml
profile: user,euclient,datasource,redis,rabbitmq
profile: euclient,redis,rabbitmq
4.5 weixin-entity定义ShutDownMsg和CharMsg
package com.wpj.entity.netty;
import lombok.Data;
@Data
public class ShutDownMsg extends NettyMsg {
{
setType(6);
}
}
package com.wpj.entity.netty;
import lombok.Data;
@Data
public class ChatMsg extends NettyMsg {
private Integer fid;
private Integer tid;
private String content; // 内容
}
4.6 weixin-entty新建RabbitMQConfig
package com.wpj.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
@Value("${netty.port}")
private Integer port;
// 创建队列
@Bean
public Queue queue(){
return new Queue("ws-queue-"+port);
}
// 创建交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("ws-exchange");
}
// 把队列绑定到交换机
@Bean
public Binding queueToExchange(){
return BindingBuilder.bind(queue()).to(fanoutExchange());
}
}
4.7 定义listener
package com.wpj.listener;
import com.google.gson.Gson;
import com.wpj.entity.netty.ChatMsg;
import com.wpj.entity.netty.ShutDownMsg;
import com.wpj.netty.channel.ChannelGroup;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "ws-queue-${netty.port}")
public class WsQueueListener {
@Autowired
private StringRedisTemplate redisTemplate;
@RabbitHandler
public void wsMsg(ShutDownMsg shutDownMsg){
// 查看当前设备号是否在本机
String did = shutDownMsg.getDid();
Channel channel = ChannelGroup.getChannel(did);
System.out.println("监听器获得数据。。"+shutDownMsg);
if(channel != null){
System.out.println("监听器发送消息给客户端完成。。。");
TextWebSocketFrame resp = new TextWebSocketFrame(new Gson().toJson(shutDownMsg));
channel.writeAndFlush(resp);
}
}
@RabbitHandler
public void wsChat(ChatMsg chatMsg){
System.out.println("监听器中收到数据:"+chatMsg);
// 根据tid查询did
String did = redisTemplate.opsForValue().get(chatMsg.getTid().toString());
// 根据did查询channel
Channel channel = ChannelGroup.getChannel(did);
if(channel != null){
TextWebSocketFrame resp = new TextWebSocketFrame(new Gson().toJson(chatMsg));
channel.writeAndFlush(resp);
System.out.println("监听器消息发送成功。。。。");
}
}
}
4.8 weixin-user修改UserController的login方法发
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/login")
public ResultEntity login(String username, String password, String did){
User user = userService.getUserByUsername(username);
if(user != null && user.getPassword().equals(password)) {
user.setPassword(null); // 密码不能写到手机里
System.out.println(user);
String redisDid = redisTemplate.opsForValue().get(user.getId().toString());
if(redisDid != null && !did.equals(redisDid)){
System.out.println("发送给交换机");
// 挤下其他的设备
ShutDownMsg shutDownMsg = new ShutDownMsg();
shutDownMsg.setDid(redisDid); // redis中的设备id
rabbitTemplate.convertAndSend("ws-exchange","",shutDownMsg);
}
// 登录成功后要保存用户id和设备id
redisTemplate.opsForValue().set(user.getId().toString(), did);
return ResultEntity.success(user);
} else{
return ResultEntity.error("用户名或密码错误");
}
}
4.9 修改login.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link href="css/mui.css" rel="stylesheet" />
<link href="css/myimg.css" rel="stylesheet" />
<script src="js/mui.js"></script>
<script src="js/weixin-utils.js"></script>
<script type="text/javascript">
mui.init();
mui.plusReady(function () {
// tap:触屏事件
document.getElementById("newuser_but").addEventListener("tap",function(){
// 打开注册页面
plus.webview.open("register.html","register.html");
});
document.getElementById("login_but").addEventListener("tap", function(){
// 先获取用户输入的值
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
plus.device.getInfo({
success:function(e){
// 2.发送请求
util.ajax({
url:url.login_url,
data:{
"username":username,
"password":password,
"did":e.uuid
},
success:function(data){
if(data.code == "ok"){
// 1.提示信息
data.msg = "登录成功";
// 2.把用户对象保存的本机的数据库
var user = data.data; // user是JOSN对象
util.setUser(user);
// 3.跳转到首页
var cpage = plus.webview.currentWebview();
plus.webview.open("index.html","index.html");
cpage.close();
}
plus.nativeUI.toast(data.msg);
}
})
}
});
})
})
</script>
</head>
<body style="text-align: center;">
<header class="mui-bar mui-bar-nav">
<h1 class="mui-title">登录</h1>
</header>
<div style="margin-top: 150px;">
<img src="image/header.jpg" style="width: 150px;" class="cimg" />
<form class="mui-input-group">
<div class="mui-input-row">
<label>用户名</label>
<input type="text" id="username" value="admin" class="mui-input-clear" placeholder="请输入用户名">
</div>
<div class="mui-input-row">
<label>密码</label>
<input type="password" id="password" value="admin" class="mui-input-password" placeholder="请输入密码">
</div>
<div class="mui-button-row">
<button type="button" id="login_but" class="mui-btn mui-btn-success" style="width: 80%;">登录</button>
</div>
</form>
<a id="newuser_but">新用户</a> <a>忘记密码</a>
<a href="http://192.168.91.1:8888/user/login?username=admin&password=admin">测试</a>
</div>
</body>
</html>
4.10 修改index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title></title>
<script src="js/mui.min.js"></script>
<link href="css/mui.min.css" rel="stylesheet"/>
<script src="js/weixin-utils.js"></script>
<script src="js/web-socket.js"></script>
<script type="text/javascript" charset="utf-8">
mui.init();
mui.plusReady(function () {
ws.init({
onopen:function(){
console.info("客户端连接成功");
plus.device.getInfo({
success:function(e){
console.info('getDeviceInfo success: ' + JSON.stringify(e));
var user = util.getUser();
var param = {"did":e.uuid, "type":1, "uid":user.id}
sendObj(param);
}
});
},
onclose:function(){
console.info("客户端断开连接");
},
onmessage:function(data){
console.info("数据"+data);
var obj = JSON.parse(data);
if(obj.type == 6){ // 挤下线
// 先把本机用户删除
plus.storage.removeItem("login_user");
// 获取当前页面
var cpage = plus.webview.currentWebview();
plus.nativeUI.toast("你在账户已在其他设备登录,如果非本人操作,请求修改密码。。。");
// 跳转到登录页面
plus.webview.open("login.html","login.html");
// 关闭当前页面
cpage.close();
}else if(obj.type == 3){ // 单聊
var fid = plus.webview.getWebviewById("chat_msg_"+obj.fid);
// 显示聊天消息
fid.evalJS("shwoMsg("+JSON.stringify(obj)+")");
//保存聊天消息
fid.evalJS("saveMsg("+JSON.stringify(obj)+")");
}
}
})
// 把4个页面放到一个数组里面
var pageArray = ["msg.html","friend.html","discovery.html","me.html"];
// 新页面的样式
var styles = {top:'0px',bottom:'50px'};
// 遍历数据创建页面
for(var i =0;i<pageArray.length;i++){
var page = pageArray[i];
// 创建一个页面
var newPage = plus.webview.create(page,page,styles);
// 给当前页面添加一个子界面
var cpage = plus.webview.currentWebview();
cpage.append(newPage);
// 隐藏其他页面
if(i != 0){
newPage.hide();
}
};
// 绑定点击事件
mui("nav").on('tap','a',function(){
// 获取点击节点的id
var id = this.getAttribute("id");
// 根据id获取页面
var pageId = pageArray[id];
// 根据页面id找到页面对象
plus.webview.getWebviewById(pageId).show();
})
})
</script>
</head>
<body>
<nav class="mui-bar mui-bar-tab">
<a class="mui-tab-item mui-active" id="0" style="touch-action: none;">
<span class="mui-icon mui-icon-weixin"></span>
<span class="mui-tab-label">消息</span>
</a>
<a class="mui-tab-item" id="1" style="touch-action: none;">
<span class="mui-icon mui-icon-contact" ></span>
<span class="mui-tab-label">好友</span>
</a>
<a class="mui-tab-item" id="2" style="touch-action: none;">
<span class="mui-icon mui-icon-navigate" ></span>
<span class="mui-tab-label">发现</span>
</a>
<a class="mui-tab-item" id="3" style="touch-action: none;">
<span class="mui-icon mui-icon-person"></span>
<span class="mui-tab-label">我的</span>
</a>
</nav>
</body>
</html>
4.11 Test
4.12 完善-修改WebSocketChannelHandler
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端断开。");
ChannelGroup.removeChannel(ctx.channel());
}
4.13 完善-ConnHandler
package com.wpj.netty.handler;
import com.google.gson.Gson;
import com.wpj.entity.netty.ConnMsg;
import com.wpj.entity.netty.ShutDownMsg;
import com.wpj.netty.channel.ChannelGroup;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.springframework.data.redis.core.StringRedisTemplate;
public class ConnHandler extends SimpleChannelInboundHandler<ConnMsg> {
private StringRedisTemplate redisTemplate;
public ConnHandler(StringRedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ConnMsg connMsg) throws Exception {
System.out.println("新的连接: " + connMsg);
// 把新到的连接添加到map中
ChannelGroup.addChannel(connMsg.getDid(),channelHandlerContext.channel());
// 根据用户id从Reids中查询did
Integer uid = connMsg.getUid();
String did = connMsg.getDid();
String redisDid = redisTemplate.opsForValue().get(uid.toString());
if(redisDid != null && !redisDid.equals(did)){
ShutDownMsg shutDownMsg = new ShutDownMsg();
TextWebSocketFrame resp = new TextWebSocketFrame(new Gson().toJson(shutDownMsg));
channelHandlerContext.writeAndFlush(resp);
}
}
}
4.13 完善-StartWebSocketServer
@Autowired
private StringRedisTemplate redisTemplate;
// 添加处理连接客户端请求的处理器
pipeline.addLast(new ConnHandler(redisTemplate));