實時通訊
- 篇幅較長,請配合目錄觀看
- 項目準備
- 1. 實時通訊-後端
- 1.1 weixin-web新建服務weixin-netty(module-springboot)
- 1.2 導包
- 1.3 編寫yml
- 1.4 編寫Handler
- 1.5 編寫服務端
- 1.6 編寫ChannleGroup
- 2. 實時通訊-前端
- 3. 不同handler處理不同數據
- 3.1 weixin-netty導包
- 3.2 weixin-entity定義NettyMsg,ConnMsg
- 3.3 定義ConnHandler
- 3.4 修改handler
- 3.5 修改服務端
- 3.6 定義HeardMsg和HeardHandler
- 3.7 修改Handler
- 3.8 修改服務器
- 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));