From d744d29b243bcb039d9306fbf0adc5ff4a0ee7cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=AE=8F=E9=9F=AC?= <3183764662@qq.com> Date: Tue, 6 Nov 2018 11:29:08 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 126 +++--------------------------------------------------- 1 file changed, 5 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index 0e527c3..0d70b8c 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,10 @@ -# InChat(当前版本1.6.0) +# tcp-wechat -> 当前主要更新分支,master将会再所有分支开放一定阶段后整改为maven包,提供给大家使用 +## 项目介绍 -## 分支介绍 im-api +针对小程序与单片机硬件执行Iot物联网通讯的一套完整Demo。 -腾讯IM(云通信)后端模仿项目,均以API形式对接,如果有前端想要对接的可以运行本分支,本分支预计终版为一个单服务并发30万用户的IM后台项目 +## 项目尚未初始化 -## 分支介绍 paho-mqtt +请勿使用。 -基于小程序端或移动web端的paho.js与[java MQTT 客户端模拟](https://github.com/eclipse/paho.mqtt.java)的消息订阅与通信,小程序Iot的Demo,目前支持ws格式 - -## 分支介绍 tcp-wechat - -基于小程序端与单片机等硬件的TCP/IP的主要通信,Iot中心作为中转,本demo将完全实现具体功能,详情请看分支主页 - -## 简介 - -***(InChat)Iot Netty Chat*** - -仿微信聊天应用,一步一步更新,基于SpringBoot-WebSocket通用框架,结合Netty进行聊天社交,并记录聊天日志, -异步存储,前端暂用SUI Mobile,添加实现TCP/IP后端通信端口(MQTT协议、可实时与单片机等TCP硬件通信)、加入图片处理流, -聊天实现文字与图片发送功能、API调用Netty长链接执行发送消息(在线数、用户列表) - -## 基本架构图(1.5.2版) - -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/nettychat/ggg1.png) - -## 功能 - ->实时聊天 ->异步CRUD处理消息日志 ->获取聊天历史 ->用户登录、记录登录用户聊天历史 ->防止二次登录 ->SUI Mobile仿微信样式 ->TCP/IP软硬件通信(8092) ->MQTT协议下的Iot物联网通信(8094) ->图片发送聊天功能 ->API调用Netty长链接执行发送消息(在线用户数、用户列表) ->下版(1.7.0):好友功能等 - -## 版本迭代介绍 - -* 1.0.0版本 - -用户登录,聊天历史,随机用户名,异步数据写入:https://segmentfault.com/a/1190000016615063 - -* 1.2.0版本 - -修复聊天记录功能,实现重复信息录入,完善前端页面,回车监听等:https://segmentfault.com/a/1190000016637814 - -* 1.3.0版本 - -用户注册登录功能,系统聊天绑定用户,禁止二次登录等,前端页面大改 - -* 1.4.1版本 - -本人主导SUI Mobile构建仿微信样式页面版,使用时开F12手机界面 - -* 1.5.2版本 - -TCP/IP软硬件通信-单片机等应用的TCP通信,Netty处理二进制图片发送聊天功能 - -* 1.5.8版本 - -MQTT协议软硬件通信等,Iot物联网 - -* 1.6.0版本 - -API调用Netty长链接执行发送消息(在线数、用户列表):https://segmentfault.com/a/1190000016603392 - - -## 配置 - ->application.yml 数据库配置、Netty参数配置 - ->TCP需先去com.myself.nettychat.tcptest包下执行CRC16myself获取发送数据, - ->再执行TCPTestClient发送数据,请勿随意更改发送格式(通信协议来的) - ->http://localhost:8080/susu/admin/loginsui 启动访问路径 - ->mqtt协议测试在mqttclient包下 - ->http://localhost:8080/susu/swagger-ui.html 查看API文档 - -## 效果图 - -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/nettychat/001%20(5).png) -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/nettychat/001%20(3).png) -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/nettychat/001%20(4).png) -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/nettychat/001%20(2).png) -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/nettychat/001%20(1).png) -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/nettychat/9.png) -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/nettychat/10.png) -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/nettychat/11.png) - -## 预留BUG - -``` -io.netty.handler.codec.CorruptedFrameException: Max frame length of 65536 has been exceeded. -图片过大,需要在前端做图片上传压缩 - -Uncaught TypeError: msg.substring is not a function at WebSocket.socket.onmessage (newChat.js:38) -前端代码的一点问题,不影响项目正常运行 - -java.io.IOException: 远程主机强迫关闭了一个现有的连接。 -TCP客户端连接主动关闭,不影响,良性报错 -``` - -## 下载地址 - -下载地址:https://github.com/UncleCatMySelf/SBToNettyChat/releases - -## 交流与提问 - -提问与Bug上报:https://github.com/UncleCatMySelf/SBToNettyChat/issues - -QQ群:628793702(仅供交流,不提供问题解答) - -## 关于作者 - -个人公众号:UncleCatMySelf - -![Image text](https://raw.githubusercontent.com/UncleCatMySelf/img-myself/master/img/%E5%85%AC%E4%BC%97%E5%8F%B7.png) From 1ee163b4df50e30015424971db03d3a1a749bf06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=AE=8F=E9=9F=AC?= <3183764662@qq.com> Date: Tue, 6 Nov 2018 14:16:11 +0800 Subject: [PATCH 2/9] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E5=A4=A7?= =?UTF-8?q?=E8=87=B4Demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- h5/chat.html | 105 ----- h5/home.html | 47 -- h5/index.html | 67 --- h5/logoSmall.png | Bin 8661 -> 0 bytes pom.xml | 9 +- sql/nettychat.sql | 71 --- .../myself/nettychat/DefaultAutoService.java | 19 - .../nettychat/NettychatApplication.java | 14 +- .../com/myself/nettychat/auto/InitServer.java | 38 -- .../nettychat/auto/ServerAutoConfigure.java | 86 ---- .../bootstrap/AbstractBootstrapServer.java | 117 ----- .../myself/nettychat/bootstrap/BaseApi.java | 76 --- .../nettychat/bootstrap/BaseAuthService.java | 13 - .../nettychat/bootstrap/BootstrapServer.java | 18 - .../nettychat/bootstrap/ChannelService.java | 40 -- .../bootstrap/NettyBootstrapServer.java | 134 ------ .../nettychat/bootstrap/bean/MqttChannel.java | 115 ----- .../bootstrap/bean/RetainMessage.java | 18 - .../bootstrap/bean/SendMqttMessage.java | 37 -- .../bootstrap/bean/SessionMessage.java | 29 -- .../nettychat/bootstrap/bean/WillMeaasge.java | 23 - .../channel/AbstractChannelService.java | 112 ----- .../channel/ClientSessionService.java | 36 -- .../bootstrap/channel/MqttChannelService.java | 440 ------------------ .../bootstrap/channel/MqttHandlerService.java | 223 --------- .../bootstrap/channel/PublishApiSevice.java | 162 ------- .../bootstrap/channel/WillService.java | 52 --- .../bootstrap/channel/cache/CacheMap.java | 137 ------ .../coder/ByteBufToWebSocketFrameEncoder.java | 26 -- .../coder/WebSocketFrameToByteBufDecoder.java | 25 - .../bootstrap/handler/DefaultMqttHandler.java | 105 ----- .../bootstrap/scan/SacnScheduled.java | 59 --- .../bootstrap/scan/ScanRunnable.java | 44 -- .../common/properties/InitNetty.java | 1 + .../nettychat/common/utils/ResultVOUtil.java | 60 +-- .../myself/nettychat/config/NettyConfig.java | 75 --- .../NettyWebSocketChannelInitializer.java | 40 -- .../myself/nettychat/config/TCPServer.java | 11 - .../config/TextWebSocketFrameHandler.java | 141 ------ .../nettychat/constont/CookieConstant.java | 15 - .../myself/nettychat/constont/H5Constant.java | 29 -- .../constont/LikeSomeCacheTemplate.java | 36 -- .../controller/NCBackController.java | 35 -- .../controller/NcChangeController.java | 88 ---- .../controller/NcChatController.java | 76 --- .../controller/NcLoginController.java | 145 ------ .../com/myself/nettychat/dataobject/User.java | 39 -- .../myself/nettychat/dataobject/UserMsg.java | 38 -- .../com/myself/nettychat/form/LoginForm.java | 20 - .../repository/UserMsgRepository.java | 13 - .../nettychat/repository/UserRepository.java | 16 - .../myself/nettychat/service/UserService.java | 22 - .../service/impl/UserServiceImpl.java | 43 -- .../myself/nettychat/store/TokenStore.java | 45 -- .../myself/nettychat/task/MsgAsyncTesk.java | 50 -- .../myself/nettychat/task/ScheduledPool.java | 33 -- .../nettychat/tcptest/TCPTestClient.java | 4 +- .../com/myself/nettychat/vo/ResultVo.java | 52 +-- src/main/resources/application.yml | 14 - src/main/resources/static/css/allchat.css | 59 --- src/main/resources/static/css/chat.css | 43 -- src/main/resources/static/css/newChat.css | 172 ------- src/main/resources/static/css/registered.css | 160 ------- src/main/resources/static/image/logoBig.jpg | Bin 137374 -> 0 bytes src/main/resources/static/image/logoSmall.png | Bin 8661 -> 0 bytes src/main/resources/static/image/nuandao.png | Bin 62980 -> 0 bytes src/main/resources/static/js/chat.js | 51 -- src/main/resources/static/js/newChat.js | 165 ------- src/main/resources/static/js/registered.js | 72 --- src/main/resources/templates/chat/allchat.ftl | 49 -- src/main/resources/templates/chat/chat.ftl | 33 -- src/main/resources/templates/common/floor.ftl | 3 - .../resources/templates/common/header.ftl | 12 - src/main/resources/templates/find/find.ftl | 41 -- src/main/resources/templates/h5.ftl | 58 --- src/main/resources/templates/home/home.ftl | 62 --- src/main/resources/templates/login/login.ftl | 57 --- .../resources/templates/login/loginSui.ftl | 124 ----- src/main/resources/templates/me/me.ftl | 78 ---- 79 files changed, 65 insertions(+), 4812 deletions(-) delete mode 100644 h5/chat.html delete mode 100644 h5/home.html delete mode 100644 h5/index.html delete mode 100644 h5/logoSmall.png delete mode 100644 sql/nettychat.sql delete mode 100644 src/main/java/com/myself/nettychat/DefaultAutoService.java delete mode 100644 src/main/java/com/myself/nettychat/auto/InitServer.java delete mode 100644 src/main/java/com/myself/nettychat/auto/ServerAutoConfigure.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/AbstractBootstrapServer.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/BaseApi.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/BaseAuthService.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/BootstrapServer.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/ChannelService.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/NettyBootstrapServer.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/bean/MqttChannel.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/bean/RetainMessage.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/bean/SendMqttMessage.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/bean/SessionMessage.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/bean/WillMeaasge.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/channel/AbstractChannelService.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/channel/ClientSessionService.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/channel/MqttChannelService.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/channel/MqttHandlerService.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/channel/PublishApiSevice.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/channel/WillService.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/channel/cache/CacheMap.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/coder/ByteBufToWebSocketFrameEncoder.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/coder/WebSocketFrameToByteBufDecoder.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/handler/DefaultMqttHandler.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/scan/SacnScheduled.java delete mode 100644 src/main/java/com/myself/nettychat/bootstrap/scan/ScanRunnable.java delete mode 100644 src/main/java/com/myself/nettychat/config/NettyConfig.java delete mode 100644 src/main/java/com/myself/nettychat/config/NettyWebSocketChannelInitializer.java delete mode 100644 src/main/java/com/myself/nettychat/config/TextWebSocketFrameHandler.java delete mode 100644 src/main/java/com/myself/nettychat/constont/CookieConstant.java delete mode 100644 src/main/java/com/myself/nettychat/constont/H5Constant.java delete mode 100644 src/main/java/com/myself/nettychat/constont/LikeSomeCacheTemplate.java delete mode 100644 src/main/java/com/myself/nettychat/controller/NcChangeController.java delete mode 100644 src/main/java/com/myself/nettychat/controller/NcChatController.java delete mode 100644 src/main/java/com/myself/nettychat/controller/NcLoginController.java delete mode 100644 src/main/java/com/myself/nettychat/dataobject/User.java delete mode 100644 src/main/java/com/myself/nettychat/dataobject/UserMsg.java delete mode 100644 src/main/java/com/myself/nettychat/form/LoginForm.java delete mode 100644 src/main/java/com/myself/nettychat/repository/UserMsgRepository.java delete mode 100644 src/main/java/com/myself/nettychat/repository/UserRepository.java delete mode 100644 src/main/java/com/myself/nettychat/service/UserService.java delete mode 100644 src/main/java/com/myself/nettychat/service/impl/UserServiceImpl.java delete mode 100644 src/main/java/com/myself/nettychat/store/TokenStore.java delete mode 100644 src/main/java/com/myself/nettychat/task/MsgAsyncTesk.java delete mode 100644 src/main/java/com/myself/nettychat/task/ScheduledPool.java delete mode 100644 src/main/resources/static/css/allchat.css delete mode 100644 src/main/resources/static/css/chat.css delete mode 100644 src/main/resources/static/css/newChat.css delete mode 100644 src/main/resources/static/css/registered.css delete mode 100644 src/main/resources/static/image/logoBig.jpg delete mode 100644 src/main/resources/static/image/logoSmall.png delete mode 100644 src/main/resources/static/image/nuandao.png delete mode 100644 src/main/resources/static/js/chat.js delete mode 100644 src/main/resources/static/js/newChat.js delete mode 100644 src/main/resources/static/js/registered.js delete mode 100644 src/main/resources/templates/chat/allchat.ftl delete mode 100644 src/main/resources/templates/chat/chat.ftl delete mode 100644 src/main/resources/templates/common/floor.ftl delete mode 100644 src/main/resources/templates/common/header.ftl delete mode 100644 src/main/resources/templates/find/find.ftl delete mode 100644 src/main/resources/templates/h5.ftl delete mode 100644 src/main/resources/templates/home/home.ftl delete mode 100644 src/main/resources/templates/login/login.ftl delete mode 100644 src/main/resources/templates/login/loginSui.ftl delete mode 100644 src/main/resources/templates/me/me.ftl diff --git a/h5/chat.html b/h5/chat.html deleted file mode 100644 index 91d15b2..0000000 --- a/h5/chat.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - WebSocket Chat - - - - -
-

SpringBoot netty 聊天室

- -
- - -
-
-
- - \ No newline at end of file diff --git a/h5/home.html b/h5/home.html deleted file mode 100644 index 541e6b7..0000000 --- a/h5/home.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - 酥酥 - - - - - - - - - - -
-
-

酥酥

-
- -
- -
-
- - - - - \ No newline at end of file diff --git a/h5/index.html b/h5/index.html deleted file mode 100644 index 32e88ea..0000000 --- a/h5/index.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - 酥酥 - - - - - - - - - - -
-
- -
-

登录

-
-
-
-
    - -
  • -
    -
    -
    -
    账号
    -
    - -
    -
    -
    -
  • -
  • -
    -
    -
    -
    密码
    -
    - -
    -
    -
    -
  • -
