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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章