springcloud篇】九. springcloud项目 五 实时通讯 一 处理数据,用户单设备登录


中国加油,武汉加油!

篇幅较长,请配合目录观看

项目准备

  1. 本案例基于 springcloud篇】九. springcloud项目 四 好友列表展示及刷新

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));

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值