-
-
-
- - -
-
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/h5/logoSmall.png b/h5/logoSmall.png deleted file mode 100644 index 08b5d51eb40008001afd2293b59a107f0bfc474d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8661 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 diff --git a/pom.xml b/pom.xml index 458d0cf..a8d0223 100644 --- a/pom.xml +++ b/pom.xml @@ -29,9 +29,14 @@ org.springframework.boot spring-boot-starter-tomcat + - org.springframework.boot - spring-boot-starter-data-jpa + org.springframework + spring-aop + + + org.springframework + spring-aspects mysql diff --git a/sql/nettychat.sql b/sql/nettychat.sql deleted file mode 100644 index 452c711..0000000 --- a/sql/nettychat.sql +++ /dev/null @@ -1,71 +0,0 @@ -/* -Navicat MySQL Data Transfer - -Source Server : mypc -Source Server Version : 50717 -Source Host : localhost:3306 -Source Database : nettychat - -Target Server Type : MYSQL -Target Server Version : 50717 -File Encoding : 65001 - -Date: 2018-08-23 10:32:48 -*/ - -SET FOREIGN_KEY_CHECKS=0; - --- ---------------------------- --- Table structure for user --- ---------------------------- -DROP TABLE IF EXISTS `user`; -CREATE TABLE `user` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `user_name` varchar(255) DEFAULT NULL, - `pass_word` varchar(255) DEFAULT NULL, - `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; - --- ---------------------------- --- Records of user --- ---------------------------- -INSERT INTO `user` VALUES ('2', 'Myself', '123456', '2018-08-14 19:47:49', '2018-08-14 19:47:49'); -INSERT INTO `user` VALUES ('3', 'Chen', '123456abc', '2018-08-20 16:31:49', '2018-08-20 16:31:49'); - --- ---------------------------- --- Table structure for user_msg --- ---------------------------- -DROP TABLE IF EXISTS `user_msg`; -CREATE TABLE `user_msg` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) DEFAULT NULL, - `msg` varchar(255) DEFAULT NULL, - `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4; - --- ---------------------------- --- Records of user_msg --- ---------------------------- -INSERT INTO `user_msg` VALUES ('8', 'Myself', '你好呀', '2018-08-20 17:26:58', '2018-08-20 17:26:58'); -INSERT INTO `user_msg` VALUES ('9', 'Myself', '你是谁?', '2018-08-20 17:27:13', '2018-08-20 17:27:13'); -INSERT INTO `user_msg` VALUES ('10', 'Myself', '在吗?', '2018-08-21 17:54:12', '2018-08-21 17:54:12'); -INSERT INTO `user_msg` VALUES ('11', 'Chen', '嗯呢', '2018-08-21 17:54:12', '2018-08-21 17:54:12'); -INSERT INTO `user_msg` VALUES ('13', 'Myself', 'yo', '2018-08-21 18:01:26', '2018-08-21 18:01:26'); -INSERT INTO `user_msg` VALUES ('14', 'Myself', '你好', '2018-08-22 16:24:22', '2018-08-22 16:24:22'); -INSERT INTO `user_msg` VALUES ('30', 'Myself', '你好呀!', '2018-08-22 17:03:42', '2018-08-22 17:03:42'); -INSERT INTO `user_msg` VALUES ('31', 'Myself', '我很好!', '2018-08-22 17:03:42', '2018-08-22 17:03:42'); -INSERT INTO `user_msg` VALUES ('32', 'Myself', '哈哈哈哈', '2018-08-22 17:11:56', '2018-08-22 17:11:56'); -INSERT INTO `user_msg` VALUES ('33', 'Myself', '哇哈哈哈哈', '2018-08-22 17:11:56', '2018-08-22 17:11:56'); -INSERT INTO `user_msg` VALUES ('34', 'Myself', 'asdf', '2018-08-22 17:22:19', '2018-08-22 17:22:19'); -INSERT INTO `user_msg` VALUES ('35', 'Myself', '厉害 厉害', '2018-08-22 17:23:20', '2018-08-22 17:23:20'); -INSERT INTO `user_msg` VALUES ('36', 'Myself', '哈哈', '2018-08-22 17:23:43', '2018-08-22 17:23:43'); -INSERT INTO `user_msg` VALUES ('37', 'Myself', '你好!', '2018-08-23 10:19:14', '2018-08-23 10:19:14'); -INSERT INTO `user_msg` VALUES ('38', 'Chen', '收到。', '2018-08-23 10:19:15', '2018-08-23 10:19:15'); -INSERT INTO `user_msg` VALUES ('39', 'Myself', '前端框架用Vue?', '2018-08-23 10:19:15', '2018-08-23 10:19:15'); -INSERT INTO `user_msg` VALUES ('40', 'Chen', '可以试试', '2018-08-23 10:19:15', '2018-08-23 10:19:15'); -INSERT INTO `user_msg` VALUES ('41', 'Myself', '下版再加一些新的功能', '2018-08-23 10:19:15', '2018-08-23 10:19:15'); -INSERT INTO `user_msg` VALUES ('42', 'Chen', 'okay', '2018-08-23 10:19:15', '2018-08-23 10:19:15'); diff --git a/src/main/java/com/myself/nettychat/DefaultAutoService.java b/src/main/java/com/myself/nettychat/DefaultAutoService.java deleted file mode 100644 index e4b4cc7..0000000 --- a/src/main/java/com/myself/nettychat/DefaultAutoService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.myself.nettychat; - -import com.myself.nettychat.bootstrap.BaseAuthService; -import org.springframework.stereotype.Service; - - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 默认权限 - **/ -@Service -public class DefaultAutoService implements BaseAuthService { - - @Override - public boolean authorized(String username, String password) { - return true; - } -} diff --git a/src/main/java/com/myself/nettychat/NettychatApplication.java b/src/main/java/com/myself/nettychat/NettychatApplication.java index 9c679bd..8eabd26 100644 --- a/src/main/java/com/myself/nettychat/NettychatApplication.java +++ b/src/main/java/com/myself/nettychat/NettychatApplication.java @@ -1,6 +1,6 @@ package com.myself.nettychat; -import com.myself.nettychat.config.NettyConfig; + import com.myself.nettychat.config.NettyTcpConfig; import com.myself.nettychat.config.TCPServer; import org.springframework.boot.SpringApplication; @@ -19,20 +19,8 @@ public class NettychatApplication { public static void main(String[] args) throws Exception{ // SpringApplication.run(NettychatApplication.class, args); ConfigurableApplicationContext context = SpringApplication.run(NettychatApplication.class, args); - NettyConfig nettyConfig = context.getBean(NettyConfig.class); NettyTcpConfig nettyTcpConfig = context.getBean(NettyTcpConfig.class); TCPServer tcpServer = context.getBean(TCPServer.class); - new Thread(new Runnable() { - @Override - public void run() { - try { - System.out.println("Web端Netty通信服务端启动成功!端口:8090"); - tcpServer.startWeb(); - }catch (Exception e){ - e.printStackTrace(); - } - } - }).start(); new Thread(new Runnable() { @Override public void run() { diff --git a/src/main/java/com/myself/nettychat/auto/InitServer.java b/src/main/java/com/myself/nettychat/auto/InitServer.java deleted file mode 100644 index 3729fc3..0000000 --- a/src/main/java/com/myself/nettychat/auto/InitServer.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.myself.nettychat.auto; - - -import com.myself.nettychat.bootstrap.BootstrapServer; -import com.myself.nettychat.bootstrap.NettyBootstrapServer; -import com.myself.nettychat.common.properties.InitNetty; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 初始化服务 - **/ -public class InitServer { - - private InitNetty serverBean; - - public InitServer(InitNetty serverBean) { - this.serverBean = serverBean; - } - - BootstrapServer bootstrapServer; - - public void open(){ - if(serverBean!=null){ - bootstrapServer = new NettyBootstrapServer(); - bootstrapServer.setServerBean(serverBean); - bootstrapServer.start(); - } - } - - - public void close(){ - if(bootstrapServer!=null){ - bootstrapServer.shutdown(); - } - } - -} diff --git a/src/main/java/com/myself/nettychat/auto/ServerAutoConfigure.java b/src/main/java/com/myself/nettychat/auto/ServerAutoConfigure.java deleted file mode 100644 index 961e458..0000000 --- a/src/main/java/com/myself/nettychat/auto/ServerAutoConfigure.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.myself.nettychat.auto; - -import com.myself.nettychat.bootstrap.scan.SacnScheduled; -import com.myself.nettychat.bootstrap.scan.ScanRunnable; -import com.myself.nettychat.common.enums.ProtocolEnum; -import com.myself.nettychat.common.properties.InitNetty; -import org.apache.commons.lang3.ObjectUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 自动化配置初始化服务 - **/ -@Configuration -@ConditionalOnClass -@EnableConfigurationProperties({InitNetty.class}) -public class ServerAutoConfigure { - - private static final int _BLACKLOG = 1024; - - private static final int CPU =Runtime.getRuntime().availableProcessors(); - - private static final int SEDU_DAY =10; - - private static final int TIMEOUT =120; - - private static final int BUF_SIZE=10*1024*1024; - - - public ServerAutoConfigure(){ - - } - - @Bean - @ConditionalOnMissingBean(name = "sacnScheduled") - public ScanRunnable initRunable(@Autowired InitNetty serverBean){ - long time =(serverBean==null || serverBean.getPeriod()<5)?10:serverBean.getPeriod(); - ScanRunnable sacnScheduled = new SacnScheduled(time); - Thread scanRunnable = new Thread(sacnScheduled); - scanRunnable.setDaemon(true); - scanRunnable.start(); - return sacnScheduled; - } - - - @Bean(initMethod = "open", destroyMethod = "close") - @ConditionalOnMissingBean - public InitServer initServer(InitNetty serverBean){ - if(!ObjectUtils.allNotNull(serverBean.getMqttport(),serverBean.getServerName())){ - throw new NullPointerException("not set port"); - } - if(serverBean.getBacklog()<1){ - serverBean.setBacklog(_BLACKLOG); - } - if(serverBean.getBossThread()<1){ - serverBean.setBossThread(CPU); - } - if(serverBean.getInitalDelay()<0){ - serverBean.setInitalDelay(SEDU_DAY); - } - if(serverBean.getPeriod()<1){ - serverBean.setPeriod(SEDU_DAY); - } - if(serverBean.getHeart()<1){ - serverBean.setHeart(TIMEOUT); - } - if(serverBean.getRevbuf()<1){ - serverBean.setRevbuf(BUF_SIZE); - } - if(serverBean.getWorkerThread()<1){ - serverBean.setWorkerThread(CPU*2); - } - if(serverBean.getProtocol()==null){ - serverBean.setProtocol(ProtocolEnum.MQTT); - } - return new InitServer(serverBean); - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/AbstractBootstrapServer.java b/src/main/java/com/myself/nettychat/bootstrap/AbstractBootstrapServer.java deleted file mode 100644 index a647848..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/AbstractBootstrapServer.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.myself.nettychat.bootstrap; - - -import com.myself.nettychat.bootstrap.coder.ByteBufToWebSocketFrameEncoder; -import com.myself.nettychat.bootstrap.coder.WebSocketFrameToByteBufDecoder; -import com.myself.nettychat.common.properties.InitNetty; -import com.myself.nettychat.common.ssl.SecureSocketSslContextFactory; -import com.myself.nettychat.common.utils.SpringBeanUtils; -import io.netty.channel.ChannelPipeline; -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.codec.mqtt.MqttDecoder; -import io.netty.handler.codec.mqtt.MqttEncoder; -import io.netty.handler.ssl.SslHandler; -import io.netty.handler.timeout.IdleStateHandler; -import org.apache.commons.lang3.ObjectUtils; -import org.jboss.netty.util.internal.SystemPropertyUtil; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import java.security.KeyStore; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 抽象类,负责加载edec handler - **/ -public abstract class AbstractBootstrapServer implements BootstrapServer { - - private String PROTOCOL = "TLS"; - - private SSLContext SERVER_CONTEXT; - - private static final String MQTT_CSV_LIST = "mqtt, mqttv3.1, mqttv3.1.1"; - - - /** - * - * @param channelPipeline channelPipeline - * @param serverBean 服务配置参数 - */ - protected void initHandler(ChannelPipeline channelPipeline, InitNetty serverBean){ - if(serverBean.isSsl()){ - if(!ObjectUtils.allNotNull(serverBean.getJksCertificatePassword(),serverBean.getJksFile(),serverBean.getJksStorePassword())){ - throw new NullPointerException("SSL file and password is null"); - } - initSsl(serverBean); - SSLEngine engine = - SERVER_CONTEXT.createSSLEngine(); - engine.setUseClientMode(false); - channelPipeline.addLast("ssl", new SslHandler(engine)); - } - - intProtocolHandler(channelPipeline,serverBean); - channelPipeline.addLast(new IdleStateHandler(serverBean.getHeart(),0,0)); - channelPipeline.addLast( SpringBeanUtils.getBean(serverBean.getMqttHander())); - - } - - private void intProtocolHandler(ChannelPipeline channelPipeline,InitNetty serverBean){ - switch (serverBean.getProtocol()){ - case MQTT: - channelPipeline.addLast("encoder", MqttEncoder.INSTANCE); - channelPipeline.addLast("decoder", new MqttDecoder()); - break; - case MQTT_WS_MQTT: - channelPipeline.addLast("httpCode", new HttpServerCodec()); - channelPipeline.addLast("aggregator", new HttpObjectAggregator(65536)); - channelPipeline.addLast("webSocketHandler", - new WebSocketServerProtocolHandler("/", MQTT_CSV_LIST)); - channelPipeline.addLast("wsDecoder", new WebSocketFrameToByteBufDecoder()); - channelPipeline.addLast("wsEncoder", new ByteBufToWebSocketFrameEncoder()); - channelPipeline.addLast("decoder", new MqttDecoder()); - channelPipeline.addLast("encoder", MqttEncoder.INSTANCE); - break; - case MQTT_WS_PAHO: - channelPipeline.addLast("httpCode", new HttpServerCodec()); - channelPipeline.addLast("aggregator", new HttpObjectAggregator(65536)); - channelPipeline.addLast("webSocketHandler", - new WebSocketServerProtocolHandler("/mqtt", MQTT_CSV_LIST)); - channelPipeline.addLast("wsDecoder", new WebSocketFrameToByteBufDecoder()); - channelPipeline.addLast("wsEncoder", new ByteBufToWebSocketFrameEncoder()); - channelPipeline.addLast("decoder", new MqttDecoder()); - channelPipeline.addLast("encoder", MqttEncoder.INSTANCE); - break; - } - } - - private void initSsl(InitNetty serverBean){ - ExecutorService executorService = Executors.newCachedThreadPool(); - executorService.submit(() -> {}); - String algorithm = SystemPropertyUtil.get("ssl.KeyManagerFactory.algorithm"); - if (algorithm == null) { - algorithm = "SunX509"; - } - SSLContext serverContext; - try { - // - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load( SecureSocketSslContextFactory.class.getResourceAsStream(serverBean.getJksFile()), - serverBean.getJksStorePassword().toCharArray()); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); - kmf.init(ks,serverBean.getJksCertificatePassword().toCharArray()); - serverContext = SSLContext.getInstance(PROTOCOL); - serverContext.init(kmf.getKeyManagers(), null, null); - } catch (Exception e) { - throw new Error( - "Failed to initialize the server-side SSLContext", e); - } - SERVER_CONTEXT = serverContext; - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/BaseApi.java b/src/main/java/com/myself/nettychat/bootstrap/BaseApi.java deleted file mode 100644 index edd8720..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/BaseApi.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.myself.nettychat.bootstrap; - -import javax.validation.constraints.NotNull; -import java.util.Arrays; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Predicate; - - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 逻辑操作封装 - **/ -public interface BaseApi { - - default void doIfElse(T t, Predicate predicate, Consumer consumer){ - if(t!=null){ - if(predicate.test(t)){ - consumer.accept(t); - } - } - } - - - default void doIfElse(T t, Predicate predicate, Consumer consumer, Consumer consumer2){ - if(t!=null){ - if(predicate.test(t)){ - consumer.accept(t); - } - else{ - consumer2.accept(t); - } - } - } - default boolean doIf(T t, Predicate... predicates){ - if(t!=null){ - for(Predicate p:predicates){ - if(!p.test(t)){ - return false; - } - } - return true; - } - return false; - } - - default void doIfAnd(T t, Consumer consumer2, Predicate... predicates){ - boolean flag =true; - if(t!=null){ - for(Predicate p:predicates){ - if(!p.test(t)){ - flag= false; - break; - } - } - } - if(flag){ - consumer2.accept(t); - } - } - - default void doIfAnd1(@NotNull T t, @NotNull Consumer consumer2, @NotNull Predicate... predicates){ - Predicate one = predicates[0]; - int l; - if((l=predicates.length)>1){ - for(int i=1;i topics); - - void loginSuccess(Channel channel, String deviceId, MqttConnectMessage mqttConnectMessage); - - void publishSuccess(Channel channel, MqttPublishMessage mqttPublishMessage); - - void closeSuccess(String deviceId,boolean isDisconnect); - - void sendWillMsg(WillMeaasge willMeaasge); - - String getDeviceId(Channel channel); - - void unsubscribe(String deviceId, List topics1); - - void doPubrel(Channel channel, int mqttMessage); - - void doPubrec(Channel channel, int mqttMessage); - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/NettyBootstrapServer.java b/src/main/java/com/myself/nettychat/bootstrap/NettyBootstrapServer.java deleted file mode 100644 index 816eea7..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/NettyBootstrapServer.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.myself.nettychat.bootstrap; - -import com.myself.nettychat.common.ip.IpUtils; -import com.myself.nettychat.common.properties.InitNetty; -import com.myself.nettychat.common.utils.RemotingUtil; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.PooledByteBufAllocator; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.epoll.Epoll; -import io.netty.channel.epoll.EpollEventLoopGroup; -import io.netty.channel.epoll.EpollServerSocketChannel; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; - -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc mtqq netty启动服务类 - **/ -@Slf4j -@Data -public class NettyBootstrapServer extends AbstractBootstrapServer { - - private InitNetty serverBean; - - public InitNetty getServerBean() { - return serverBean; - } - - public void setServerBean(InitNetty serverBean) { - this.serverBean = serverBean; - } - - private EventLoopGroup bossGroup; - - private EventLoopGroup workGroup; - - ServerBootstrap bootstrap=null ;// 启动辅助类 - - /** - * 服务开启 - */ - public void start() { - initEventPool(); - bootstrap.group(bossGroup, workGroup) - .channel(useEpoll()?EpollServerSocketChannel.class:NioServerSocketChannel.class) - .option(ChannelOption.SO_REUSEADDR, serverBean.isReuseaddr()) - .option(ChannelOption.SO_BACKLOG, serverBean.getBacklog()) - .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) - .option(ChannelOption.SO_RCVBUF, serverBean.getRevbuf()) - .childHandler(new ChannelInitializer() { - protected void initChannel(SocketChannel ch) throws Exception { - initHandler(ch.pipeline(),serverBean); - } - }) - .childOption(ChannelOption.TCP_NODELAY, serverBean.isNodelay()) - .childOption(ChannelOption.SO_KEEPALIVE, serverBean.isKeepalive()) - .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); - bootstrap.bind(IpUtils.getHost(),serverBean.getMqttport()).addListener((ChannelFutureListener) channelFuture -> { - if (channelFuture.isSuccess()) - log.info("服务端启动成功【" + IpUtils.getHost() + ":" + serverBean.getMqttport() + "】"); - else - log.info("服务端启动失败【" + IpUtils.getHost() + ":" + serverBean.getMqttport() + "】"); - }); - } - /** - * 初始化EnentPool 参数 - */ - private void initEventPool(){ - bootstrap= new ServerBootstrap(); - if(useEpoll()){ - bossGroup = new EpollEventLoopGroup(serverBean.getBossThread(), new ThreadFactory() { - private AtomicInteger index = new AtomicInteger(0); - - public Thread newThread(Runnable r) { - return new Thread(r, "LINUX_BOSS_" + index.incrementAndGet()); - } - }); - workGroup = new EpollEventLoopGroup(serverBean.getWorkerThread(), new ThreadFactory() { - private AtomicInteger index = new AtomicInteger(0); - - public Thread newThread(Runnable r) { - return new Thread(r, "LINUX_WORK_" + index.incrementAndGet()); - } - }); - - } - else { - bossGroup = new NioEventLoopGroup(serverBean.getBossThread(), new ThreadFactory() { - private AtomicInteger index = new AtomicInteger(0); - - public Thread newThread(Runnable r) { - return new Thread(r, "BOSS_" + index.incrementAndGet()); - } - }); - workGroup = new NioEventLoopGroup(serverBean.getWorkerThread(), new ThreadFactory() { - private AtomicInteger index = new AtomicInteger(0); - - public Thread newThread(Runnable r) { - return new Thread(r, "WORK_" + index.incrementAndGet()); - } - }); - } - } - - /** - * 关闭资源 - */ - public void shutdown() { - if(workGroup!=null && bossGroup!=null ){ - try { - bossGroup.shutdownGracefully().sync();// 优雅关闭 - workGroup.shutdownGracefully().sync(); - } catch (InterruptedException e) { - log.info("服务端关闭资源失败【" + IpUtils.getHost() + ":" + serverBean.getMqttport() + "】"); - } - } - } - - private boolean useEpoll() { - return RemotingUtil.isLinuxPlatform() - && Epoll.isAvailable(); - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/bean/MqttChannel.java b/src/main/java/com/myself/nettychat/bootstrap/bean/MqttChannel.java deleted file mode 100644 index 9c830ce..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/bean/MqttChannel.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.myself.nettychat.bootstrap.bean; - -import io.netty.channel.Channel; -import io.netty.util.AttributeKey; -import lombok.Builder; -import lombok.Data; - -import com.myself.nettychat.common.enums.SubStatus; -import com.myself.nettychat.common.enums.SessionStatus; - -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc channel 封装类 - **/ -@Builder -@Data -public class MqttChannel { - - private transient volatile Channel channel; - - - private String deviceId; - - - private boolean isWill; - - - private volatile SubStatus subStatus; // 是否订阅过主题 - - - private Set topic ; - - - - private volatile SessionStatus sessionStatus; // 在线 - 离线 - - - - private volatile boolean cleanSession; // 当为 true 时 channel close 时 从缓存中删除 此channel - - - - - private ConcurrentHashMap message ; // messageId - message(qos1) // 待确认消息 - - - private Set receive; - - public void addRecevice(int messageId){ - receive.add(messageId); - } - - public boolean checkRecevice(int messageId){ - return receive.contains(messageId); - } - - public boolean removeRecevice(int messageId){ - return receive.remove(messageId); - } - - - public void addSendMqttMessage(int messageId,SendMqttMessage msg){ - message.put(messageId,msg); - } - - - public SendMqttMessage getSendMqttMessage(int messageId){ - return message.get(messageId); - } - - - public void removeSendMqttMessage(int messageId){ - message.remove(messageId); - } - - - /** - * 判断当前channel 是否登录过 - * @return - */ - public boolean isLogin(){ - return Optional.ofNullable(this.channel).map(channel1 -> { - AttributeKey _login = AttributeKey.valueOf("login"); - return channel1.isActive() && channel1.hasAttr(_login); - }).orElse(false); - } - - /** - * 非正常关闭 - */ - public void close(){ - Optional.ofNullable(this.channel).ifPresent(channel1 -> channel1.close()); - } - - /** - * 通道是否活跃 - * @return - */ - public boolean isActive(){ - return channel!=null&&this.channel.isActive(); - } - - - - public boolean addTopic(Set topics){ - return topic.addAll(topics); - } - - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/bean/RetainMessage.java b/src/main/java/com/myself/nettychat/bootstrap/bean/RetainMessage.java deleted file mode 100644 index 814696a..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/bean/RetainMessage.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.myself.nettychat.bootstrap.bean; - -import io.netty.handler.codec.mqtt.MqttQoS; -import lombok.Builder; -import lombok.Data; - -@Builder -@Data -public class RetainMessage { - - private byte[] byteBuf; - - private MqttQoS qoS; - public String getString(){ - return new String(byteBuf); - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/bean/SendMqttMessage.java b/src/main/java/com/myself/nettychat/bootstrap/bean/SendMqttMessage.java deleted file mode 100644 index ac5faef..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/bean/SendMqttMessage.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.myself.nettychat.bootstrap.bean; - - -import io.netty.channel.Channel; -import io.netty.handler.codec.mqtt.MqttQoS; -import lombok.Builder; -import lombok.Data; - -import com.myself.nettychat.common.enums.ConfirmStatus; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc mqtts 消息 - **/ -@Builder -@Data -public class SendMqttMessage { - - - private int messageId; - - private Channel channel; - - private volatile ConfirmStatus confirmStatus; - - private long time; - - private byte[] byteBuf; - - private boolean isRetain; - - private MqttQoS qos; - - private String topic; - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/bean/SessionMessage.java b/src/main/java/com/myself/nettychat/bootstrap/bean/SessionMessage.java deleted file mode 100644 index 2613cd6..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/bean/SessionMessage.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.myself.nettychat.bootstrap.bean; - - -import io.netty.handler.codec.mqtt.MqttQoS; -import lombok.Builder; -import lombok.Data; - - -/** - * @author MySelf - * @create 2018/9/22 - * @desc Session会话数据保存 - **/ -@Builder -@Data -public class SessionMessage { - - private byte[] byteBuf; - - private MqttQoS qoS; - - private String topic; - - - public String getString(){ - return new String(byteBuf); - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/bean/WillMeaasge.java b/src/main/java/com/myself/nettychat/bootstrap/bean/WillMeaasge.java deleted file mode 100644 index 4022d28..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/bean/WillMeaasge.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.myself.nettychat.bootstrap.bean; - -import lombok.Builder; -import lombok.Data; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 遗嘱消息 - **/ -@Builder -@Data -public class WillMeaasge { - - private String willTopic; - - private String willMessage; - - private boolean isRetain; - - private int qos; - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/channel/AbstractChannelService.java b/src/main/java/com/myself/nettychat/bootstrap/channel/AbstractChannelService.java deleted file mode 100644 index c0e51f2..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/channel/AbstractChannelService.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.myself.nettychat.bootstrap.channel; - -import com.myself.nettychat.bootstrap.BaseApi; -import com.myself.nettychat.bootstrap.ChannelService; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.myself.nettychat.bootstrap.bean.MqttChannel; -import com.myself.nettychat.bootstrap.bean.RetainMessage; -import com.myself.nettychat.bootstrap.channel.cache.CacheMap; -import com.myself.nettychat.bootstrap.scan.ScanRunnable; -import io.netty.channel.Channel; -import io.netty.util.AttributeKey; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -import java.util.Collection; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 抽象类 - **/ -@Slf4j -public abstract class AbstractChannelService extends PublishApiSevice implements ChannelService , BaseApi { - - protected AttributeKey _login = AttributeKey.valueOf("login"); - - protected AttributeKey _deviceId = AttributeKey.valueOf("deviceId"); - - protected static char SPLITOR = '/'; - - protected ExecutorService executorService =Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2); - - - protected static CacheMap cacheMap= new CacheMap<>(); - - - protected static ConcurrentHashMap mqttChannels = new ConcurrentHashMap<>(); // deviceId - mqChannel 登录 - - - protected static ConcurrentHashMap> retain = new ConcurrentHashMap<>(); // topic - 保留消息 - - - - protected static Cache> mqttChannelCache = CacheBuilder.newBuilder().maximumSize(100).build(); - - public AbstractChannelService(ScanRunnable scanRunnable) { - super(scanRunnable); - } - - - protected Collection getChannels(String topic,TopicFilter topicFilter){ - try { - return mqttChannelCache.get(topic, () -> topicFilter.filter(topic)); - } catch (Exception e) { - log.info(String.format("guava cache key topic【%s】 channel value== null ",topic)); - } - return null; - } - - - @FunctionalInterface - interface TopicFilter{ - Collection filter(String topic); - } - - protected boolean deleteChannel(String topic,MqttChannel mqttChannel){ - return Optional.ofNullable(topic).map(s -> { - mqttChannelCache.invalidate(s); - return cacheMap.delete(getTopic(s),mqttChannel); - }).orElse(false); - } - - protected boolean addChannel(String topic,MqttChannel mqttChannel) - { - return Optional.ofNullable(topic).map(s -> { - mqttChannelCache.invalidate(s); - return cacheMap.putData(getTopic(s),mqttChannel); - }).orElse(false); - } - - /** - * 获取channel - */ - public MqttChannel getMqttChannel(String deviceId){ - return Optional.ofNullable(deviceId).map(s -> mqttChannels.get(s)) - .orElse(null); - - } - - /** - * 获取channelId - */ - public String getDeviceId(Channel channel){ - return Optional.ofNullable(channel).map( channel1->channel1.attr(_deviceId).get()) - .orElse(null); - } - - - - protected String[] getTopic(String topic) { - return Optional.ofNullable(topic).map(s -> - StringUtils.split(topic,SPLITOR) - ).orElse(null); - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/channel/ClientSessionService.java b/src/main/java/com/myself/nettychat/bootstrap/channel/ClientSessionService.java deleted file mode 100644 index 9a23c8b..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/channel/ClientSessionService.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.myself.nettychat.bootstrap.channel; - -import com.myself.nettychat.bootstrap.bean.SessionMessage; -import org.springframework.stereotype.Service; - -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 会话保留处理 - **/ -@Service -public class ClientSessionService { - - private static ConcurrentHashMap> queueSession = new ConcurrentHashMap<>(); // 连接关闭后 保留此session 数据 deviceId - - - public void saveSessionMsg(String deviceId, SessionMessage sessionMessage) { - ConcurrentLinkedQueue sessionMessages = queueSession.getOrDefault(deviceId, new ConcurrentLinkedQueue<>()); - boolean flag; - do{ - flag = sessionMessages.add(sessionMessage); - } - while (!flag); - queueSession.put(deviceId,sessionMessages); - } - - public ConcurrentLinkedQueue getByteBuf(String deviceId){ - return Optional.ofNullable(deviceId).map(s -> queueSession.get(s)) - .orElse(null); - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/channel/MqttChannelService.java b/src/main/java/com/myself/nettychat/bootstrap/channel/MqttChannelService.java deleted file mode 100644 index fa5ec21..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/channel/MqttChannelService.java +++ /dev/null @@ -1,440 +0,0 @@ -package com.myself.nettychat.bootstrap.channel; - -import com.myself.nettychat.bootstrap.bean.*; -import com.myself.nettychat.bootstrap.scan.ScanRunnable; -import com.myself.nettychat.common.enums.ConfirmStatus; -import com.myself.nettychat.common.enums.SessionStatus; -import com.myself.nettychat.common.enums.SubStatus; -import com.myself.nettychat.common.exception.ConnectionException; -import com.myself.nettychat.common.utils.ByteBufUtil; -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.handler.codec.mqtt.*; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; - -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CopyOnWriteArraySet; - - -/** - * @author MySelf - * @create 2018/9/22 - * @desc Channel事件处理service - **/ -@Slf4j -@Component -public class MqttChannelService extends AbstractChannelService { - - @Autowired - private ClientSessionService clientSessionService; - - @Autowired - private WillService willService; - - private final ScanRunnable scanRunnable; - - public MqttChannelService(ScanRunnable scanRunnable) { - super(scanRunnable); - this.scanRunnable = scanRunnable; - } - - - /** - * 取消订阅 - */ - @Override - public void unsubscribe(String deviceId, List topics1) { - Optional.ofNullable(mqttChannels.get(deviceId)).ifPresent(mqttChannel -> { - topics1.forEach(topic -> { - deleteChannel(topic,mqttChannel); - }); - }); - } - - /** - * 登录成功后 回复 - */ - private void replyLogin(Channel channel, MqttConnectMessage mqttConnectMessage) { - MqttFixedHeader mqttFixedHeader1 = mqttConnectMessage.fixedHeader(); - MqttConnectVariableHeader mqttConnectVariableHeader = mqttConnectMessage.variableHeader(); - final MqttConnectPayload payload = mqttConnectMessage.payload(); - String deviceId = getDeviceId(channel); - MqttChannel build = MqttChannel.builder().channel(channel).cleanSession(mqttConnectVariableHeader.isCleanSession()) - .deviceId(payload.clientIdentifier()) - .sessionStatus(SessionStatus.OPEN) - .isWill(mqttConnectVariableHeader.isWillFlag()) - .subStatus(SubStatus.NO) - .topic(new CopyOnWriteArraySet<>()) - .message(new ConcurrentHashMap<>()) - .receive(new CopyOnWriteArraySet<>()) - .build(); - if (connectSuccess(deviceId, build)) { // 初始化存储mqttchannel - if (mqttConnectVariableHeader.isWillFlag()) { // 遗嘱消息标志 - boolean b = doIf(mqttConnectVariableHeader, mqttConnectVariableHeader1 -> (payload.willMessage() != null) - , mqttConnectVariableHeader1 -> (payload.willTopic() != null)); - if (!b) { - throw new ConnectionException("will message and will topic is not null"); - } - // 处理遗嘱消息 - final WillMeaasge buildWill = WillMeaasge.builder(). - qos(mqttConnectVariableHeader.willQos()) - .willMessage(deviceId) - .willTopic(payload.willTopic()) - .isRetain(mqttConnectVariableHeader.isWillRetain()) - .build(); - willService.save(payload.clientIdentifier(), buildWill); - } else { - willService.del(payload.clientIdentifier()); - boolean b = doIf(mqttConnectVariableHeader, mqttConnectVariableHeader1 -> (!mqttConnectVariableHeader1.isWillRetain()), - mqttConnectVariableHeader1 -> (mqttConnectVariableHeader1.willQos() == 0)); - if (!b) { - throw new ConnectionException("will retain should be null and will QOS equal 0"); - } - } - doIfElse(mqttConnectVariableHeader, mqttConnectVariableHeader1 -> (mqttConnectVariableHeader1.isCleanSession()), mqttConnectVariableHeader1 -> { - MqttConnectReturnCode connectReturnCode = MqttConnectReturnCode.CONNECTION_ACCEPTED; - MqttConnAckVariableHeader mqttConnAckVariableHeader = new MqttConnAckVariableHeader(connectReturnCode, false); - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader( - MqttMessageType.CONNACK, mqttFixedHeader1.isDup(), MqttQoS.AT_MOST_ONCE, mqttFixedHeader1.isRetain(), 0x02); - MqttConnAckMessage connAck = new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); - channel.writeAndFlush(connAck);// 清理会话 - }, mqttConnectVariableHeader1 -> { - MqttConnectReturnCode connectReturnCode = MqttConnectReturnCode.CONNECTION_ACCEPTED; - MqttConnAckVariableHeader mqttConnAckVariableHeader = new MqttConnAckVariableHeader(connectReturnCode, true); - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader( - MqttMessageType.CONNACK, mqttFixedHeader1.isDup(), MqttQoS.AT_MOST_ONCE, mqttFixedHeader1.isRetain(), 0x02); - MqttConnAckMessage connAck = new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); - channel.writeAndFlush(connAck);// 非清理会话 - - }); //发送 session 数据 - ConcurrentLinkedQueue sessionMessages = clientSessionService.getByteBuf(payload.clientIdentifier()); - doIfElse(sessionMessages, messages -> messages != null && !messages.isEmpty(), byteBufs -> { - SessionMessage sessionMessage; - while ((sessionMessage = byteBufs.poll()) != null) { - switch (sessionMessage.getQoS()) { - case EXACTLY_ONCE: - sendQosConfirmMsg(MqttQoS.EXACTLY_ONCE,getMqttChannel(deviceId), sessionMessage.getTopic(), sessionMessage.getByteBuf()); - break; - case AT_MOST_ONCE: - sendQos0Msg(channel, sessionMessage.getTopic(), sessionMessage.getByteBuf()); - break; - case AT_LEAST_ONCE: - sendQosConfirmMsg(MqttQoS.AT_LEAST_ONCE,getMqttChannel(deviceId), sessionMessage.getTopic(), sessionMessage.getByteBuf()); - break; - } - } - - }); - } - } - - - - /** - * qos2 第二步 - */ - @Override - public void doPubrel(Channel channel, int messageId) { - MqttChannel mqttChannel = getMqttChannel(getDeviceId(channel)); - doIfElse(mqttChannel,mqttChannel1 ->mqttChannel1.isLogin(),mqttChannel1 -> { - mqttChannel1.removeRecevice(messageId); - sendToPubComp(channel,messageId); - }); - } - - - - /** - * qos2 第三步 - */ - @Override - public void doPubrec(Channel channel, int mqttMessage) { - sendPubRel(channel,false,mqttMessage); - } - - /** - * 连接成功后 - * @param deviceId - * @param build - */ - @Override - public boolean connectSuccess(String deviceId, MqttChannel build) { - return Optional.ofNullable(mqttChannels.get(deviceId)) - .map(mqttChannel -> { - switch (mqttChannel.getSessionStatus()){ - case OPEN: - return false; - case CLOSE: - switch (mqttChannel.getSubStatus()){ - case YES: // 清除订阅 topic - deleteSubTopic(mqttChannel).stream() - .forEach(s -> cacheMap.putData(getTopic(s),build)); - break; - } - } - mqttChannels.put(deviceId,build); - return true; - }).orElseGet(() -> { - mqttChannels.put(deviceId,build); - return true; - }); - } - - - /** - * 订阅成功后 (发送保留消息) - */ - public void suscribeSuccess(String deviceId, Set topics){ - doIfElse(topics,topics1->!CollectionUtils.isEmpty(topics1),strings -> { - MqttChannel mqttChannel = mqttChannels.get(deviceId); - mqttChannel.setSubStatus(SubStatus.YES); // 设置订阅主题标识 - mqttChannel.addTopic(strings); - executorService.execute(() -> { - Optional.ofNullable(mqttChannel).ifPresent(mqttChannel1 -> { - if(mqttChannel1.isLogin()){ - strings.parallelStream().forEach(topic -> { - addChannel(topic,mqttChannel); - sendRetain(topic,mqttChannel); // 发送保留消息 - }); - } - }); - }); - }); - } - - - /** - *成功登陆 (发送会话消息) - * @param channel - * @param deviceId - * @param mqttConnectMessage - */ - @Override - public void loginSuccess(Channel channel, String deviceId, MqttConnectMessage mqttConnectMessage) { - channel.attr(_login).set(true); - channel.attr(_deviceId).set(deviceId); - replyLogin(channel, mqttConnectMessage); - } - - - /** - * 发布消息成功 () - * @param channel - * @param mqttPublishMessage - */ - @Override - public void publishSuccess(Channel channel, MqttPublishMessage mqttPublishMessage) { - MqttFixedHeader mqttFixedHeader = mqttPublishMessage.fixedHeader(); - MqttPublishVariableHeader mqttPublishVariableHeader = mqttPublishMessage.variableHeader(); - MqttChannel mqttChannel = getMqttChannel(getDeviceId(channel)); - ByteBuf payload = mqttPublishMessage.payload(); - byte[] bytes = ByteBufUtil.copyByteBuf(payload); // - int messageId = mqttPublishVariableHeader.messageId(); - executorService.execute(() -> { - if (channel.hasAttr(_login) && mqttChannel != null) { - boolean isRetain; - switch (mqttFixedHeader.qosLevel()) { - case AT_MOST_ONCE: // 至多一次 - break; - case AT_LEAST_ONCE: - sendPubBack(channel, messageId); - break; - case EXACTLY_ONCE: - sendPubRec(mqttChannel, messageId); - break; - } - if ((isRetain=mqttFixedHeader.isRetain()) && mqttFixedHeader.qosLevel() != MqttQoS.AT_MOST_ONCE) { //是保留消息 qos >0 - saveRetain(mqttPublishVariableHeader.topicName(), - RetainMessage.builder() - .byteBuf(bytes) - .qoS(mqttFixedHeader.qosLevel()) - .build(), false); - } else if (mqttFixedHeader.isRetain() && mqttFixedHeader.qosLevel() == MqttQoS.AT_MOST_ONCE) { // 是保留消息 qos=0 清除之前保留消息 保留现在 - saveRetain(mqttPublishVariableHeader.topicName(), - RetainMessage.builder() - .byteBuf(bytes) - .qoS(mqttFixedHeader.qosLevel()) - .build(), true); - } - if (!mqttChannel.checkRecevice(messageId)) { - push(mqttPublishVariableHeader.topicName(), mqttFixedHeader.qosLevel(), bytes,isRetain); - mqttChannel.addRecevice(messageId); - } - } - }); - - } - /** - * 推送消息给订阅者 - */ - private void push(String topic, MqttQoS qos, byte[] bytes, boolean isRetain){ - Collection subChannels = getChannels(topic, topic1 -> cacheMap.getData(getTopic(topic1))); - if(!CollectionUtils.isEmpty(subChannels)){ - subChannels.parallelStream().forEach(subChannel -> { - switch (subChannel.getSessionStatus()){ - case OPEN: // 在线 - if(subChannel.isActive()){ // 防止channel失效 但是离线状态没更改 - switch (qos){ - case AT_LEAST_ONCE: - sendQosConfirmMsg(MqttQoS.AT_LEAST_ONCE,subChannel,topic,bytes); - break; - case AT_MOST_ONCE: - sendQos0Msg(subChannel.getChannel(),topic,bytes); - break; - case EXACTLY_ONCE: - sendQosConfirmMsg(MqttQoS.EXACTLY_ONCE,subChannel,topic,bytes); - break; - } - } - else{ - if(!subChannel.isCleanSession() & !isRetain){ - clientSessionService.saveSessionMsg(subChannel.getDeviceId(), - SessionMessage.builder().byteBuf(bytes).qoS(qos).topic(topic).build() ); - break; - } - } - break; - case CLOSE: // 连接 设置了 clean session =false - clientSessionService.saveSessionMsg(subChannel.getDeviceId(), - SessionMessage.builder().byteBuf(bytes).qoS(qos).topic(topic).build() ); - break; - } - }); - } - } - - /** - * 关闭channel 操作 - * @param deviceId - */ - @Override - public void closeSuccess(String deviceId,boolean isDisconnect) { - if(StringUtils.isNotBlank(deviceId)){ - executorService.execute(() -> { - MqttChannel mqttChannel = mqttChannels.get(deviceId); - Optional.ofNullable(mqttChannel).ifPresent(mqttChannel1 -> { - mqttChannel1.setSessionStatus(SessionStatus.CLOSE); // 设置关闭 - mqttChannel1.close(); // 关闭channel - mqttChannel1.setChannel(null); - if(!mqttChannel1.isCleanSession()){ // 保持会话 - // 处理 qos1 未确认数据 - ConcurrentHashMap message = mqttChannel1.getMessage(); - Optional.ofNullable(message).ifPresent(integerConfirmMessageConcurrentHashMap -> { - integerConfirmMessageConcurrentHashMap.forEach((integer, confirmMessage) -> doIfElse(confirmMessage, sendMqttMessage ->sendMqttMessage.getConfirmStatus()== ConfirmStatus.PUB, sendMqttMessage ->{ - clientSessionService.saveSessionMsg(mqttChannel.getDeviceId(), SessionMessage.builder() - .byteBuf(sendMqttMessage.getByteBuf()) - .qoS(sendMqttMessage.getQos()) - .topic(sendMqttMessage.getTopic()) - .build()); // 把待确认数据转入session中 - } - )); - - }); - } - else{ // 删除sub topic-消息 - mqttChannels.remove(deviceId); // 移除channelId 不保持会话 直接删除 保持会话 旧的在重新connect时替换 - switch (mqttChannel1.getSubStatus()){ - case YES: - deleteSubTopic(mqttChannel1); - break; - } - } - if(mqttChannel1.isWill()){ // 发送遗言 - if(!isDisconnect){ // 不是disconnection操作 - willService.doSend(deviceId); - } - } - }); - }); - } - } - - /** - * 清除channel 订阅主题 - * @param mqttChannel - */ - public Set deleteSubTopic(MqttChannel mqttChannel){ - Set topics = mqttChannel.getTopic(); - topics.parallelStream().forEach(topic -> cacheMap.delete(getTopic(topic),mqttChannel)); - return topics; - } - - /** - * 发送 遗嘱消息(有的channel 已经关闭 但是保持了 session 此时加入session 数据中 ) - * @param willMeaasge 遗嘱消息 - */ - public void sendWillMsg(WillMeaasge willMeaasge){ - Collection mqttChannels = getChannels(willMeaasge.getWillTopic(), topic -> cacheMap.getData(getTopic(topic))); - if(!CollectionUtils.isEmpty(mqttChannels)){ - mqttChannels.forEach(mqttChannel -> { - switch (mqttChannel.getSessionStatus()){ - case CLOSE: - clientSessionService.saveSessionMsg(mqttChannel.getDeviceId(), - SessionMessage.builder() - .topic(willMeaasge.getWillTopic()) - .qoS(MqttQoS.valueOf(willMeaasge.getQos())) - .byteBuf(willMeaasge.getWillMessage().getBytes()).build()); - break; - case OPEN: - writeWillMsg(mqttChannel,willMeaasge); - break; - } - }); - } - } - - /** - * 保存保留消息 - * @param topic 主题 - * @param retainMessage 信息 - */ - private void saveRetain(String topic, RetainMessage retainMessage, boolean isClean){ - ConcurrentLinkedQueue retainMessages = retain.getOrDefault(topic, new ConcurrentLinkedQueue<>()); - if(!retainMessages.isEmpty() && isClean){ - retainMessages.clear(); - } - boolean flag; - do{ - flag = retainMessages.add(retainMessage); - } - while (!flag); - retain.put(topic, retainMessages); - } - - /** - * 发送保留消息 - */ - public void sendRetain(String topic,MqttChannel mqttChannel){ - retain.forEach((_topic, retainMessages) -> { - if(StringUtils.startsWith(_topic,topic)){ - Optional.ofNullable(retainMessages).ifPresent(pubMessages1 -> { - retainMessages.parallelStream().forEach(retainMessage -> { - log.info("【发送保留消息】"+mqttChannel.getChannel().remoteAddress()+":"+retainMessage.getString()+"【成功】"); - switch (retainMessage.getQoS()){ - case AT_MOST_ONCE: - sendQos0Msg(mqttChannel.getChannel(),_topic,retainMessage.getByteBuf()); - break; - case AT_LEAST_ONCE: - sendQosConfirmMsg(MqttQoS.AT_LEAST_ONCE,mqttChannel,_topic,retainMessage.getByteBuf()); - break; - case EXACTLY_ONCE: - sendQosConfirmMsg(MqttQoS.EXACTLY_ONCE,mqttChannel,_topic,retainMessage.getByteBuf()); - break; - } - }); - }); - } - }); - - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/channel/MqttHandlerService.java b/src/main/java/com/myself/nettychat/bootstrap/channel/MqttHandlerService.java deleted file mode 100644 index abc4d56..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/channel/MqttHandlerService.java +++ /dev/null @@ -1,223 +0,0 @@ -package com.myself.nettychat.bootstrap.channel; - -import com.myself.nettychat.bootstrap.BaseApi; -import com.myself.nettychat.bootstrap.BaseAuthService; -import com.myself.nettychat.bootstrap.ChannelService; -import com.myself.nettychat.bootstrap.bean.SendMqttMessage; -import com.myself.nettychat.common.enums.ConfirmStatus; -import com.myself.nettychat.common.mqtts.ServerMqttHandlerService; -import io.netty.channel.Channel; -import io.netty.handler.codec.mqtt.*; -import io.netty.handler.timeout.IdleStateEvent; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -@Slf4j -@Component -public class MqttHandlerService extends ServerMqttHandlerService implements BaseApi { - - @Autowired - ChannelService mqttChannelService; - - private final BaseAuthService baseAuthService; - - public MqttHandlerService(BaseAuthService baseAuthService) { - this.baseAuthService = baseAuthService; - } - - /** - * 登录 - * - */ - @Override - public boolean login(Channel channel, MqttConnectMessage mqttConnectMessage) { -// 校验规则 自定义校验规则 - MqttConnectPayload payload = mqttConnectMessage.payload(); - String deviceId = payload.clientIdentifier(); - if (StringUtils.isBlank(deviceId)) { - MqttConnectReturnCode connectReturnCode = MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED; - connectBack(channel,connectReturnCode); - return false; - } - - if(mqttConnectMessage.variableHeader().hasPassword() && mqttConnectMessage.variableHeader().hasUserName() - && !baseAuthService.authorized(payload.userName(),payload.password())){ - MqttConnectReturnCode connectReturnCode = MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD; - connectBack(channel,connectReturnCode); - return false; - } - return Optional.ofNullable(mqttChannelService.getMqttChannel(deviceId)) - .map(mqttChannel -> { - switch (mqttChannel.getSessionStatus()){ - case OPEN: - return false; - } - mqttChannelService.loginSuccess(channel, deviceId, mqttConnectMessage); - return true; - }).orElseGet(() -> { - mqttChannelService.loginSuccess(channel, deviceId, mqttConnectMessage); - return true; - }); - - } - - private void connectBack(Channel channel, MqttConnectReturnCode connectReturnCode){ - MqttConnAckVariableHeader mqttConnAckVariableHeader = new MqttConnAckVariableHeader(connectReturnCode, true); - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader( - MqttMessageType.CONNACK,false, MqttQoS.AT_MOST_ONCE, false, 0x02); - MqttConnAckMessage connAck = new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); - channel.writeAndFlush(connAck); - } - - - /** - * 发布 - */ - @Override - public void publish(Channel channel, MqttPublishMessage mqttPublishMessage) { - mqttChannelService.publishSuccess(channel, mqttPublishMessage); - } - - /** - * 订阅 - */ - @Override - public void subscribe(Channel channel, MqttSubscribeMessage mqttSubscribeMessage) { - Set topics = mqttSubscribeMessage.payload().topicSubscriptions().stream().map(mqttTopicSubscription -> - mqttTopicSubscription.topicName() - ).collect(Collectors.toSet()); - mqttChannelService.suscribeSuccess(mqttChannelService.getDeviceId(channel), topics); - subBack(channel, mqttSubscribeMessage, topics.size()); - } - - private void subBack(Channel channel, MqttSubscribeMessage mqttSubscribeMessage, int num) { - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0); - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(mqttSubscribeMessage.variableHeader().messageId()); - List grantedQoSLevels = new ArrayList<>(num); - for (int i = 0; i < num; i++) { - grantedQoSLevels.add(mqttSubscribeMessage.payload().topicSubscriptions().get(i).qualityOfService().value()); - } - MqttSubAckPayload payload = new MqttSubAckPayload(grantedQoSLevels); - MqttSubAckMessage mqttSubAckMessage = new MqttSubAckMessage(mqttFixedHeader, variableHeader, payload); - channel.writeAndFlush(mqttSubAckMessage); - } - - - /** - * 关闭通道 - */ - @Override - public void close(Channel channel) { - mqttChannelService.closeSuccess(mqttChannelService.getDeviceId(channel), false); - channel.close(); - } - - /** - * 回复pong消息 - */ - @Override - public void pong(Channel channel) { - if (channel.isOpen() && channel.isActive() && channel.isWritable()) { - log.info("收到来自:【" + channel.remoteAddress().toString() + "】心跳"); - MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0); - channel.writeAndFlush(new MqttMessage(fixedHeader)); - } - } - - /** - * 取消订阅 - */ - @Override - public void unsubscribe(Channel channel, MqttUnsubscribeMessage mqttMessage) { - List topics1 = mqttMessage.payload().topics(); - mqttChannelService.unsubscribe(mqttChannelService.getDeviceId(channel), topics1); - unSubBack(channel, mqttMessage.variableHeader().messageId()); - } - - /** - * 回复取消订阅 - */ - private void unSubBack(Channel channel, int messageId) { - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0x02); - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId); - MqttUnsubAckMessage mqttUnsubAckMessage = new MqttUnsubAckMessage(mqttFixedHeader, variableHeader); - channel.writeAndFlush(mqttUnsubAckMessage); - } - - - /** - * 消息回复确认(qos1 级别 保证收到消息 但是可能会重复) - */ - @Override - public void puback(Channel channel, MqttMessage mqttMessage) { - MqttMessageIdVariableHeader messageIdVariableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader(); - int messageId = messageIdVariableHeader.messageId(); - mqttChannelService.getMqttChannel(mqttChannelService.getDeviceId(channel)).getSendMqttMessage(messageId).setConfirmStatus(ConfirmStatus.COMPLETE); // 复制为空 - } - - - /** - * disconnect 主动断线 - */ - @Override - public void disconnect(Channel channel) { - mqttChannelService.closeSuccess(mqttChannelService.getDeviceId(channel), true); - } - - - /** - * qos2 发布收到 - */ - @Override - public void pubrec(Channel channel, MqttMessage mqttMessage ) { - MqttMessageIdVariableHeader messageIdVariableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader(); - int messageId = messageIdVariableHeader.messageId(); - mqttChannelService.getMqttChannel(mqttChannelService.getDeviceId(channel)).getSendMqttMessage(messageId).setConfirmStatus(ConfirmStatus.PUBREL); // 复制为空 - mqttChannelService.doPubrec(channel, messageId); - } - - /** - * qos2 发布释放 - */ - @Override - public void pubrel(Channel channel, MqttMessage mqttMessage ) { - MqttMessageIdVariableHeader mqttMessageIdVariableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader(); - int messageId = mqttMessageIdVariableHeader.messageId(); - mqttChannelService.getMqttChannel(mqttChannelService.getDeviceId(channel)).getSendMqttMessage(messageId).setConfirmStatus(ConfirmStatus.COMPLETE); // 复制为空 - mqttChannelService.doPubrel(channel, messageId); - - } - - /** - * qos2 发布完成 - */ - @Override - public void pubcomp(Channel channel, MqttMessage mqttMessage ) { - MqttMessageIdVariableHeader mqttMessageIdVariableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader(); - int messageId = mqttMessageIdVariableHeader.messageId(); - SendMqttMessage sendMqttMessage = mqttChannelService.getMqttChannel(mqttChannelService.getDeviceId(channel)).getSendMqttMessage(messageId); - sendMqttMessage.setConfirmStatus(ConfirmStatus.COMPLETE); // 复制为空 - } - - @Override - public void doTimeOut(Channel channel, IdleStateEvent evt) { - log.info("【PingPongService:doTimeOut 心跳超时】" + channel.remoteAddress() + "【channel 关闭】"); - switch (evt.state()) { - case READER_IDLE: - close(channel); - case WRITER_IDLE: - close(channel); - case ALL_IDLE: - close(channel); - } - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/channel/PublishApiSevice.java b/src/main/java/com/myself/nettychat/bootstrap/channel/PublishApiSevice.java deleted file mode 100644 index 4af733f..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/channel/PublishApiSevice.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.myself.nettychat.bootstrap.channel; - -import com.myself.nettychat.bootstrap.bean.MqttChannel; -import com.myself.nettychat.bootstrap.bean.SendMqttMessage; -import com.myself.nettychat.bootstrap.bean.WillMeaasge; -import com.myself.nettychat.bootstrap.scan.ScanRunnable; -import com.myself.nettychat.common.utils.MessageId; -import com.myself.nettychat.common.enums.ConfirmStatus; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.handler.codec.mqtt.*; -import lombok.extern.slf4j.Slf4j; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 发送消息以及确认 - **/ -@Slf4j -public class PublishApiSevice { - - private final ScanRunnable scanRunnable; - - public PublishApiSevice(ScanRunnable scanRunnable) { - this.scanRunnable = scanRunnable; - } - - - /** - * 写入遗嘱消息 - */ - protected void writeWillMsg(MqttChannel mqttChannel, WillMeaasge willMeaasge) { -// dup保证消息可靠传输,默认为0,只占用一个字节,表示第一次发送。不能用于检测消息重复发送等 - switch (willMeaasge.getQos()){ - case 0: // qos0 - sendQos0Msg(mqttChannel.getChannel(),willMeaasge.getWillTopic(),willMeaasge.getWillMessage().getBytes()); - break; - case 1: // qos1 - sendQosConfirmMsg(MqttQoS.AT_LEAST_ONCE,mqttChannel,willMeaasge.getWillTopic(),willMeaasge.getWillMessage().getBytes()); - break; - case 2: // qos2 - sendQosConfirmMsg(MqttQoS.EXACTLY_ONCE,mqttChannel,willMeaasge.getWillTopic(),willMeaasge.getWillMessage().getBytes()); - break; - } - - - } - - protected void sendQosConfirmMsg(MqttQoS qos, MqttChannel mqttChannel, String topic, byte[] bytes) { - if(mqttChannel.isLogin()){ - int messageId = MessageId.messageId(); - switch (qos){ - case AT_LEAST_ONCE: - mqttChannel.addSendMqttMessage(messageId,sendQos1Msg(mqttChannel.getChannel(),topic,false,bytes,messageId)); - break; - case EXACTLY_ONCE: - mqttChannel.addSendMqttMessage(messageId,sendQos2Msg(mqttChannel.getChannel(),topic,false,bytes,messageId)); - break; - } - } - - } - - - /** - * 发送 qos1 类的消息 - */ - private SendMqttMessage sendQos1Msg(Channel channel, String topic, boolean isDup, byte[] byteBuf, int messageId){ - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH,isDup, MqttQoS.AT_LEAST_ONCE,false,0); - MqttPublishVariableHeader mqttPublishVariableHeader = new MqttPublishVariableHeader(topic,messageId ); - MqttPublishMessage mqttPublishMessage = new MqttPublishMessage(mqttFixedHeader,mqttPublishVariableHeader, Unpooled.wrappedBuffer(byteBuf)); - channel.writeAndFlush(mqttPublishMessage); - return addQueue(channel,messageId,topic,byteBuf,MqttQoS.AT_LEAST_ONCE, ConfirmStatus.PUB); - } - - - - /** - * 发送 qos0 类的消息 byte - */ - protected void sendQos0Msg(Channel channel, String topic, byte[] byteBuf){ - if(channel!=null){ - sendQos0Msg(channel,topic,byteBuf,0); - } - } - private void sendQos0Msg(Channel channel, String topic, byte[] byteBuf,int messageId){ - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH,false, MqttQoS.AT_MOST_ONCE,false,0); - MqttPublishVariableHeader mqttPublishVariableHeader = new MqttPublishVariableHeader(topic,messageId ); - MqttPublishMessage mqttPublishMessage = new MqttPublishMessage(mqttFixedHeader,mqttPublishVariableHeader,Unpooled.wrappedBuffer(byteBuf)); - channel.writeAndFlush(mqttPublishMessage); - } - - - - - private SendMqttMessage sendQos2Msg(Channel channel, String topic,boolean isDup, byte[] byteBuf, int messageId) { - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH,isDup, MqttQoS.EXACTLY_ONCE,false,0); - MqttPublishVariableHeader mqttPublishVariableHeader = new MqttPublishVariableHeader(topic,messageId ); - MqttPublishMessage mqttPublishMessage = new MqttPublishMessage(mqttFixedHeader,mqttPublishVariableHeader, Unpooled.wrappedBuffer(byteBuf)); - channel.writeAndFlush(mqttPublishMessage); - return addQueue(channel,messageId,topic,byteBuf,MqttQoS.EXACTLY_ONCE,ConfirmStatus.PUB); - } - - - /** - * 发送qos1 publish 确认消息 - */ - protected void sendPubBack(Channel channel,int messageId){ - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK,false, MqttQoS.AT_MOST_ONCE,false,0x02); - MqttMessageIdVariableHeader from = MqttMessageIdVariableHeader.from(messageId); - MqttPubAckMessage mqttPubAckMessage = new MqttPubAckMessage(mqttFixedHeader,from); - channel.writeAndFlush(mqttPubAckMessage); - } - - - /** - * 发送qos2 publish 确认消息 第一步 - */ - protected void sendPubRec( MqttChannel mqttChannel,int messageId){ - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC,false, MqttQoS.AT_LEAST_ONCE,false,0x02); - MqttMessageIdVariableHeader from = MqttMessageIdVariableHeader.from(messageId); - MqttPubAckMessage mqttPubAckMessage = new MqttPubAckMessage(mqttFixedHeader,from); - Channel channel = mqttChannel.getChannel(); - channel.writeAndFlush(mqttPubAckMessage); - SendMqttMessage sendMqttMessage = addQueue(channel, messageId, null, null, null, ConfirmStatus.PUBREC); - mqttChannel.addSendMqttMessage(messageId,sendMqttMessage); - } - - /** - * 发送qos2 publish 确认消息 第二步 - */ - protected void sendPubRel(Channel channel,boolean isDup,int messageId){ - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBREL,isDup, MqttQoS.AT_LEAST_ONCE,false,0x02); - MqttMessageIdVariableHeader from = MqttMessageIdVariableHeader.from(messageId); - MqttPubAckMessage mqttPubAckMessage = new MqttPubAckMessage(mqttFixedHeader,from); - channel.writeAndFlush(mqttPubAckMessage); - } - - /** - * 发送qos2 publish 确认消息 第三步 - */ - protected void sendToPubComp(Channel channel,int messageId){ - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP,false, MqttQoS.AT_MOST_ONCE,false,0x02); - MqttMessageIdVariableHeader from = MqttMessageIdVariableHeader.from(messageId); - MqttPubAckMessage mqttPubAckMessage = new MqttPubAckMessage(mqttFixedHeader,from); - channel.writeAndFlush(mqttPubAckMessage); - } - - private SendMqttMessage addQueue(Channel channel,int messageId,String topic,byte[] datas,MqttQoS mqttQoS,ConfirmStatus confirmStatus){ - SendMqttMessage build = SendMqttMessage.builder(). - channel(channel). - confirmStatus(confirmStatus). - messageId(messageId) - .topic(topic) - .qos(mqttQoS) - .byteBuf(datas) - .time(System.currentTimeMillis()).build(); - scanRunnable.addQueue(build); - return build; - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/channel/WillService.java b/src/main/java/com/myself/nettychat/bootstrap/channel/WillService.java deleted file mode 100644 index 3aa4213..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/channel/WillService.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.myself.nettychat.bootstrap.channel; - -import com.myself.nettychat.bootstrap.BaseApi; -import com.myself.nettychat.bootstrap.ChannelService; -import com.myself.nettychat.bootstrap.bean.WillMeaasge; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.concurrent.ConcurrentHashMap; - -@Slf4j -@Component -@Data -@NoArgsConstructor -public class WillService implements BaseApi { - - @Autowired - ChannelService channelService; - - private static ConcurrentHashMap willMeaasges = new ConcurrentHashMap<>(); // deviceid -WillMeaasge - - - - /** - * 保存遗嘱消息 - */ - public void save(String deviceid, WillMeaasge build) { - willMeaasges.put(deviceid,build); // 替换旧的 - } - - - public void doSend( String deviceId) { // 客户端断开连接后 开启遗嘱消息发送 - if(StringUtils.isNotBlank(deviceId)&&(willMeaasges.get(deviceId))!=null){ - WillMeaasge willMeaasge = willMeaasges.get(deviceId); - channelService.sendWillMsg(willMeaasge); // 发送遗嘱消息 - if(!willMeaasge.isRetain()){ // 移除 - willMeaasges.remove(deviceId); - log.info("deviceId will message["+willMeaasge.getWillMessage()+"] is removed"); - } - } - } - - /** - * 删除遗嘱消息 - */ - public void del(String deviceid ) {willMeaasges.remove(deviceid);} - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/channel/cache/CacheMap.java b/src/main/java/com/myself/nettychat/bootstrap/channel/cache/CacheMap.java deleted file mode 100644 index 68f805e..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/channel/cache/CacheMap.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.myself.nettychat.bootstrap.channel.cache; - -import lombok.extern.slf4j.Slf4j; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -/** - * @author MySelf - * @create 2018/9/22 - * @desc 缓存操作 - **/ -@Slf4j -public class CacheMap { - - private ConcurrentHashMap> datas = new ConcurrentHashMap<>(); - - public boolean putData(K[] topic, V v){ - if(topic.length==1){ - Node kvNode = buildOne(topic[0], v); - if(kvNode!=null && kvNode.topic.equals(topic[0])){ - return true; - } - } - else{ - Node kvNode = buildOne(topic[0], null); - for(int i=1;i kvNode = datas.get(ks[0]); - for(int i=1;i getData(K[] ks){ - if(ks.length==1){ - return datas.get(ks[0]).get(); - } - else{ - Node node = datas.get(ks[0]); - if(node!=null){ - List all = new ArrayList<>(); - all.addAll(node.get()); - for(int i=1;i buildOne(K k,V v){ - - Node node = this.datas.computeIfAbsent(k, key -> { - Node kObjectNode = new Node<>(k); - return kObjectNode; - }); - if(v!=null){ - node.put(v); - } - return node; - } - - - - class Node{ - - private final K topic; - - - private volatile ConcurrentHashMap> map =new ConcurrentHashMap<>() ; - - - List vs = new CopyOnWriteArrayList<>(); - - - public K getTopic() {return topic;} - - Node(K topic) { - this.topic = topic; - } - - public boolean delValue(V v){ - return vs.remove(v); - } - - public Node putNextValue(K k,V v){ - Node kvNode = map.computeIfAbsent(k, key -> { - Node node = new Node<>(k); - return node; - }); - if(v!=null){ - kvNode.put(v); - } - return kvNode; - } - - - public Node getNext(K k){ - return map.get(k); - } - - - public boolean put(V v){ - return vs.add(v); - } - - - public List get(){ - return vs; - } - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/coder/ByteBufToWebSocketFrameEncoder.java b/src/main/java/com/myself/nettychat/bootstrap/coder/ByteBufToWebSocketFrameEncoder.java deleted file mode 100644 index 6c34606..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/coder/ByteBufToWebSocketFrameEncoder.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.myself.nettychat.bootstrap.coder; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageEncoder; -import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; - -import java.util.List; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 转换 - **/ -public class ByteBufToWebSocketFrameEncoder extends MessageToMessageEncoder { - - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { - if (byteBuf == null) { - return; - } - BinaryWebSocketFrame result = new BinaryWebSocketFrame(); - result.content().writeBytes(byteBuf); - out.add(result); - } -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/coder/WebSocketFrameToByteBufDecoder.java b/src/main/java/com/myself/nettychat/bootstrap/coder/WebSocketFrameToByteBufDecoder.java deleted file mode 100644 index 032d657..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/coder/WebSocketFrameToByteBufDecoder.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.myself.nettychat.bootstrap.coder; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageDecoder; -import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; - -import java.util.List; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 转换 - **/ -public class WebSocketFrameToByteBufDecoder extends MessageToMessageDecoder { - - - @Override - protected void decode(ChannelHandlerContext ctx, BinaryWebSocketFrame wsFrame, List out) throws Exception { - ByteBuf buf = wsFrame.content(); - //避免计数器为0,报错 - buf.retain(); - out.add(buf); - } -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/handler/DefaultMqttHandler.java b/src/main/java/com/myself/nettychat/bootstrap/handler/DefaultMqttHandler.java deleted file mode 100644 index 8c4fbee..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/handler/DefaultMqttHandler.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.myself.nettychat.bootstrap.handler; - -import com.myself.nettychat.bootstrap.ChannelService; -import com.myself.nettychat.bootstrap.bean.MqttChannel; -import com.myself.nettychat.common.mqtts.MqttHandlerIntf; -import com.myself.nettychat.common.mqtts.MqttHander; -import com.myself.nettychat.common.mqtts.ServerMqttHandlerService; -import com.myself.nettychat.common.exception.NoFindHandlerException; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.mqtt.*; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 默认MQTTHandler处理 - **/ -@Slf4j -@Component -@ChannelHandler.Sharable -public class DefaultMqttHandler extends MqttHander { - - private final MqttHandlerIntf mqttHandlerApi; - - @Autowired - ChannelService channelService; - - - public DefaultMqttHandler(MqttHandlerIntf mqttHandlerApi) { - super(mqttHandlerApi); - this.mqttHandlerApi = mqttHandlerApi; - } - - @Override - public void doMessage(ChannelHandlerContext channelHandlerContext, MqttMessage mqttMessage) { - Channel channel = channelHandlerContext.channel(); - ServerMqttHandlerService serverMqttHandlerService; - if(mqttHandlerApi instanceof ServerMqttHandlerService){ - serverMqttHandlerService =(ServerMqttHandlerService)mqttHandlerApi; - } - else{ - throw new NoFindHandlerException("server handler 不匹配"); - } - MqttFixedHeader mqttFixedHeader = mqttMessage.fixedHeader(); - if(mqttFixedHeader.messageType().equals(MqttMessageType.CONNECT)){ - if(!serverMqttHandlerService.login(channel, (MqttConnectMessage) mqttMessage)){ - channel.close(); - } - return ; - } - MqttChannel mqttChannel = channelService.getMqttChannel(channelService.getDeviceId(channel)); - if(mqttChannel!=null && mqttChannel.isLogin()){ - switch (mqttFixedHeader.messageType()){ - case PUBLISH: - serverMqttHandlerService.publish(channel, (MqttPublishMessage) mqttMessage); - break; - case SUBSCRIBE: - serverMqttHandlerService.subscribe(channel, (MqttSubscribeMessage) mqttMessage); - break; - case PINGREQ: - serverMqttHandlerService.pong(channel); - break; - case DISCONNECT: - serverMqttHandlerService.disconnect(channel); - break; - case UNSUBSCRIBE: - serverMqttHandlerService.unsubscribe(channel,(MqttUnsubscribeMessage)mqttMessage); - break; - case PUBACK: - mqttHandlerApi.puback(channel,mqttMessage); - break; - case PUBREC: - mqttHandlerApi.pubrec(channel,mqttMessage); - break; - case PUBREL: - mqttHandlerApi.pubrel(channel,mqttMessage); - break; - case PUBCOMP: - mqttHandlerApi.pubcomp(channel,mqttMessage); - break; - default: - break; - } - } - } - - - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - log.info("【DefaultMqttHandler:channelActive】"+ctx.channel().remoteAddress().toString()+"链接成功"); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - log.error("exception",cause); - mqttHandlerApi.close(ctx.channel()); - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/scan/SacnScheduled.java b/src/main/java/com/myself/nettychat/bootstrap/scan/SacnScheduled.java deleted file mode 100644 index 15cc0ce..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/scan/SacnScheduled.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.myself.nettychat.bootstrap.scan; - -import com.myself.nettychat.bootstrap.bean.SendMqttMessage; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.handler.codec.mqtt.*; -import lombok.extern.slf4j.Slf4j; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 扫描消息确认 - **/ -@Slf4j -public class SacnScheduled extends ScanRunnable { - - private final long time; - - public SacnScheduled(long time) { - this.time = time; - } - - private boolean checkTime(long time) { - return System.currentTimeMillis()-time>=10*1000; - } - - @Override - public void doInfo(SendMqttMessage poll) { - if(checkTime(poll.getTime()) && poll.getChannel().isActive()){ - poll.setTime(System.currentTimeMillis()); - switch (poll.getConfirmStatus()){ - case PUB: - pubMessage(poll.getChannel(),poll); - break; - case PUBREL: - sendAck(MqttMessageType.PUBREL,poll); - break; - case PUBREC: - sendAck(MqttMessageType.PUBREC,poll); - break; - } - } - } - - private void pubMessage(Channel channel, SendMqttMessage mqttMessage){ - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH,true, mqttMessage.getQos(),mqttMessage.isRetain(),0); - MqttPublishVariableHeader mqttPublishVariableHeader = new MqttPublishVariableHeader(mqttMessage.getTopic(),mqttMessage.getMessageId()); - MqttPublishMessage mqttPublishMessage = new MqttPublishMessage(mqttFixedHeader,mqttPublishVariableHeader, Unpooled.wrappedBuffer(mqttMessage.getByteBuf())); - channel.writeAndFlush(mqttPublishMessage); - } - - protected void sendAck(MqttMessageType type,SendMqttMessage mqttMessage){ - MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(type,true, MqttQoS.AT_LEAST_ONCE,false,0x02); - MqttMessageIdVariableHeader from = MqttMessageIdVariableHeader.from(mqttMessage.getMessageId()); - MqttPubAckMessage mqttPubAckMessage = new MqttPubAckMessage(mqttFixedHeader,from); - mqttMessage.getChannel().writeAndFlush(mqttPubAckMessage); - } - -} diff --git a/src/main/java/com/myself/nettychat/bootstrap/scan/ScanRunnable.java b/src/main/java/com/myself/nettychat/bootstrap/scan/ScanRunnable.java deleted file mode 100644 index 4d4cc8c..0000000 --- a/src/main/java/com/myself/nettychat/bootstrap/scan/ScanRunnable.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.myself.nettychat.bootstrap.scan; - -import com.myself.nettychat.bootstrap.bean.SendMqttMessage; -import lombok.extern.slf4j.Slf4j; -import com.myself.nettychat.common.enums.ConfirmStatus; -import java.util.List; -import java.util.concurrent.LinkedBlockingQueue; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 扫描未确认的消息 - **/ -@Slf4j -public abstract class ScanRunnable implements Runnable{ - - LinkedBlockingQueue queue =new LinkedBlockingQueue(); - - public boolean addQueue(SendMqttMessage t){ - return queue.add(t); - } - - public boolean addQueues(List ts){ - return queue.addAll(ts); - } - - - @Override - public void run() { - for(;;){ - try { - SendMqttMessage poll= queue.take(); - if(poll.getConfirmStatus()!= ConfirmStatus.COMPLETE){ - doInfo(poll); - queue.offer(poll); - } - } catch (InterruptedException e) { - log.error("scan InterruptedException",e); - } - } - } - public abstract void doInfo( SendMqttMessage poll); - -} diff --git a/src/main/java/com/myself/nettychat/common/properties/InitNetty.java b/src/main/java/com/myself/nettychat/common/properties/InitNetty.java index ab6ad0c..8a7af93 100644 --- a/src/main/java/com/myself/nettychat/common/properties/InitNetty.java +++ b/src/main/java/com/myself/nettychat/common/properties/InitNetty.java @@ -13,6 +13,7 @@ * @Date:Created in 10:54 2018\8\14 0014 */ @Data +@Component @ConfigurationProperties(prefix = "netty") public class InitNetty { diff --git a/src/main/java/com/myself/nettychat/common/utils/ResultVOUtil.java b/src/main/java/com/myself/nettychat/common/utils/ResultVOUtil.java index b2b678d..52fdf21 100644 --- a/src/main/java/com/myself/nettychat/common/utils/ResultVOUtil.java +++ b/src/main/java/com/myself/nettychat/common/utils/ResultVOUtil.java @@ -1,30 +1,30 @@ -package com.myself.nettychat.common.utils; - -import com.myself.nettychat.vo.ResultVo; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 20:59 2018\10\7 0007 - */ -public class ResultVOUtil { - public static ResultVo success(Object object){ - ResultVo resultVO = new ResultVo(); - resultVO.setData(object); - resultVO.setCode(200); - resultVO.setMsg("成功"); - return resultVO; - } - - public static ResultVo success(){ - return success(null); - } - - public static ResultVo error(Integer code, String msg){ - ResultVo resultVO = new ResultVo(); - resultVO.setCode(code); - resultVO.setMsg(msg); - return resultVO; - } -} +package com.myself.nettychat.common.utils; + +import com.myself.nettychat.vo.ResultVo; + +/** + * @Author:UncleCatMySelf + * @Email:zhupeijie_java@126.com + * @QQ:1341933031 + * @Date:Created in 20:59 2018\10\7 0007 + */ +public class ResultVOUtil { + public static ResultVo success(Object object){ + ResultVo resultVO = new ResultVo(); + resultVO.setData(object); + resultVO.setCode(200); + resultVO.setMsg("成功"); + return resultVO; + } + + public static ResultVo success(){ + return success(null); + } + + public static ResultVo error(Integer code, String msg){ + ResultVo resultVO = new ResultVo(); + resultVO.setCode(code); + resultVO.setMsg(msg); + return resultVO; + } +} diff --git a/src/main/java/com/myself/nettychat/config/NettyConfig.java b/src/main/java/com/myself/nettychat/config/NettyConfig.java deleted file mode 100644 index 2648087..0000000 --- a/src/main/java/com/myself/nettychat/config/NettyConfig.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.myself.nettychat.config; - -import com.myself.nettychat.common.properties.InitNetty; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.ChannelOption; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.logging.LogLevel; -import io.netty.handler.logging.LoggingHandler; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Component; - -import java.net.InetSocketAddress; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 11:00 2018\8\14 0014 - */ -@Component -public class NettyConfig { - - @Autowired - private InitNetty nettyAccountConfig; - - @Bean(name = "bossGroup", destroyMethod = "shutdownGracefully") - public NioEventLoopGroup bossGroup(){ - return new NioEventLoopGroup(nettyAccountConfig.getBossThread()); - } - - @Bean(name = "workerGroup", destroyMethod = "shutdownGracefully") - public NioEventLoopGroup workerGroup(){ - return new NioEventLoopGroup(nettyAccountConfig.getWorkerThread()); - } - - @Bean(name = "webSocketAddress") - public InetSocketAddress tcpPost(){ - return new InetSocketAddress(nettyAccountConfig.getWebport()); - } - - @Bean(name = "tcpChannelOptions") - public Map, Object> tcpChannelOptions(){ - Map, Object> options = new HashMap, Object>(); - options.put(ChannelOption.TCP_NODELAY,nettyAccountConfig.isNodelay()); - options.put(ChannelOption.SO_KEEPALIVE, nettyAccountConfig.isKeepalive()); - options.put(ChannelOption.SO_BACKLOG, nettyAccountConfig.getBacklog()); - options.put(ChannelOption.SO_REUSEADDR,nettyAccountConfig.isReuseaddr()); - return options; - } - - @Autowired - @Qualifier("somethingChannelInitializer") - private NettyWebSocketChannelInitializer nettyWebSocketChannelInitializer; - - @Bean(name = "serverBootstrap") - public ServerBootstrap bootstrap(){ - ServerBootstrap b = new ServerBootstrap(); - b.group(bossGroup(), workerGroup()) - .channel(NioServerSocketChannel.class) - .handler(new LoggingHandler(LogLevel.DEBUG)) - .childHandler(nettyWebSocketChannelInitializer); - Map, Object> tcpChannelOptions = tcpChannelOptions(); - Set> keySet = tcpChannelOptions.keySet(); - for (@SuppressWarnings("rawtypes") ChannelOption option : keySet) { - b.option(option, tcpChannelOptions.get(option)); - } - return b; - } -} diff --git a/src/main/java/com/myself/nettychat/config/NettyWebSocketChannelInitializer.java b/src/main/java/com/myself/nettychat/config/NettyWebSocketChannelInitializer.java deleted file mode 100644 index a2f2eb4..0000000 --- a/src/main/java/com/myself/nettychat/config/NettyWebSocketChannelInitializer.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.myself.nettychat.config; - -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.socket.SocketChannel; -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.stream.ChunkedWriteHandler; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 11:00 2018\8\14 0014 - */ -@Component -@Qualifier("somethingChannelInitializer") -public class NettyWebSocketChannelInitializer extends ChannelInitializer { - - @Autowired - private TextWebSocketFrameHandler textWebSocketFrameHandler; - - @Override - public void initChannel(SocketChannel ch) throws Exception { - ChannelPipeline pipeline = ch.pipeline(); - - pipeline.addLast(new HttpServerCodec()); - pipeline.addLast(new HttpObjectAggregator(65536)); - pipeline.addLast(new ChunkedWriteHandler()); - pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); - pipeline.addLast(textWebSocketFrameHandler); //这里不能使用new,不然在handler中不能注入依赖 - - } - -} diff --git a/src/main/java/com/myself/nettychat/config/TCPServer.java b/src/main/java/com/myself/nettychat/config/TCPServer.java index ae478f4..973af54 100644 --- a/src/main/java/com/myself/nettychat/config/TCPServer.java +++ b/src/main/java/com/myself/nettychat/config/TCPServer.java @@ -20,29 +20,20 @@ @Component public class TCPServer { - @Autowired - @Qualifier("serverBootstrap") private ServerBootstrap serverBootstrap; @Autowired @Qualifier("tcpServerBootstrap") private ServerBootstrap tcpServerBootstrap; - @Autowired - @Qualifier("webSocketAddress") - private InetSocketAddress webPort; @Autowired @Qualifier("tcpSocketAddress") private InetSocketAddress tcpTcpPort; - private Channel serverChannel; private Channel tcpServerChannel; - public void startWeb() throws Exception { - serverChannel = serverBootstrap.bind(webPort).sync().channel().closeFuture().sync().channel(); - } public void startTcp() throws Exception { tcpServerChannel = tcpServerBootstrap.bind(tcpTcpPort).sync().channel().closeFuture().sync().channel(); @@ -50,8 +41,6 @@ public void startTcp() throws Exception { @PreDestroy public void stop() throws Exception { - serverChannel.close(); - serverChannel.parent().close(); tcpServerChannel.close(); tcpServerChannel.parent().close(); } diff --git a/src/main/java/com/myself/nettychat/config/TextWebSocketFrameHandler.java b/src/main/java/com/myself/nettychat/config/TextWebSocketFrameHandler.java deleted file mode 100644 index a47c5aa..0000000 --- a/src/main/java/com/myself/nettychat/config/TextWebSocketFrameHandler.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.myself.nettychat.config; - -import com.myself.nettychat.task.MsgAsyncTesk; -import com.myself.nettychat.constont.LikeRedisTemplate; -import com.myself.nettychat.constont.LikeSomeCacheTemplate; -import com.myself.nettychat.common.utils.StringUtil; -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.channel.group.ChannelGroup; -import io.netty.channel.group.DefaultChannelGroup; -import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; -import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import io.netty.handler.codec.http.websocketx.WebSocketFrame; -import io.netty.util.concurrent.GlobalEventExecutor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import java.io.FileOutputStream; - - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 11:01 2018\8\14 0014 - */ -@Component -@Qualifier("textWebSocketFrameHandler") -@ChannelHandler.Sharable -public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler{ - - public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); - - @Autowired - private LikeRedisTemplate redisTemplate; - @Autowired - private LikeSomeCacheTemplate cacheTemplate; - @Autowired - private MsgAsyncTesk msgAsyncTesk; - - @Override - protected void channelRead0(ChannelHandlerContext ctx, - Object msg) throws Exception { - if(msg instanceof TextWebSocketFrame){ - textWebSocketFrame(ctx, (TextWebSocketFrame) msg); - }else if(msg instanceof WebSocketFrame){ //websocket帧类型 已连接 - handleWebSocketFrame(ctx, (WebSocketFrame) msg); - } - } - - private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { - if(frame instanceof BinaryWebSocketFrame){ - //返回客户端 - BinaryWebSocketFrame imgBack= (BinaryWebSocketFrame) frame.copy(); - for (Channel channel : channels){ - channel.writeAndFlush(imgBack.retain()); - } - //保存服务器 - BinaryWebSocketFrame img= (BinaryWebSocketFrame) frame; - ByteBuf byteBuf=img.content(); - try { - FileOutputStream outputStream=new FileOutputStream("D:\\a.jpg"); - byteBuf.readBytes(outputStream,byteBuf.capacity()); - byteBuf.clear(); - }catch (Exception e){ - e.printStackTrace(); - } - } - } - - private void textWebSocketFrame(ChannelHandlerContext ctx, TextWebSocketFrame msg) { - Channel incoming = ctx.channel(); - String rName = StringUtil.getName(msg.text()); - String rMsg = StringUtil.getMsg(msg.text()); - if (rMsg.equals("")){ - return; - } - //用户登录判断 - if (redisTemplate.check(incoming.id(),rName)){ - //临时存储聊天数据 - cacheTemplate.save(rName,rMsg); - //存储随机链接ID与对应登录用户名 - redisTemplate.save(incoming.id(),rName); - //存储登录用户名与链接实例,方便API调用链接实例 - redisTemplate.saveChannel(rName,incoming); - }else{ - incoming.writeAndFlush(new TextWebSocketFrame("存在二次登陆,系统已为你自动断开本次链接")); - channels.remove(ctx.channel()); - ctx.close(); - return; - } - for (Channel channel : channels) { - //将当前每个聊天内容进行存储 - if (channel != incoming){ - channel.writeAndFlush(new TextWebSocketFrame( "[" + rName + "]" + rMsg)); - } else { - channel.writeAndFlush(new TextWebSocketFrame(rMsg + "[" + rName + "]" )); - } - } - } - - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception { - System.out.println(ctx.channel().remoteAddress()); - channels.add(ctx.channel()); - } - - @Override - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { - //删除存储池对应实例 - String name = (String) redisTemplate.getName(ctx.channel().id()); - redisTemplate.deleteChannel(name); - //删除默认存储对应关系 - redisTemplate.delete(ctx.channel().id()); - channels.remove(ctx.channel()); - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - //在线 - } - - - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - //掉线 - msgAsyncTesk.saveChatMsgTask(); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) - throws Exception { - //异常 - cause.printStackTrace(); - ctx.close(); - } -} diff --git a/src/main/java/com/myself/nettychat/constont/CookieConstant.java b/src/main/java/com/myself/nettychat/constont/CookieConstant.java deleted file mode 100644 index 14cb499..0000000 --- a/src/main/java/com/myself/nettychat/constont/CookieConstant.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.myself.nettychat.constont; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 18:09 2018\8\13 0013 - */ -public interface CookieConstant { - - String TOKEN = "token"; - - Integer EXPIRE = 3600;//半小时 - -} diff --git a/src/main/java/com/myself/nettychat/constont/H5Constant.java b/src/main/java/com/myself/nettychat/constont/H5Constant.java deleted file mode 100644 index d5fc6da..0000000 --- a/src/main/java/com/myself/nettychat/constont/H5Constant.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.myself.nettychat.constont; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 15:58 2018\8\13 0013 - */ -public interface H5Constant { - //登录页面 - String LOGIN = "login/login"; - //登录页面 - String LOGIN_SUI = "login/loginSui"; - - //首页2 - String HOME = "home/home"; - //首页 - String INDEX = "index"; - //聊天界面 - //String CHAT = "h5"; - - String ME = "me/me"; - - String FIND = "find/find"; - - String CHAT = "chat/chat"; - - String ALLCHAT = "chat/allchat"; -} diff --git a/src/main/java/com/myself/nettychat/constont/LikeSomeCacheTemplate.java b/src/main/java/com/myself/nettychat/constont/LikeSomeCacheTemplate.java deleted file mode 100644 index e1ca623..0000000 --- a/src/main/java/com/myself/nettychat/constont/LikeSomeCacheTemplate.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.myself.nettychat.constont; - -import com.myself.nettychat.dataobject.UserMsg; -import org.omg.CORBA.OBJ_ADAPTER; -import org.springframework.stereotype.Component; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 聊天内容临时存储 - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 13:38 2018\8\14 0014 - */ -@Component -public class LikeSomeCacheTemplate { - - private List SomeCache = new LinkedList<>(); - - public void save(Object user,Object msg){ - UserMsg userMsg = new UserMsg(); - userMsg.setName(String.valueOf(user)); - userMsg.setMsg(String.valueOf(msg)); - SomeCache.add(userMsg); - } - - public List cloneCacheMap(){ - return SomeCache; - } - - public void clearCacheMap(){ - SomeCache.clear(); - } -} diff --git a/src/main/java/com/myself/nettychat/controller/NCBackController.java b/src/main/java/com/myself/nettychat/controller/NCBackController.java index 579cd9e..f2c05ef 100644 --- a/src/main/java/com/myself/nettychat/controller/NCBackController.java +++ b/src/main/java/com/myself/nettychat/controller/NCBackController.java @@ -18,41 +18,6 @@ @RequestMapping("/back") public class NCBackController { - @Autowired - private LikeRedisTemplate redisTemplate; - /** - * 获取在线用户数 - * @return {@link ResultVo} - */ - @GetMapping("/size") - public ResultVo getSize(){ - return ResultVOUtil.success(redisTemplate.getSize()); - } - - /** - * 获取在线用户列表 - * @return {@link ResultVo} - */ - @GetMapping("/online") - public ResultVo getOnline(){ - return ResultVOUtil.success(redisTemplate.getOnline()); - } - - /** - * API调用向在线用户发送消息 - * @param name 用户名 - * @param msg 消息 - * @return {@link ResultVo} - */ - @PostMapping("/send") - public ResultVo send(@RequestParam String name,@RequestParam String msg){ - Channel channel = (Channel) redisTemplate.getChannel(name); - if (channel == null){ - return ResultVOUtil.error(555,"当前用户连接已断开"); - } - String result = SendUtil.sendTest(msg,channel); - return ResultVOUtil.success(result); - } } diff --git a/src/main/java/com/myself/nettychat/controller/NcChangeController.java b/src/main/java/com/myself/nettychat/controller/NcChangeController.java deleted file mode 100644 index feb492b..0000000 --- a/src/main/java/com/myself/nettychat/controller/NcChangeController.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.myself.nettychat.controller; - -import com.myself.nettychat.constont.CookieConstant; -import com.myself.nettychat.constont.H5Constant; -import com.myself.nettychat.dataobject.User; -import com.myself.nettychat.service.UserService; -import com.myself.nettychat.store.TokenStore; -import com.myself.nettychat.common.utils.CookieUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import java.util.Map; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 15:59 2018\9\5 0005 - */ -@Controller -@RequestMapping("/su") -public class NcChangeController { - - @Autowired - private UserService userService; - - /** - * 我的中心界面 - * @param map - * @return - */ - @GetMapping("/me") - public ModelAndView Me(Map map){ - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - HttpServletRequest request = attributes.getRequest(); - Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN); - if (cookie == null){ - map.put("msg","cookie中不存在token"); - return new ModelAndView(H5Constant.LOGIN_SUI,map); - } - Integer userId = (Integer) TokenStore.get(cookie.getValue()); - if (userId == null){ - map.put("msg","用户信息不存在"); - return new ModelAndView(H5Constant.LOGIN_SUI,map); - } - User user = userService.findOne(userId); - map.put("userName",user.getUserName()); - return new ModelAndView(H5Constant.ME,map); - } - - /** - * 发现 - * @param map - * @return - */ - @GetMapping("/find") - public ModelAndView find(Map map){ - return new ModelAndView(H5Constant.FIND); - } - - /** - * 聊天 - * @param map - * @return - */ - @GetMapping("/chat") - public ModelAndView chat(Map map){ - return new ModelAndView(H5Constant.CHAT); - } - - /** - * 主页 - * @param map - * @return - */ - @GetMapping("/home") - public ModelAndView home(Map map){ - return new ModelAndView(H5Constant.HOME); - } - -} diff --git a/src/main/java/com/myself/nettychat/controller/NcChatController.java b/src/main/java/com/myself/nettychat/controller/NcChatController.java deleted file mode 100644 index 3a7d79b..0000000 --- a/src/main/java/com/myself/nettychat/controller/NcChatController.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.myself.nettychat.controller; - -import com.myself.nettychat.constont.CookieConstant; -import com.myself.nettychat.constont.H5Constant; -import com.myself.nettychat.dataobject.User; -import com.myself.nettychat.dataobject.UserMsg; -import com.myself.nettychat.repository.UserMsgRepository; -import com.myself.nettychat.service.UserService; -import com.myself.nettychat.store.TokenStore; -import com.myself.nettychat.common.utils.CookieUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 14:32 2018\8\14 0014 - */ -@Controller -@RequestMapping("/chat") -public class NcChatController { - - @Autowired - private UserMsgRepository userMsgRepository; - - @Autowired - private UserService userService; - - @GetMapping("/netty") - public ModelAndView netty(@RequestParam(value = "page",defaultValue = "1") Integer page, - @RequestParam(value = "size",defaultValue = "10") Integer size, - Map map){ - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - HttpServletRequest request = attributes.getRequest(); - Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN); - if (cookie == null){ - map.put("msg","cookie中不存在token"); - return new ModelAndView(H5Constant.LOGIN,map); - } - Integer userId = (Integer) TokenStore.get(cookie.getValue()); - if (userId == null){ - map.put("msg","用户信息不存在"); - return new ModelAndView(H5Constant.LOGIN,map); - } - User user = userService.findOne(userId); - Sort sort = new Sort(Sort.Direction.DESC,"id"); - Pageable pageable = new PageRequest(page-1,size,sort); - Page userMsgPage = userMsgRepository.findAll(pageable); - //日期颠倒 - List userMsgList = new ArrayList<>(); - for (int i = 0,j = userMsgPage.getContent().size()-1; i < userMsgPage.getContent().size();i++,j--){ - userMsgList.add(userMsgPage.getContent().get(j)); - } - map.put("userName",user.getUserName()); - map.put("userMsgList",userMsgList); - return new ModelAndView(H5Constant.ALLCHAT,map); - } - -} diff --git a/src/main/java/com/myself/nettychat/controller/NcLoginController.java b/src/main/java/com/myself/nettychat/controller/NcLoginController.java deleted file mode 100644 index 2db26fb..0000000 --- a/src/main/java/com/myself/nettychat/controller/NcLoginController.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.myself.nettychat.controller; - -import com.myself.nettychat.constont.CookieConstant; -import com.myself.nettychat.constont.H5Constant; -import com.myself.nettychat.dataobject.User; -import com.myself.nettychat.form.LoginForm; -import com.myself.nettychat.repository.UserMsgRepository; -import com.myself.nettychat.service.UserService; -import com.myself.nettychat.store.TokenStore; -import com.myself.nettychat.common.utils.CookieUtil; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletResponse; -import javax.validation.Valid; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 16:01 2018\8\18 0018 - */ -@Controller -@RequestMapping("/admin") -public class NcLoginController { - - @Autowired - private UserService userService; - @Autowired - private UserMsgRepository userMsgRepository; - - /** - * 登录页面 - * @return - */ -// @GetMapping("/login") -// public ModelAndView login(Map map){ -// return new ModelAndView(H5Constant.LOGIN); -// } - - - /** - * 登录页面SUI - * @return - */ - @GetMapping("/loginsui") - public ModelAndView loginSui(Map map){ - return new ModelAndView(H5Constant.LOGIN_SUI); - } - - /** - * 注册页面 - * @return - */ - @GetMapping("/regis") - public ModelAndView register(){ - return new ModelAndView(H5Constant.LOGIN_SUI); - } - - - - /** - * 执行注册 - * @param form - * @param bindingResult - * @param response - * @param map - * @return - */ - @PostMapping("/toRegister") - public ModelAndView toRegister(@Valid LoginForm form, BindingResult bindingResult , HttpServletResponse response, - Map map){ - if (bindingResult.hasErrors()){ - map.put("msg",bindingResult.getFieldError().getDefaultMessage()); - return new ModelAndView(H5Constant.LOGIN_SUI,map); - } - List userList = userService.findAll(); - for (User item:userList){ - if (item.getUserName().equals(form.getFUserName())){ - map.put("msg","用户名已存在,请重新填写唯一用户名"); - return new ModelAndView(H5Constant.LOGIN_SUI,map); - } - } - User user = new User(); - BeanUtils.copyProperties(form,user); - userService.save(user); - map.put("userName",user.getUserName()); - map.put("passWord",user.getPassWord()); - return new ModelAndView(H5Constant.LOGIN_SUI,map); - } - - /** - * 登录判断 - * @return - */ - @PostMapping("/toLogin") - public ModelAndView toLogin(@RequestParam(value = "page",defaultValue = "1") Integer page, - @RequestParam(value = "size",defaultValue = "10") Integer size, - @Valid LoginForm form, BindingResult bindingResult , HttpServletResponse response, - Map map){ - if (bindingResult.hasErrors()){ - map.put("msg",bindingResult.getFieldError().getDefaultMessage()); - return new ModelAndView(H5Constant.LOGIN_SUI,map); - } - try { - User user = userService.findByUserName(form.getFUserName()); - if (user.getPassWord().equals(form.getFPassWord())){ - //登录成功 - String token = UUID.randomUUID().toString(); - //将token信息添加到系统缓存中 - TokenStore.add(token,user.getId()); - //将Token信息添加到Cookie中 - CookieUtil.set(response, CookieConstant.TOKEN,token,CookieConstant.EXPIRE); -// Sort sort = new Sort(Sort.Direction.DESC,"id"); -// Pageable pageable = new PageRequest(page-1,size,sort); -// Page userMsgPage = userMsgRepository.findAll(pageable); -// //日期颠倒 -// List userMsgList = new ArrayList<>(); -// for (int i = 0,j = userMsgPage.getContent().size()-1; i < userMsgPage.getContent().size();i++,j--){ -// userMsgList.add(userMsgPage.getContent().get(j)); -// } -// map.put("userMsgList",userMsgList); -// map.put("userName",user.getUserName()); - return new ModelAndView(H5Constant.HOME); - }else{ - map.put("msg","密码错误"); - return new ModelAndView(H5Constant.LOGIN_SUI,map); - } - }catch (Exception e){ - map.put("msg","用户不存在"); - return new ModelAndView(H5Constant.LOGIN_SUI,map); - } - } - -} diff --git a/src/main/java/com/myself/nettychat/dataobject/User.java b/src/main/java/com/myself/nettychat/dataobject/User.java deleted file mode 100644 index d00bbad..0000000 --- a/src/main/java/com/myself/nettychat/dataobject/User.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.myself.nettychat.dataobject; - -import lombok.Data; -import org.hibernate.annotations.DynamicUpdate; - -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import java.io.Serializable; -import java.util.Date; - -/** - * 主用户信息表 - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 15:44 2018\8\13 0013 - */ -@Data -@Entity -@DynamicUpdate -public class User implements Serializable { - - private static final long serialVersionUID = 8143981246513357880L; - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - private String userName; - - private String passWord; - - private Date createTime; - - private Date updateTime; - -} diff --git a/src/main/java/com/myself/nettychat/dataobject/UserMsg.java b/src/main/java/com/myself/nettychat/dataobject/UserMsg.java deleted file mode 100644 index a651839..0000000 --- a/src/main/java/com/myself/nettychat/dataobject/UserMsg.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.myself.nettychat.dataobject; - -import lombok.Data; -import org.hibernate.annotations.DynamicUpdate; - -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import java.io.Serializable; -import java.util.Date; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 14:03 2018\8\14 0014 - */ -@Data -@Entity -@DynamicUpdate -public class UserMsg implements Serializable { - - private static final long serialVersionUID = 4133316147283239759L; - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - private String name; - - private String msg; - - private Date createTime; - - private Date updateTime; - -} diff --git a/src/main/java/com/myself/nettychat/form/LoginForm.java b/src/main/java/com/myself/nettychat/form/LoginForm.java deleted file mode 100644 index 2792a74..0000000 --- a/src/main/java/com/myself/nettychat/form/LoginForm.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.myself.nettychat.form; - -import lombok.Data; - -import javax.validation.constraints.NotEmpty; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 16:02 2018\8\13 0013 - */ -@Data -public class LoginForm { - @NotEmpty(message = "用户名不能为空") - private String fUserName; - @NotEmpty(message = "密码不能为空") - private String fPassWord; - -} diff --git a/src/main/java/com/myself/nettychat/repository/UserMsgRepository.java b/src/main/java/com/myself/nettychat/repository/UserMsgRepository.java deleted file mode 100644 index 6e98ace..0000000 --- a/src/main/java/com/myself/nettychat/repository/UserMsgRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.myself.nettychat.repository; - -import com.myself.nettychat.dataobject.UserMsg; -import org.springframework.data.jpa.repository.JpaRepository; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 14:06 2018\8\14 0014 - */ -public interface UserMsgRepository extends JpaRepository { -} diff --git a/src/main/java/com/myself/nettychat/repository/UserRepository.java b/src/main/java/com/myself/nettychat/repository/UserRepository.java deleted file mode 100644 index be6c18a..0000000 --- a/src/main/java/com/myself/nettychat/repository/UserRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.myself.nettychat.repository; - -import com.myself.nettychat.dataobject.User; -import org.springframework.data.jpa.repository.JpaRepository; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 15:47 2018\8\13 0013 - */ -public interface UserRepository extends JpaRepository { - - User findByUserName(String userName); - -} diff --git a/src/main/java/com/myself/nettychat/service/UserService.java b/src/main/java/com/myself/nettychat/service/UserService.java deleted file mode 100644 index d106993..0000000 --- a/src/main/java/com/myself/nettychat/service/UserService.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.myself.nettychat.service; - -import com.myself.nettychat.dataobject.User; - -import java.util.List; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 15:52 2018\8\18 0018 - */ -public interface UserService { - - User findOne(Integer id); - - User save(User user); - - User findByUserName(String userName); - - List findAll(); -} diff --git a/src/main/java/com/myself/nettychat/service/impl/UserServiceImpl.java b/src/main/java/com/myself/nettychat/service/impl/UserServiceImpl.java deleted file mode 100644 index 951cc04..0000000 --- a/src/main/java/com/myself/nettychat/service/impl/UserServiceImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.myself.nettychat.service.impl; - -import com.myself.nettychat.dataobject.User; -import com.myself.nettychat.repository.UserRepository; -import com.myself.nettychat.service.UserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.List; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 15:52 2018\8\18 0018 - */ -@Service -public class UserServiceImpl implements UserService { - - @Autowired - private UserRepository repository; - - - @Override - public User findOne(Integer id) { - return repository.getOne(id); - } - - @Override - public User save(User user) { - return repository.save(user); - } - - @Override - public User findByUserName(String userName) { - return repository.findByUserName(userName); - } - - @Override - public List findAll() { - return repository.findAll(); - } -} diff --git a/src/main/java/com/myself/nettychat/store/TokenStore.java b/src/main/java/com/myself/nettychat/store/TokenStore.java deleted file mode 100644 index 224cb97..0000000 --- a/src/main/java/com/myself/nettychat/store/TokenStore.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.myself.nettychat.store; - -import lombok.Data; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 全局Token存储环境实例 - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 18:00 2018\8\13 0013 - */ -@Data -public class TokenStore { - - private static Map TokenStoreMap = new ConcurrentHashMap(); - - /** - * 添加到全局Token缓存中 - * @param token - * @param userId - */ - public static void add(String token,Object userId){ - TokenStoreMap.put(token,userId); - } - - /** - * 从全局Token缓存中移除 - * @param token - */ - public static void remove(String token){ - TokenStoreMap.remove(token); - } - - /** - * 获取Token对象 - * @param token - * @return - */ - public static Object get(String token){ - return TokenStoreMap.get(token); - } -} diff --git a/src/main/java/com/myself/nettychat/task/MsgAsyncTesk.java b/src/main/java/com/myself/nettychat/task/MsgAsyncTesk.java deleted file mode 100644 index 445e50b..0000000 --- a/src/main/java/com/myself/nettychat/task/MsgAsyncTesk.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.myself.nettychat.task; - -import com.myself.nettychat.constont.LikeSomeCacheTemplate; -import com.myself.nettychat.dataobject.User; -import com.myself.nettychat.dataobject.UserMsg; -import com.myself.nettychat.repository.UserMsgRepository; -import com.myself.nettychat.repository.UserRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.annotation.AsyncResult; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.concurrent.Future; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 13:50 2018\8\14 0014 - */ -@Component -public class MsgAsyncTesk { - - @Autowired - private LikeSomeCacheTemplate cacheTemplate; - - @Autowired - private UserMsgRepository userMsgRepository; - - @Autowired - private UserRepository userRepository; - - @Async - public Future saveChatMsgTask() throws Exception{ - - List userMsgList = cacheTemplate.cloneCacheMap(); - for (UserMsg item:userMsgList){ - //保护措施 - User user = userRepository.findByUserName(item.getName()); - if (user != null){ - userMsgRepository.save(item); - } - } - //清空临时缓存 - cacheTemplate.clearCacheMap(); - return new AsyncResult<>(true); - } - -} diff --git a/src/main/java/com/myself/nettychat/task/ScheduledPool.java b/src/main/java/com/myself/nettychat/task/ScheduledPool.java deleted file mode 100644 index ebff4f8..0000000 --- a/src/main/java/com/myself/nettychat/task/ScheduledPool.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.myself.nettychat.task; - -import com.myself.nettychat.common.properties.InitNetty; -import com.myself.nettychat.common.pool.Scheduled; -import org.springframework.stereotype.Service; - -import java.util.concurrent.*; - - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 定时任务 - **/ -@Service -public class ScheduledPool implements Scheduled { - - private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(20); - - private final InitNetty serverBean; - - public ScheduledPool(InitNetty serverBean) { - this.serverBean = serverBean; - } - - @Override - public ScheduledFuture submit(Runnable runnable) { - int initalDelay = serverBean.getInitalDelay(); - int period = serverBean.getPeriod(); - return scheduledExecutorService.scheduleAtFixedRate(runnable, initalDelay, period, TimeUnit.SECONDS); - } - -} diff --git a/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java b/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java index 083983a..79347d5 100644 --- a/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java +++ b/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java @@ -19,7 +19,7 @@ public class TCPTestClient { public static void main(String[] args) throws IOException { //10万测试 - for (int i = 0;i<100000;i++){ + for (int i = 0;i<10;i++){ new Thread(new Runnable() { @Override public void run() { @@ -49,7 +49,7 @@ private static void runtest() throws IOException{ while(flag){ //if (i == 1){ //帧头+ID+数据类型+24把锁状态+crc校验+帧尾 - String str = "test"; + String str = "gzF5690137563CC8syyyyyyyyyyyyyyyyynnnnnnnf92fxr"; //发送数据到服务端 out.println(str); if("bye".equals(str)){ diff --git a/src/main/java/com/myself/nettychat/vo/ResultVo.java b/src/main/java/com/myself/nettychat/vo/ResultVo.java index a83c84e..a331794 100644 --- a/src/main/java/com/myself/nettychat/vo/ResultVo.java +++ b/src/main/java/com/myself/nettychat/vo/ResultVo.java @@ -1,28 +1,24 @@ -package com.myself.nettychat.vo; - -import lombok.Data; - -import java.io.Serializable; - -/** - * API 统一返回对象 - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 20:58 2018\10\7 0007 - */ -@Data -public class ResultVo implements Serializable { - - private static final long serialVersionUID = -1020280450330091843L; - - /** 错误码. */ - private Integer code; - - /** 提示信息. */ - private String msg; - - /** 具体内容. */ - private T data; - -} +package com.myself.nettychat.vo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * Created by MySelf on 2018/11/6. + */ +@Data +public class ResultVo implements Serializable { + + private static final long serialVersionUID = -1020280450330091843L; + + /** 错误码. */ + private Integer code; + + /** 提示信息. */ + private String msg; + + /** 具体内容. */ + private T data; + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 77aaf54..ad5a9c4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,15 +1,3 @@ -spring: - datasource: - driver-class-name: com.mysql.jdbc.Driver - username: root - password: root - url: jdbc:mysql://localhost:3306/nettychat?characterEncoding=utf-8&useSSL=false - jpa: - show-sql: true -# database: oracle -# properties: -# hibernate: -# dialect: org.hibernate.dialect.Oracle12cDialect server: servlet: context-path: /susu @@ -28,9 +16,7 @@ netty: revbuf: 10485760 # Socket参数,TCP数据接收缓冲区大小。 heart: 180 # 读超时时间 ssl: false # 使用ssl加密 - mqttHander: com.myself.nettychat.bootstrap.handler.DefaultMqttHandler # 默认处理 initalDelay: 10 # mqtts qos1 qos2 消息 重发延迟 - protocol: MQTT # MQTT MQTT_WS_MQTT(mqtts.js) MQTT_WS_PAHO(paho.js) period: 10 # mqtts qos1 qos2 消息 重发周期 jksFile: /securesocket.jks # ssl 加密 jks文件地址 jksStorePassword: mu$tch8ng3 # 读取jks密码 diff --git a/src/main/resources/static/css/allchat.css b/src/main/resources/static/css/allchat.css deleted file mode 100644 index b35a89f..0000000 --- a/src/main/resources/static/css/allchat.css +++ /dev/null @@ -1,59 +0,0 @@ -.history{ - width: 90%; -} - -.his{ - margin-right: auto; - border-left: 20px; - padding-left: 20px; -} - -.chat { - width: 100%; - position: absolute; - box-sizing: border-box; - padding: 10px 4px; - padding-top: 20px; - bottom: 0; -} - -.msgCente{ - text-align: center; - margin-bottom: 100px; -} - -.chatimg{ - height: 90px; - width: 90px; -} - -.msgRight { - text-align: right; -} - -.msgLeft { - text-align: left; -} - -.msgLeft, -.msgRight { - margin-bottom: 26px; -} - -.msgLeft>span, -.msgRight>span { - padding: 10px; - font-size: 16px; - line-height: 20px; - border-radius: 10px; -} - -.msgLeft>span { - background-color: #d8f1f9; - box-shadow: inset -2px -3px 8px 1px #75ddff; -} - -.msgRight>span { - background-color: #FFF; - box-shadow: inset -2px -3px 8px 1px #d8d8d8; -} \ No newline at end of file diff --git a/src/main/resources/static/css/chat.css b/src/main/resources/static/css/chat.css deleted file mode 100644 index 3a25d39..0000000 --- a/src/main/resources/static/css/chat.css +++ /dev/null @@ -1,43 +0,0 @@ -form { - width: 406px; - height: 650px; - border: 4px solid #98bcde; - border-radius: 10px; - margin: 0 auto; - background-color: #eceff9; - display: flex; - flex-wrap: wrap; - justify-content: space-around; -} - -h3 { - color: #92acdc; - text-align: center; - font-size: 26px; -} - -textarea { - resize: none; - font-size: 20px; - width: 401px; - height: 511px; -} - -.msg { - width: 324px; - height: 40px; - text-indent: 10px; - font-size: 20px; - outline: none; -} - -.btn { - width: 78px; - height: 46px; - background-color: #d8f1f9; - border-radius: 6px; - border: 1px solid #98bcde; - font-size: 18px; - color: #92acdc; - font-weight: bold; -} \ No newline at end of file diff --git a/src/main/resources/static/css/newChat.css b/src/main/resources/static/css/newChat.css deleted file mode 100644 index 77f1406..0000000 --- a/src/main/resources/static/css/newChat.css +++ /dev/null @@ -1,172 +0,0 @@ -.container { - width: 750px; - border: 4px solid #98bcde; - border-radius: 10px; - margin: 100px auto; - background-color: #fff; - /*display: flex; - justify-content: space-around;*/ - display: table; - clear: both; - overflow: hidden; -} - -.left_content { - width: 160px; - height: 600px; - background-color: #d8f1f9; - float: left; - text-align: center; -} - -.content { - width: 590px; - height: 600px; - background-color: #FFF; - float: right; -} - -.content_top { - width: 100%; - height: 80px; - background-color: #6fdcff; - position: relative; -} - -.content_top .tips { - text-align: center; - font-size: 16px; - line-height: 30px; - color: #000000; -} - -.content_bodyer { - width: 100%; - height: 410px; - background: #f4f4f4; - overflow: hidden; - position: relative; -} - -.chat { - width: 100%; - position: absolute; - box-sizing: border-box; - padding: 10px 4px; - padding-top: 20px; - bottom: 0; -} - -form { - width: 100%; - height: 110px; - position: relative; -} - -textarea { - resize: none; - font-size: 20px; - width: 100%; - height: 511px; -} - -.msg { - width: 486px; - border: none; - height: 74px; - box-sizing: border-box; - padding: 4px 10px; - font-size: 20px; - outline: none; -} - -.btn { - width: 78px; - height: 30px; - background-color: #d8f1f9; - border-radius: 6px; - border: 1px solid #98bcde; - font-size: 16px; - color: #92acdc; - font-weight: bold; - position: absolute; - bottom: 4px; - right: 4px; -} - -.openHistory { - font-size: 16px; - color: #000; - z-index: 10; - position: absolute; - bottom: 5px; - right: 5px; - cursor: pointer; -} - -.history { - width: 250px; - height: 410px; - background-color: rgba(0, 0, 0, 0.5); - color: #FFF; - font-size: 16px; - position: absolute; - top: 0; - right: 0; - display: none; - z-index: 10; -} - -.scrollbar { - width: 12px; - height: 100%; - background: black; - position: absolute; - right: 0; - top: 0; - opacity: 0.5; - overflow: hidden; - display: none; - z-index: 10; -} - -.scrollbar .thumb { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 40px; - background: #6d6a6a; - cursor: pointer; -} - -.msgRight { - text-align: right; -} - -.msgLeft { - text-align: left; -} - -.msgLeft, -.msgRight { - margin-bottom: 26px; -} - -.msgLeft>span, -.msgRight>span { - padding: 10px; - font-size: 16px; - line-height: 20px; - border-radius: 10px; -} - -.msgLeft>span { - background-color: #d8f1f9; - box-shadow: inset -2px -3px 8px 1px #75ddff; -} - -.msgRight>span { - background-color: #FFF; - box-shadow: inset -2px -3px 8px 1px #d8d8d8; -} \ No newline at end of file diff --git a/src/main/resources/static/css/registered.css b/src/main/resources/static/css/registered.css deleted file mode 100644 index 206cf22..0000000 --- a/src/main/resources/static/css/registered.css +++ /dev/null @@ -1,160 +0,0 @@ -* { - margin: 0; - padding: 0; -} - -.bodyer .panel { - width: 362px; - height: 320px; - border-radius: 6px; - margin: 90px auto; - background: #FFF; - position: relative; - overflow: hidden; -} - -.logo { - width: 100px; - height: 100px; - background: url("../image/logoSmall.png") no-repeat; - position: absolute; - top: 29px; - right: -8px; - transform: rotate(30deg); -} - -.panel_tit { - text-align: center; - line-height: 40px; - font-size: 24px; - padding-top: 20px; - color: #92acdc; -} - -.bodyer .panel .register { - padding-left: 45px; - float: left; - color: #999; - width: 268px; - position: relative; -} - -.bodyer .panel .register .register_top { - font-size: 14px; - margin: 30px 0 26px; -} - -.bodyer .panel .register .register_top span { - margin: 0 2px; -} - -.bodyer .panel .register .register_top a { - font-size: 14px; - text-decoration: none; -} - -.bodyer .panel .register .register_top a:first-child { - color: #303030; -} - -.bodyer .panel .register .register_top a:last-child { - color: #999; -} - -.bodyer .panel .register input { - font-size: 14px; - width: 268px; - height: 38px; - display: block; - box-sizing: border-box; - border: 1px solid #dfdfdf; - padding: 8px 0 8px 36px; -} - -.bodyer .panel .register .user_icon { - position: relative; - margin-bottom: 18px; -} - -.bodyer .panel .register .user_icon input:focus { - border: 1px solid #98bcde; - outline: #98bcde solid 1px; -} - -.bodyer .panel .register .user_icon i { - position: absolute; - display: block; - width: 30px; - height: 30px; - background: url("../image/nuandao.png") no-repeat -310px -390px; -} - -.bodyer .panel .register .pass_icon { - position: relative; - margin-bottom: 30px; -} - -.bodyer .panel .register .pass_icon input:focus { - border: 1px solid #98bcde; - outline: #98bcde solid 1px; -} - -.bodyer .panel .register .pass_icon i { - position: absolute; - display: block; - width: 30px; - height: 30px; - background: url("../image/nuandao.png") no-repeat -333px -434px; -} - -.bodyer .panel .register .pass_icon span { - position: absolute; - left: 0; - bottom: -20px; - color: #d9534f; - font-size: 13px; - display: none; -} - -.bodyer .panel .register input.btn1 { - margin: 0; - padding: 0; - display: block; - width: 100%; - height: 40px; - font-size: 16px; - line-height: 40px; - text-align: center; - text-decoration: none; - color: #FFF; - background: #98bcde; - margin-bottom: 20px; -} - -.bodyer .panel .register input.btn1:hover { - background: #5694ce; -} - -.tips { - width: 100px; - height: 30px; - text-align: center; - font-size: 14px; - line-height: 30px; - background-color: rgba(0, 0, 0, 0.5); - color: #FFF; - position: absolute; - top: 35%; - left: 40%; - display: none; -} - -.mask2 { - width: 100%; - height: 100%; - z-index: -100; - position: fixed; - top: 0; - left: 0; - background: rgba(0, 0, 0, 0.5); -} \ No newline at end of file diff --git a/src/main/resources/static/image/logoBig.jpg b/src/main/resources/static/image/logoBig.jpg deleted file mode 100644 index 0c62304cc054799f416de6d121214a05ecd90830..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137374 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

#9~{ty;#sH!*QVc3J0 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 diff --git a/src/main/resources/static/image/nuandao.png b/src/main/resources/static/image/nuandao.png deleted file mode 100644 index be015d1bf319cd13e32c5b9bcf66451d732dd398..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62980 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 diff --git a/src/main/resources/static/js/chat.js b/src/main/resources/static/js/chat.js deleted file mode 100644 index 3380613..0000000 --- a/src/main/resources/static/js/chat.js +++ /dev/null @@ -1,51 +0,0 @@ -var socket; -if(!window.WebSocket) { - window.WebSocket = window.MozWebSocket; -} - -if(window.WebSocket) { - socket = new WebSocket("ws://localhost:8090/ws"); - socket.onmessage = function(event) { - var ta = document.getElementById('responseText'); - ta.value = ta.value + '\n' + event.data - }; - socket.onopen = function(event) { - var ta = document.getElementById('responseText'); - if(msg.length > 0){ - ta.value = "--- 连接开启! ---"+'\n'+msg; - }else{ - ta.value = "--- 连接开启! ---" - } - }; - socket.onclose = function(event) { - var ta = document.getElementById('responseText'); - ta.value = ta.value + "连接被关闭"; - }; -} else { - alert("你的浏览器不支持 WebSocket!"); -} - -function send(message) { - if(!window.WebSocket) { - return; - } - if(socket.readyState == WebSocket.OPEN) { - socket.send(message); - } else { - alert("连接没有开启."); - } -} - -window.onbeforeunload = function(event) { - event.returnValue = "刷新提醒"; -} - -; -document.onkeydown = function(e) { - var userName = document.getElementById('userName'); - if(e.keyCode == 13) { - var message = userName.value + '-' +document.getElementsByClassName('msg')[0].value; - send(message); - document.getElementsByClassName('msg')[0].value = ''; - } -} \ No newline at end of file diff --git a/src/main/resources/static/js/newChat.js b/src/main/resources/static/js/newChat.js deleted file mode 100644 index 1bc7e78..0000000 --- a/src/main/resources/static/js/newChat.js +++ /dev/null @@ -1,165 +0,0 @@ -var socket; -if(!window.WebSocket) { - window.WebSocket = window.MozWebSocket; -} - -var chars = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']; - -function generateMixed(n) { - var res = ""; - for(var i = 0; i < n ; i ++) { - var id = Math.ceil(Math.random()*35); - res += chars[id]; - } - return res; -} - -if(window.WebSocket) { - socket = new WebSocket("ws://localhost:8090/ws"); - socket.onmessage = function(event) { - var msg = event.data; - console.log(msg); - if(msg instanceof Blob){ - console.log("blobs"); - var idran = generateMixed(3); - var ta = "

"; - $('.chat').append(ta); - //var previewImg = document.querySelector('img'); - var previewImg = document.getElementById(idran); - - var reader = new FileReader(); - // 监听reader对象的的onload事件,当图片加载完成时,把base64编码賦值给预览图片 - reader.addEventListener("load", function () { - previewImg.src = reader.result; - }, false); - // 调用reader.readAsDataURL()方法,把图片转成base64 - reader.readAsDataURL(msg); - } - if(msg.substring(0, 1) == '[') { - var ta = "
"+event.data+"
"; - $('.chat').append(ta); - } else { - var ta = "
"+event.data+"
"; - $('.chat').append(ta); - } - }; - socket.onopen = function(event) { - $('.tips').html('连接开启!'); - $('.tips').css('color', 'green'); - - var his = $('#TTHistory'); - - console.log(msg); - his.html(msg); - }; - socket.onclose = function(event) { - $('.tips').html('连接被关闭'); - $('.tips').css('color', 'red'); - }; -} else { - var ta = "
你的浏览器不支持 WebSocket!
"; - $('.content_bodyer').append(ta); -} - -function send(message) { - if(!window.WebSocket) { - return; - } - if(socket.readyState == WebSocket.OPEN) { - socket.send(message); - } else { - alert("连接没有开启."); - } -} - -window.onbeforeunload = function(event) { - event.returnValue = "刷新提醒"; -} - -; -// document.onkeydown = function(e) { -// -// var userName = $('#userName'); -// if(e.keyCode == 13) { -// $('.chat').css('bottom', 0); -// var message = userName.val() + '-' + $('.msg').val().trim(); -// send(message); -// $('.msg').val(''); -// } -// } - -function sendd() { - var userName = $('#userName'); - var message = userName.val() + '-' + $('.msg').val().trim(); - send(message); - sendFile(); - $('.msg').val(''); -} - -$('.openHistory').click(function() { - $('.history').fadeToggle(); -}); - -$('.content_bodyer').mouseenter(function() { - $('.scrollbar').fadeIn(); -}) - -$('.content_bodyer').mouseleave(function() { - $('.scrollbar').fadeOut(); -}) - -function sendFile(){ - var thum = $('#file')[0].files[0]; - if(!thum) return; - console.log(thum); - var reader = new FileReader(); - //以二进制形式读取文件 - reader.readAsArrayBuffer(thum); - //文件读取完毕后该函数响应 - reader.onload = function loaded(evt) { - console.log(evt); - var blob = evt.target.result; - //发送二进制表示的文件 - socket.send(blob); - console.log(blob); - - console.log("finnish"); - } -} - -var thumb = $('.thumb'); -var scrollBar = $('.scrollbar'); -var chat = $('.chat'); - -thumb.on('mousedown', function(e) { - chat.css('bottom', 'initial'); - thumb.isMouseDown = true; - return false; -}); - -thumb.on('selectstart', function() { - return false; -}); - -$(window).on('mouseup', function(e) { - thumb.isMouseDown = false; -}); - -$(window).on('mousemove', function(e) { - if (thumb.isMouseDown){ - var pos = e.clientY - scrollBar.parent().offset().top - thumb.height() / 2; - if (pos < 0 ){ - pos = 0; - } else if(pos > scrollBar.height() - thumb.height()){ - pos = scrollBar.height() - thumb.height(); - } - thumb.css("top", pos + "px"); - - // 计算thumb所在的位置占父亲的% - var percentage = thumb.offset().top / (scrollBar.height() - thumb.height()); - var top = (chat.height() - chat.parent().height()) * percentage; - chat.css("top", -top + "px"); - } -}) - - diff --git a/src/main/resources/static/js/registered.js b/src/main/resources/static/js/registered.js deleted file mode 100644 index ebd3ea0..0000000 --- a/src/main/resources/static/js/registered.js +++ /dev/null @@ -1,72 +0,0 @@ -var register = $('.register_top').children().first(); -var register2 = $('.register_top').children().last(); -register.click(function () { - $(register).css('color', ' #303030'); - register2.css('color', '#999'); - $('.btn1').val('登陆'); - $('form').attr("action", "/susu/admin/toLogin"); -}); -register2.click(function () { - $(register2).css('color', ' #303030'); - register.css('color', '#999'); - $('.btn1').val('注册'); - $('form').attr("action", "/susu/admin/toRegister"); -}); -$('.btn3').click(function () { - $('.more').slideToggle(0); -}); - -/** - * 用户名验证 - */ -var input1 = $('.user_icon').children('input'); -var userName = /^[\u4e00-\u9fa5_a-zA-Z]{2,5}$/; -input1.blur(function () { - var val = input1.val().trim(); - if (val.length == 0) { - $($('.Span').children()[0]).css('display', 'block'); - $($('.Span').children()[0]).siblings().css('display', 'none'); - } else if (userName.test(val)) { - $($('.Span').children('span')).css('display', 'none'); - } else { - $($('.Span').children()[1]).css('display', 'block'); - $($('.Span').children()[1]).siblings().css('display', 'none'); - } -}); - -/** - * 密码验证 - */ -var input2 = $('.pass_icon').children('input'); -// 关于密码的正则表达式(6-16位数字和字母的组合) -var Password = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$/; -input2.blur(function () { - var val = input2.val().trim(); - if (val.length == 0) { - $($('.Span').children()[2]).css('display', 'block'); - $($('.Span').children()[2]).siblings().css('display', 'none'); - } else if (Password.test(val)) { - console.log(1); - $($('.Span').children('span')).css('display', 'none'); - } else { - $($('.Span').children()[3]).css('display', 'block'); - $($('.Span').children()[3]).siblings().css('display', 'none'); - } -}); - -//登录注册校验 -$('form').submit(function(e) { - var User = $('.user_icon').children('input'); - var Pass = $('.pass_icon').children('input'); - var md5_pwd= $('#md5_pwd'); - if (userName.test(User) && Password.test(Pass)) { - return true; - } -}) - -function MsgTo() { - $('.tips').fadeIn(10, function() { - $('.tips').fadeOut(1500); - }); -} - diff --git a/src/main/resources/templates/chat/allchat.ftl b/src/main/resources/templates/chat/allchat.ftl deleted file mode 100644 index 317cd89..0000000 --- a/src/main/resources/templates/chat/allchat.ftl +++ /dev/null @@ -1,49 +0,0 @@ - - -<#include "../common/header.ftl"> - - - -
-
- - - -

全体用户

-
- -
-
- -
-
- -
-
-
-
-
- -
- -
-
- <#--Image preview area...--> -
-
- - -<#include "../common/floor.ftl"> - - - \ No newline at end of file diff --git a/src/main/resources/templates/chat/chat.ftl b/src/main/resources/templates/chat/chat.ftl deleted file mode 100644 index f18b178..0000000 --- a/src/main/resources/templates/chat/chat.ftl +++ /dev/null @@ -1,33 +0,0 @@ - - -<#include "../common/header.ftl"> - - -<#include "../common/floor.ftl"> - - \ No newline at end of file diff --git a/src/main/resources/templates/common/floor.ftl b/src/main/resources/templates/common/floor.ftl deleted file mode 100644 index 9b8124e..0000000 --- a/src/main/resources/templates/common/floor.ftl +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/main/resources/templates/common/header.ftl b/src/main/resources/templates/common/header.ftl deleted file mode 100644 index d519ed4..0000000 --- a/src/main/resources/templates/common/header.ftl +++ /dev/null @@ -1,12 +0,0 @@ - - - - 酥酥 - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/find/find.ftl b/src/main/resources/templates/find/find.ftl deleted file mode 100644 index b0feb38..0000000 --- a/src/main/resources/templates/find/find.ftl +++ /dev/null @@ -1,41 +0,0 @@ - - -<#include "../common/header.ftl"> - -
-
-

酥酥

-
- -
-
-
    -
  • -
    -
    朋友圈
    -
    -
  • -
-
-
-
-<#include "../common/floor.ftl"> - - \ No newline at end of file diff --git a/src/main/resources/templates/h5.ftl b/src/main/resources/templates/h5.ftl deleted file mode 100644 index a650b30..0000000 --- a/src/main/resources/templates/h5.ftl +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - Su Su - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/home/home.ftl b/src/main/resources/templates/home/home.ftl deleted file mode 100644 index f76b3b4..0000000 --- a/src/main/resources/templates/home/home.ftl +++ /dev/null @@ -1,62 +0,0 @@ - - -<#include "../common/header.ftl"> - - -
-
-

酥酥

-
- -
- - - <#--
--> -
-
    -
  • -
    -
    全体用户
    -
    -
  • -
-
- <#-- - <#--
--> - <#--
--> - <#--
--> - <#--
--> -
-
-<#include "../common/floor.ftl"> - - - \ No newline at end of file diff --git a/src/main/resources/templates/login/login.ftl b/src/main/resources/templates/login/login.ftl deleted file mode 100644 index 17e5a2a..0000000 --- a/src/main/resources/templates/login/login.ftl +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - Su Su - - - - - - - -
-
- - - -
-
- -

“酥酥”聊天室

-
-
- 登录 - | - 注册 -
-
-
-
- - -
- 请填写用户名 - 2-5位中文字符和英文字符 - 请填写6-16位数字和字母的组合密码 - 密码格式有误 - <#--${msg!''}--> -
-
- - -
${msg!''}
- -
-
- - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/login/loginSui.ftl b/src/main/resources/templates/login/loginSui.ftl deleted file mode 100644 index 40a962c..0000000 --- a/src/main/resources/templates/login/loginSui.ftl +++ /dev/null @@ -1,124 +0,0 @@ - - -<#include "../common/header.ftl"> - -
-
- -
-

登录

-
- - - - -
-
-
    -
  • -
    -
    -
    -
    账号
    -
    - -
    -
    -
    -
  • -
  • -
    -
    -
    -
    密码
    -
    - -
    -
    -
    -
  • -
-
-
-
- - -
-
-
- -
- -
-
-

注册

-
-
-
- -
-
    - -
  • -
    -
    -
    -
    账号
    -
    - -
    -
    -
    -
  • -
  • -
    -
    -
    -
    密码
    -
    - -
    -
    -
    -
  • -
-
-
-
- - -
-
- -
-
-
- -
-<#include "../common/floor.ftl"> - - - \ No newline at end of file diff --git a/src/main/resources/templates/me/me.ftl b/src/main/resources/templates/me/me.ftl deleted file mode 100644 index 5997f0d..0000000 --- a/src/main/resources/templates/me/me.ftl +++ /dev/null @@ -1,78 +0,0 @@ - - -<#include "../common/header.ftl"> - -
-
-

酥酥

-
- -
-
-
    -
  • -
    -
    ${userName!''}
    -
    -
  • -
-
-
-
-
    -
  • -
    -
    收藏
    -
    -
  • -
-
-
-
    -
  • -
    -
    相册
    -
    -
  • -
-
-
-
    -
  • -
    -
    卡包
    -
    -
  • -
-
-
-
    -
  • -
    -
    表情
    -
    -
  • -
-
-
-
-<#include "../common/floor.ftl"> - - \ No newline at end of file From d5a78c3e580fd7036eb0bf3a945b509f31a53496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=AE=8F=E9=9F=AC?= <3183764662@qq.com> Date: Tue, 6 Nov 2018 14:46:41 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=E5=BA=94api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nettychat/constont/LikeRedisTemplate.java | 106 ------------------ .../controller/NCBackController.java | 37 +++++- 2 files changed, 35 insertions(+), 108 deletions(-) delete mode 100644 src/main/java/com/myself/nettychat/constont/LikeRedisTemplate.java diff --git a/src/main/java/com/myself/nettychat/constont/LikeRedisTemplate.java b/src/main/java/com/myself/nettychat/constont/LikeRedisTemplate.java deleted file mode 100644 index f9d3ad3..0000000 --- a/src/main/java/com/myself/nettychat/constont/LikeRedisTemplate.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.myself.nettychat.constont; - -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @Author:UncleCatMySelf - * @Email:zhupeijie_java@126.com - * @QQ:1341933031 - * @Date:Created in 11:46 2018\8\14 0014 - */ -@Component -public class LikeRedisTemplate { - - private Map RedisMap = new ConcurrentHashMap<>(); - - private Map SecondRedisMap = new ConcurrentHashMap<>(); - - /**存放链接池实例*/ - private Map ChannelRedisMap = new ConcurrentHashMap<>(); - - public void save(Object id,Object name){ - RedisMap.put(id,name); - SecondRedisMap.put(name,id); - } - - /** - * 存储对应的用户名与Netty链接实例 - * @param name 登录用户名 - * @param channel Netty链接实例 - */ - public void saveChannel(Object name,Object channel){ - ChannelRedisMap.put(name,channel); - } - - /** - * 删除存储池实例 - * @param name 登录用户名 - */ - public void deleteChannel(Object name){ - ChannelRedisMap.remove(name); - } - - /** - * 获取存储池中的链接实例 - * @param name 登录用户名 - * @return {@link io.netty.channel.Channel 链接实例} - */ - public Object getChannel(Object name){ - return ChannelRedisMap.get(name); - } - - /** - * 获取储存池链接数 - * @return 在线数 - */ - public Integer getSize(){ - return ChannelRedisMap.size(); - } - - /** - * 获取连接对应用户名称 - * @param id 连接Id - * @return 用户名称 - */ - public Object getName(Object id){ - return RedisMap.get(id); - } - - - public boolean check(Object id,Object name){ - if (SecondRedisMap.get(name) == null){ - return true; - } - if (id.equals(SecondRedisMap.get(name))){ - return true; - }else{ - return false; - } - } - - public void delete(Object id){ - try { - SecondRedisMap.remove(RedisMap.get(id)); - RedisMap.remove(id); - }catch (NullPointerException e){ - e.printStackTrace(); - } - } - - /** - * 返回在线用户列表信息 - * @return 用户名列表 - */ - public Object getOnline() { - List result = new ArrayList<>(); - for (Object key:ChannelRedisMap.keySet()){ - result.add(key); - } - return result; - } -} diff --git a/src/main/java/com/myself/nettychat/controller/NCBackController.java b/src/main/java/com/myself/nettychat/controller/NCBackController.java index f2c05ef..516eaba 100644 --- a/src/main/java/com/myself/nettychat/controller/NCBackController.java +++ b/src/main/java/com/myself/nettychat/controller/NCBackController.java @@ -1,11 +1,10 @@ package com.myself.nettychat.controller; +import com.myself.nettychat.common.utils.Const; import com.myself.nettychat.common.utils.ResultVOUtil; import com.myself.nettychat.common.utils.SendUtil; -import com.myself.nettychat.constont.LikeRedisTemplate; import com.myself.nettychat.vo.ResultVo; import io.netty.channel.Channel; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** @@ -18,6 +17,40 @@ @RequestMapping("/back") public class NCBackController { + /** + * 获取当前连接数 + * @return + */ + @GetMapping(value = "/get_channel_size") + public ResultVo getChannelSize(){ + return ResultVOUtil.success(Const.getSzie()); + } + /** + * 获取连接Id数组 + * @return + */ + @GetMapping(value = "/get_channel_id_list") + public ResultVo getChannelIDList(){ + return ResultVOUtil.success(Const.getIdList()); + } + + /** + * 向存在链接发送指定的端口 + * @param channelId 连接Id + * @param lock 打开第几把锁 + * @return + */ + @PostMapping(value = "/send_to_channel") + public ResultVo SendToChannel(String channelId,Integer lock){ + SendUtil sendUtil = new SendUtil(); + Channel channel = Const.get(channelId); + if (channel != null){ + if (sendUtil.send(lock,channel,channelId,Const.CONTROL_TYPE)){ + return ResultVOUtil.success("【发送成功】"); + } + } + return ResultVOUtil.error(888,"【发送失败】"); + } } From f5675299ff5b97925d71f216596d863b30aa9c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=AE=8F=E9=9F=AC?= <3183764662@qq.com> Date: Tue, 6 Nov 2018 15:10:33 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/myself/nettychat/config/TCPServerHandler.java | 10 +++------- .../com/myself/nettychat/tcptest/TCPTestClient.java | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/myself/nettychat/config/TCPServerHandler.java b/src/main/java/com/myself/nettychat/config/TCPServerHandler.java index f5afdfc..8cc291c 100644 --- a/src/main/java/com/myself/nettychat/config/TCPServerHandler.java +++ b/src/main/java/com/myself/nettychat/config/TCPServerHandler.java @@ -47,10 +47,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception String realChannelID = Const.isChannel(ctx.channel()); System.out.println(realChannelID); Const.ChangeClientId(realChannelID,ChannelID); - } - //检查重复链接ID 不同实例 切换实例 - if(Const.hasChannelID(ChannelID)){ - Const.changeChannel(ChannelID,ctx.channel()); + }else if(Const.hasChannelID(ChannelID) && !ctx.channel().equals(Const.get(ChannelID))){ + ctx.close(); } data = DataResction.ResctionDataNoID(data); String type = DataResction.ResctionType(data); @@ -208,11 +206,9 @@ private void futureByController(ChannelHandlerContext ctx,final String realData, @Override public void run() { System.out.println("-------尝试执行SQL操作--------控制类型"); -// + } },0,TimeUnit.SECONDS); -// ctx.writeAndFlush(CallBackMessage.sendString( -// CRC16MySelf.getAllString(ChannelID,Const.RESULT_TYPE,Const.SUCCESS))); ctx.writeAndFlush(CallBackMessage.Check1_test.duplicate()); } diff --git a/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java b/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java index 79347d5..dbb7f2d 100644 --- a/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java +++ b/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java @@ -19,7 +19,7 @@ public class TCPTestClient { public static void main(String[] args) throws IOException { //10万测试 - for (int i = 0;i<10;i++){ + for (int i = 0;i<2;i++){ new Thread(new Runnable() { @Override public void run() { From 8141b85f6f90cbc0a1202e2755c95802ff9675fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=AE=8F=E9=9F=AC?= <3183764662@qq.com> Date: Tue, 6 Nov 2018 15:30:45 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0d70b8c..144dbb4 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,66 @@ ## 项目介绍 -针对小程序与单片机硬件执行Iot物联网通讯的一套完整Demo。 +针对小程序与单片机硬件执行Iot物联网通讯(TCP/IP)的一套完整Demo。 -## 项目尚未初始化 +## 项目配置 -请勿使用。 +> application.xml + +配置tcp端口:8092 + +> com.myself.nettychat.tcptest.TCPTestClient + +配置连接IP与端口,修改代码中的参数 + +## Demo场景 + +小程序端选购售货机中的商品,点击购买(小程序API向Iot中心发送对应商品的开锁信息),Iot中心中转开锁信息给单片机,单片机接收信息打开对应的锁。 + +通信机制:帧头+ID+数据类型+24把锁状态+crc校验+帧尾(可以按照需求进行定制) + +> com.myself.nettychat.config.TCPServerHandler (通信接收的处理类) + + +## API(小程序调用接口) + +> http://localhost:8080/susu/back/get_channel_size GET + +请求Iot中心,获取当前连接存活状态下的链接实例 + +``` +{ + "code": 200, + "msg": "成功", + "data": 1 +} +``` + +> http://localhost:8080/susu/back/get_channel_id_list GET + +请求Iot中心,当前存活状态下的链接Id列表 + +``` +{ + "code": 200, + "msg": "成功", + "data": [ + "F5690137563CC8" + ] +} +``` + +> http://localhost:8080/susu/back/send_to_channel POST + +参数 +* channelId //第二个API获取到的链接Id +* lock //将要打开的第几把锁 1-24(看单片机接入的锁的数量) + +``` +{ + "code": 200, + "msg": "成功", + "data": "【发送成功】" +} +``` From 584f8e38edf6112f56d0715e8b1274710812bd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=AE=8F=E9=9F=AC?= <3183764662@qq.com> Date: Tue, 6 Nov 2018 15:35:13 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 144dbb4..40cba08 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,14 @@ 配置连接IP与端口,修改代码中的参数 +## 启动流程 + +1、启动项目,tcp监听成功 + +2、运行com.myself.nettychat.tcptest.TCPTestClient (记得先改ip或端口,如果你有修改的话) + +3、运行PostMan,请求下方的API 进行通信测试 + ## Demo场景 小程序端选购售货机中的商品,点击购买(小程序API向Iot中心发送对应商品的开锁信息),Iot中心中转开锁信息给单片机,单片机接收信息打开对应的锁。 From 463593d19d1e8fdb85c52979e8ec8ad83cee4402 Mon Sep 17 00:00:00 2001 From: hacker <3183764662@qq.com> Date: Thu, 8 Nov 2018 22:57:45 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E8=A6=81=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- h5/chat.html | 107 ++++ .../nettychat/NettychatApplication.java | 15 +- .../nettychat/common/enums/ConfirmStatus.java | 13 - .../nettychat/common/enums/ProtocolEnum.java | 14 - .../nettychat/common/enums/QosStatus.java | 14 - .../nettychat/common/enums/SessionStatus.java | 13 - .../nettychat/common/enums/SubStatus.java | 11 - .../common/exception/ConnectionException.java | 14 - .../exception/NoFindHandlerException.java | 14 - .../myself/nettychat/common/ip/IpUtils.java | 68 --- .../mqtts/ClientMqttHandlerService.java | 27 -- .../nettychat/common/mqtts/MqttHander.java | 50 -- .../common/mqtts/MqttHandlerIntf.java | 26 - .../mqtts/ServerMqttHandlerService.java | 28 -- .../common/pool/DefaultThreadFactory.java | 43 -- .../nettychat/common/pool/ExecutorQueue.java | 57 --- .../nettychat/common/pool/Scheduled.java | 15 - .../common/pool/StandardThreadExecutor.java | 100 ---- .../common/properties/InitNetty.java | 6 - .../common/ssl/SecureSocketKeyStore.java | 456 ------------------ .../ssl/SecureSocketSslContextFactory.java | 89 ---- .../ssl/SecureSokcetTrustManagerFactory.java | 63 --- .../nettychat/common/ssl/StreamReader.java | 25 - .../nettychat/common/ssl/X509CertTool.java | 69 --- .../nettychat/common/utils/SendUtil.java | 42 ++ .../common/zookeeper/ZkStateListener.java | 22 - .../nettychat/common/zookeeper/ZkUtils.java | 366 -------------- .../nettychat/common/zookeeper/testZk.java | 256 ---------- .../myself/nettychat/config/NettyConfig.java | 75 +++ .../NettyWebSocketChannelInitializer.java | 40 ++ .../myself/nettychat/config/TCPServer.java | 11 + .../nettychat/config/TCPServerHandler.java | 220 ++------- .../config/TextWebSocketFrameHandler.java | 80 +++ .../controller/NCBackController.java | 79 ++- .../com/myself/nettychat/dto/CacheDTO.java | 22 + .../nettychat/tcptest/TCPTestClient.java | 30 +- .../nettychat/template/CacheTemplate.java | 55 +++ 37 files changed, 545 insertions(+), 2090 deletions(-) create mode 100644 h5/chat.html delete mode 100644 src/main/java/com/myself/nettychat/common/enums/ConfirmStatus.java delete mode 100644 src/main/java/com/myself/nettychat/common/enums/ProtocolEnum.java delete mode 100644 src/main/java/com/myself/nettychat/common/enums/QosStatus.java delete mode 100644 src/main/java/com/myself/nettychat/common/enums/SessionStatus.java delete mode 100644 src/main/java/com/myself/nettychat/common/enums/SubStatus.java delete mode 100644 src/main/java/com/myself/nettychat/common/exception/ConnectionException.java delete mode 100644 src/main/java/com/myself/nettychat/common/exception/NoFindHandlerException.java delete mode 100644 src/main/java/com/myself/nettychat/common/ip/IpUtils.java delete mode 100644 src/main/java/com/myself/nettychat/common/mqtts/ClientMqttHandlerService.java delete mode 100644 src/main/java/com/myself/nettychat/common/mqtts/MqttHander.java delete mode 100644 src/main/java/com/myself/nettychat/common/mqtts/MqttHandlerIntf.java delete mode 100644 src/main/java/com/myself/nettychat/common/mqtts/ServerMqttHandlerService.java delete mode 100644 src/main/java/com/myself/nettychat/common/pool/DefaultThreadFactory.java delete mode 100644 src/main/java/com/myself/nettychat/common/pool/ExecutorQueue.java delete mode 100644 src/main/java/com/myself/nettychat/common/pool/Scheduled.java delete mode 100644 src/main/java/com/myself/nettychat/common/pool/StandardThreadExecutor.java delete mode 100644 src/main/java/com/myself/nettychat/common/ssl/SecureSocketKeyStore.java delete mode 100644 src/main/java/com/myself/nettychat/common/ssl/SecureSocketSslContextFactory.java delete mode 100644 src/main/java/com/myself/nettychat/common/ssl/SecureSokcetTrustManagerFactory.java delete mode 100644 src/main/java/com/myself/nettychat/common/ssl/StreamReader.java delete mode 100644 src/main/java/com/myself/nettychat/common/ssl/X509CertTool.java delete mode 100644 src/main/java/com/myself/nettychat/common/zookeeper/ZkStateListener.java delete mode 100644 src/main/java/com/myself/nettychat/common/zookeeper/ZkUtils.java delete mode 100644 src/main/java/com/myself/nettychat/common/zookeeper/testZk.java create mode 100644 src/main/java/com/myself/nettychat/config/NettyConfig.java create mode 100644 src/main/java/com/myself/nettychat/config/NettyWebSocketChannelInitializer.java create mode 100644 src/main/java/com/myself/nettychat/config/TextWebSocketFrameHandler.java create mode 100644 src/main/java/com/myself/nettychat/dto/CacheDTO.java create mode 100644 src/main/java/com/myself/nettychat/template/CacheTemplate.java diff --git a/h5/chat.html b/h5/chat.html new file mode 100644 index 0000000..48aeb1e --- /dev/null +++ b/h5/chat.html @@ -0,0 +1,107 @@ + + + + + WebSocket Chat + + + + +
+

SpringBoot netty 聊天室

+ +
+ + +
+
+
+ + \ No newline at end of file diff --git a/src/main/java/com/myself/nettychat/NettychatApplication.java b/src/main/java/com/myself/nettychat/NettychatApplication.java index 8eabd26..76029f4 100644 --- a/src/main/java/com/myself/nettychat/NettychatApplication.java +++ b/src/main/java/com/myself/nettychat/NettychatApplication.java @@ -1,6 +1,6 @@ package com.myself.nettychat; - +import com.myself.nettychat.config.NettyConfig; import com.myself.nettychat.config.NettyTcpConfig; import com.myself.nettychat.config.TCPServer; import org.springframework.boot.SpringApplication; @@ -17,10 +17,21 @@ public class NettychatApplication { public static void main(String[] args) throws Exception{ -// SpringApplication.run(NettychatApplication.class, args); ConfigurableApplicationContext context = SpringApplication.run(NettychatApplication.class, args); + NettyConfig nettyConfig = context.getBean(NettyConfig.class); NettyTcpConfig nettyTcpConfig = context.getBean(NettyTcpConfig.class); TCPServer tcpServer = context.getBean(TCPServer.class); + new Thread(new Runnable() { + @Override + public void run() { + try { + System.out.println("Web端Netty通信服务端启动成功!端口:8090"); + tcpServer.startWeb(); + }catch (Exception e){ + e.printStackTrace(); + } + } + }).start(); new Thread(new Runnable() { @Override public void run() { diff --git a/src/main/java/com/myself/nettychat/common/enums/ConfirmStatus.java b/src/main/java/com/myself/nettychat/common/enums/ConfirmStatus.java deleted file mode 100644 index 968b807..0000000 --- a/src/main/java/com/myself/nettychat/common/enums/ConfirmStatus.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.myself.nettychat.common.enums; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 确认状态 - **/ -public enum ConfirmStatus { - PUB, - PUBREC, - PUBREL, - COMPLETE, -} diff --git a/src/main/java/com/myself/nettychat/common/enums/ProtocolEnum.java b/src/main/java/com/myself/nettychat/common/enums/ProtocolEnum.java deleted file mode 100644 index 19855ea..0000000 --- a/src/main/java/com/myself/nettychat/common/enums/ProtocolEnum.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.myself.nettychat.common.enums; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 协议 - **/ -public enum ProtocolEnum { - MQTT, - - MQTT_WS_MQTT, - - MQTT_WS_PAHO, -} diff --git a/src/main/java/com/myself/nettychat/common/enums/QosStatus.java b/src/main/java/com/myself/nettychat/common/enums/QosStatus.java deleted file mode 100644 index 0a3643a..0000000 --- a/src/main/java/com/myself/nettychat/common/enums/QosStatus.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.myself.nettychat.common.enums; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc Qos确认状态 - **/ -public enum QosStatus { - - PUBD, // 已发送 没收到RECD (发送) - - RECD, //publish 推送回复过(发送) - -} diff --git a/src/main/java/com/myself/nettychat/common/enums/SessionStatus.java b/src/main/java/com/myself/nettychat/common/enums/SessionStatus.java deleted file mode 100644 index 593d63f..0000000 --- a/src/main/java/com/myself/nettychat/common/enums/SessionStatus.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.myself.nettychat.common.enums; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc Channel 会话状态 - **/ -public enum SessionStatus { - - OPEN, - CLOSE - -} diff --git a/src/main/java/com/myself/nettychat/common/enums/SubStatus.java b/src/main/java/com/myself/nettychat/common/enums/SubStatus.java deleted file mode 100644 index 2ab833c..0000000 --- a/src/main/java/com/myself/nettychat/common/enums/SubStatus.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.myself.nettychat.common.enums; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 是否订阅过主题 - **/ -public enum SubStatus { - YES, - NO -} diff --git a/src/main/java/com/myself/nettychat/common/exception/ConnectionException.java b/src/main/java/com/myself/nettychat/common/exception/ConnectionException.java deleted file mode 100644 index 76d1cb2..0000000 --- a/src/main/java/com/myself/nettychat/common/exception/ConnectionException.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.myself.nettychat.common.exception; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 连接异常 - **/ -public class ConnectionException extends RuntimeException { - - public ConnectionException(String message) { - super(message); - } - -} diff --git a/src/main/java/com/myself/nettychat/common/exception/NoFindHandlerException.java b/src/main/java/com/myself/nettychat/common/exception/NoFindHandlerException.java deleted file mode 100644 index 1b1ed2a..0000000 --- a/src/main/java/com/myself/nettychat/common/exception/NoFindHandlerException.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.myself.nettychat.common.exception; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 找不到Handler异常 - **/ -public class NoFindHandlerException extends RuntimeException { - - public NoFindHandlerException(String message) { - super(message); - } - -} diff --git a/src/main/java/com/myself/nettychat/common/ip/IpUtils.java b/src/main/java/com/myself/nettychat/common/ip/IpUtils.java deleted file mode 100644 index 4ccfc3d..0000000 --- a/src/main/java/com/myself/nettychat/common/ip/IpUtils.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.myself.nettychat.common.ip; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.util.Enumeration; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc ip操作 - **/ -public class IpUtils { - - /*** - * 获取外网IP - * @return - */ - public static String internetIp() { - try { - - Enumeration networks = NetworkInterface.getNetworkInterfaces(); - InetAddress inetAddress = null; - Enumeration inetAddresses = null; - while (networks.hasMoreElements()) { - inetAddresses = networks.nextElement().getInetAddresses(); - while (inetAddresses.hasMoreElements()) { - inetAddress = inetAddresses.nextElement(); - if (inetAddress != null - && inetAddress instanceof Inet4Address - && !inetAddress.isSiteLocalAddress() - && !inetAddress.isLoopbackAddress() - && inetAddress.getHostAddress().indexOf(":") == -1) { - return inetAddress.getHostAddress(); - } - } - } - - return null; - - } catch (Exception e) { - - throw new RuntimeException(e); - } - } - - /** - * 获取内网IP - * - * @return - */ - public static String intranetIp() { - try { - return InetAddress.getLocalHost().getHostAddress(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * 获取服务启动host - * @return - */ - public static String getHost(){ - return internetIp()==null?intranetIp():internetIp(); - } - -} diff --git a/src/main/java/com/myself/nettychat/common/mqtts/ClientMqttHandlerService.java b/src/main/java/com/myself/nettychat/common/mqtts/ClientMqttHandlerService.java deleted file mode 100644 index 56f9fda..0000000 --- a/src/main/java/com/myself/nettychat/common/mqtts/ClientMqttHandlerService.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.myself.nettychat.common.mqtts; - -import io.netty.channel.Channel; -import io.netty.handler.codec.mqtt.*; -import io.netty.handler.timeout.IdleStateEvent; - - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 抽象出客户端的事件 - **/ -public abstract class ClientMqttHandlerService implements MqttHandlerIntf { - - @Override - public void doTimeOut(Channel channel, IdleStateEvent evt) { - heart(channel,evt); - } - - public abstract void heart(Channel channel, IdleStateEvent evt); - - public abstract void suback(Channel channel,MqttSubAckMessage mqttMessage) ; - - public abstract void pubBackMessage(Channel channel, int i); - - public abstract void unsubBack(Channel channel, MqttMessage mqttMessage); -} diff --git a/src/main/java/com/myself/nettychat/common/mqtts/MqttHander.java b/src/main/java/com/myself/nettychat/common/mqtts/MqttHander.java deleted file mode 100644 index 061dd77..0000000 --- a/src/main/java/com/myself/nettychat/common/mqtts/MqttHander.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.myself.nettychat.common.mqtts; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.codec.mqtt.MqttFixedHeader; -import io.netty.handler.codec.mqtt.MqttMessage; -import io.netty.handler.timeout.IdleStateEvent; -import lombok.extern.slf4j.Slf4j; - -import java.util.Optional; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc mtqq协议处理器 - **/ -@Slf4j -public abstract class MqttHander extends SimpleChannelInboundHandler { - - MqttHandlerIntf mqttHandlerApi; - - public MqttHander(MqttHandlerIntf mqttHandlerIntf){ - this.mqttHandlerApi=mqttHandlerIntf; - } - - @Override - protected void channelRead0(ChannelHandlerContext channelHandlerContext, MqttMessage mqttMessage) throws Exception { - MqttFixedHeader mqttFixedHeader = mqttMessage.fixedHeader(); - Optional.ofNullable(mqttFixedHeader) - .ifPresent(mqttFixedHeader1 -> doMessage(channelHandlerContext,mqttMessage)); - } - - - public abstract void doMessage(ChannelHandlerContext channelHandlerContext, MqttMessage mqttMessage); - - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - log.info("【DefaultMqttHandler:channelInactive】"+ctx.channel().localAddress().toString()+"关闭成功"); - mqttHandlerApi.close(ctx.channel()); - } - - @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { - if(evt instanceof IdleStateEvent){ - mqttHandlerApi.doTimeOut(ctx.channel(),(IdleStateEvent)evt); - } - super.userEventTriggered(ctx, evt); - } - -} diff --git a/src/main/java/com/myself/nettychat/common/mqtts/MqttHandlerIntf.java b/src/main/java/com/myself/nettychat/common/mqtts/MqttHandlerIntf.java deleted file mode 100644 index 4f274ee..0000000 --- a/src/main/java/com/myself/nettychat/common/mqtts/MqttHandlerIntf.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.myself.nettychat.common.mqtts; - -import io.netty.channel.Channel; -import io.netty.handler.codec.mqtt.MqttMessage; -import io.netty.handler.timeout.IdleStateEvent; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 自定义 对外暴露,消息处理API - **/ -public interface MqttHandlerIntf { - - void close(Channel channel); - - void puback(Channel channel, MqttMessage mqttMessage); - - void pubrec(Channel channel, MqttMessage mqttMessage); - - void pubrel(Channel channel, MqttMessage mqttMessage); - - void pubcomp(Channel channel, MqttMessage mqttMessage); - - void doTimeOut(Channel channel, IdleStateEvent evt); - -} diff --git a/src/main/java/com/myself/nettychat/common/mqtts/ServerMqttHandlerService.java b/src/main/java/com/myself/nettychat/common/mqtts/ServerMqttHandlerService.java deleted file mode 100644 index fd62ee5..0000000 --- a/src/main/java/com/myself/nettychat/common/mqtts/ServerMqttHandlerService.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.myself.nettychat.common.mqtts; - -import io.netty.channel.Channel; -import io.netty.handler.codec.mqtt.*; -import io.netty.handler.timeout.IdleStateEvent; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 抽象出服务端的事件 - **/ -public abstract class ServerMqttHandlerService implements MqttHandlerIntf { - - public abstract boolean login(Channel channel, MqttConnectMessage mqttConnectMessage); - - public abstract void publish(Channel channel, MqttPublishMessage mqttPublishMessage); - - public abstract void subscribe(Channel channel, MqttSubscribeMessage mqttSubscribeMessage); - - public abstract void pong(Channel channel); - - public abstract void unsubscribe(Channel channel, MqttUnsubscribeMessage mqttMessage); - - public abstract void disconnect(Channel channel); - - public abstract void doTimeOut(Channel channel, IdleStateEvent evt); - -} diff --git a/src/main/java/com/myself/nettychat/common/pool/DefaultThreadFactory.java b/src/main/java/com/myself/nettychat/common/pool/DefaultThreadFactory.java deleted file mode 100644 index b6c776f..0000000 --- a/src/main/java/com/myself/nettychat/common/pool/DefaultThreadFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.myself.nettychat.common.pool; - -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 线程池 - **/ -public class DefaultThreadFactory implements ThreadFactory { - - private static final AtomicInteger poolNumber = new AtomicInteger(1); - private final ThreadGroup threadGroup; - private final AtomicInteger currentThreadNumber = new AtomicInteger(1); - private final String namePrefix; - private int priority = Thread.NORM_PRIORITY; - private boolean isDaemon = false; - - public DefaultThreadFactory(String prefix) { - this(prefix, false); - } - - public DefaultThreadFactory(String prefix, boolean isDaemon) { - this(prefix, isDaemon, Thread.NORM_PRIORITY); - } - - public DefaultThreadFactory(String prefix, boolean isDaemon, int priority) { - SecurityManager s = System.getSecurityManager(); - this.threadGroup = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); - this.namePrefix = prefix + "-" + poolNumber.getAndIncrement() + "-thread-"; - this.isDaemon = isDaemon; - this.priority = priority; - } - - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(threadGroup, r, namePrefix + currentThreadNumber.getAndIncrement(), 0); - thread.setDaemon(isDaemon); - thread.setPriority(priority); - return thread; - } -} diff --git a/src/main/java/com/myself/nettychat/common/pool/ExecutorQueue.java b/src/main/java/com/myself/nettychat/common/pool/ExecutorQueue.java deleted file mode 100644 index 0217bc1..0000000 --- a/src/main/java/com/myself/nettychat/common/pool/ExecutorQueue.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.myself.nettychat.common.pool; - -import java.util.concurrent.LinkedTransferQueue; -import java.util.concurrent.RejectedExecutionException; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc LinkedTransferQueue 能保证更高性能,相比与LinkedBlockingQueue有明显提升 - * 不过LinkedTransferQueue的缺点是没有队列长度控制,需要在外层协助控制 - **/ -public class ExecutorQueue extends LinkedTransferQueue { - - private static final long serialVersionUID = -265236426751004839L; - - private StandardThreadExecutor threadPoolExecutor; - - public ExecutorQueue() { - super(); - } - - public void setStandardThreadExecutor(StandardThreadExecutor threadPoolExecutor) { - this.threadPoolExecutor = threadPoolExecutor; - } - - // 注:代码来源于 tomcat - public boolean force(Runnable o) { - if (threadPoolExecutor.isShutdown()) { - throw new RejectedExecutionException("Executor not running, can't force a command into the queue"); - } - // forces the item onto the queue, to be used if the task is rejected - return super.offer(o); - } - - // 注:tomcat的代码进行一些小变更 - public boolean offer(Runnable o) { - int poolSize = threadPoolExecutor.getPoolSize(); - - // we are maxed out on threads, simply queue the object - if (poolSize == threadPoolExecutor.getMaximumPoolSize()) { - return super.offer(o); - } - // we have idle threads, just add it to the queue - // note that we don't use getActiveCount(), see BZ 49730 - if (poolSize >= threadPoolExecutor.getSubmittedTasksCount()) { - return super.offer(o); - } - // if we have less threads than maximum force creation of a new - // thread - if (poolSize < threadPoolExecutor.getMaximumPoolSize()) { - return false; - } - // if we reached here, we need to add it to the queue - return super.offer(o); - } - -} diff --git a/src/main/java/com/myself/nettychat/common/pool/Scheduled.java b/src/main/java/com/myself/nettychat/common/pool/Scheduled.java deleted file mode 100644 index f3711ca..0000000 --- a/src/main/java/com/myself/nettychat/common/pool/Scheduled.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.myself.nettychat.common.pool; - -import java.util.concurrent.ScheduledFuture; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 接口 - **/ -@FunctionalInterface -public interface Scheduled { - - ScheduledFuture submit(Runnable runnable); - -} diff --git a/src/main/java/com/myself/nettychat/common/pool/StandardThreadExecutor.java b/src/main/java/com/myself/nettychat/common/pool/StandardThreadExecutor.java deleted file mode 100644 index 8172d49..0000000 --- a/src/main/java/com/myself/nettychat/common/pool/StandardThreadExecutor.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.myself.nettychat.common.pool; - -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 代码和思路主要来自于: - * tomcat: org.apache.catalina.core.StandardThreadExecutor - * - * java.util.concurrent.threadPoolExecutor execute执行策略: - * 优先offer到queue,queue满后再扩充线程到maxThread,如果已经到了maxThread就reject - * 比较适合于CPU密集型应用(比如runnable内部执行的操作都在JVM内部,memory copy, or compute等等) - * - * StandardThreadExecutor execute执行策略: - * 优先扩充线程到maxThread,再offer到queue,如果满了就reject - * 比较适合于业务处理需要远程资源的场景 - **/ -public class StandardThreadExecutor extends ThreadPoolExecutor { - - public static final int DEFAULT_MIN_THREADS = 20; - public static final int DEFAULT_MAX_THREADS = 200; - public static final int DEFAULT_MAX_IDLE_TIME = 60000; // 1 minutes - - protected AtomicInteger submittedTasksCount; // 正在处理的任务数 - private int maxSubmittedTaskCount; // 最大允许同时处理的任务数 - - public StandardThreadExecutor() { - this(DEFAULT_MIN_THREADS, DEFAULT_MAX_THREADS); - } - - public StandardThreadExecutor(int coreThread, int maxThreads) { - this(coreThread, maxThreads, maxThreads); - } - - public StandardThreadExecutor(int coreThread, int maxThreads, long keepAliveTime, TimeUnit unit) { - this(coreThread, maxThreads, keepAliveTime, unit, maxThreads); - } - - public StandardThreadExecutor(int coreThreads, int maxThreads, int queueCapacity) { - this(coreThreads, maxThreads, queueCapacity, Executors.defaultThreadFactory()); - } - - public StandardThreadExecutor(int coreThreads, int maxThreads, int queueCapacity, ThreadFactory threadFactory) { - this(coreThreads, maxThreads, DEFAULT_MAX_IDLE_TIME, TimeUnit.MILLISECONDS, queueCapacity, threadFactory); - } - - public StandardThreadExecutor(int coreThreads, int maxThreads, long keepAliveTime, TimeUnit unit, int queueCapacity) { - this(coreThreads, maxThreads, keepAliveTime, unit, queueCapacity, Executors.defaultThreadFactory()); - } - - public StandardThreadExecutor(int coreThreads, int maxThreads, long keepAliveTime, TimeUnit unit, - int queueCapacity, ThreadFactory threadFactory) { - this(coreThreads, maxThreads, keepAliveTime, unit, queueCapacity, threadFactory, new AbortPolicy()); - } - - public StandardThreadExecutor(int coreThreads, int maxThreads, long keepAliveTime, TimeUnit unit, - int queueCapacity, ThreadFactory threadFactory, RejectedExecutionHandler handler) { - super(coreThreads, maxThreads, keepAliveTime, unit, new ExecutorQueue(), threadFactory, handler); - ((ExecutorQueue) getQueue()).setStandardThreadExecutor(this); - - submittedTasksCount = new AtomicInteger(0); - - // 最大并发任务限制: 队列buffer数 + 最大线程数 - maxSubmittedTaskCount = queueCapacity + maxThreads; - } - - @Override - public void execute(Runnable command) { - int count = submittedTasksCount.incrementAndGet(); - - // 超过最大的并发任务限制,进行 reject - // 依赖的LinkedTransferQueue没有长度限制,因此这里进行控制 - if (count > maxSubmittedTaskCount) { - submittedTasksCount.decrementAndGet(); - getRejectedExecutionHandler().rejectedExecution(command, this); - } - - try { - super.execute(command); - } catch (RejectedExecutionException rx) { - // there could have been contention around the queue - if (!((ExecutorQueue) getQueue()).force(command)) { - submittedTasksCount.decrementAndGet(); - getRejectedExecutionHandler().rejectedExecution(command, this); - } - rx.printStackTrace(); - } - } - - public int getSubmittedTasksCount() { - return this.submittedTasksCount.get(); - } - - - protected void afterExecute(Runnable r, Throwable t) { - submittedTasksCount.decrementAndGet(); - } -} diff --git a/src/main/java/com/myself/nettychat/common/properties/InitNetty.java b/src/main/java/com/myself/nettychat/common/properties/InitNetty.java index 8a7af93..eed34b4 100644 --- a/src/main/java/com/myself/nettychat/common/properties/InitNetty.java +++ b/src/main/java/com/myself/nettychat/common/properties/InitNetty.java @@ -3,8 +3,6 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -import com.myself.nettychat.common.enums.ProtocolEnum; -import com.myself.nettychat.common.mqtts.MqttHander; /** * @Author:UncleCatMySelf @@ -17,8 +15,6 @@ @ConfigurationProperties(prefix = "netty") public class InitNetty { - private ProtocolEnum protocol; - private int webport; private int tcpport; @@ -53,8 +49,6 @@ public class InitNetty { private String jksCertificatePassword; - private Class mqttHander ; - private int initalDelay ; private int period ; diff --git a/src/main/java/com/myself/nettychat/common/ssl/SecureSocketKeyStore.java b/src/main/java/com/myself/nettychat/common/ssl/SecureSocketKeyStore.java deleted file mode 100644 index 3bae691..0000000 --- a/src/main/java/com/myself/nettychat/common/ssl/SecureSocketKeyStore.java +++ /dev/null @@ -1,456 +0,0 @@ -package com.myself.nettychat.common.ssl; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.security.KeyStore; - -/** - * A bogus key store which provides all the required information to create an - * example SSL connection. - * - * To generate a bogus key store: - * - *
- * keytool -genkey -alias securesocket \ -keysize 2048 -validity 36500 \  -keyalg RSA -dname "CN=securesocket" \   -keypass inc0rrect -storepass mu$tch8ng3 \  -keystore cert.jks
- *
- *
- *
- *
- * 
- */ -public class SecureSocketKeyStore { - - private static final byte[] CERT_BYTES = { (byte) 254, (byte) 237, - (byte) 254, (byte) 237, (byte) 0, (byte) 0, (byte) 0, (byte) 2, - (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0, (byte) 0, - (byte) 0, (byte) 1, (byte) 0, (byte) 12, (byte) 115, (byte) 101, - (byte) 99, (byte) 117, (byte) 114, (byte) 101, (byte) 115, - (byte) 111, (byte) 99, (byte) 107, (byte) 101, (byte) 116, - (byte) 0, (byte) 0, (byte) 1, (byte) 69, (byte) 231, (byte) 201, - (byte) 156, (byte) 140, (byte) 0, (byte) 0, (byte) 5, (byte) 0, - (byte) 48, (byte) 130, (byte) 4, (byte) 252, (byte) 48, (byte) 14, - (byte) 6, (byte) 10, (byte) 43, (byte) 6, (byte) 1, (byte) 4, - (byte) 1, (byte) 42, (byte) 2, (byte) 17, (byte) 1, (byte) 1, - (byte) 5, (byte) 0, (byte) 4, (byte) 130, (byte) 4, (byte) 232, - (byte) 221, (byte) 18, (byte) 203, (byte) 171, (byte) 175, - (byte) 82, (byte) 132, (byte) 227, (byte) 115, (byte) 143, - (byte) 38, (byte) 191, (byte) 42, (byte) 202, (byte) 130, - (byte) 171, (byte) 75, (byte) 6, (byte) 161, (byte) 120, - (byte) 204, (byte) 61, (byte) 106, (byte) 160, (byte) 81, (byte) 9, - (byte) 204, (byte) 153, (byte) 166, (byte) 38, (byte) 246, - (byte) 13, (byte) 43, (byte) 19, (byte) 100, (byte) 132, (byte) 45, - (byte) 90, (byte) 143, (byte) 1, (byte) 231, (byte) 182, (byte) 89, - (byte) 228, (byte) 183, (byte) 17, (byte) 95, (byte) 129, - (byte) 229, (byte) 42, (byte) 182, (byte) 126, (byte) 114, - (byte) 76, (byte) 124, (byte) 123, (byte) 246, (byte) 152, - (byte) 0, (byte) 141, (byte) 212, (byte) 111, (byte) 52, - (byte) 243, (byte) 112, (byte) 31, (byte) 117, (byte) 124, - (byte) 142, (byte) 24, (byte) 59, (byte) 198, (byte) 164, - (byte) 253, (byte) 21, (byte) 177, (byte) 189, (byte) 74, - (byte) 218, (byte) 110, (byte) 83, (byte) 154, (byte) 49, - (byte) 186, (byte) 159, (byte) 173, (byte) 202, (byte) 94, - (byte) 174, (byte) 183, (byte) 223, (byte) 119, (byte) 109, - (byte) 110, (byte) 72, (byte) 93, (byte) 208, (byte) 195, - (byte) 19, (byte) 89, (byte) 33, (byte) 34, (byte) 186, (byte) 12, - (byte) 86, (byte) 156, (byte) 156, (byte) 210, (byte) 111, - (byte) 110, (byte) 44, (byte) 106, (byte) 36, (byte) 67, - (byte) 168, (byte) 7, (byte) 179, (byte) 244, (byte) 53, - (byte) 134, (byte) 10, (byte) 86, (byte) 179, (byte) 34, (byte) 60, - (byte) 184, (byte) 179, (byte) 162, (byte) 69, (byte) 24, - (byte) 168, (byte) 100, (byte) 183, (byte) 206, (byte) 64, - (byte) 4, (byte) 32, (byte) 66, (byte) 237, (byte) 228, (byte) 92, - (byte) 6, (byte) 213, (byte) 141, (byte) 147, (byte) 198, - (byte) 141, (byte) 216, (byte) 41, (byte) 0, (byte) 101, (byte) 65, - (byte) 41, (byte) 185, (byte) 128, (byte) 229, (byte) 107, - (byte) 25, (byte) 89, (byte) 148, (byte) 16, (byte) 194, - (byte) 101, (byte) 100, (byte) 243, (byte) 147, (byte) 77, - (byte) 230, (byte) 11, (byte) 151, (byte) 99, (byte) 124, - (byte) 55, (byte) 195, (byte) 185, (byte) 30, (byte) 234, - (byte) 83, (byte) 61, (byte) 109, (byte) 131, (byte) 156, - (byte) 244, (byte) 133, (byte) 66, (byte) 39, (byte) 153, (byte) 9, - (byte) 34, (byte) 218, (byte) 201, (byte) 143, (byte) 190, - (byte) 127, (byte) 119, (byte) 102, (byte) 6, (byte) 83, - (byte) 134, (byte) 96, (byte) 170, (byte) 79, (byte) 196, - (byte) 214, (byte) 47, (byte) 215, (byte) 37, (byte) 250, - (byte) 64, (byte) 8, (byte) 165, (byte) 203, (byte) 44, (byte) 53, - (byte) 113, (byte) 147, (byte) 251, (byte) 29, (byte) 26, - (byte) 38, (byte) 193, (byte) 11, (byte) 223, (byte) 212, - (byte) 114, (byte) 96, (byte) 162, (byte) 39, (byte) 48, - (byte) 200, (byte) 172, (byte) 182, (byte) 254, (byte) 180, - (byte) 198, (byte) 11, (byte) 128, (byte) 75, (byte) 74, (byte) 93, - (byte) 226, (byte) 157, (byte) 80, (byte) 14, (byte) 9, (byte) 217, - (byte) 236, (byte) 205, (byte) 153, (byte) 35, (byte) 242, - (byte) 130, (byte) 140, (byte) 25, (byte) 16, (byte) 156, - (byte) 247, (byte) 230, (byte) 5, (byte) 247, (byte) 0, (byte) 34, - (byte) 196, (byte) 15, (byte) 118, (byte) 255, (byte) 185, - (byte) 199, (byte) 59, (byte) 99, (byte) 27, (byte) 187, (byte) 83, - (byte) 81, (byte) 12, (byte) 71, (byte) 69, (byte) 127, (byte) 130, - (byte) 164, (byte) 97, (byte) 195, (byte) 216, (byte) 215, - (byte) 61, (byte) 29, (byte) 196, (byte) 62, (byte) 160, - (byte) 188, (byte) 209, (byte) 173, (byte) 230, (byte) 0, - (byte) 204, (byte) 225, (byte) 1, (byte) 5, (byte) 42, (byte) 223, - (byte) 232, (byte) 187, (byte) 190, (byte) 67, (byte) 126, - (byte) 235, (byte) 178, (byte) 218, (byte) 179, (byte) 46, - (byte) 186, (byte) 156, (byte) 186, (byte) 6, (byte) 191, - (byte) 68, (byte) 239, (byte) 31, (byte) 16, (byte) 204, (byte) 24, - (byte) 68, (byte) 164, (byte) 88, (byte) 10, (byte) 174, (byte) 26, - (byte) 54, (byte) 187, (byte) 149, (byte) 132, (byte) 128, - (byte) 173, (byte) 165, (byte) 8, (byte) 69, (byte) 96, (byte) 49, - (byte) 57, (byte) 223, (byte) 110, (byte) 29, (byte) 215, - (byte) 98, (byte) 42, (byte) 15, (byte) 153, (byte) 228, - (byte) 216, (byte) 61, (byte) 160, (byte) 230, (byte) 34, - (byte) 40, (byte) 232, (byte) 136, (byte) 139, (byte) 140, - (byte) 236, (byte) 251, (byte) 119, (byte) 242, (byte) 199, - (byte) 167, (byte) 61, (byte) 141, (byte) 89, (byte) 29, (byte) 82, - (byte) 114, (byte) 229, (byte) 198, (byte) 27, (byte) 133, - (byte) 87, (byte) 0, (byte) 53, (byte) 69, (byte) 42, (byte) 91, - (byte) 174, (byte) 82, (byte) 244, (byte) 160, (byte) 82, - (byte) 142, (byte) 221, (byte) 106, (byte) 151, (byte) 241, - (byte) 214, (byte) 64, (byte) 14, (byte) 28, (byte) 2, (byte) 3, - (byte) 145, (byte) 143, (byte) 18, (byte) 165, (byte) 247, - (byte) 178, (byte) 211, (byte) 16, (byte) 222, (byte) 76, - (byte) 60, (byte) 119, (byte) 130, (byte) 199, (byte) 230, - (byte) 229, (byte) 3, (byte) 22, (byte) 100, (byte) 135, - (byte) 103, (byte) 60, (byte) 181, (byte) 191, (byte) 56, - (byte) 249, (byte) 181, (byte) 169, (byte) 210, (byte) 25, - (byte) 152, (byte) 201, (byte) 226, (byte) 119, (byte) 71, - (byte) 204, (byte) 70, (byte) 220, (byte) 103, (byte) 46, - (byte) 166, (byte) 125, (byte) 40, (byte) 86, (byte) 208, - (byte) 114, (byte) 138, (byte) 24, (byte) 27, (byte) 219, - (byte) 123, (byte) 161, (byte) 52, (byte) 14, (byte) 38, - (byte) 244, (byte) 112, (byte) 238, (byte) 121, (byte) 90, - (byte) 34, (byte) 157, (byte) 131, (byte) 53, (byte) 245, - (byte) 162, (byte) 89, (byte) 188, (byte) 6, (byte) 202, - (byte) 164, (byte) 130, (byte) 34, (byte) 232, (byte) 74, - (byte) 45, (byte) 137, (byte) 164, (byte) 200, (byte) 197, - (byte) 247, (byte) 64, (byte) 110, (byte) 122, (byte) 49, - (byte) 116, (byte) 137, (byte) 253, (byte) 170, (byte) 232, - (byte) 120, (byte) 26, (byte) 171, (byte) 228, (byte) 229, - (byte) 49, (byte) 56, (byte) 56, (byte) 106, (byte) 110, (byte) 12, - (byte) 109, (byte) 93, (byte) 105, (byte) 241, (byte) 196, - (byte) 11, (byte) 18, (byte) 89, (byte) 108, (byte) 146, - (byte) 224, (byte) 161, (byte) 181, (byte) 236, (byte) 74, - (byte) 128, (byte) 24, (byte) 239, (byte) 22, (byte) 146, (byte) 0, - (byte) 69, (byte) 182, (byte) 246, (byte) 43, (byte) 59, - (byte) 208, (byte) 33, (byte) 48, (byte) 81, (byte) 0, (byte) 70, - (byte) 225, (byte) 222, (byte) 122, (byte) 178, (byte) 138, - (byte) 12, (byte) 207, (byte) 233, (byte) 164, (byte) 13, - (byte) 176, (byte) 123, (byte) 95, (byte) 68, (byte) 238, - (byte) 134, (byte) 66, (byte) 95, (byte) 194, (byte) 192, - (byte) 225, (byte) 244, (byte) 14, (byte) 78, (byte) 53, - (byte) 189, (byte) 217, (byte) 229, (byte) 203, (byte) 192, - (byte) 34, (byte) 38, (byte) 169, (byte) 63, (byte) 239, - (byte) 128, (byte) 172, (byte) 143, (byte) 75, (byte) 7, - (byte) 237, (byte) 125, (byte) 179, (byte) 235, (byte) 229, - (byte) 98, (byte) 8, (byte) 211, (byte) 237, (byte) 116, (byte) 75, - (byte) 27, (byte) 211, (byte) 131, (byte) 245, (byte) 89, - (byte) 150, (byte) 35, (byte) 49, (byte) 207, (byte) 113, - (byte) 237, (byte) 114, (byte) 125, (byte) 134, (byte) 191, - (byte) 110, (byte) 30, (byte) 119, (byte) 131, (byte) 175, - (byte) 166, (byte) 201, (byte) 255, (byte) 200, (byte) 1, - (byte) 126, (byte) 163, (byte) 172, (byte) 52, (byte) 118, - (byte) 184, (byte) 221, (byte) 165, (byte) 167, (byte) 165, - (byte) 20, (byte) 135, (byte) 32, (byte) 222, (byte) 188, - (byte) 250, (byte) 64, (byte) 161, (byte) 67, (byte) 236, - (byte) 212, (byte) 131, (byte) 44, (byte) 32, (byte) 70, (byte) 0, - (byte) 24, (byte) 178, (byte) 83, (byte) 155, (byte) 145, - (byte) 136, (byte) 131, (byte) 120, (byte) 181, (byte) 164, - (byte) 155, (byte) 172, (byte) 41, (byte) 213, (byte) 164, - (byte) 98, (byte) 169, (byte) 152, (byte) 184, (byte) 170, - (byte) 107, (byte) 7, (byte) 21, (byte) 228, (byte) 175, - (byte) 192, (byte) 238, (byte) 68, (byte) 197, (byte) 119, - (byte) 228, (byte) 225, (byte) 156, (byte) 235, (byte) 241, - (byte) 172, (byte) 171, (byte) 236, (byte) 128, (byte) 78, - (byte) 117, (byte) 152, (byte) 123, (byte) 93, (byte) 156, - (byte) 57, (byte) 238, (byte) 211, (byte) 188, (byte) 47, - (byte) 62, (byte) 45, (byte) 127, (byte) 58, (byte) 38, (byte) 29, - (byte) 131, (byte) 95, (byte) 85, (byte) 149, (byte) 112, - (byte) 215, (byte) 207, (byte) 41, (byte) 201, (byte) 30, - (byte) 149, (byte) 73, (byte) 245, (byte) 179, (byte) 176, - (byte) 246, (byte) 203, (byte) 204, (byte) 252, (byte) 13, - (byte) 98, (byte) 151, (byte) 93, (byte) 87, (byte) 241, - (byte) 166, (byte) 46, (byte) 249, (byte) 148, (byte) 49, - (byte) 141, (byte) 136, (byte) 49, (byte) 77, (byte) 250, - (byte) 191, (byte) 157, (byte) 90, (byte) 84, (byte) 51, - (byte) 129, (byte) 133, (byte) 66, (byte) 253, (byte) 99, - (byte) 243, (byte) 34, (byte) 142, (byte) 197, (byte) 4, - (byte) 126, (byte) 7, (byte) 217, (byte) 126, (byte) 205, - (byte) 250, (byte) 141, (byte) 231, (byte) 225, (byte) 203, - (byte) 171, (byte) 246, (byte) 201, (byte) 48, (byte) 96, - (byte) 207, (byte) 74, (byte) 253, (byte) 120, (byte) 114, - (byte) 163, (byte) 192, (byte) 24, (byte) 12, (byte) 10, - (byte) 210, (byte) 94, (byte) 136, (byte) 152, (byte) 185, - (byte) 109, (byte) 87, (byte) 35, (byte) 159, (byte) 238, - (byte) 122, (byte) 200, (byte) 107, (byte) 103, (byte) 243, - (byte) 250, (byte) 152, (byte) 68, (byte) 66, (byte) 170, (byte) 0, - (byte) 134, (byte) 229, (byte) 168, (byte) 182, (byte) 30, - (byte) 89, (byte) 240, (byte) 121, (byte) 106, (byte) 148, - (byte) 142, (byte) 49, (byte) 242, (byte) 215, (byte) 233, - (byte) 57, (byte) 120, (byte) 204, (byte) 180, (byte) 239, - (byte) 199, (byte) 133, (byte) 255, (byte) 71, (byte) 3, - (byte) 132, (byte) 228, (byte) 110, (byte) 66, (byte) 227, - (byte) 122, (byte) 82, (byte) 118, (byte) 173, (byte) 218, - (byte) 54, (byte) 99, (byte) 167, (byte) 154, (byte) 3, (byte) 189, - (byte) 25, (byte) 123, (byte) 169, (byte) 42, (byte) 184, - (byte) 59, (byte) 36, (byte) 131, (byte) 206, (byte) 248, - (byte) 90, (byte) 32, (byte) 183, (byte) 86, (byte) 62, (byte) 149, - (byte) 107, (byte) 243, (byte) 71, (byte) 197, (byte) 124, - (byte) 155, (byte) 214, (byte) 91, (byte) 29, (byte) 81, (byte) 28, - (byte) 115, (byte) 98, (byte) 130, (byte) 184, (byte) 135, - (byte) 13, (byte) 191, (byte) 147, (byte) 43, (byte) 10, - (byte) 178, (byte) 99, (byte) 165, (byte) 210, (byte) 87, - (byte) 87, (byte) 148, (byte) 31, (byte) 198, (byte) 129, - (byte) 32, (byte) 181, (byte) 3, (byte) 144, (byte) 61, (byte) 5, - (byte) 166, (byte) 252, (byte) 73, (byte) 205, (byte) 230, - (byte) 178, (byte) 162, (byte) 46, (byte) 56, (byte) 99, (byte) 77, - (byte) 97, (byte) 236, (byte) 121, (byte) 157, (byte) 139, - (byte) 153, (byte) 217, (byte) 171, (byte) 19, (byte) 68, - (byte) 36, (byte) 14, (byte) 123, (byte) 249, (byte) 101, - (byte) 127, (byte) 184, (byte) 123, (byte) 7, (byte) 124, - (byte) 68, (byte) 98, (byte) 34, (byte) 139, (byte) 224, - (byte) 173, (byte) 246, (byte) 196, (byte) 180, (byte) 70, - (byte) 207, (byte) 168, (byte) 211, (byte) 255, (byte) 84, - (byte) 0, (byte) 174, (byte) 11, (byte) 160, (byte) 155, - (byte) 127, (byte) 228, (byte) 81, (byte) 226, (byte) 115, - (byte) 142, (byte) 200, (byte) 107, (byte) 4, (byte) 204, - (byte) 219, (byte) 192, (byte) 189, (byte) 56, (byte) 127, - (byte) 184, (byte) 187, (byte) 161, (byte) 106, (byte) 62, - (byte) 225, (byte) 211, (byte) 115, (byte) 30, (byte) 172, - (byte) 191, (byte) 66, (byte) 25, (byte) 66, (byte) 235, - (byte) 107, (byte) 41, (byte) 186, (byte) 40, (byte) 239, - (byte) 173, (byte) 11, (byte) 247, (byte) 89, (byte) 79, - (byte) 135, (byte) 86, (byte) 73, (byte) 77, (byte) 164, (byte) 34, - (byte) 109, (byte) 236, (byte) 56, (byte) 198, (byte) 141, - (byte) 87, (byte) 74, (byte) 172, (byte) 56, (byte) 24, (byte) 150, - (byte) 233, (byte) 233, (byte) 165, (byte) 122, (byte) 201, - (byte) 112, (byte) 232, (byte) 23, (byte) 12, (byte) 166, - (byte) 128, (byte) 114, (byte) 139, (byte) 207, (byte) 233, - (byte) 47, (byte) 220, (byte) 172, (byte) 175, (byte) 40, - (byte) 109, (byte) 82, (byte) 142, (byte) 130, (byte) 177, - (byte) 50, (byte) 127, (byte) 196, (byte) 106, (byte) 172, - (byte) 178, (byte) 71, (byte) 178, (byte) 204, (byte) 99, - (byte) 113, (byte) 33, (byte) 189, (byte) 188, (byte) 168, - (byte) 76, (byte) 92, (byte) 230, (byte) 211, (byte) 239, - (byte) 75, (byte) 71, (byte) 64, (byte) 197, (byte) 26, (byte) 222, - (byte) 19, (byte) 213, (byte) 161, (byte) 144, (byte) 20, - (byte) 126, (byte) 192, (byte) 156, (byte) 15, (byte) 113, - (byte) 64, (byte) 73, (byte) 7, (byte) 241, (byte) 217, (byte) 127, - (byte) 171, (byte) 199, (byte) 66, (byte) 32, (byte) 179, (byte) 4, - (byte) 181, (byte) 93, (byte) 121, (byte) 193, (byte) 10, - (byte) 169, (byte) 255, (byte) 152, (byte) 199, (byte) 95, - (byte) 177, (byte) 227, (byte) 135, (byte) 21, (byte) 64, - (byte) 203, (byte) 9, (byte) 79, (byte) 243, (byte) 114, (byte) 2, - (byte) 201, (byte) 157, (byte) 180, (byte) 52, (byte) 193, - (byte) 66, (byte) 34, (byte) 155, (byte) 52, (byte) 35, (byte) 93, - (byte) 31, (byte) 96, (byte) 77, (byte) 12, (byte) 80, (byte) 195, - (byte) 96, (byte) 247, (byte) 251, (byte) 237, (byte) 36, - (byte) 170, (byte) 7, (byte) 3, (byte) 251, (byte) 243, (byte) 47, - (byte) 180, (byte) 98, (byte) 207, (byte) 176, (byte) 106, - (byte) 237, (byte) 114, (byte) 91, (byte) 229, (byte) 56, - (byte) 94, (byte) 154, (byte) 32, (byte) 62, (byte) 240, - (byte) 132, (byte) 4, (byte) 144, (byte) 227, (byte) 140, - (byte) 137, (byte) 76, (byte) 15, (byte) 117, (byte) 82, - (byte) 223, (byte) 168, (byte) 135, (byte) 33, (byte) 91, - (byte) 173, (byte) 4, (byte) 245, (byte) 192, (byte) 95, - (byte) 135, (byte) 22, (byte) 138, (byte) 89, (byte) 1, (byte) 14, - (byte) 230, (byte) 143, (byte) 195, (byte) 93, (byte) 133, - (byte) 194, (byte) 252, (byte) 188, (byte) 31, (byte) 39, - (byte) 162, (byte) 59, (byte) 148, (byte) 219, (byte) 213, - (byte) 179, (byte) 195, (byte) 165, (byte) 67, (byte) 68, - (byte) 39, (byte) 178, (byte) 143, (byte) 192, (byte) 177, - (byte) 221, (byte) 236, (byte) 63, (byte) 40, (byte) 205, - (byte) 26, (byte) 81, (byte) 127, (byte) 5, (byte) 213, (byte) 192, - (byte) 22, (byte) 147, (byte) 98, (byte) 207, (byte) 153, (byte) 8, - (byte) 108, (byte) 75, (byte) 182, (byte) 148, (byte) 0, - (byte) 151, (byte) 15, (byte) 178, (byte) 98, (byte) 145, - (byte) 255, (byte) 213, (byte) 142, (byte) 63, (byte) 247, - (byte) 42, (byte) 161, (byte) 246, (byte) 21, (byte) 128, - (byte) 47, (byte) 248, (byte) 217, (byte) 70, (byte) 195, - (byte) 151, (byte) 236, (byte) 73, (byte) 153, (byte) 230, - (byte) 152, (byte) 217, (byte) 12, (byte) 189, (byte) 65, - (byte) 85, (byte) 189, (byte) 204, (byte) 212, (byte) 161, - (byte) 210, (byte) 217, (byte) 74, (byte) 75, (byte) 186, - (byte) 122, (byte) 167, (byte) 149, (byte) 178, (byte) 202, - (byte) 205, (byte) 246, (byte) 225, (byte) 225, (byte) 190, - (byte) 56, (byte) 42, (byte) 162, (byte) 215, (byte) 107, - (byte) 45, (byte) 121, (byte) 235, (byte) 195, (byte) 219, - (byte) 22, (byte) 0, (byte) 0, (byte) 0, (byte) 1, (byte) 0, - (byte) 5, (byte) 88, (byte) 46, (byte) 53, (byte) 48, (byte) 57, - (byte) 0, (byte) 0, (byte) 2, (byte) 211, (byte) 48, (byte) 130, - (byte) 2, (byte) 207, (byte) 48, (byte) 130, (byte) 1, (byte) 183, - (byte) 160, (byte) 3, (byte) 2, (byte) 1, (byte) 2, (byte) 2, - (byte) 4, (byte) 58, (byte) 247, (byte) 71, (byte) 185, (byte) 48, - (byte) 13, (byte) 6, (byte) 9, (byte) 42, (byte) 134, (byte) 72, - (byte) 134, (byte) 247, (byte) 13, (byte) 1, (byte) 1, (byte) 11, - (byte) 5, (byte) 0, (byte) 48, (byte) 23, (byte) 49, (byte) 21, - (byte) 48, (byte) 19, (byte) 6, (byte) 3, (byte) 85, (byte) 4, - (byte) 3, (byte) 19, (byte) 12, (byte) 115, (byte) 101, (byte) 99, - (byte) 117, (byte) 114, (byte) 101, (byte) 115, (byte) 111, - (byte) 99, (byte) 107, (byte) 101, (byte) 116, (byte) 48, - (byte) 32, (byte) 23, (byte) 13, (byte) 49, (byte) 52, (byte) 48, - (byte) 53, (byte) 49, (byte) 48, (byte) 50, (byte) 48, (byte) 49, - (byte) 56, (byte) 52, (byte) 48, (byte) 90, (byte) 24, (byte) 15, - (byte) 50, (byte) 49, (byte) 49, (byte) 52, (byte) 48, (byte) 52, - (byte) 49, (byte) 54, (byte) 50, (byte) 48, (byte) 49, (byte) 56, - (byte) 52, (byte) 48, (byte) 90, (byte) 48, (byte) 23, (byte) 49, - (byte) 21, (byte) 48, (byte) 19, (byte) 6, (byte) 3, (byte) 85, - (byte) 4, (byte) 3, (byte) 19, (byte) 12, (byte) 115, (byte) 101, - (byte) 99, (byte) 117, (byte) 114, (byte) 101, (byte) 115, - (byte) 111, (byte) 99, (byte) 107, (byte) 101, (byte) 116, - (byte) 48, (byte) 130, (byte) 1, (byte) 34, (byte) 48, (byte) 13, - (byte) 6, (byte) 9, (byte) 42, (byte) 134, (byte) 72, (byte) 134, - (byte) 247, (byte) 13, (byte) 1, (byte) 1, (byte) 1, (byte) 5, - (byte) 0, (byte) 3, (byte) 130, (byte) 1, (byte) 15, (byte) 0, - (byte) 48, (byte) 130, (byte) 1, (byte) 10, (byte) 2, (byte) 130, - (byte) 1, (byte) 1, (byte) 0, (byte) 153, (byte) 113, (byte) 7, - (byte) 44, (byte) 219, (byte) 76, (byte) 101, (byte) 226, - (byte) 138, (byte) 96, (byte) 219, (byte) 60, (byte) 167, - (byte) 138, (byte) 222, (byte) 6, (byte) 78, (byte) 169, (byte) 64, - (byte) 188, (byte) 156, (byte) 190, (byte) 119, (byte) 16, - (byte) 34, (byte) 228, (byte) 250, (byte) 253, (byte) 119, - (byte) 75, (byte) 240, (byte) 60, (byte) 242, (byte) 52, - (byte) 137, (byte) 146, (byte) 20, (byte) 130, (byte) 202, - (byte) 226, (byte) 125, (byte) 19, (byte) 7, (byte) 34, (byte) 8, - (byte) 61, (byte) 243, (byte) 202, (byte) 225, (byte) 206, - (byte) 223, (byte) 53, (byte) 74, (byte) 56, (byte) 222, (byte) 47, - (byte) 99, (byte) 235, (byte) 57, (byte) 73, (byte) 90, (byte) 198, - (byte) 109, (byte) 104, (byte) 36, (byte) 255, (byte) 124, - (byte) 57, (byte) 155, (byte) 248, (byte) 120, (byte) 56, - (byte) 56, (byte) 38, (byte) 41, (byte) 216, (byte) 1, (byte) 216, - (byte) 216, (byte) 100, (byte) 239, (byte) 79, (byte) 222, - (byte) 34, (byte) 21, (byte) 182, (byte) 112, (byte) 136, - (byte) 137, (byte) 16, (byte) 141, (byte) 15, (byte) 83, (byte) 94, - (byte) 245, (byte) 36, (byte) 203, (byte) 178, (byte) 137, - (byte) 159, (byte) 86, (byte) 220, (byte) 253, (byte) 112, - (byte) 200, (byte) 50, (byte) 135, (byte) 215, (byte) 190, - (byte) 21, (byte) 186, (byte) 84, (byte) 21, (byte) 96, (byte) 126, - (byte) 253, (byte) 115, (byte) 209, (byte) 241, (byte) 94, - (byte) 115, (byte) 219, (byte) 0, (byte) 25, (byte) 253, - (byte) 209, (byte) 182, (byte) 118, (byte) 230, (byte) 10, - (byte) 50, (byte) 131, (byte) 39, (byte) 249, (byte) 136, - (byte) 11, (byte) 101, (byte) 192, (byte) 12, (byte) 210, - (byte) 179, (byte) 237, (byte) 213, (byte) 68, (byte) 101, - (byte) 58, (byte) 187, (byte) 255, (byte) 240, (byte) 164, - (byte) 147, (byte) 72, (byte) 148, (byte) 227, (byte) 155, - (byte) 88, (byte) 250, (byte) 101, (byte) 253, (byte) 87, - (byte) 140, (byte) 168, (byte) 39, (byte) 163, (byte) 133, - (byte) 150, (byte) 252, (byte) 226, (byte) 234, (byte) 52, - (byte) 88, (byte) 40, (byte) 56, (byte) 23, (byte) 105, (byte) 236, - (byte) 4, (byte) 113, (byte) 98, (byte) 4, (byte) 0, (byte) 117, - (byte) 59, (byte) 77, (byte) 236, (byte) 135, (byte) 93, (byte) 54, - (byte) 30, (byte) 6, (byte) 126, (byte) 90, (byte) 15, (byte) 105, - (byte) 89, (byte) 216, (byte) 154, (byte) 72, (byte) 134, - (byte) 209, (byte) 74, (byte) 197, (byte) 237, (byte) 51, - (byte) 37, (byte) 33, (byte) 106, (byte) 50, (byte) 71, (byte) 134, - (byte) 169, (byte) 173, (byte) 88, (byte) 111, (byte) 217, - (byte) 117, (byte) 184, (byte) 97, (byte) 1, (byte) 38, (byte) 76, - (byte) 112, (byte) 170, (byte) 190, (byte) 250, (byte) 96, - (byte) 17, (byte) 45, (byte) 117, (byte) 183, (byte) 82, - (byte) 155, (byte) 10, (byte) 53, (byte) 15, (byte) 214, (byte) 36, - (byte) 134, (byte) 249, (byte) 146, (byte) 98, (byte) 99, - (byte) 64, (byte) 158, (byte) 99, (byte) 227, (byte) 21, (byte) 92, - (byte) 98, (byte) 90, (byte) 202, (byte) 214, (byte) 134, - (byte) 233, (byte) 212, (byte) 149, (byte) 2, (byte) 3, (byte) 1, - (byte) 0, (byte) 1, (byte) 163, (byte) 33, (byte) 48, (byte) 31, - (byte) 48, (byte) 29, (byte) 6, (byte) 3, (byte) 85, (byte) 29, - (byte) 14, (byte) 4, (byte) 22, (byte) 4, (byte) 20, (byte) 115, - (byte) 110, (byte) 177, (byte) 165, (byte) 41, (byte) 26, - (byte) 142, (byte) 198, (byte) 221, (byte) 63, (byte) 79, - (byte) 252, (byte) 219, (byte) 159, (byte) 68, (byte) 102, - (byte) 76, (byte) 153, (byte) 128, (byte) 164, (byte) 48, - (byte) 13, (byte) 6, (byte) 9, (byte) 42, (byte) 134, (byte) 72, - (byte) 134, (byte) 247, (byte) 13, (byte) 1, (byte) 1, (byte) 11, - (byte) 5, (byte) 0, (byte) 3, (byte) 130, (byte) 1, (byte) 1, - (byte) 0, (byte) 118, (byte) 55, (byte) 245, (byte) 122, - (byte) 159, (byte) 155, (byte) 98, (byte) 122, (byte) 229, - (byte) 186, (byte) 23, (byte) 207, (byte) 109, (byte) 225, - (byte) 220, (byte) 74, (byte) 51, (byte) 218, (byte) 10, - (byte) 115, (byte) 137, (byte) 103, (byte) 127, (byte) 28, - (byte) 30, (byte) 184, (byte) 149, (byte) 249, (byte) 193, - (byte) 206, (byte) 208, (byte) 181, (byte) 191, (byte) 128, - (byte) 18, (byte) 208, (byte) 24, (byte) 132, (byte) 147, - (byte) 184, (byte) 198, (byte) 82, (byte) 204, (byte) 183, - (byte) 127, (byte) 87, (byte) 234, (byte) 136, (byte) 197, - (byte) 34, (byte) 232, (byte) 124, (byte) 210, (byte) 2, - (byte) 192, (byte) 69, (byte) 246, (byte) 25, (byte) 232, - (byte) 162, (byte) 0, (byte) 157, (byte) 216, (byte) 194, - (byte) 26, (byte) 207, (byte) 225, (byte) 169, (byte) 59, - (byte) 246, (byte) 52, (byte) 51, (byte) 150, (byte) 210, - (byte) 50, (byte) 118, (byte) 58, (byte) 154, (byte) 45, - (byte) 128, (byte) 138, (byte) 47, (byte) 174, (byte) 83, - (byte) 117, (byte) 18, (byte) 224, (byte) 9, (byte) 146, - (byte) 180, (byte) 178, (byte) 22, (byte) 76, (byte) 82, - (byte) 229, (byte) 16, (byte) 150, (byte) 127, (byte) 13, - (byte) 122, (byte) 218, (byte) 159, (byte) 195, (byte) 232, - (byte) 168, (byte) 206, (byte) 105, (byte) 82, (byte) 37, - (byte) 252, (byte) 186, (byte) 223, (byte) 222, (byte) 7, - (byte) 106, (byte) 87, (byte) 218, (byte) 89, (byte) 22, - (byte) 252, (byte) 7, (byte) 177, (byte) 52, (byte) 180, (byte) 9, - (byte) 16, (byte) 29, (byte) 57, (byte) 192, (byte) 209, - (byte) 225, (byte) 155, (byte) 16, (byte) 219, (byte) 38, - (byte) 90, (byte) 174, (byte) 152, (byte) 140, (byte) 252, - (byte) 114, (byte) 133, (byte) 106, (byte) 24, (byte) 107, - (byte) 227, (byte) 80, (byte) 166, (byte) 63, (byte) 47, (byte) 16, - (byte) 15, (byte) 89, (byte) 242, (byte) 19, (byte) 87, (byte) 193, - (byte) 250, (byte) 222, (byte) 223, (byte) 183, (byte) 61, - (byte) 91, (byte) 17, (byte) 92, (byte) 35, (byte) 142, (byte) 44, - (byte) 153, (byte) 135, (byte) 86, (byte) 97, (byte) 70, - (byte) 205, (byte) 38, (byte) 192, (byte) 18, (byte) 244, - (byte) 61, (byte) 46, (byte) 21, (byte) 145, (byte) 99, (byte) 72, - (byte) 142, (byte) 37, (byte) 19, (byte) 219, (byte) 167, - (byte) 62, (byte) 71, (byte) 197, (byte) 86, (byte) 152, - (byte) 139, (byte) 122, (byte) 231, (byte) 122, (byte) 206, - (byte) 42, (byte) 142, (byte) 164, (byte) 237, (byte) 19, - (byte) 60, (byte) 95, (byte) 239, (byte) 191, (byte) 64, - (byte) 188, (byte) 94, (byte) 154, (byte) 199, (byte) 252, - (byte) 62, (byte) 26, (byte) 181, (byte) 194, (byte) 141, - (byte) 13, (byte) 1, (byte) 112, (byte) 161, (byte) 195, - (byte) 149, (byte) 116, (byte) 57, (byte) 118, (byte) 114, - (byte) 248, (byte) 235, (byte) 54, (byte) 229, (byte) 48, - (byte) 53, (byte) 30, (byte) 145, (byte) 199, (byte) 207, - (byte) 49, (byte) 175, (byte) 44, (byte) 172, (byte) 120, - (byte) 254, (byte) 181, (byte) 100, (byte) 113, (byte) 191, - (byte) 64, (byte) 131, (byte) 125, (byte) 80, (byte) 180, - (byte) 229, (byte) 109, (byte) 97, (byte) 8, (byte) 166, - (byte) 155, (byte) 72, (byte) 252, (byte) 84, (byte) 62, (byte) 97, - (byte) 80, (byte) 26, (byte) 17, (byte) 143, (byte) 96, (byte) 16, - (byte) 204, (byte) 86, (byte) 61, (byte) 226, (byte) 149 }; - - - public static KeyStore getKeyStore() - { - KeyStore ks = null; - try{ - ks = KeyStore.getInstance("JKS"); - ks.load(asInputStream(), getKeyStorePassword()); - }catch(Exception ex){ - throw new RuntimeException("Failed to load SSL key store.", ex); - } - return ks; - } - - public static InputStream asInputStream() { - return new ByteArrayInputStream(CERT_BYTES); - } - - public static char[] getCertificatePassword() { - return "inc0rrect".toCharArray(); - } - - public static char[] getKeyStorePassword() { - return "mu$tch8ng3".toCharArray(); - } - - public static String getCertificatePasswordString() { - return "inc0rrect"; - } - - public static String getKeyStorePasswordString() { - return "mu$tch8ng3"; - } - - private SecureSocketKeyStore() { - - } - -} diff --git a/src/main/java/com/myself/nettychat/common/ssl/SecureSocketSslContextFactory.java b/src/main/java/com/myself/nettychat/common/ssl/SecureSocketSslContextFactory.java deleted file mode 100644 index d4a0bf7..0000000 --- a/src/main/java/com/myself/nettychat/common/ssl/SecureSocketSslContextFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.myself.nettychat.common.ssl; - -import io.netty.util.internal.SystemPropertyUtil; - -import javax.net.ssl.*; -import java.security.KeyStore; -import java.security.SecureRandom; - -/** - * Creates a bogus {@link SSLContext}. A client-side context created by this - * factory accepts any certificate even if it is invalid. A server-side context - * created by this factory sends a bogus certificate defined in {@link }. - *

- * You will have to create your context differently in a real world application. - * - *

Client Certificate Authentication

- * - * To enable client certificate authentication: - *
    - *
  • Enable client authentication on the server side by calling - * {@link SSLEngine#setNeedClientAuth(boolean)} before creating - * {@link }.
  • - *
  • When initializing an {@link SSLContext} on the client side, - * specify the {@link KeyManager} that contains the client certificate as - * the first argument of {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)}.
  • - *
  • When initializing an {@link SSLContext} on the server side, - * specify the proper {@link TrustManager} as the second argument of - * {@link SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} - * to validate the client certificate.
  • - *
- */ -public final class SecureSocketSslContextFactory { - - private static final String PROTOCOL = "TLS"; - private static final SSLContext SERVER_CONTEXT; - private static final SSLContext CLIENT_CONTEXT; - - static { - String algorithm = SystemPropertyUtil.get("ssl.KeyManagerFactory.algorithm"); - if (algorithm == null) { - algorithm = "SunX509"; - } - - SSLContext serverContext; - SSLContext clientContext; - try { - // - //SecureSocketSslContextFactory.class.getResourceAsStream("/securesocket.jks") - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(SecureSocketKeyStore.asInputStream(), - SecureSocketKeyStore.getKeyStorePassword()); - - // Set up key manager factory to use our key store - KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); - kmf.init(ks, SecureSocketKeyStore.getCertificatePassword()); - - // Initialize the SSLContext to work with our key managers. - serverContext = SSLContext.getInstance(PROTOCOL); - serverContext.init(kmf.getKeyManagers(), null, null); - } catch (Exception e) { - throw new Error( - "Failed to initialize the server-side SSLContext", e); - } - - try { - clientContext = SSLContext.getInstance(PROTOCOL); - clientContext.init(null, SecureSokcetTrustManagerFactory.getTrustManagers(), null); - } catch (Exception e) { - throw new Error( - "Failed to initialize the client-side SSLContext", e); - } - - SERVER_CONTEXT = serverContext; - CLIENT_CONTEXT = clientContext; - } - - public static SSLContext getServerContext() { - return SERVER_CONTEXT; - } - - public static SSLContext getClientContext() { - return CLIENT_CONTEXT; - } - - private SecureSocketSslContextFactory() { - // Unused - } - -} diff --git a/src/main/java/com/myself/nettychat/common/ssl/SecureSokcetTrustManagerFactory.java b/src/main/java/com/myself/nettychat/common/ssl/SecureSokcetTrustManagerFactory.java deleted file mode 100644 index fdc57a1..0000000 --- a/src/main/java/com/myself/nettychat/common/ssl/SecureSokcetTrustManagerFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.myself.nettychat.common.ssl; - -import javax.net.ssl.ManagerFactoryParameters; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactorySpi; -import javax.net.ssl.X509TrustManager; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.cert.X509Certificate; - -/** - * Bogus {@link TrustManagerFactorySpi} which accepts any certificate - * even if it is invalid. - */ -public class SecureSokcetTrustManagerFactory extends TrustManagerFactorySpi { - - private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() { - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { - // Always trust - it is an example. - // You should do something in the real world. - // You will reach here only if you enabled client certificate auth, - // as described in SecureChatSslContextFactory. - System.err.println( - "UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN()); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { - // Always trust - it is an example. - // You should do something in the real world. - System.err.println( - "UNKNOWN SERVER CERTIFICATE: 222 " + chain[0].getSubjectDN()); - } - }; - - public static TrustManager[] getTrustManagers() { - return new TrustManager[] { DUMMY_TRUST_MANAGER }; - } - - @Override - protected TrustManager[] engineGetTrustManagers() { - return getTrustManagers(); - } - - @Override - protected void engineInit(KeyStore keystore) throws KeyStoreException { - // Unused - } - - @Override - protected void engineInit(ManagerFactoryParameters managerFactoryParameters) - throws InvalidAlgorithmParameterException { - // Unused - } - -} diff --git a/src/main/java/com/myself/nettychat/common/ssl/StreamReader.java b/src/main/java/com/myself/nettychat/common/ssl/StreamReader.java deleted file mode 100644 index 25cf5b7..0000000 --- a/src/main/java/com/myself/nettychat/common/ssl/StreamReader.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.myself.nettychat.common.ssl; - -import java.io.InputStream; - -public class StreamReader { - - public String toByteArray(InputStream fin) - { - int i = -1; - StringBuilder buf = new StringBuilder(); - try{ - while((i=fin.read())!=-1){ - if(buf.length()>0) buf.append(","); - buf.append("(byte)"); - buf.append(i); - } - - }catch(Throwable e){ - ; - } - - return buf.toString(); - } - -} diff --git a/src/main/java/com/myself/nettychat/common/ssl/X509CertTool.java b/src/main/java/com/myself/nettychat/common/ssl/X509CertTool.java deleted file mode 100644 index be36d26..0000000 --- a/src/main/java/com/myself/nettychat/common/ssl/X509CertTool.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.myself.nettychat.common.ssl; - - - - -import sun.security.x509.*; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.SecureRandom; -import java.security.cert.X509Certificate; -import java.util.Date; - - -/** - * This class would require rt.jar in the class path in order to - * generated it alternative is using keytool. - */ -public class X509CertTool { - - /** - * Create a self-signed X.509 Certificate - * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB" - * @param pair the KeyPair - * @param days how many days from now the Certificate is valid for - * @param algorithm the signing algorithm, eg "SHA1withRSA" - */ - @SuppressWarnings("restriction") - X509Certificate generateCertificate(String dn, KeyPair pair, int days, - String algorithm) throws GeneralSecurityException, IOException { - PrivateKey privkey = pair.getPrivate(); - X509CertInfo info = new X509CertInfo(); - Date from = new Date(); - Date to = new Date(from.getTime() + days * 86400000l); - CertificateValidity interval = new CertificateValidity(from, to); - BigInteger sn = new BigInteger(64, new SecureRandom()); - X500Name owner = new X500Name(dn); - - info.set(X509CertInfo.VALIDITY, interval); - info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn)); - info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner)); - info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner)); - info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic())); - info.set(X509CertInfo.VERSION, new CertificateVersion( - CertificateVersion.V3)); - AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid); - info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo)); - - // Sign the cert to identify the algorithm that's used. - X509CertImpl cert = new X509CertImpl(info); - cert.sign(privkey, algorithm); - - // Update the algorith, and resign. - algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG); - info.set(CertificateAlgorithmId.NAME + "." - + CertificateAlgorithmId.ALGORITHM, algo); - cert = new X509CertImpl(info); - cert.sign(privkey, algorithm); - return cert; - } - - public static void main(String[] args) { - - } - -} diff --git a/src/main/java/com/myself/nettychat/common/utils/SendUtil.java b/src/main/java/com/myself/nettychat/common/utils/SendUtil.java index c902996..a110caa 100644 --- a/src/main/java/com/myself/nettychat/common/utils/SendUtil.java +++ b/src/main/java/com/myself/nettychat/common/utils/SendUtil.java @@ -33,6 +33,48 @@ public boolean send(Integer item, Channel channel, String channelID, String type return false; } + /** + * 消息测试 + * @param channel + * @param result + * @return + */ + public boolean send(Channel channel, String result){ + try { + if (channel != null){ + System.out.println("send:" + result); + ByteBuf msg = Unpooled.unreleasableBuffer( + Unpooled.copiedBuffer(result, Charset.forName("UTF-8"))); + channel.writeAndFlush(msg.duplicate()); + return true; + } + }catch (Exception e){ + System.out.println(e.getMessage()); + return false; + } + return false; + } + + /** + * 向websocket端发送信息 + * @param channel + * @param result + * @return + */ + public boolean sendWebSocket(Channel channel, String result){ + try { + if (channel != null){ + System.out.println("send:" + result); + channel.writeAndFlush(new TextWebSocketFrame(result)); + return true; + } + }catch (Exception e){ + System.out.println(e.getMessage()); + return false; + } + return false; + } + /** * 广播发送事件 * @param items diff --git a/src/main/java/com/myself/nettychat/common/zookeeper/ZkStateListener.java b/src/main/java/com/myself/nettychat/common/zookeeper/ZkStateListener.java deleted file mode 100644 index b2658fa..0000000 --- a/src/main/java/com/myself/nettychat/common/zookeeper/ZkStateListener.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.myself.nettychat.common.zookeeper; - -import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.state.ConnectionState; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc zookeeper 监听 - **/ -public interface ZkStateListener { - - default void connectedEvent(CuratorFramework curator, ConnectionState state) { - } - - default void ReconnectedEvent(CuratorFramework curator, ConnectionState state) { - } - - default void lostEvent(CuratorFramework curator, ConnectionState state) { - } - -} diff --git a/src/main/java/com/myself/nettychat/common/zookeeper/ZkUtils.java b/src/main/java/com/myself/nettychat/common/zookeeper/ZkUtils.java deleted file mode 100644 index 1cc853b..0000000 --- a/src/main/java/com/myself/nettychat/common/zookeeper/ZkUtils.java +++ /dev/null @@ -1,366 +0,0 @@ -package com.myself.nettychat.common.zookeeper; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import com.google.common.base.Charsets; -import com.google.common.base.Objects; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.CuratorFrameworkFactory; -import org.apache.curator.framework.api.GetDataBuilder; -import org.apache.curator.framework.recipes.cache.NodeCache; -import org.apache.curator.framework.recipes.cache.PathChildrenCache; -import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; -import org.apache.curator.framework.recipes.cache.TreeCache; -import org.apache.curator.framework.state.ConnectionState; -import org.apache.curator.retry.RetryNTimes; -import org.apache.curator.utils.CloseableUtils; -import org.apache.curator.utils.ZKPaths; -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.data.Stat; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -@Slf4j -@Data -@NoArgsConstructor -public class ZkUtils { - - private CuratorFramework zkClient = null; - - ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); - - List pathChildrenCaches = new CopyOnWriteArrayList<>(); - - List nodeCaches = new CopyOnWriteArrayList<>(); - - List treeCaches = new CopyOnWriteArrayList<>(); - - - /** - * 初始化zk链接 - * - * @param zookeeperServer - * @param connectionTimeout - * @param sessionTimeout - * @param maxRetries - * @param retriesSleepTime - * @param namespace - * @param listener - */ - public void init(String zookeeperServer, - int connectionTimeout, - int sessionTimeout, - int maxRetries, int retriesSleepTime, - String namespace, - ZkStateListener listener) { - if (zkClient == null) { - zkClient = CuratorFrameworkFactory.builder() - .connectString(zookeeperServer) - .connectionTimeoutMs(connectionTimeout) - .sessionTimeoutMs(sessionTimeout) - .namespace(namespace) - .retryPolicy(new RetryNTimes(maxRetries, retriesSleepTime)) - .build(); - } - zkClient.getConnectionStateListenable().addListener((curatorFramework, connectionState) -> { - pathChildrenCaches.clear(); - nodeCaches.clear(); - treeCaches.clear(); - if (connectionState == ConnectionState.CONNECTED) { - listener.connectedEvent(curatorFramework, connectionState); - } else if (connectionState == ConnectionState.RECONNECTED) { - listener.ReconnectedEvent(curatorFramework, connectionState); - } else if (connectionState == ConnectionState.LOST) { - listener.lostEvent(curatorFramework, connectionState); - } - }); - zkClient.start(); - } - /** - * 销毁所有 - */ - public void destory() { - pathChildrenCaches.stream().forEach(cache -> CloseableUtils.closeQuietly(cache)); - pathChildrenCaches.clear(); - pathChildrenCaches = null; - nodeCaches.stream().forEach(cache -> CloseableUtils.closeQuietly(cache)); - nodeCaches.clear(); - nodeCaches = null; - treeCaches.stream().forEach(cache -> CloseableUtils.closeQuietly(cache)); - treeCaches.clear(); - treeCaches = null; - if (zkClient != null) { - CloseableUtils.closeQuietly(zkClient); - } - } - - - /** - * 创建节点 - * - * @param path - * @param data - * @param mode - * @return - */ - public boolean createNode(String path, String data, CreateMode mode) { - if (!ObjectUtils.allNotNull(zkClient, path)) { - return Boolean.FALSE; - } - try { - Stat stat = exists(path); - if (stat == null) { - mode = mode == null ? CreateMode.PERSISTENT : mode; - String opResult; - if (ObjectUtils.allNotNull(data)) { - opResult = zkClient.create().creatingParentContainersIfNeeded().withMode(mode).forPath(path, data.getBytes(Charsets.UTF_8)); - } else { - opResult = zkClient.create().creatingParentContainersIfNeeded().withMode(mode).forPath(path); - } - return Objects.equal(opResult, path); - } - return Boolean.TRUE; - } catch (Exception e) { - log.error("create node fail! path: {}, error: {}", path, e); - } - return Boolean.FALSE; - } - - - /** - * 删除节点 递归删除子节点 - * - * @param path - * @param version - * @return - */ - public boolean deleteNode(String path, Integer version) { - if (!ObjectUtils.allNotNull(zkClient, path)) { - return Boolean.FALSE; - } - try { - Stat stat = exists(path); - if (stat != null) { - if (version == null) { - zkClient.delete().deletingChildrenIfNeeded().forPath(path); - } else { - zkClient.delete().deletingChildrenIfNeeded().withVersion(version).forPath(path); - } - } - return Boolean.TRUE; - } catch (Exception e) { - log.error("delete node fail! path: {}, error: {}", path, e); - } - return Boolean.FALSE; - } - - /** - * 删除节点 - * - * @param path - * @return - */ - public boolean deleteNode(String path) { - return deleteNode(path, null); - } - - /** - * 获取指定节点的值 - * - * @param path - * @return - */ - public byte[] getNodeData(String path) { - if (!ObjectUtils.allNotNull(zkClient, path)) { - return null; - } - try { - Stat stat = exists(path); - if (stat != null) { - return zkClient.getData().forPath(path); - } - } catch (Exception e) { - log.error("get node data fail! path: {}, error: {}", path, e); - } - return null; - } - - /** - * 获取指定节点的值 - * - * @param path - * @return - */ - public String getNodeDataStr(String path) { - byte[] val = getNodeData(path); - return val == null ? null : new String(val, Charsets.UTF_8); - } - - /** - * 检查节点是否存在 - * - * @param path - * @return - */ - public Stat exists(String path) { - if (!ObjectUtils.allNotNull(zkClient, path)) { - return null; - } - try { - return zkClient.checkExists().forPath(path); - } catch (Exception e) { - log.error("check node exists fail! path: {}, error: {}", path, e); - } - return null; - } - - /** - * 检查节点是否存在 - * - * @param path - * @return - */ - public boolean checkExists(String path) { - return exists(path) == null ? Boolean.FALSE : Boolean.TRUE; - } - - /** - * 设置节点数据 - * - * @param path - * @param data - * @return - */ - public boolean setNodeData(String path, String data) { - return setNodeData(path, data.getBytes(Charsets.UTF_8), null); - } - - /** - * 设置节点数据 - * - * @param path - * @param data - * @param version - * @return - */ - public boolean setNodeData(String path, byte[] data, Integer version) { - if (!ObjectUtils.allNotNull(zkClient, path)) { - return Boolean.FALSE; - } - try { - Stat stat = exists(path); - if (stat != null) { - if (version == null) { - zkClient.setData().forPath(path, data); - } else { - zkClient.setData().withVersion(version).forPath(path, data); - } - return Boolean.TRUE; - } - } catch (Exception e) { - log.error("set node data fail! path: {}, error: {}", path, e); - } - return Boolean.FALSE; - } - - /** - * 设置子节点更改监听 - * - * @param path - * @throws Exception - */ - public boolean listenerPathChildrenCache(String path, BiConsumer biConsumer) { - - if (!ObjectUtils.allNotNull(zkClient, path, biConsumer)) { - return Boolean.FALSE; - } - try { - Stat stat = exists(path); - if (stat != null) { - PathChildrenCache watcher = new PathChildrenCache(zkClient, path, true); - watcher.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT); - //该模式下 watcher在重连的时候会自动 rebuild - watcher.getListenable().addListener(biConsumer::accept, pool); - if (!pathChildrenCaches.contains(watcher)) { - pathChildrenCaches.add(watcher); - } -// else{ -// watcher.rebuild(); -// } - return Boolean.TRUE; - } - } catch (Exception e) { - log.error("listen path children cache fail! path:{} , error:{}", path, e); - } - return Boolean.FALSE; - } - - - /** - * 读取指定节点的子菜单的值 - * - * @param path - * @return - */ - public Map readTargetChildsData(String path) { - if (!ObjectUtils.allNotNull(zkClient, path)) { - return null; - } - Map map = null; - try { - Stat stat = exists(path); - if (stat != null) { - List childrens = zkClient.getChildren().forPath(path); - GetDataBuilder dataBuilder = zkClient.getData(); - if (childrens != null) { - map = childrens.stream().collect(Collectors.toMap(Function.identity(), (child) -> { - try { - return new String(dataBuilder.forPath(ZKPaths.makePath(path, child)), Charsets.UTF_8); - } catch (Exception e1) { - return null; - } - })); - } - } - } catch (Exception e) { - log.error("get target childs data fail!, path:{} , error:{}", path, e); - } - return map; - - } - - - public static void main(String[] a) throws Exception { - ZkUtils zkUtils = new ZkUtils(); - zkUtils.init("127.0.0.1:2181", 1000, 2000, 5, 1000, "test", new ZkStateListener() { - @Override - public void connectedEvent(CuratorFramework curator, ConnectionState state) { - - } - - @Override - public void ReconnectedEvent(CuratorFramework curator, ConnectionState state) { - - } - - @Override - public void lostEvent(CuratorFramework curator, ConnectionState state) { - - } - }); - zkUtils.getZkClient().create().creatingParentContainersIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/yy/uu/ii"); - - Thread.sleep(Long.MAX_VALUE); - } - -} diff --git a/src/main/java/com/myself/nettychat/common/zookeeper/testZk.java b/src/main/java/com/myself/nettychat/common/zookeeper/testZk.java deleted file mode 100644 index e51292b..0000000 --- a/src/main/java/com/myself/nettychat/common/zookeeper/testZk.java +++ /dev/null @@ -1,256 +0,0 @@ -package com.myself.nettychat.common.zookeeper; - -import org.apache.curator.RetryPolicy; -import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.CuratorFrameworkFactory; -import org.apache.curator.framework.api.ACLProvider; -import org.apache.curator.framework.api.CuratorEvent; -import org.apache.curator.framework.api.CuratorListener; -import org.apache.curator.framework.recipes.cache.*; -import org.apache.curator.retry.ExponentialBackoffRetry; -import org.apache.curator.retry.RetryNTimes; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZooDefs; -import org.apache.zookeeper.data.ACL; -import org.apache.zookeeper.data.Id; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * @author MySelf - * @create 2018/9/22 - * @desc 测试 - **/ -public class testZk { - - /** - * 先测试玩玩 - * @描述:XXXXXXX - * @param args - * @return void - * @exception - * @createTime:2016年5月17日 - * @author: songqinghu - * @throws Exception - */ - public static void main(String[] args) throws Exception { - CuratorFramework client = clientTwo(); - //setListenterDateNode(); - //setListenterThreeOne(client); - // setListenterThreeTwo(client); - setListenterThreeThree(client); - // getDataNode(client, "/two"); - // setDataNode(client, "/two", "sss"); - - } - - /** - * - * @描述:创建一个zookeeper连接---连接方式一: 最简单的连接 - * @return void - * @exception - * @createTime:2016年5月17日 - * @author: songqinghu - */ - private static CuratorFramework clientOne(){ - //zk 地址 - String connectString = "10.125.2.44:2181"; - // 连接时间 和重试次数 - RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); - CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy); - client.start(); - return client; - } - - /** - * - * @描述:创建一个zookeeper连接---连接方式二:优选这个 - * @return void - * @exception - * @createTime:2016年5月17日 - * @author: songqinghu - */ - private static CuratorFramework clientTwo(){ - - //默认创建的根节点是没有做权限控制的--需要自己手动加权限???---- - ACLProvider aclProvider = new ACLProvider() { - private List acl ; - @Override - public List getDefaultAcl() { - if(acl ==null){ - ArrayList acl = ZooDefs.Ids.CREATOR_ALL_ACL; - acl.clear(); - acl.add(new ACL(ZooDefs.Perms.ALL, new Id("auth", "admin:admin") )); - this.acl = acl; - } - return acl; - } - @Override - public List getAclForPath(String path) { - return acl; - } - }; - String scheme = "digest"; - byte[] auth = "admin:admin".getBytes(); - int connectionTimeoutMs = 5000; - String connectString = "127.0.0.1:2181"; - String namespace = "testnamespace"; - CuratorFramework client = CuratorFrameworkFactory.builder().aclProvider(aclProvider). - authorization(scheme, auth). - connectionTimeoutMs(connectionTimeoutMs). - connectString(connectString). - namespace(namespace). - retryPolicy(new RetryNTimes(Integer.MAX_VALUE, 1000)).build(); - client.start(); - return client; - } - - - /** - * - * @描述:第一种监听器的添加方式: 对指定的节点进行添加操作 - * 仅仅能监控指定的本节点的数据修改,删除 操作 并且只能监听一次 --->不好 - * @return void - * @exception - * @createTime:2016年5月18日 - * @author: songqinghu - * @throws Exception - */ - private static void setListenterOne(CuratorFramework client) throws Exception{ - // 注册观察者,当节点变动时触发 - byte[] data = client.getData().usingWatcher(new Watcher() { - @Override - public void process(WatchedEvent event) { - System.out.println("获取 two 节点 监听器 : " + event); - } - }).forPath("/two"); - System.out.println("two 节点数据: "+ new String(data)); - } - /** - * - * @描述:第二种监听器的添加方式: - * 也是一次性的监听操作,使用后就无法在继续监听了 - * @return void - * @exception - * @createTime:2016年5月18日 - * @author: songqinghu - * @throws Exception - */ - private static void setListenterTwo(CuratorFramework client) throws Exception{ - - ExecutorService pool = Executors.newCachedThreadPool(); - - CuratorListener listener = new CuratorListener() { - @Override - public void eventReceived(CuratorFramework client, CuratorEvent event) throws Exception { - System.out.println("监听器 : "+ event.toString()); - } - }; - client.getCuratorListenable().addListener(listener,pool); - client.getData().inBackground().forPath("/two"); - client.getData().inBackground().forPath("/two"); - client.getData().inBackground().forPath("/two"); - client.getData().inBackground().forPath("/two"); - Thread.sleep(Long.MAX_VALUE ); - } - /** - * - * @描述:第三种监听器的添加方式: Cache 的三种实现 实践 - * Path Cache:监视一个路径下1)孩子结点的创建、2)删除,3)以及结点数据的更新。 - * 产生的事件会传递给注册的PathChildrenCacheListener。 - * Node Cache:监视一个结点的创建、更新、删除,并将结点的数据缓存在本地。 - * Tree Cache:Path Cache和Node Cache的“合体”,监视路径下的创建、更新、删除事件,并缓存路径下所有孩子结点的数据。 - * @return void - * @exception - * @createTime:2016年5月18日 - * @author: songqinghu - * @throws Exception - */ - //1.path Cache 连接 路径 是否获取数据 - //能监听所有的字节点 且是无限监听的模式 但是 指定目录下节点的子节点不再监听 - private static void setListenterThreeOne(CuratorFramework client) throws Exception{ - ExecutorService pool = Executors.newCachedThreadPool(); - PathChildrenCache childrenCache = new PathChildrenCache(client, "/test", true); - PathChildrenCacheListener childrenCacheListener = new PathChildrenCacheListener() { - @Override - public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception { - System.out.println("开始进行事件分析:-----"); - ChildData data = event.getData(); - switch (event.getType()) { - case CHILD_ADDED: - System.out.println("CHILD_ADDED : "+ data.getPath() +" 数据:"+ data.getData()); - break; - case CHILD_REMOVED: - System.out.println("CHILD_REMOVED : "+ data.getPath() +" 数据:"+ data.getData()); - break; - case CHILD_UPDATED: - System.out.println("CHILD_UPDATED : "+ data.getPath() +" 数据:"+ data.getData()); - break; - default: - break; - } - } - }; - childrenCache.getListenable().addListener(childrenCacheListener); - System.out.println("Register zk watcher successfully!"); - childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT); - } - - //2.Node Cache 监控本节点的变化情况 连接 目录 是否压缩 - //监听本节点的变化 节点可以进行修改操作 删除节点后会再次创建(空节点) - private static void setListenterThreeTwo(CuratorFramework client) throws Exception{ - ExecutorService pool = Executors.newCachedThreadPool(); - //设置节点的cache - final NodeCache nodeCache = new NodeCache(client, "/test", false); - nodeCache.getListenable().addListener(new NodeCacheListener() { - @Override - public void nodeChanged() throws Exception { - System.out.println("the test node is change and result is :"); - System.out.println("path : "+nodeCache.getCurrentData().getPath()); - System.out.println("data : "+new String(nodeCache.getCurrentData().getData())); - System.out.println("stat : "+nodeCache.getCurrentData().getStat()); - } - }); - nodeCache.start(); - } - //3.Tree Cache - // 监控 指定节点和节点下的所有的节点的变化--无限监听 可以进行本节点的删除(不在创建) - private static void setListenterThreeThree(CuratorFramework client) throws Exception{ - ExecutorService pool = Executors.newCachedThreadPool(); - //设置节点的cache - TreeCache treeCache = new TreeCache(client, "/test"); - //设置监听器和处理过程 - treeCache.getListenable().addListener(new TreeCacheListener() { - @Override - public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception { - ChildData data = event.getData(); - if(data !=null){ - switch (event.getType()) { - case NODE_ADDED: - System.out.println("NODE_ADDED : "+ data.getPath() +" 数据:"+ new String(data.getData())); - break; - case NODE_REMOVED: - System.out.println("NODE_REMOVED : "+ data.getPath() +" 数据:"+ new String(data.getData())); - break; - case NODE_UPDATED: - System.out.println("NODE_UPDATED : "+ data.getPath() +" 数据:"+ new String(data.getData())); - break; - - default: - break; - } - }else{ - System.out.println( "data is null : "+ event.getType()); - } - } - }); - //开始监听 - treeCache.start(); - - } - -} diff --git a/src/main/java/com/myself/nettychat/config/NettyConfig.java b/src/main/java/com/myself/nettychat/config/NettyConfig.java new file mode 100644 index 0000000..2648087 --- /dev/null +++ b/src/main/java/com/myself/nettychat/config/NettyConfig.java @@ -0,0 +1,75 @@ +package com.myself.nettychat.config; + +import com.myself.nettychat.common.properties.InitNetty; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @Author:UncleCatMySelf + * @Email:zhupeijie_java@126.com + * @QQ:1341933031 + * @Date:Created in 11:00 2018\8\14 0014 + */ +@Component +public class NettyConfig { + + @Autowired + private InitNetty nettyAccountConfig; + + @Bean(name = "bossGroup", destroyMethod = "shutdownGracefully") + public NioEventLoopGroup bossGroup(){ + return new NioEventLoopGroup(nettyAccountConfig.getBossThread()); + } + + @Bean(name = "workerGroup", destroyMethod = "shutdownGracefully") + public NioEventLoopGroup workerGroup(){ + return new NioEventLoopGroup(nettyAccountConfig.getWorkerThread()); + } + + @Bean(name = "webSocketAddress") + public InetSocketAddress tcpPost(){ + return new InetSocketAddress(nettyAccountConfig.getWebport()); + } + + @Bean(name = "tcpChannelOptions") + public Map, Object> tcpChannelOptions(){ + Map, Object> options = new HashMap, Object>(); + options.put(ChannelOption.TCP_NODELAY,nettyAccountConfig.isNodelay()); + options.put(ChannelOption.SO_KEEPALIVE, nettyAccountConfig.isKeepalive()); + options.put(ChannelOption.SO_BACKLOG, nettyAccountConfig.getBacklog()); + options.put(ChannelOption.SO_REUSEADDR,nettyAccountConfig.isReuseaddr()); + return options; + } + + @Autowired + @Qualifier("somethingChannelInitializer") + private NettyWebSocketChannelInitializer nettyWebSocketChannelInitializer; + + @Bean(name = "serverBootstrap") + public ServerBootstrap bootstrap(){ + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup(), workerGroup()) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.DEBUG)) + .childHandler(nettyWebSocketChannelInitializer); + Map, Object> tcpChannelOptions = tcpChannelOptions(); + Set> keySet = tcpChannelOptions.keySet(); + for (@SuppressWarnings("rawtypes") ChannelOption option : keySet) { + b.option(option, tcpChannelOptions.get(option)); + } + return b; + } +} diff --git a/src/main/java/com/myself/nettychat/config/NettyWebSocketChannelInitializer.java b/src/main/java/com/myself/nettychat/config/NettyWebSocketChannelInitializer.java new file mode 100644 index 0000000..a2f2eb4 --- /dev/null +++ b/src/main/java/com/myself/nettychat/config/NettyWebSocketChannelInitializer.java @@ -0,0 +1,40 @@ +package com.myself.nettychat.config; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +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.stream.ChunkedWriteHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + + +/** + * @Author:UncleCatMySelf + * @Email:zhupeijie_java@126.com + * @QQ:1341933031 + * @Date:Created in 11:00 2018\8\14 0014 + */ +@Component +@Qualifier("somethingChannelInitializer") +public class NettyWebSocketChannelInitializer extends ChannelInitializer { + + @Autowired + private TextWebSocketFrameHandler textWebSocketFrameHandler; + + @Override + public void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(65536)); + pipeline.addLast(new ChunkedWriteHandler()); + pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); + pipeline.addLast(textWebSocketFrameHandler); //这里不能使用new,不然在handler中不能注入依赖 + + } + +} diff --git a/src/main/java/com/myself/nettychat/config/TCPServer.java b/src/main/java/com/myself/nettychat/config/TCPServer.java index 973af54..ae478f4 100644 --- a/src/main/java/com/myself/nettychat/config/TCPServer.java +++ b/src/main/java/com/myself/nettychat/config/TCPServer.java @@ -20,20 +20,29 @@ @Component public class TCPServer { + @Autowired + @Qualifier("serverBootstrap") private ServerBootstrap serverBootstrap; @Autowired @Qualifier("tcpServerBootstrap") private ServerBootstrap tcpServerBootstrap; + @Autowired + @Qualifier("webSocketAddress") + private InetSocketAddress webPort; @Autowired @Qualifier("tcpSocketAddress") private InetSocketAddress tcpTcpPort; + private Channel serverChannel; private Channel tcpServerChannel; + public void startWeb() throws Exception { + serverChannel = serverBootstrap.bind(webPort).sync().channel().closeFuture().sync().channel(); + } public void startTcp() throws Exception { tcpServerChannel = tcpServerBootstrap.bind(tcpTcpPort).sync().channel().closeFuture().sync().channel(); @@ -41,6 +50,8 @@ public void startTcp() throws Exception { @PreDestroy public void stop() throws Exception { + serverChannel.close(); + serverChannel.parent().close(); tcpServerChannel.close(); tcpServerChannel.parent().close(); } diff --git a/src/main/java/com/myself/nettychat/config/TCPServerHandler.java b/src/main/java/com/myself/nettychat/config/TCPServerHandler.java index 8cc291c..87955da 100644 --- a/src/main/java/com/myself/nettychat/config/TCPServerHandler.java +++ b/src/main/java/com/myself/nettychat/config/TCPServerHandler.java @@ -1,6 +1,8 @@ package com.myself.nettychat.config; import com.myself.nettychat.common.utils.*; +import com.myself.nettychat.dto.CacheDTO; +import com.myself.nettychat.template.CacheTemplate; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; @@ -10,9 +12,11 @@ import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.concurrent.ScheduledFuture; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -30,210 +34,42 @@ public class TCPServerHandler extends ChannelInboundHandlerAdapter { static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + @Autowired + private CacheTemplate cacheTemplate; + @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String ChannelID = null; - try{ + try { String data = (String)msg; - System.out.println(data); - if (DataValida.ValidateHeadAndFeet(data)){ - data = DataResction.ResctionHeadAndFeet(data); - if (DataValida.ValidateCRCCode(DataResction.ResctionData(data),DataResction.ResctionCRCCode(data))){ - data = DataResction.ResctionData(data); - ChannelID = DataResction.ResctionID(data); - System.out.println("Const.hasChannelID(ChannelID):"+ Const.hasChannelID(ChannelID)); - //更换连接ID - if (!Const.hasChannelID(ChannelID)){ - String realChannelID = Const.isChannel(ctx.channel()); - System.out.println(realChannelID); - Const.ChangeClientId(realChannelID,ChannelID); - }else if(Const.hasChannelID(ChannelID) && !ctx.channel().equals(Const.get(ChannelID))){ - ctx.close(); - } - data = DataResction.ResctionDataNoID(data); - String type = DataResction.ResctionType(data); - String RealData = DataResction.ResctionRealData(data); - //数据类型判断 - switch (type){ - case "s": - //控制类型 - futureByController(ctx,RealData,ChannelID); - break; - case "g": - //经纬度传输 - futureByLoLa(ctx,RealData,ChannelID); - break; - case "v": - //设备电量信息 - RealData = DataResction.ResctionPower(RealData); - futureByCharge(ctx,RealData,ChannelID); - break; - case "p": - //设备检测物体信息 - futureByPStates(ctx,RealData,ChannelID); - break; - case "r": - //设备开关异常 - futureByException(ctx,RealData,ChannelID); - break; - case "j": - //客户端执行结果 - futureByChlientResult(ctx,RealData,ChannelID); - break; - case "t": - futureBYTesting(ctx,RealData,ChannelID); - break; - default: - //其他类型 - ctx.writeAndFlush(CallBackMessage.sendString( - CRC16MySelf.getAllString(ChannelID,Const.RESULT_TYPE,Const.ERROR))); - break; + //切换真实ID + String realChannelID = Const.isChannel(ctx.channel()); + ChannelID = data.substring(0,4); + String type = data.substring(4,5); + System.out.println(ChannelID); + Const.ChangeClientId(realChannelID,ChannelID); + if (type.equals("c")){ + String tip = data.substring(data.length()-1,data.length()); + System.out.println("tip="+tip); + if (cacheTemplate.hasCacheDTO(ChannelID)){ + System.out.println("true"); + List cacheDTOList = cacheTemplate.getDTO(ChannelID); + System.out.println(cacheDTOList.size()); + for (CacheDTO item: cacheDTOList){ + if (tip.equals(item.getMsg())){ + SendUtil sendUtil = new SendUtil(); + sendUtil.sendWebSocket((Channel) cacheTemplate.getChannel(item.getToken()),"开锁成功!"); + System.out.println("通知成功"); + } } - } else { - ctx.writeAndFlush(CallBackMessage.ERROR.duplicate()); - ctx.close(); } - } else { - ctx.writeAndFlush(CallBackMessage.ERROR.duplicate()); - ctx.close(); } + ctx.writeAndFlush(CallBackMessage.Check1_test.duplicate()); }finally { ReferenceCountUtil.release(msg); } } - - /** - * 客户端执行开锁测试执行方法 - * @param ctx - * @param realData - * @param ChannelID - */ - private void futureBYTesting(ChannelHandlerContext ctx, String realData, String ChannelID) { - Set ids = Const.getIdList(); - System.out.println("测试广播事件执行"); - for (String item : ids){ - SendUtil sendUtil = new SendUtil(); - Channel channel = Const.get(item); - if (channel != null){ - sendUtil.sendAll(realData,channel,item,Const.RESULT_TEXT); - } - } - } - - /** - * 客户端执行结果执行方法 - * @param ctx - * @param realData - */ - private void futureByChlientResult(ChannelHandlerContext ctx, String realData,String ChannelID) { - //测试方法 - ScheduledFuture future = ctx.channel().eventLoop().schedule( - new Runnable() { - @Override - public void run() { - System.out.println("-------尝试执行SQL操作--------客户端执行结果"); - } - },0, TimeUnit.SECONDS); - ctx.writeAndFlush(CallBackMessage.sendString( - CRC16MySelf.getAllString(ChannelID,Const.RESULT_TYPE,Const.SUCCESS))); - } - - /** - * 开关异常执行方法 - * @param ctx - * @param realData - */ - private void futureByException(ChannelHandlerContext ctx,final String realData,final String ChannelID) { - //测试方法 - ScheduledFuture future = ctx.channel().eventLoop().schedule( - new Runnable() { - @Override - public void run() { - System.out.println("-------尝试执行SQL操作--------开关异常"); - } - },0,TimeUnit.SECONDS); - ctx.writeAndFlush(CallBackMessage.sendString( - CRC16MySelf.getAllString(ChannelID,Const.RESULT_TYPE,Const.SUCCESS))); - } - - /** - * 设备电量执行方法 - * @param ctx - * @param realData - */ - private void futureByCharge(ChannelHandlerContext ctx,final String realData,final String ChannelID) { - //测试方法 - ScheduledFuture future = ctx.channel().eventLoop().schedule( - new Runnable() { - @Override - public void run() { - System.out.println("-------尝试执行SQL操作--------设备电量"); - } - },0,TimeUnit.SECONDS); - ctx.writeAndFlush(CallBackMessage.sendString( - CRC16MySelf.getAllString(ChannelID,Const.RESULT_TYPE,Const.SUCCESS))); - } - - /** - * 经纬度传输执行方法 - * @param ctx - * @param realData - */ - private void futureByLoLa(ChannelHandlerContext ctx, String realData, final String ChannelID) { - final String Longitude = DataResction.ResctionLongitude(realData); - final String Latitude = DataResction.ResctionLatitude(realData); - ScheduledFuture future = ctx.channel().eventLoop().schedule( - new Runnable() { - @Override - public void run() { - System.out.println("-------尝试执行SQL操作--------经纬度传输"); - } - },0,TimeUnit.SECONDS); - ctx.writeAndFlush(CallBackMessage.sendString( - CRC16MySelf.getAllString(ChannelID,Const.RESULT_TYPE,Const.SUCCESS))); - } - - /** - * 控制类执行方法 - * @param ctx - * @param realData - */ - private void futureByController(ChannelHandlerContext ctx,final String realData, final String ChannelID) { - //SQL入库操作 - ScheduledFuture future = ctx.channel().eventLoop().schedule( - new Runnable() { - @Override - public void run() { - System.out.println("-------尝试执行SQL操作--------控制类型"); - - } - },0,TimeUnit.SECONDS); - ctx.writeAndFlush(CallBackMessage.Check1_test.duplicate()); - } - - private void futureByPStates(ChannelHandlerContext ctx,final String realData,final String channelID) { - System.out.println("检测物体事件执行"); - ScheduledFuture future = ctx.channel().eventLoop().schedule( - new Runnable() { - @Override - public void run() { - System.out.println("-------尝试执行SQL操作--------物体检测类型"); - } - },0,TimeUnit.SECONDS); - } - - private String getUpdateKey(String channelID, String pstates, String realData) { - Integer openid = null; - for (int i = 0; i < realData.length(); i++){ - if(pstates.charAt(i) != realData.charAt(i)){ - openid = i; - break; - } - } - return channelID + "_" + openid; - } - @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); diff --git a/src/main/java/com/myself/nettychat/config/TextWebSocketFrameHandler.java b/src/main/java/com/myself/nettychat/config/TextWebSocketFrameHandler.java new file mode 100644 index 0000000..1ecfc3d --- /dev/null +++ b/src/main/java/com/myself/nettychat/config/TextWebSocketFrameHandler.java @@ -0,0 +1,80 @@ +package com.myself.nettychat.config; + +import com.myself.nettychat.common.utils.StringUtil; +import com.myself.nettychat.template.CacheTemplate; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.util.concurrent.GlobalEventExecutor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + + +/** + * @Author:UncleCatMySelf + * @Email:zhupeijie_java@126.com + * @QQ:1341933031 + * @Date:Created in 11:01 2018\8\14 0014 + */ +@Component +@Qualifier("textWebSocketFrameHandler") +@ChannelHandler.Sharable +public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler{ + + public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + @Autowired + private CacheTemplate cacheTemplate; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, + Object msg) throws Exception { + textWebSocketFrame(ctx, (TextWebSocketFrame) msg); + } + + private void textWebSocketFrame(ChannelHandlerContext ctx, TextWebSocketFrame msg) { + Channel incoming = ctx.channel(); + String token = StringUtil.getName(msg.text()); + String rMsg = StringUtil.getMsg(msg.text()); + System.out.println(token + rMsg); + cacheTemplate.saveChannel(token,incoming); + //TODO 心跳机制 + incoming.writeAndFlush(new TextWebSocketFrame("okay")); + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + System.out.println(ctx.channel().remoteAddress()); + channels.add(ctx.channel()); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + //删除存储池对应实例 + channels.remove(ctx.channel()); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + //在线 + } + + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + //掉线 + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) + throws Exception { + //异常 + cause.printStackTrace(); + ctx.close(); + } +} diff --git a/src/main/java/com/myself/nettychat/controller/NCBackController.java b/src/main/java/com/myself/nettychat/controller/NCBackController.java index 516eaba..05ab733 100644 --- a/src/main/java/com/myself/nettychat/controller/NCBackController.java +++ b/src/main/java/com/myself/nettychat/controller/NCBackController.java @@ -3,10 +3,16 @@ import com.myself.nettychat.common.utils.Const; import com.myself.nettychat.common.utils.ResultVOUtil; import com.myself.nettychat.common.utils.SendUtil; +import com.myself.nettychat.dto.CacheDTO; +import com.myself.nettychat.template.CacheTemplate; import com.myself.nettychat.vo.ResultVo; import io.netty.channel.Channel; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + /** * @Author:UncleCatMySelf * @Email:zhupeijie_java@126.com @@ -17,36 +23,65 @@ @RequestMapping("/back") public class NCBackController { - /** - * 获取当前连接数 - * @return - */ - @GetMapping(value = "/get_channel_size") - public ResultVo getChannelSize(){ - return ResultVOUtil.success(Const.getSzie()); - } + @Autowired + private CacheTemplate cacheTemplate; - /** - * 获取连接Id数组 - * @return - */ - @GetMapping(value = "/get_channel_id_list") - public ResultVo getChannelIDList(){ - return ResultVOUtil.success(Const.getIdList()); - } +// /** +// * 获取当前连接数 +// * @return +// */ +// @GetMapping(value = "/get_channel_size") +// public ResultVo getChannelSize(){ +// return ResultVOUtil.success(Const.getSzie()); +// } +// +// /** +// * 获取连接Id数组 +// * @return +// */ +// @GetMapping(value = "/get_channel_id_list") +// public ResultVo getChannelIDList(){ +// return ResultVOUtil.success(Const.getIdList()); +// } + +// /** +// * 向存在链接发送指定的端口 +// * @param channelId 连接Id +// * @param lock 打开第几把锁 +// * @return +// */ +// @PostMapping(value = "/send_to_channel") +// public ResultVo SendToChannel(String channelId,Integer lock){ +// SendUtil sendUtil = new SendUtil(); +// Channel channel = Const.get(channelId); +// if (channel != null){ +// if (sendUtil.send(lock,channel,channelId,Const.CONTROL_TYPE)){ +// return ResultVOUtil.success("【发送成功】"); +// } +// } +// return ResultVOUtil.error(888,"【发送失败】"); +// } /** - * 向存在链接发送指定的端口 - * @param channelId 连接Id - * @param lock 打开第几把锁 + * 单业务模拟-开锁 + * @param channelId 柜子ID--与tcp通信ID一致 + * @param msg 打开第几把锁 + * @param token 用户标识 * @return */ - @PostMapping(value = "/send_to_channel") - public ResultVo SendToChannel(String channelId,Integer lock){ + @PostMapping(value = "/send_test") + public ResultVo SendTest(String channelId,String msg,String token){ SendUtil sendUtil = new SendUtil(); Channel channel = Const.get(channelId); if (channel != null){ - if (sendUtil.send(lock,channel,channelId,Const.CONTROL_TYPE)){ + if (sendUtil.send(channel,msg)){ + String tip = msg.substring(msg.length()-1,msg.length()); + System.out.println(tip); + CacheDTO cacheDTO = new CacheDTO(token,tip); + List cacheDTOList = new ArrayList<>(); + cacheDTOList.add(cacheDTO); + //存储等待二次通知判断 + cacheTemplate.saveDTO(channelId,cacheDTOList); return ResultVOUtil.success("【发送成功】"); } } diff --git a/src/main/java/com/myself/nettychat/dto/CacheDTO.java b/src/main/java/com/myself/nettychat/dto/CacheDTO.java new file mode 100644 index 0000000..a0c8ccd --- /dev/null +++ b/src/main/java/com/myself/nettychat/dto/CacheDTO.java @@ -0,0 +1,22 @@ +package com.myself.nettychat.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Author:UncleCatMySelf + * @Email:zhupeijie_java@126.com + * @QQ:1341933031 + * @Date:Created in 22:01 2018\11\8 0008 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CacheDTO { + + private String token; + + private String msg; + +} diff --git a/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java b/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java index dbb7f2d..f2abae9 100644 --- a/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java +++ b/src/main/java/com/myself/nettychat/tcptest/TCPTestClient.java @@ -19,7 +19,7 @@ public class TCPTestClient { public static void main(String[] args) throws IOException { //10万测试 - for (int i = 0;i<2;i++){ + for (int i = 0;i<1;i++){ new Thread(new Runnable() { @Override public void run() { @@ -47,21 +47,21 @@ private static void runtest() throws IOException{ boolean flag = true; int i = 1; while(flag){ - //if (i == 1){ - //帧头+ID+数据类型+24把锁状态+crc校验+帧尾 - String str = "gzF5690137563CC8syyyyyyyyyyyyyyyyynnnnnnnf92fxr"; - //发送数据到服务端 - out.println(str); - if("bye".equals(str)){ - flag = false; - }else{ - try{ - //从服务器端接收数据有个时间限制(系统自设,也可以自己设置),超过了这个时间,便会抛出该异常 - String echo = buf.readLine(); - System.out.println(echo); - }catch(SocketTimeoutException e){ - System.out.println("Time out, No response"); + try{ + out.println("4400_test"); + //从服务器端接收数据有个时间限制(系统自设,也可以自己设置),超过了这个时间,便会抛出该异常 + String echo = buf.readLine(); + System.out.println(echo); + if ("stop".equals(echo)){ + flag = false; + }else if("c".equals(echo.substring(0,1))){ + System.err.println(echo); + String str = "4400c"+echo.substring(1,2); + //发送数据到服务端 + out.println(str); } + }catch(SocketTimeoutException e){ + System.out.println("Time out, No response"); } sleep(5000); } diff --git a/src/main/java/com/myself/nettychat/template/CacheTemplate.java b/src/main/java/com/myself/nettychat/template/CacheTemplate.java new file mode 100644 index 0000000..f4dd65b --- /dev/null +++ b/src/main/java/com/myself/nettychat/template/CacheTemplate.java @@ -0,0 +1,55 @@ +package com.myself.nettychat.template; + +import com.myself.nettychat.dto.CacheDTO; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @Author:UncleCatMySelf + * @Email:zhupeijie_java@126.com + * @QQ:1341933031 + * @Date:Created in 21:46 2018\11\8 0008 + */ +@Component +public class CacheTemplate { + + /**存放链接池实例*/ + private Map ChannelRedisMap = new ConcurrentHashMap<>(); + /**用户与柜子与锁关系*/ + private Map> DTOMap = new ConcurrentHashMap<>(); + + /** + * 存储对应的用户名与Netty链接实例 + * @param name 登录用户名 + * @param channel Netty链接实例 + */ + public void saveChannel(String name,Object channel){ + ChannelRedisMap.put(name,channel); + } + + /** + * 获取存储池中的链接实例 + * @param name 登录用户名 + * @return {@link io.netty.channel.Channel 链接实例} + */ + public Object getChannel(String name){ + return ChannelRedisMap.get(name); + } + + public void saveDTO(String channelId,List cacheDTOS){ + System.out.println("添加"+channelId); + DTOMap.put(channelId, cacheDTOS); + } + + public List getDTO(String channelId){ + return DTOMap.get(channelId); + } + + public boolean hasCacheDTO(String channelId){ + return DTOMap.containsKey(channelId); + } + +} From 077a711369e0ea1d1a4100d01678f693c77fd580 Mon Sep 17 00:00:00 2001 From: hacker <3183764662@qq.com> Date: Sat, 17 Nov 2018 14:50:32 +0800 Subject: [PATCH 8/9] updateMD --- README.md | 44 +++++++++++++++++-------------- doc/doc.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 doc/doc.md diff --git a/README.md b/README.md index 40cba08..dacd8c0 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,47 @@ # tcp-wechat -## 项目介绍 +## Summery -针对小程序与单片机硬件执行Iot物联网通讯(TCP/IP)的一套完整Demo。 +A complete demo of IoT IoT Communications (TCP/IP) for small programs and single-chip hardware. -## 项目配置 +## 中文说明 + +* [中文说明](doc/doc.md) + +## Project configuration + +> The following instructions are for tag V0.9.1 > application.xml -配置tcp端口:8092 +Configuring TCP Ports:8092 > com.myself.nettychat.tcptest.TCPTestClient -配置连接IP与端口,修改代码中的参数 +Configure connection IP and ports to modify parameters in code -## 启动流程 +## Startup Process -1、启动项目,tcp监听成功 +1、Startup Project, TCP listening succeeded -2、运行com.myself.nettychat.tcptest.TCPTestClient (记得先改ip或端口,如果你有修改的话) +2、Run Com.myself.nettychat.tcptest.TCPTestClient (Remember to change the IP or port first, if you have any changes) -3、运行PostMan,请求下方的API 进行通信测试 +3、Run postman to request communication testing at the API below -## Demo场景 +## Demo Scene -小程序端选购售货机中的商品,点击购买(小程序API向Iot中心发送对应商品的开锁信息),Iot中心中转开锁信息给单片机,单片机接收信息打开对应的锁。 +Small terminal purchase of goods in the vending machine, click to Buy (small program API to the IoT center to send the unlock information of the corresponding product), IoT Center Transit unlock information to the MCU, single-chip computer receiving information to open the corresponding lock. -通信机制:帧头+ID+数据类型+24把锁状态+crc校验+帧尾(可以按照需求进行定制) +Communication mechanism: Frame head +id+ data type +24 lock state +CRC Check + frame tail (can be customized according to demand) -> com.myself.nettychat.config.TCPServerHandler (通信接收的处理类) +> com.myself.nettychat.config.TCPServerHandler (Processing classes for communication reception) -## API(小程序调用接口) +## API(Small program Call interface) > http://localhost:8080/susu/back/get_channel_size GET -请求Iot中心,获取当前连接存活状态下的链接实例 +Request IoT hub to get a link instance in the current connection survival state ``` { @@ -47,7 +53,7 @@ > http://localhost:8080/susu/back/get_channel_id_list GET -请求Iot中心,当前存活状态下的链接Id列表 +Request IoT hub, List of link IDs in current surviving state ``` { @@ -61,9 +67,9 @@ > http://localhost:8080/susu/back/send_to_channel POST -参数 -* channelId //第二个API获取到的链接Id -* lock //将要打开的第几把锁 1-24(看单片机接入的锁的数量) +Parameters +* channelId //The link ID obtained by the second API +* lock //Lock 1-24 to be opened (see the number of locks connected by a single chip computer) ``` { diff --git a/doc/doc.md b/doc/doc.md new file mode 100644 index 0000000..1fb6b1f --- /dev/null +++ b/doc/doc.md @@ -0,0 +1,77 @@ +# tcp-wechat + +## 项目介绍 + +针对小程序与单片机硬件执行Iot物联网通讯(TCP/IP)的一套完整Demo。 + +## 项目配置 + +> 以下说明针对 tag V0.9.1 + +> application.xml + +配置tcp端口:8092 + +> com.myself.nettychat.tcptest.TCPTestClient + +配置连接IP与端口,修改代码中的参数 + +## 启动流程 + +1、启动项目,tcp监听成功 + +2、运行com.myself.nettychat.tcptest.TCPTestClient (记得先改ip或端口,如果你有修改的话) + +3、运行PostMan,请求下方的API 进行通信测试 + +## Demo场景 + +小程序端选购售货机中的商品,点击购买(小程序API向Iot中心发送对应商品的开锁信息),Iot中心中转开锁信息给单片机,单片机接收信息打开对应的锁。 + +通信机制:帧头+ID+数据类型+24把锁状态+crc校验+帧尾(可以按照需求进行定制) + +> com.myself.nettychat.config.TCPServerHandler (通信接收的处理类) + + +## API(小程序调用接口) + +> http://localhost:8080/susu/back/get_channel_size GET + +请求Iot中心,获取当前连接存活状态下的链接实例 + +``` +{ + "code": 200, + "msg": "成功", + "data": 1 +} +``` + +> http://localhost:8080/susu/back/get_channel_id_list GET + +请求Iot中心,当前存活状态下的链接Id列表 + +``` +{ + "code": 200, + "msg": "成功", + "data": [ + "F5690137563CC8" + ] +} +``` + +> http://localhost:8080/susu/back/send_to_channel POST + +参数 +* channelId //第二个API获取到的链接Id +* lock //将要打开的第几把锁 1-24(看单片机接入的锁的数量) + +``` +{ + "code": 200, + "msg": "成功", + "data": "【发送成功】" +} +``` + From ece8fba503f24c7b27509b45b9d4f185ccf9bda8 Mon Sep 17 00:00:00 2001 From: hacker <3183764662@qq.com> Date: Thu, 6 Dec 2018 23:17:15 +0800 Subject: [PATCH 9/9] update --- README.md | 44 ++++++++++++++----------------- doc/doc.md | 77 ------------------------------------------------------ 2 files changed, 20 insertions(+), 101 deletions(-) delete mode 100644 doc/doc.md diff --git a/README.md b/README.md index dacd8c0..1fb6b1f 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,43 @@ # tcp-wechat -## Summery +## 项目介绍 -A complete demo of IoT IoT Communications (TCP/IP) for small programs and single-chip hardware. +针对小程序与单片机硬件执行Iot物联网通讯(TCP/IP)的一套完整Demo。 -## 中文说明 +## 项目配置 -* [中文说明](doc/doc.md) - -## Project configuration - -> The following instructions are for tag V0.9.1 +> 以下说明针对 tag V0.9.1 > application.xml -Configuring TCP Ports:8092 +配置tcp端口:8092 > com.myself.nettychat.tcptest.TCPTestClient -Configure connection IP and ports to modify parameters in code +配置连接IP与端口,修改代码中的参数 -## Startup Process +## 启动流程 -1、Startup Project, TCP listening succeeded +1、启动项目,tcp监听成功 -2、Run Com.myself.nettychat.tcptest.TCPTestClient (Remember to change the IP or port first, if you have any changes) +2、运行com.myself.nettychat.tcptest.TCPTestClient (记得先改ip或端口,如果你有修改的话) -3、Run postman to request communication testing at the API below +3、运行PostMan,请求下方的API 进行通信测试 -## Demo Scene +## Demo场景 -Small terminal purchase of goods in the vending machine, click to Buy (small program API to the IoT center to send the unlock information of the corresponding product), IoT Center Transit unlock information to the MCU, single-chip computer receiving information to open the corresponding lock. +小程序端选购售货机中的商品,点击购买(小程序API向Iot中心发送对应商品的开锁信息),Iot中心中转开锁信息给单片机,单片机接收信息打开对应的锁。 -Communication mechanism: Frame head +id+ data type +24 lock state +CRC Check + frame tail (can be customized according to demand) +通信机制:帧头+ID+数据类型+24把锁状态+crc校验+帧尾(可以按照需求进行定制) -> com.myself.nettychat.config.TCPServerHandler (Processing classes for communication reception) +> com.myself.nettychat.config.TCPServerHandler (通信接收的处理类) -## API(Small program Call interface) +## API(小程序调用接口) > http://localhost:8080/susu/back/get_channel_size GET -Request IoT hub to get a link instance in the current connection survival state +请求Iot中心,获取当前连接存活状态下的链接实例 ``` { @@ -53,7 +49,7 @@ Request IoT hub to get a link instance in the current connection survival state > http://localhost:8080/susu/back/get_channel_id_list GET -Request IoT hub, List of link IDs in current surviving state +请求Iot中心,当前存活状态下的链接Id列表 ``` { @@ -67,9 +63,9 @@ Request IoT hub, List of link IDs in current surviving state > http://localhost:8080/susu/back/send_to_channel POST -Parameters -* channelId //The link ID obtained by the second API -* lock //Lock 1-24 to be opened (see the number of locks connected by a single chip computer) +参数 +* channelId //第二个API获取到的链接Id +* lock //将要打开的第几把锁 1-24(看单片机接入的锁的数量) ``` { diff --git a/doc/doc.md b/doc/doc.md deleted file mode 100644 index 1fb6b1f..0000000 --- a/doc/doc.md +++ /dev/null @@ -1,77 +0,0 @@ -# tcp-wechat - -## 项目介绍 - -针对小程序与单片机硬件执行Iot物联网通讯(TCP/IP)的一套完整Demo。 - -## 项目配置 - -> 以下说明针对 tag V0.9.1 - -> application.xml - -配置tcp端口:8092 - -> com.myself.nettychat.tcptest.TCPTestClient - -配置连接IP与端口,修改代码中的参数 - -## 启动流程 - -1、启动项目,tcp监听成功 - -2、运行com.myself.nettychat.tcptest.TCPTestClient (记得先改ip或端口,如果你有修改的话) - -3、运行PostMan,请求下方的API 进行通信测试 - -## Demo场景 - -小程序端选购售货机中的商品,点击购买(小程序API向Iot中心发送对应商品的开锁信息),Iot中心中转开锁信息给单片机,单片机接收信息打开对应的锁。 - -通信机制:帧头+ID+数据类型+24把锁状态+crc校验+帧尾(可以按照需求进行定制) - -> com.myself.nettychat.config.TCPServerHandler (通信接收的处理类) - - -## API(小程序调用接口) - -> http://localhost:8080/susu/back/get_channel_size GET - -请求Iot中心,获取当前连接存活状态下的链接实例 - -``` -{ - "code": 200, - "msg": "成功", - "data": 1 -} -``` - -> http://localhost:8080/susu/back/get_channel_id_list GET - -请求Iot中心,当前存活状态下的链接Id列表 - -``` -{ - "code": 200, - "msg": "成功", - "data": [ - "F5690137563CC8" - ] -} -``` - -> http://localhost:8080/susu/back/send_to_channel POST - -参数 -* channelId //第二个API获取到的链接Id -* lock //将要打开的第几把锁 1-24(看单片机接入的锁的数量) - -``` -{ - "code": 200, - "msg": "成功", - "data": "【发送成功】" -} -``` -