├── .gitignore ├── README.md ├── README ├── pic │ ├── cloudpush-framework.png │ ├── cloudpush-logo.png │ ├── httpSend.gif │ ├── send.gif │ ├── websocketSend.gif │ ├── wx+.jpg │ └── wx_money.png └── wiki │ ├── nacos-import.jpg │ ├── nacos-login.jpg │ ├── nacos-start.jpg │ └── nacos-update.jpg ├── cloudpush-api ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── huangliang │ └── api │ ├── annotation │ └── LogOperate.java │ ├── config │ ├── RedisComConfig.java │ └── RocketMQConfig.java │ ├── constants │ ├── CommonConsts.java │ ├── Constants.java │ └── RedisPrefix.java │ ├── entity │ ├── Client.java │ ├── WebsocketMessage.java │ ├── request │ │ ├── AbstractRequest.java │ │ └── SendRequest.java │ └── response │ │ └── Response.java │ ├── exception │ ├── NetException.java │ └── ServiceException.java │ └── util │ ├── DateUtils.java │ ├── NetUtils.java │ ├── ObjUtils.java │ ├── RedisUtils.java │ └── UUIDUtils.java ├── cloudpush-eureka ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── huangliang │ │ │ └── eureka │ │ │ ├── CloudpushEurekaApplication.java │ │ │ ├── config │ │ │ └── RedisConfig.java │ │ │ └── listener │ │ │ └── EurekaStateChangeListener.java │ └── resources │ │ ├── application.yml │ │ ├── bootstrap.properties │ │ └── logback.xml_bak │ └── test │ └── java │ └── com │ └── huangliang │ └── eureka │ └── CloudpushEurekaApplicationTests.java ├── cloudpush-gateway ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── huangliang │ │ │ └── cloudpushgateway │ │ │ ├── CloudpushGatewayApplication.java │ │ │ ├── config │ │ │ └── RedisConfig.java │ │ │ ├── route │ │ │ └── RedisRouteDefinitionRepository.java │ │ │ └── service │ │ │ └── RouteService.java │ └── resources │ │ ├── application-config.yml │ │ ├── application.yml │ │ └── bootstrap.properties │ └── test │ └── java │ └── com │ └── huangliang │ └── cloudpushgateway │ └── CloudpushGatewayApplicationTests.java ├── cloudpush-portal ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── huangliang │ │ │ └── cloudpushportal │ │ │ ├── CloudpushPortalApplication.java │ │ │ ├── aspect │ │ │ └── LogAdvice.java │ │ │ ├── config │ │ │ ├── GlobalExceptionHandler.java │ │ │ └── Swagger2.java │ │ │ ├── controller │ │ │ ├── MessageController.java │ │ │ ├── NacosTest.java │ │ │ └── ServerController.java │ │ │ ├── entity │ │ │ └── res │ │ │ │ └── WebsocketServer.java │ │ │ ├── service │ │ │ ├── MessageService.java │ │ │ └── messagedispatch │ │ │ │ ├── HttpDispatchServiceImpl.java │ │ │ │ ├── MQDispatchServiceImpl.java │ │ │ │ └── MessageDispatchService.java │ │ │ └── templet │ │ │ └── Redis.java │ └── resources │ │ ├── application.yml │ │ └── bootstrap.properties │ └── test │ └── java │ └── com │ └── huangliang │ └── cloudpushportal │ └── CloudpushPortalApplicationTests.java ├── cloudpush-task ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── huangliang │ │ └── cloudpushtask │ │ ├── CloudpushTaskApplication.java │ │ ├── task │ │ └── CountWebsocketServerWeight.java │ │ └── templet │ │ └── Redis.java │ └── resources │ ├── application.yml │ └── bootstrap.properties ├── cloudpush-websocket ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── huangliang │ │ │ └── cloudpushwebsocket │ │ │ ├── CloudpushWebsocketApplication.java │ │ │ ├── config │ │ │ └── ComConfig.java │ │ │ ├── constants │ │ │ ├── AttrConstants.java │ │ │ └── MessageConstants.java │ │ │ ├── controller │ │ │ ├── ChannelController.java │ │ │ └── MessageController.java │ │ │ ├── netty │ │ │ ├── HttpRequestHandler.java │ │ │ ├── Server.java │ │ │ ├── ServerInitializer.java │ │ │ └── WebsocketRequestHandler.java │ │ │ ├── service │ │ │ ├── HttpResponseService.java │ │ │ ├── MessageHttpService.java │ │ │ ├── channel │ │ │ │ └── ChannelService.java │ │ │ ├── message │ │ │ │ ├── MessageSendService.java │ │ │ │ └── MqConsumerService.java │ │ │ └── websocket │ │ │ │ ├── IWebSocketService.java │ │ │ │ ├── WebsocketRequestService.java │ │ │ │ ├── WebsocketServiceStrategy.java │ │ │ │ ├── handlerClose │ │ │ │ └── CloseWebSocketService.java │ │ │ │ ├── handlerPing │ │ │ │ └── PingWebSocketService.java │ │ │ │ └── handlerText │ │ │ │ ├── BussinessService.java │ │ │ │ ├── HeartBeatService.java │ │ │ │ ├── IMessageService.java │ │ │ │ ├── MessageServiceStrategy.java │ │ │ │ └── TextWebSocketService.java │ │ │ ├── task │ │ │ ├── ScanClientsNotOnline.java │ │ │ └── UpdateRedisChannelActiveTimeTask.java │ │ │ ├── templet │ │ │ └── Redis.java │ │ │ └── util │ │ │ ├── NettyUtil.java │ │ │ └── WebsocketMessageGenerateUtils.java │ └── resources │ │ ├── application-config.yml │ │ ├── application.yml │ │ ├── bootstrap.properties │ │ └── logback.xml │ └── test │ └── java │ └── com │ └── huangliang │ └── cloudpushwebsocket │ └── CloudpushWebsocketApplicationTests.java ├── pom.xml └── tools ├── apache-jmeter-5.0.zip ├── client.html ├── nacos-config.zip └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | #package files 4 | 5 | *.war 6 | *.ear 7 | 8 | #kdiff3 ignore 9 | target/ 10 | 11 | #eclipse ignore 12 | .settings/ 13 | .project 14 | .classpath 15 | 16 | #idea 17 | .idea/ 18 | /idea/ 19 | *.ipr 20 | *.iml 21 | *.iws 22 | 23 | # temp file 24 | 25 | *.log 26 | *.cache 27 | *.diff 28 | *.patch 29 | *.tmp 30 | 31 | #system ignore 32 | .DS_Store 33 | Thumbs.db -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![cloudpush-logo](README/pic/cloudpush-logo.png) 2 | 3 | # cloudpush- 分布式推送产品 4 | cloudpush是我对于springcloud微服务框架的一个学习实践,旨在学习微服务架构的同时,实现一个基于websocket协议,可平滑水平拓展的分布式推送产品。 5 | 6 | [项目相关博客](https://juejin.im/post/5da457eef265da5b7326e9eb) 7 | 8 | [启动教程](https://github.com/liangQAQ/cloudpush/wiki) 9 | 10 | # 应用架构图 11 | ![cloudpush-framework](README/pic/cloudpush-framework.png) 12 | 13 | # 项目用到的技术 14 | 目前核心的技术栈采用的是SpringBoot2.0.1.RELEASE 15 | 16 | ## 前端使用的技术 17 | 管理台页面待开发。。 18 | 19 | ## 后端使用的技术 20 | 后端的主要架构是基于springboot. 21 | 22 | * SpringCloud 23 | * Netty 24 | * Nacos 25 | * Redis 26 | * RocketMQ 27 | 28 | # 项目模块说明 29 | 30 | | tools 工具包 | 压缩包 | 项目,或者测试中需要用的到中间件包,直接用docker更方便。。 | 31 | | ------------------------------------------------------------ | --------- | ---------------------------------------------------- | 32 | | cloudpush-api 公共组件部分 | jar | 公共组件,很多地方都有引用 | 33 | | cloudpush-eureka 注册中心 | web项目 | 为啥有了nacos还要eureka:通过@EventListener额外增加对websocket服务状态的监听,从而动态地将websocket服务中额外启动的netty服务的ip、port维护进redis(用zk来维护这个地址也一样) | 34 | | cloudpush-portal 接口服务 | web项目 | 提供http接口服务 | 35 | | cloudpush-task 后台计算服务 | web项目 | 现仅仅根据各个websocket的连接数计算路由权重存进redis,不是运行系统的必要条件| 36 | | cloudpush-websocket 推送服务 | web项目 | 由springboot启动的一个netty服务,启动的netty端口提供websocket的握手,消息的收发服务 | 37 | | cloudpush-gateway 统一网关 | web项目 | 统一入口,便于未来鉴权,现在用作根据redis中的websocket路由权重,生成ws连接的动态路由规则(优先分配给连接数较少的实例)| 38 | 39 | 40 | # 项目开发进度 41 | ## 注意事项 42 | 43 | * 项目在批量推送的功能中,出于性能考虑,用到了redis的管道特性(一次查询出多个客户端所属的websocket节点),遂无法使用cluster分片集群,所以。。要么哨兵一个master处理写操作,要么使用Codis作代理,实现redis集群多个master处理写操作。 44 | 45 | ## 后台实现的功能有 46 | 47 | * 推送功能(根据客户端的连接时的标识参数,以http请求或者websocket消息的形式,给一个或者多个客户端推送指定内容的websocket消息) 48 | * 踢出过期客户端功能(客户端上报心跳,ping消息或者封装心跳格式的websocket消息,会刷新所在的websocket节点所维护的客户端的活跃时间) 49 | 50 | ## 前台项目整体的规划有 51 | 52 | * 提供管理后台页面 53 | 54 | ## 后台规划 55 | 56 | * 接入监控组件对应用所在服务器的cpu、网卡、内存等健康指标的监控 57 | * 结合客户端收到消息的回执和在推送流程中的埋点,接入ELK组件实现对消息送达率,客户端会话时长的统计 58 | 59 | # 效果图 60 | ## 以http形式触发的推送 61 | ![gif](README/pic/httpSend.gif) 62 | ## 以websocket形式触发的推送 63 | ![gif2](README/pic/websocketSend.gif) 64 | 65 | # 消息解析 66 | ### http 67 | { 68 | "to": ["1607080309668","1607071389121"], 69 | "msg": { 70 | "key1": "value", 71 | "key2": "value2" 72 | }, 73 | "sendToAll": false 74 | } 75 | ### websocket 76 | {"activeTime":1607080644685,"from":"system","messageId":"725cf41a5798474fb31a1258bed2d5d8","msg":{"key1":"value","key2":"value2"},"requestId":"f231012a-b2ed-40b1-841a-45538dc48ee1","sessionId":"172.31.236.11:9000_1607080309668_20201204191140","to":"1607080309668","trigger":1,"msgType":1} 77 | ## 客户端收到的websocket消息详解 78 | | 值 | 意义 | 79 | | -- | ---- | 80 | | activeTime | 发生时间 | 81 | | from | 消息来源 | 82 | | messageId | 唯一消息id,用于回执重发保证送达率(暂未实现) | 83 | | requestId | 请求id,以http形式触发的时候会存在,用于写es统计 | 84 | | sessionId | 会话id,在一次连接中保持一致,用于写es统计 | 85 | | msg | 具体推送的消息内容 | 86 | | to | 推送的目的地(客户端标识) | 87 | | trigger | 消息触发方式(http或者websocket) | 88 | | msgType | //错误代码ERROR(-1,"error"),//连接类型消息CONNECTION(0,"connection"),//发送的业务类型消息BUSSINESS(1,"bussiness"),//发送的业务类型消息的回执BUSSINESS_ACK(2,"bussiness_ack"),//心跳类型HEARTBEAT(3,"heartbeat"),//心跳类型回执HEARTBEAT_ACK(4,"heartbeat_ack");| 89 | 90 | # 如何贡献 91 | 92 | 项目对你有帮助或者启发的话 93 | 94 | * 老哥点个star 95 | * 提个issue或者pr参与开发 96 | * 甚至打赏瓶水也ok O(∩_∩)O
97 | ![wx](README/pic/wx_money.png) 98 | 99 | 100 | # 技术交流及问题解答 101 | * 微信号: 102 | ![wx+](README/pic/wx%2B.jpg) 103 | 申请表明来意 104 | 105 | 106 | -------------------------------------------------------------------------------- /README/pic/cloudpush-framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/pic/cloudpush-framework.png -------------------------------------------------------------------------------- /README/pic/cloudpush-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/pic/cloudpush-logo.png -------------------------------------------------------------------------------- /README/pic/httpSend.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/pic/httpSend.gif -------------------------------------------------------------------------------- /README/pic/send.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/pic/send.gif -------------------------------------------------------------------------------- /README/pic/websocketSend.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/pic/websocketSend.gif -------------------------------------------------------------------------------- /README/pic/wx+.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/pic/wx+.jpg -------------------------------------------------------------------------------- /README/pic/wx_money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/pic/wx_money.png -------------------------------------------------------------------------------- /README/wiki/nacos-import.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/wiki/nacos-import.jpg -------------------------------------------------------------------------------- /README/wiki/nacos-login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/wiki/nacos-login.jpg -------------------------------------------------------------------------------- /README/wiki/nacos-start.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/wiki/nacos-start.jpg -------------------------------------------------------------------------------- /README/wiki/nacos-update.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/README/wiki/nacos-update.jpg -------------------------------------------------------------------------------- /cloudpush-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | cloudpush 7 | com.huangliang 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | cloudpush-api 13 | 14 | 15 | 16 | org.projectlombok 17 | lombok 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-data-redis 22 | 23 | 24 | commons-collections 25 | commons-collections 26 | 3.2.2 27 | 28 | 29 | org.springframework.cloud 30 | spring-cloud-starter-alibaba-nacos-config 31 | 0.2.1.RELEASE 32 | 33 | 34 | org.yaml 35 | snakeyaml 36 | 1.23 37 | 38 | 39 | 40 | 41 | io.springfox 42 | springfox-swagger2 43 | 2.6.1 44 | 45 | 46 | io.springfox 47 | springfox-swagger-ui 48 | 2.6.1 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/annotation/LogOperate.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Inherited 6 | @Documented 7 | @Retention(RetentionPolicy.RUNTIME) 8 | @Target(ElementType.METHOD) 9 | public @interface LogOperate { 10 | String module() default ""; 11 | } 12 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/config/RedisComConfig.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.config; 2 | 3 | import org.springframework.data.redis.connection.RedisConnectionFactory; 4 | import org.springframework.data.redis.core.RedisTemplate; 5 | import org.springframework.data.redis.serializer.StringRedisSerializer; 6 | 7 | /** 8 | * redis全局配置 9 | */ 10 | public class RedisComConfig { 11 | 12 | public static RedisTemplate getTemplate(RedisConnectionFactory factory){ 13 | RedisTemplate template = new RedisTemplate(); 14 | template.setConnectionFactory(factory); 15 | // Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 16 | // ObjectMapper mapper = new ObjectMapper(); 17 | // mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 18 | // mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 19 | // jackson2JsonRedisSerializer.setObjectMapper(mapper); 20 | StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); 21 | // key采用String的序列化方式 22 | template.setKeySerializer(stringRedisSerializer); 23 | // hash的key也采用String的序列化方式 24 | template.setHashKeySerializer(stringRedisSerializer); 25 | // value序列化方式采用jackson 26 | template.setValueSerializer(stringRedisSerializer); 27 | // hash的value序列化方式采用String 28 | template.setHashValueSerializer(stringRedisSerializer); 29 | template.afterPropertiesSet(); 30 | return template; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/config/RocketMQConfig.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.config; 2 | 3 | import com.huangliang.api.constants.Constants; 4 | 5 | public class RocketMQConfig { 6 | 7 | /** 8 | * 根据具体的实例名得到对应websocket服务的MQ topic地址 9 | * @param instantceId 10 | * @return 11 | */ 12 | public static String getWebsocketTopic(String instantceId){ 13 | return Constants.ROCKETMQ_TOPIC_PREFIX + getMqInstance(instantceId); 14 | } 15 | 16 | /** 17 | * 根据具体的实例名得到对应websocket服务的MQ topic地址 18 | * @param instantceId 19 | * @return 20 | */ 21 | public static String getWebsocketGroup(String instantceId){ 22 | return Constants.ROCKETMQ_GROUP_PREFIX + getMqInstance(instantceId); 23 | } 24 | 25 | /** 26 | * mq群组中不允许出现实例中的":",故替换成"-" 27 | * @param instanceId 28 | * @return 29 | */ 30 | private static String getMqInstance(String instanceId){ 31 | return instanceId.replace(":","-").replace(".","-"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/constants/CommonConsts.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.constants; 2 | 3 | public interface CommonConsts 4 | { 5 | String REQUST_SUC = "success"; 6 | 7 | String REQUST_FAIL = "failure"; 8 | 9 | Boolean TRUE = true; 10 | 11 | Boolean FALSE = false; 12 | 13 | Integer LIMI_TALL = -1; 14 | 15 | Integer LIMI_MAX = 10; 16 | 17 | String SUCCESS = "0"; 18 | 19 | String ERROR = "-1"; 20 | 21 | String MD5_KEY = "qqq_live"; 22 | 23 | Integer AUTO_CHECK_HOSTER = 1; 24 | 25 | // 编码格式 26 | String DECODE = "UTF-8"; 27 | 28 | // 高清标识 29 | String HD = "1"; 30 | 31 | // 标清标识 32 | String SD = "0"; 33 | 34 | String YES = "1"; 35 | 36 | String NO = "0"; 37 | 38 | // 10进制常量 39 | String TEN = "0"; 40 | 41 | // 16进制常量 42 | String SIXTEN = "1"; 43 | 44 | // 单位为秒 45 | String SECEND = "1"; 46 | 47 | // 单位为豪秒 48 | String MILLISSECEND = "0"; 49 | 50 | // 逗号分隔符 51 | String COMMA_FLAG = ","; 52 | 53 | // 句号分隔符 54 | String PERIOD_FLAG = "."; 55 | 56 | // 分号分隔符 57 | String SEMICOLON_FLAG = ";"; 58 | 59 | // 分号分隔符 60 | String COLON_FLAG = ":"; 61 | 62 | // &分隔符 63 | String AND_SPLIT = "&"; 64 | 65 | // /分隔符 66 | String FILE_SEPARATOR = "/"; 67 | 68 | // String FILE_SEPARATOR = File.separator; 69 | 70 | // M3U8后缀 71 | String M3U8 = ".m3u8"; 72 | 73 | String UPPER_M3U8 = ".M3U8"; 74 | 75 | // 等号分隔 76 | String EQUAL_SIGN = "="; 77 | 78 | // 生成9位随机数 79 | Integer NINE_RANDOM = 999999999; 80 | 81 | String STRING_HTTP = "http://"; 82 | 83 | String STRING_WS = "ws://"; 84 | 85 | } 86 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/constants/Constants.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.constants; 2 | 3 | public interface Constants { 4 | 5 | String WEBSOCKET_SERVER = "websocket"; 6 | 7 | String ROCKETMQ_TOPIC_PREFIX = "websocket"; 8 | 9 | String ROCKETMQ_GROUP_PREFIX = "group"; 10 | 11 | String UPGRADE = "Upgrade"; 12 | 13 | String SYSTEM = "system"; 14 | 15 | String POST = "POST"; 16 | 17 | String GET = "GET"; 18 | 19 | String CHANNELID = "channelId"; 20 | 21 | String SESSIONID = "sessionId"; 22 | 23 | String Trigger = "Trigger"; 24 | 25 | String MSG = "msg"; 26 | } 27 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/constants/RedisPrefix.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.constants; 2 | 3 | public interface RedisPrefix { 4 | //websocket的服务地址,类型-hash,key为websocket节点的实例名(ip:port),value为websocket端口 5 | public static String WEBSOCKETSERVER = "websocket_server"; 6 | 7 | //websocket实例与对应连接的权重关系 8 | public static String WEBSOCKETWEIGHT = "websocket_weight"; 9 | 10 | //客户端连接前缀,后缀为客户端标识-hash 11 | public static String PREFIX_CLIENT = "client_"; 12 | 13 | //websocket服务所连接的客户端id集合前缀-list 14 | public static String PREFIX_SERVERCLIENTS = "serverclients_"; 15 | } 16 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/entity/Client.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.entity; 2 | 3 | import ch.qos.logback.core.util.DatePatternToRegexUtil; 4 | import com.huangliang.api.util.DateUtils; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.format.annotation.DateTimeFormat; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | /** 14 | * 客户端websocket连接对象属性描述 15 | */ 16 | @Data 17 | @NoArgsConstructor 18 | public class Client implements Serializable { 19 | 20 | //唯一标识 21 | private String channelId; 22 | 23 | //所连接的主机 24 | private String host; 25 | 26 | //上次活跃时间 27 | private String lastActiveTime; 28 | 29 | //创建时间 30 | private String createTime; 31 | 32 | public Client(String channelId, String host) { 33 | this.channelId = channelId; 34 | this.host = host; 35 | this.lastActiveTime = DateUtils.getCurrentDateTime(); 36 | this.createTime = this.lastActiveTime; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/entity/WebsocketMessage.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.entity; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import com.fasterxml.jackson.annotation.JsonIgnore; 6 | import com.fasterxml.jackson.annotation.JsonInclude; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | import java.io.Serializable; 12 | import java.util.Date; 13 | 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class WebsocketMessage implements Serializable { 18 | 19 | //消息所属的请求id 20 | private String requestId; 21 | //会话id,在同一次中保持一致 22 | private String sessionId; 23 | //推送消息的id,推送和回执保持一致 24 | @JsonInclude(value=JsonInclude.Include.NON_NULL) 25 | private String messageId; 26 | //消息的类型 27 | @JsonInclude(value=JsonInclude.Include.NON_NULL) 28 | private Integer msgType; 29 | //目标客户端标识 30 | @JsonInclude(value=JsonInclude.Include.NON_NULL) 31 | private String[] to; 32 | //消息内容 33 | @JsonInclude(value=JsonInclude.Include.NON_NULL) 34 | private JSONObject msg; 35 | //来源 36 | @JsonIgnore 37 | private String from = "system"; 38 | //触发类型 1.接口调用触发 2.websocket通信触发 39 | private Integer trigger; 40 | @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") 41 | private Date activeTime = new Date(); 42 | //错误信息 43 | private String resultMsg; 44 | 45 | public WebsocketMessage(String messageId, Integer type, String[] to, JSONObject msg, String from, Integer trigger) { 46 | this.messageId = messageId; 47 | this.msgType = type; 48 | this.to = to; 49 | this.msg = msg; 50 | this.from = from; 51 | this.trigger = trigger; 52 | } 53 | 54 | public WebsocketMessage(String requestId, String sessionId, String messageId, Integer type, String[] to, JSONObject msg, String from, Integer trigger) { 55 | this.requestId = requestId; 56 | this.sessionId = sessionId; 57 | this.messageId = messageId; 58 | this.msgType = type; 59 | this.to = to; 60 | this.msg = msg; 61 | this.from = from; 62 | this.trigger = trigger; 63 | } 64 | 65 | //消息类型标识 66 | public enum MsgType { 67 | //错误代码 68 | ERROR(-1,"error"), 69 | //发送的业务类型消息 70 | CONNECTION(0,"connection"), 71 | //发送的业务类型消息 72 | BUSSINESS(1,"bussiness"), 73 | //发送的业务类型消息的回执 74 | BUSSINESS_ACK(2,"bussiness_ack"), 75 | //心跳类型 76 | HEARTBEAT(3,"heartbeat"), 77 | //心跳类型回执 78 | HEARTBEAT_ACK(4,"heartbeat_ack"); 79 | 80 | public Integer code; 81 | public String info; 82 | 83 | MsgType(Integer code, String info){ 84 | this.code = code; 85 | this.info = info; 86 | } 87 | public Integer getCode() { 88 | return code; 89 | } 90 | public String getInfo() { 91 | return info; 92 | } 93 | } 94 | 95 | public enum Trigger { 96 | //触发推送的方式 97 | //1.接口请求的方式 98 | HTTP(1,"HTTP"), 99 | //2.websocket消息触发 100 | WEBSOCKET(2,"WEBSOCKET"); 101 | 102 | public Integer code; 103 | public String info; 104 | 105 | Trigger(Integer code, String info) { 106 | this.code = code; 107 | this.info = info; 108 | } 109 | public Integer getCode() { 110 | return code; 111 | } 112 | public String getInfo() { 113 | return info; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/entity/request/AbstractRequest.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.entity.request; 2 | 3 | import io.swagger.annotations.ApiModelProperty; 4 | import lombok.Data; 5 | 6 | import java.util.UUID; 7 | 8 | @Data 9 | public abstract class AbstractRequest { 10 | @ApiModelProperty(value="请求的唯一id" ,required=false) 11 | private String requestId = UUID.randomUUID().toString(); 12 | } 13 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/entity/request/SendRequest.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.entity.request; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.Data; 8 | 9 | import java.io.Serializable; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Data 14 | @ApiModel(value="推送请求对象模型") 15 | public class SendRequest extends AbstractRequest implements Serializable { 16 | 17 | /** 18 | * 发送目的地(客户端) 19 | */ 20 | @ApiModelProperty(value="推送目的地的数组" ,required=true) 21 | private List to = new ArrayList<>(); 22 | /** 23 | * 发送内容 24 | */ 25 | @ApiModelProperty(value="推送消息的内容" ,required=true) 26 | private JSONObject msg; 27 | 28 | /** 29 | * 推送发起者默认系统 30 | */ 31 | private String from = "system"; 32 | 33 | @ApiModelProperty(value="延时推送(单位秒,暂未实现)" ,required=false) 34 | private Integer delay; 35 | 36 | @ApiModelProperty(value="是否发送给所有人" ,required=false) 37 | private Boolean sendToAll = false; 38 | } 39 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/entity/response/Response.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.entity.response; 2 | 3 | import com.huangliang.api.constants.CommonConsts; 4 | 5 | public class Response 6 | { 7 | /** 8 | * code = 0 时返回true 9 | * code != 0 时返回false 10 | */ 11 | private boolean success; 12 | 13 | /** 14 | * 消息状态码 15 | * 0 表示成功 16 | * 其它表示失败 17 | */ 18 | private String code; 19 | 20 | private String msg; 21 | 22 | /** 23 | * 返回数据 24 | */ 25 | private T data; 26 | 27 | public Response() 28 | { 29 | this.code = CommonConsts.SUCCESS; 30 | this.success = CommonConsts.TRUE; 31 | this.msg = CommonConsts.REQUST_SUC; 32 | } 33 | 34 | public Response(String msg,T data) 35 | { 36 | this.code = CommonConsts.SUCCESS; 37 | this.success = CommonConsts.TRUE; 38 | this.data = data; 39 | this.msg = msg; 40 | } 41 | 42 | public Response(String code, String retInfo) 43 | { 44 | this.code = code; 45 | this.msg = retInfo; 46 | if (CommonConsts.SUCCESS.equals(code)) 47 | { 48 | success = true; 49 | } 50 | else 51 | { 52 | success = false; 53 | } 54 | } 55 | 56 | public Response(String code, String msg, T data) 57 | { 58 | this.code = code; 59 | this.msg = msg; 60 | this.data = data; 61 | if (CommonConsts.SUCCESS.equals(code)) 62 | { 63 | success = true; 64 | } 65 | else 66 | { 67 | success = false; 68 | } 69 | } 70 | 71 | public String getCode() { 72 | return code; 73 | } 74 | 75 | public void setCode(String code) { 76 | this.code = code; 77 | } 78 | 79 | public String getMsg() 80 | { 81 | return msg; 82 | } 83 | 84 | public void setMsg(String msg) 85 | { 86 | this.msg = msg; 87 | } 88 | 89 | public T getData() 90 | { 91 | return data; 92 | } 93 | 94 | public void setData(T data) 95 | { 96 | this.data = data; 97 | } 98 | 99 | public boolean isSuccess() 100 | { 101 | return success; 102 | } 103 | 104 | public void setSuccess(boolean success) 105 | { 106 | this.success = success; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/exception/NetException.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.exception; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class NetException extends Exception{ 7 | 8 | private Integer code; 9 | 10 | private String message; 11 | 12 | private Exception exception; 13 | 14 | public NetException(String message) { 15 | this.message = message; 16 | } 17 | 18 | public NetException(Integer code, String message) { 19 | super(); 20 | this.code = code; 21 | this.message = message; 22 | } 23 | public NetException(Integer code, Exception exception) { 24 | super(); 25 | this.code = code; 26 | this.exception = exception; 27 | } 28 | public NetException(String message, Exception exception) { 29 | super(); 30 | this.message = message; 31 | this.exception = exception; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/exception/ServiceException.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.exception; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ServiceException extends RuntimeException{ 7 | 8 | private Integer code; 9 | 10 | private String message; 11 | 12 | public ServiceException(String message) { 13 | this.message = message; 14 | } 15 | 16 | public ServiceException(Integer code, String message) { 17 | super(); 18 | this.code = code; 19 | this.message = message; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/util/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.util; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | public class DateUtils { 8 | public static String DATE_FORMAT = "yyyy-MM-dd"; 9 | 10 | public static String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; 11 | public static String DATE_TIME_FORMAT_II = "yyyyMMddHHmmss"; 12 | 13 | public static String DATE_FORMAT_CHINESE = "yyyy年M月d日"; 14 | 15 | /** 16 | * 获取当前日期 17 | * 18 | * 19 | * @return 20 | * 21 | */ 22 | public static String getCurrentDate() { 23 | String datestr = null; 24 | SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_FORMAT); 25 | datestr = df.format(new Date()); 26 | return datestr; 27 | } 28 | 29 | /** 30 | * 获取当前日期时间 31 | * 32 | * 33 | * @return 34 | * 35 | */ 36 | public static String getCurrentDateTime() { 37 | String datestr = null; 38 | SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_TIME_FORMAT); 39 | datestr = df.format(new Date()); 40 | return datestr; 41 | } 42 | public static String getCurrentDateTimeFormat() { 43 | String datestr = null; 44 | SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_TIME_FORMAT_II); 45 | datestr = df.format(new Date()); 46 | return datestr; 47 | } 48 | 49 | /** 50 | * 获取当前日期时间 51 | * 52 | * 53 | * @return 54 | * 55 | */ 56 | public static String getCurrentDateTime(String Dateformat) { 57 | String datestr = null; 58 | SimpleDateFormat df = new SimpleDateFormat(Dateformat); 59 | datestr = df.format(new Date()); 60 | return datestr; 61 | } 62 | 63 | public static String dateToDateTime(Date date) { 64 | SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_TIME_FORMAT); 65 | return df.format(date); 66 | } 67 | /** 68 | * 将字符串日期转换为日期格式 69 | * 70 | * 71 | * @param datestr 72 | * @return 73 | * 74 | */ 75 | public static Date stringToDate(String datestr) { 76 | 77 | if(datestr ==null ||datestr.equals("")){ 78 | return null; 79 | } 80 | Date date = new Date(); 81 | SimpleDateFormat df = new SimpleDateFormat(DateUtils.DATE_FORMAT); 82 | try { 83 | date = df.parse(datestr); 84 | } catch (ParseException e) { 85 | date=DateUtils.stringToDate(datestr,"yyyyMMdd"); 86 | } 87 | return date; 88 | } 89 | 90 | /** 91 | * 将字符串日期转换为日期格式 92 | * 自定義格式 93 | * 94 | * @param datestr 95 | * @return 96 | * 97 | */ 98 | public static Date stringToDate(String datestr, String dateformat) { 99 | Date date = new Date(); 100 | SimpleDateFormat df = new SimpleDateFormat(dateformat); 101 | try { 102 | date = df.parse(datestr); 103 | } catch (ParseException e) { 104 | e.printStackTrace(); 105 | } 106 | return date; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/util/ObjUtils.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Modifier; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | @Slf4j 11 | public class ObjUtils { 12 | public static Map ObjToMap(Object obj){ 13 | Map map=new HashMap(); 14 | Field[] fields = obj.getClass().getDeclaredFields(); 15 | for(Field field:fields){ 16 | if(field.getName().equalsIgnoreCase("class")){ 17 | continue; 18 | } 19 | field.setAccessible(true); 20 | try { 21 | map.put(field.getName(), field.get(obj)); 22 | } catch (IllegalAccessException e) { 23 | log.error("属性获取异常",e); 24 | } 25 | } 26 | return map; 27 | } 28 | 29 | public static Map ObjToByteMap(Object obj){ 30 | Map map=new HashMap(); 31 | Field[] fields = obj.getClass().getDeclaredFields(); 32 | for(Field field:fields){ 33 | if(field.getName().equalsIgnoreCase("class")){ 34 | continue; 35 | } 36 | field.setAccessible(true); 37 | try { 38 | map.put(field.getName().getBytes(), field.get(obj).toString().getBytes()); 39 | } catch (IllegalAccessException e) { 40 | log.error("属性获取异常",e); 41 | } 42 | } 43 | return map; 44 | } 45 | public Object mapToObj(Map map,Class clz) throws Exception{ 46 | Object obj = clz.newInstance(); 47 | Field[] declaredFields = obj.getClass().getDeclaredFields(); 48 | for(Field field:declaredFields){ 49 | int mod = field.getModifiers(); 50 | if(Modifier.isStatic(mod) || Modifier.isFinal(mod)){ 51 | continue; 52 | } 53 | field.setAccessible(true); 54 | field.set(obj, map.get(field.getName())); 55 | } 56 | return obj; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/util/RedisUtils.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.util; 2 | 3 | import com.huangliang.api.constants.RedisPrefix; 4 | import org.springframework.dao.DataAccessException; 5 | import org.springframework.data.redis.connection.RedisConnection; 6 | import org.springframework.data.redis.core.RedisCallback; 7 | 8 | import java.util.List; 9 | 10 | public class RedisUtils { 11 | 12 | public static RedisCallback getClientHostByClientFromRedis(List requestClients){ 13 | return new RedisCallback() { 14 | @Override 15 | public Object doInRedis(RedisConnection redisConnection) throws DataAccessException { 16 | redisConnection.openPipeline(); 17 | for (String channelId : requestClients) { 18 | redisConnection.hGet((RedisPrefix.PREFIX_CLIENT + channelId).getBytes(),"host".getBytes()); 19 | } 20 | return null; 21 | } 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cloudpush-api/src/main/java/com/huangliang/api/util/UUIDUtils.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.api.util; 2 | 3 | import java.util.UUID; 4 | 5 | public class UUIDUtils { 6 | public static String getUUID(){ 7 | return UUID.randomUUID().toString().replace("-",""); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cloudpush-eureka/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | -------------------------------------------------------------------------------- /cloudpush-eureka/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if (mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if (!outputFile.getParentFile().exists()) { 87 | if (!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /cloudpush-eureka/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/cloudpush-eureka/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /cloudpush-eureka/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /cloudpush-eureka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cloudpush 7 | com.huangliang 8 | 1.0-SNAPSHOT 9 | 10 | com.huangliang 11 | cloudpush-eureka 12 | 0.0.1-SNAPSHOT 13 | cloudpush-eureka 14 | Demo project for Spring Boot 15 | 16 | 17 | 1.8 18 | com.huangliang.eureka.CloudpushEurekaApplication 19 | 20 | 21 | 22 | 23 | cloudpush-api 24 | com.huangliang 25 | 1.0-SNAPSHOT 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-redis 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-starter-netflix-eureka-server 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | 47 | 48 | ${artifactId} 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-maven-plugin 53 | 54 | com.huangliang.eureka.CloudpushEurekaApplication 55 | 56 | 57 | 58 | 59 | repackage 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | src/main/resources 68 | false 69 | 70 | **/** 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /cloudpush-eureka/src/main/java/com/huangliang/eureka/CloudpushEurekaApplication.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.eureka; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class CloudpushEurekaApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(CloudpushEurekaApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /cloudpush-eureka/src/main/java/com/huangliang/eureka/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.eureka.config; 2 | 3 | import com.huangliang.api.config.RedisComConfig; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.redis.connection.RedisConnectionFactory; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | 9 | @Configuration 10 | public class RedisConfig { 11 | 12 | @Bean 13 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 14 | return RedisComConfig.getTemplate(factory); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cloudpush-eureka/src/main/java/com/huangliang/eureka/listener/EurekaStateChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.eureka.listener; 2 | 3 | import com.huangliang.api.constants.Constants; 4 | import com.huangliang.api.constants.RedisPrefix; 5 | import com.netflix.appinfo.InstanceInfo; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.springframework.cloud.netflix.eureka.server.event.*; 9 | import org.springframework.context.event.EventListener; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.stereotype.Component; 12 | 13 | import javax.annotation.Resource; 14 | 15 | /** 16 | * 监听提供websocket的服务的状态,维护进redis,供portal服务提供websocket地址给客户端进行连接 17 | */ 18 | @Component 19 | @Slf4j 20 | public class EurekaStateChangeListener { 21 | 22 | @Resource 23 | private RedisTemplate redisTemplate; 24 | 25 | /** 26 | * 服务下线事件 27 | * @param event 28 | */ 29 | @EventListener 30 | public void listen(EurekaInstanceCanceledEvent event) { 31 | String appName = event.getAppName(); 32 | String serverId = event.getServerId(); 33 | if(Constants.WEBSOCKET_SERVER.equalsIgnoreCase(appName)){ 34 | redisTemplate.opsForHash().delete(RedisPrefix.WEBSOCKETSERVER,serverId); 35 | } 36 | log.info("服务下线事件:"+appName.toLowerCase()+":"+serverId); 37 | } 38 | 39 | /** 40 | * 服务注册事件 41 | * @param event 42 | */ 43 | @EventListener(condition = "#event.replication==false") 44 | public void listen(EurekaInstanceRegisteredEvent event) { 45 | InstanceInfo instanceInfo = event.getInstanceInfo(); 46 | log.info("服务注册事件:"+instanceInfo.getAppName().toLowerCase()+"-"+instanceInfo.getIPAddr()+":"+instanceInfo.getPort()); 47 | if(Constants.WEBSOCKET_SERVER.equalsIgnoreCase(instanceInfo.getAppName())){ 48 | if(!redisTemplate.opsForHash().hasKey(RedisPrefix.WEBSOCKETSERVER,instanceInfo.getInstanceId())){ 49 | redisTemplate.opsForHash().put(RedisPrefix.WEBSOCKETSERVER,instanceInfo.getInstanceId(),""); 50 | } 51 | } 52 | } 53 | 54 | @EventListener 55 | public void listen(EurekaRegistryAvailableEvent event) { 56 | 57 | } 58 | 59 | /** 60 | * 服务续约事件 61 | */ 62 | @EventListener 63 | public void listen(EurekaInstanceRenewedEvent event) { 64 | log.info("服务续约事件"); 65 | } 66 | 67 | @EventListener 68 | public void listen(EurekaServerStartedEvent event) { 69 | //Server启动 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cloudpush-eureka/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8000 3 | eureka: 4 | instance: 5 | hostname: eureka-server 6 | client: 7 | register-with-eureka: false # 8 | fetch-registry: false # 9 | # service-url: 10 | # defaultZone: http://localhost:8000/eureka/ 11 | server: 12 | enable-self-preservation: false #关闭自我保护机制 13 | eviction-interval-timer-in-ms: 5000 #扫描服务在线的时长 14 | 15 | # 16 | #spring: 17 | # redis: 18 | # host: 10.9.216.1 19 | # #host: 192.168.99.100 20 | # port: 6399 21 | ## 集群环境打开下面注释,单机不需要打开 22 | ## cluster: 23 | ## 集群信息 24 | ## nodes: xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx,xxx.xxx.xxx.xxx:xxxx 25 | ## #默认值是5 一般当此值设置过大时,容易报:Too many Cluster redirections 26 | ## maxRedirects: 3 27 | # # password: 28 | # timeout: 3000ms 29 | # jedis: 30 | # pool: 31 | # max-active: 8 32 | # min-idle: 0 33 | # max-wait: -1ms -------------------------------------------------------------------------------- /cloudpush-eureka/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | #前缀名 2 | #spring.cloud.nacos.config.prefix=example 3 | # 配置中心url 4 | spring.cloud.nacos.config.server-addr=NacosCloudpush:8848 5 | spring.cloud.nacos.config.file-extension=yaml 6 | spring.cloud.nacos.config.ext-config[0].data-id=common-redis.yaml 7 | spring.cloud.nacos.config.ext-config[1].data-id=common-eureka.yaml 8 | -------------------------------------------------------------------------------- /cloudpush-eureka/src/main/resources/logback.xml_bak: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{80} - %msg%n 12 | 13 | 14 | 15 | ${log_dir}/eureka_logback.log 16 | 17 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{80} - %msg%n 18 | 19 | true 20 | false 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ERROR 30 | ACCEPT 31 | DENY 32 | 33 | 34 | 35 | 36 | ${log_dir}/%d{yyyy-MM-dd}/error-log.log 37 | 39 | ${maxHistory} 40 | 41 | 42 | 48 | 52 | 53 | 54 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | WARN 65 | ACCEPT 66 | DENY 67 | 68 | 69 | 70 | ${log_dir}/%d{yyyy-MM-dd}/warn-log.log 71 | 72 | 73 | ${maxHistory} 74 | 75 | 76 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | INFO 88 | ACCEPT 89 | DENY 90 | 91 | 92 | 93 | ${log_dir}/%d{yyyy-MM-dd}/info-log.log 94 | 95 | 96 | ${maxHistory} 97 | 98 | 99 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | ERROR 111 | ACCEPT 112 | DENY 113 | 114 | 115 | 116 | ${log_dir}/%d{yyyy-MM-dd}/debug-log.log 117 | 118 | 119 | ${maxHistory} 120 | 121 | 122 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | ERROR 134 | ACCEPT 135 | DENY 136 | 137 | 138 | 139 | ${log_dir}/%d{yyyy-MM-dd}/trace-log.log 140 | 141 | 142 | ${maxHistory} 143 | 144 | 145 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /cloudpush-eureka/src/test/java/com/huangliang/eureka/CloudpushEurekaApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.eureka; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class CloudpushEurekaApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /cloudpush-gateway/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /cloudpush-gateway/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if (mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if (!outputFile.getParentFile().exists()) { 87 | if (!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /cloudpush-gateway/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/cloudpush-gateway/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /cloudpush-gateway/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /cloudpush-gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | cloudpush 8 | com.huangliang 9 | 1.0-SNAPSHOT 10 | 11 | 12 | com.huangliang 13 | cloudpush-gateway 14 | 0.0.1-SNAPSHOT 15 | cloudpush-gateway 16 | Demo project for Spring Boot 17 | 18 | 19 | 20 | cloudpush-api 21 | com.huangliang 22 | 1.0-SNAPSHOT 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-data-redis 27 | 28 | 29 | org.springframework.cloud 30 | spring-cloud-starter-gateway 31 | 2.1.2.RELEASE 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-starter-netflix-eureka-client 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-starter-openfeign 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-actuator 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-test 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.springframework.cloud 58 | spring-cloud-dependencies 59 | ${spring-cloud.version} 60 | pom 61 | import 62 | 63 | 64 | 65 | 66 | 67 | ${artifactId} 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-maven-plugin 72 | 73 | com.huangliang.cloudpushgateway.CloudpushGatewayApplication 74 | 75 | 76 | 77 | 78 | repackage 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | src/main/resources 87 | false 88 | 89 | **/** 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /cloudpush-gateway/src/main/java/com/huangliang/cloudpushgateway/CloudpushGatewayApplication.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushgateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @SpringBootApplication 10 | public class CloudpushGatewayApplication { 11 | 12 | // @Bean 13 | // public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { 14 | // return builder.routes() 15 | // //basic proxy 16 | // .route(r -> r.path("/baidu/ss") 17 | // .uri("http://10.9.212.119:8001/server/get") 18 | // ).build(); 19 | // } 20 | 21 | public static void main(String[] args) { 22 | SpringApplication.run(CloudpushGatewayApplication.class, args); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /cloudpush-gateway/src/main/java/com/huangliang/cloudpushgateway/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushgateway.config; 2 | 3 | import com.huangliang.api.config.RedisComConfig; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.redis.connection.RedisConnectionFactory; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | 9 | @Configuration 10 | public class RedisConfig { 11 | 12 | @Bean 13 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 14 | return RedisComConfig.getTemplate(factory); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cloudpush-gateway/src/main/java/com/huangliang/cloudpushgateway/route/RedisRouteDefinitionRepository.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushgateway.route; 2 | 3 | import com.huangliang.cloudpushgateway.service.RouteService; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; 7 | import org.springframework.cloud.gateway.route.RouteDefinition; 8 | import org.springframework.cloud.gateway.route.RouteDefinitionRepository; 9 | import org.springframework.stereotype.Component; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | 13 | import java.net.URI; 14 | import java.net.URISyntaxException; 15 | import java.util.ArrayList; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * 自定义路由,从redis中获取动态的netty提供的websocket端口 22 | */ 23 | @Component 24 | @Slf4j 25 | public class RedisRouteDefinitionRepository implements RouteDefinitionRepository { 26 | 27 | @Autowired 28 | private RouteService routeService; 29 | 30 | @Override 31 | public Flux getRouteDefinitions() { 32 | List routeDefinitions = routeService.getRoutes(); 33 | return Flux.fromIterable(routeDefinitions); 34 | } 35 | 36 | @Override 37 | public Mono save(Mono route) { 38 | return null; 39 | } 40 | 41 | @Override 42 | public Mono delete(Mono routeId) { 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cloudpush-gateway/src/main/java/com/huangliang/cloudpushgateway/service/RouteService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushgateway.service; 2 | 3 | import com.huangliang.api.constants.CommonConsts; 4 | import com.huangliang.api.constants.Constants; 5 | import com.huangliang.api.constants.RedisPrefix; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.commons.collections.CollectionUtils; 9 | import org.apache.commons.lang.StringUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.context.properties.ConfigurationProperties; 13 | import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; 14 | import org.springframework.cloud.gateway.route.RouteDefinition; 15 | import org.springframework.cloud.gateway.support.WeightConfig; 16 | import org.springframework.context.annotation.PropertySource; 17 | import org.springframework.data.redis.core.RedisTemplate; 18 | import org.springframework.scheduling.annotation.EnableScheduling; 19 | import org.springframework.scheduling.annotation.Scheduled; 20 | import org.springframework.stereotype.Component; 21 | import org.springframework.stereotype.Service; 22 | 23 | import javax.annotation.PostConstruct; 24 | import java.net.URI; 25 | import java.net.URISyntaxException; 26 | import java.util.*; 27 | 28 | /** 29 | * 定时扫描redis中websocket的地址,刷新本地缓存 30 | */ 31 | @Component 32 | @EnableScheduling 33 | @Slf4j 34 | public class RouteService { 35 | 36 | @Autowired 37 | private RedisTemplate redisTemplate; 38 | //拦截ws的url 39 | @Value("${config.route.websocket.path}") 40 | private String WS_Predicate_Path; 41 | //拦截ws的权重群组名 42 | @Value("${config.route.websocket.group}") 43 | private String WS_Predicate_GROUP; 44 | 45 | private List routes = null; 46 | 47 | //10s刷新一次本地缓存 48 | @PostConstruct 49 | @Scheduled(fixedRate=10000) 50 | public void refresh(){ 51 | List currentRoutes = new ArrayList<>(); 52 | //获取websocket服务的实例列表 53 | Map map = redisTemplate.opsForHash().entries(RedisPrefix.WEBSOCKETSERVER); 54 | //获取websocket服务的实例对应的权重列表 55 | Map instantceWeight = redisTemplate.opsForHash().entries(RedisPrefix.WEBSOCKETWEIGHT); 56 | int totalCount = 0; 57 | int instanceCount = 0; 58 | for(Map.Entry entry : map.entrySet()){ 59 | if(StringUtils.isEmpty(entry.getValue())){ 60 | continue;//只返回设置好netty websocket端口的实例地址 61 | } 62 | RouteDefinition route = getRouteDefinition(entry,instantceWeight); 63 | currentRoutes.add(route); 64 | totalCount = totalCount + instanceCount ; 65 | } 66 | this.routes = currentRoutes; 67 | log.info("刷新websocket服务列表为[{}]",this.routes.toString()); 68 | } 69 | 70 | /** 71 | * 72 | * @param entry websocket实例名 对应的端口 73 | * @param instanceWeight 对应的实例权重 74 | * @return 75 | */ 76 | private RouteDefinition getRouteDefinition(Map.Entry entry,Map instanceWeight){ 77 | RouteDefinition r = new RouteDefinition(); 78 | try { 79 | String websocketUrl =CommonConsts.STRING_WS //ws:// 80 | +entry.getKey().split(CommonConsts.COLON_FLAG)[0]//ip 81 | +CommonConsts.COLON_FLAG+entry.getValue()//:port 82 | +CommonConsts.FILE_SEPARATOR;// / 83 | r.setId(WS_Predicate_GROUP+"_"+websocketUrl); 84 | r.setUri(new URI(websocketUrl)); 85 | } catch (URISyntaxException e) { 86 | log.error("路径异常",e); 87 | } 88 | List predicateDefinitions = new ArrayList<>(); 89 | PredicateDefinition path = new PredicateDefinition(); 90 | 91 | //设置路径 92 | path.setName("Path"); 93 | path.addArg("pattern",WS_Predicate_Path); 94 | predicateDefinitions.add(path); 95 | 96 | //设置权重 97 | PredicateDefinition weight = new PredicateDefinition(); 98 | weight.setName("Weight"); 99 | weight.addArg("weight.group",WS_Predicate_GROUP); 100 | weight.addArg("weight.weight",instanceWeight==null||instanceWeight.size()==0?"1":instanceWeight.get(entry.getKey())+""); 101 | predicateDefinitions.add(weight); 102 | 103 | r.setPredicates(predicateDefinitions); 104 | return r; 105 | } 106 | 107 | public List getRoutes() { 108 | return routes; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /cloudpush-gateway/src/main/resources/application-config.yml: -------------------------------------------------------------------------------- 1 | config: 2 | route: 3 | websocket: 4 | path: /ws/** 5 | group: wsgroup -------------------------------------------------------------------------------- /cloudpush-gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8003 3 | spring: 4 | application: 5 | name: gateway 6 | cloud: 7 | gateway: 8 | discovery: 9 | locator: 10 | enabled: true #设为true便开启通过服务中心的自动根据 serviceId 创建路由的功能 11 | lowerCaseServiceId: true #实例名自动小写 12 | routes: 13 | #------------------------------------------------------------------------ 14 | - id: portal #id千万不能省略 15 | uri: lb://portal 16 | predicates: 17 | - Path=/portal/** 18 | filters: 19 | - StripPrefix=1 #表示从二级url路径转发 请求/portal/xxxx被转化为${uri}+/xxx 20 | # - id: ws #id千万不能省略 21 | # uri: ws://127.0.0.1:9000/ 22 | # predicates: 23 | # - Path=/ws/** 24 | # filters: 25 | # - StripPrefix=1 #表示从二级url路径转发 请求/portal/xxxx被转化为${uri}+/xxx 26 | # - id: web1_v1 27 | # uri: https://www.baidu.com/ 28 | # predicates: 29 | # - Path=/web/** 30 | # - Weight= web1,1 31 | # filters: 32 | # - StripPrefix=1 33 | # - id: web1_v2 34 | # uri: https://www.163.com/ 35 | # predicates: 36 | # - Path=/web/** 37 | # - Weight= web1,1 38 | # filters: 39 | # - StripPrefix=1 40 | # redis: 41 | # #host: 127.0.0.1 42 | # host: 192.168.99.100 43 | # port: 6379 44 | # timeout: 1000ms 45 | # jedis: 46 | # pool: 47 | # max-active: 8 48 | # max-idle: 8 49 | # min-idle: 0 50 | profiles: 51 | active: dev 52 | #引入config.yml 53 | include: config 54 | eureka: 55 | instance: 56 | prefer-ip-address: true 57 | # client: 58 | # service-url: 59 | # defaultZone: http://localhost:8000/eureka/ 60 | #开启actuator管理api,后面要关闭 61 | management: 62 | endpoints: 63 | web: 64 | exposure: 65 | include: "*" -------------------------------------------------------------------------------- /cloudpush-gateway/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/cloudpush-gateway/src/main/resources/bootstrap.properties -------------------------------------------------------------------------------- /cloudpush-gateway/src/test/java/com/huangliang/cloudpushgateway/CloudpushGatewayApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushgateway; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class CloudpushGatewayApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /cloudpush-portal/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | -------------------------------------------------------------------------------- /cloudpush-portal/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if (mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if (!outputFile.getParentFile().exists()) { 87 | if (!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /cloudpush-portal/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/cloudpush-portal/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /cloudpush-portal/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /cloudpush-portal/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cloudpush 7 | com.huangliang 8 | 1.0-SNAPSHOT 9 | 10 | 11 | com.huangliang 12 | cloudpush-portal 13 | 0.0.1-SNAPSHOT 14 | cloudpush-portal 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | cloudpush-api 24 | com.huangliang 25 | 1.0-SNAPSHOT 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-redis 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-starter-netflix-eureka-client 38 | 39 | 40 | io.github.rhwayfun 41 | spring-boot-rocketmq-starter 42 | 0.0.3.RELEASE 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-aop 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-test 51 | test 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.cloud 59 | spring-cloud-dependencies 60 | ${spring-cloud.version} 61 | pom 62 | import 63 | 64 | 65 | 66 | 67 | 68 | ${artifactId} 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 74 | 75 | com.huangliang.cloudpushportal.CloudpushPortalApplication 76 | 77 | 78 | 79 | 80 | repackage 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/CloudpushPortalApplication.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 6 | 7 | @EnableSwagger2 8 | @SpringBootApplication 9 | public class CloudpushPortalApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(CloudpushPortalApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/aspect/LogAdvice.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.aspect; 2 | 3 | import com.huangliang.api.constants.CommonConsts; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.aspectj.lang.JoinPoint; 6 | import org.aspectj.lang.ProceedingJoinPoint; 7 | import org.aspectj.lang.annotation.*; 8 | import org.aspectj.lang.reflect.MethodSignature; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | 12 | import java.lang.reflect.Method; 13 | import java.lang.reflect.Parameter; 14 | 15 | @Slf4j 16 | @Component 17 | @Aspect 18 | public class LogAdvice { 19 | 20 | @Pointcut("@annotation(com.huangliang.api.annotation.LogOperate)") //切点 21 | public void log(){ 22 | 23 | } 24 | 25 | @Around(value = "log()") //环绕增强,切点为log这个切点 26 | public Object around(ProceedingJoinPoint point) throws Throwable { //这里使用参数为ProceedingJoinPoint 类型,只有环绕增强可以使用,并且在方法中必须执行proceed方法,否则被增强的方法不会执行 27 | Long start = System.currentTimeMillis(); 28 | Object result = point.proceed(); 29 | Long end = System.currentTimeMillis(); 30 | Method method = ((MethodSignature)point.getSignature()).getMethod(); 31 | RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); 32 | log.info("调用{},耗时[{}ms],参数[{}]",requestMapping.value(),end-start,generateParam(method,point.getArgs())); 33 | return result; 34 | } 35 | 36 | private String generateParam(Method method, Object[] args) { 37 | if(method.getParameterCount()<=0){ 38 | return ""; 39 | } 40 | StringBuffer result = new StringBuffer(); 41 | Parameter[] arg = method.getParameters(); 42 | for(int i =0 ;i < arg.length ; i++){ 43 | //键值对 44 | result.append(args[i].toString()).append(CommonConsts.COMMA_FLAG); 45 | } 46 | return result.substring(0,result.length()-1); 47 | } 48 | 49 | @Before(value = "log()") //除了环绕增强,其他使用的是joinPoint 类型 50 | public void before(JoinPoint point) throws Throwable { 51 | //System.out.println("before exec"); 52 | } 53 | 54 | @After(value = "log()") 55 | public void after(JoinPoint point) throws Throwable { 56 | //System.out.println("after exec"); 57 | } 58 | 59 | @AfterThrowing("log()") 60 | public void afterThrowing(JoinPoint point){ 61 | //System.out.println("after throw"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/config/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.config; 2 | 3 | import com.huangliang.api.constants.CommonConsts; 4 | import com.huangliang.api.entity.response.Response; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | 10 | @ControllerAdvice 11 | @Slf4j 12 | public class GlobalExceptionHandler { 13 | 14 | @ExceptionHandler(value = Exception.class) 15 | @ResponseBody 16 | public Response defaultErrorHandler(Exception e) { 17 | log.error(e.getMessage(),e); 18 | return new Response(CommonConsts.ERROR,e.getMessage()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/config/Swagger2.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import springfox.documentation.builders.ApiInfoBuilder; 6 | import springfox.documentation.builders.PathSelectors; 7 | import springfox.documentation.builders.RequestHandlerSelectors; 8 | import springfox.documentation.service.ApiInfo; 9 | import springfox.documentation.spi.DocumentationType; 10 | import springfox.documentation.spring.web.plugins.Docket; 11 | 12 | @Configuration 13 | public class Swagger2 { 14 | 15 | @Bean 16 | public Docket createRestApi() { 17 | return new Docket(DocumentationType.SWAGGER_2) 18 | .apiInfo(apiInfo()) 19 | .select() 20 | .apis(RequestHandlerSelectors.basePackage("com.huangliang.cloudpushportal.controller")) 21 | .paths(PathSelectors.any()) 22 | .build(); 23 | } 24 | 25 | private ApiInfo apiInfo() { 26 | return new ApiInfoBuilder() 27 | .title("springboot利用swagger构建api文档") 28 | .description("简单优雅的restfun风格,http://www.baidu.com/") 29 | .termsOfServiceUrl("http://www.baidu.com/") 30 | .version("1.0") 31 | .build(); 32 | } 33 | } -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/controller/MessageController.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.controller; 2 | 3 | import com.huangliang.api.annotation.LogOperate; 4 | import com.huangliang.api.entity.request.SendRequest; 5 | import com.huangliang.api.entity.response.Response; 6 | import com.huangliang.cloudpushportal.service.MessageService; 7 | import io.swagger.annotations.Api; 8 | import io.swagger.annotations.ApiOperation; 9 | import org.apache.commons.collections.CollectionUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import javax.validation.Valid; 17 | import java.util.Set; 18 | 19 | @RestController 20 | @Api(value = "MessageController") 21 | public class MessageController { 22 | 23 | @Autowired 24 | private MessageService messageService; 25 | 26 | @ApiOperation(value="消息推送接口", notes="根据用户标识进行推送,返回不存在的用户") 27 | @LogOperate() 28 | @RequestMapping(value="/message/send",method = RequestMethod.POST) 29 | public Response send(@RequestBody @Valid SendRequest request){ 30 | Response result = null; 31 | Set notExist = messageService.execute(request); 32 | if(CollectionUtils.isNotEmpty(notExist)){ 33 | result = new Response("存在找不到的客户端",notExist); 34 | }else{ 35 | result = new Response(); 36 | } 37 | return result; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/controller/NacosTest.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.controller; 2 | 3 | 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.cloud.context.config.annotation.RefreshScope; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | @RefreshScope 11 | public class NacosTest { 12 | 13 | @Value("${useraaa:sss}") 14 | private String a1; 15 | 16 | @Value("${useLocalCache:false}") 17 | private boolean useLocalCache; 18 | 19 | @RequestMapping("/user") 20 | public String a1(){ 21 | return a1+"_"+useLocalCache; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/controller/ServerController.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.controller; 2 | 3 | import com.huangliang.api.constants.CommonConsts; 4 | import com.huangliang.api.constants.RedisPrefix; 5 | import com.huangliang.api.entity.response.Response; 6 | import com.huangliang.cloudpushportal.entity.res.WebsocketServer; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.util.CollectionUtils; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import javax.annotation.Resource; 14 | import javax.validation.constraints.NotNull; 15 | import java.util.Map; 16 | 17 | @RestController 18 | public class ServerController { 19 | 20 | @Resource 21 | private RedisTemplate redisTemplate; 22 | 23 | /** 24 | * 获取websocket的服务地址(ip+port) 25 | * @param channelId 26 | * @return 27 | */ 28 | @RequestMapping(value = "/server/get",method = RequestMethod.GET) 29 | public Response getServer(@NotNull String channelId){ 30 | String websocketServer = null; 31 | //查询客户端之前所连接的实例 32 | Map client = redisTemplate.opsForHash().entries(RedisPrefix.PREFIX_CLIENT+channelId); 33 | if(client!=null){ 34 | //如果是已连接过的用户,优先分配之前的websocket服务地址 35 | websocketServer = client.get("server"); 36 | } 37 | //查询所有可连接的实例 38 | Map servers = (Map) redisTemplate.opsForHash().entries(RedisPrefix.WEBSOCKETSERVER); 39 | 40 | if(CollectionUtils.isEmpty(servers)){ 41 | return new Response(CommonConsts.ERROR,"没有可用的服务,请稍后再试"); 42 | } 43 | 44 | if(!servers.containsKey(websocketServer)){ 45 | //重新分配较少连接数的websocket服务 46 | Long min = Long.MAX_VALUE; 47 | for(String key : servers.keySet()){ 48 | Long count = redisTemplate.opsForSet().size(RedisPrefix.PREFIX_SERVERCLIENTS+key); 49 | if(count < min){ 50 | min = count; 51 | websocketServer = key; 52 | } 53 | } 54 | } 55 | //解析得到真正的websocket地址 56 | websocketServer = getWebsocketServer(websocketServer,servers); 57 | return new Response(CommonConsts.SUCCESS,CommonConsts.REQUST_SUC,new WebsocketServer(websocketServer)); 58 | } 59 | 60 | /** 61 | * 解析websocket的连接地址,map中存的是 62 | * @param websocketServer 63 | * @param servers 64 | * @return 65 | */ 66 | private String getWebsocketServer(String websocketServer, Map servers) { 67 | return websocketServer.split(":")[0] +":"+ servers.get(websocketServer); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/entity/res/WebsocketServer.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.entity.res; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | public class WebsocketServer implements Serializable { 11 | private String address; 12 | } 13 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/service/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.service; 2 | 3 | import com.huangliang.api.constants.Constants; 4 | import com.huangliang.api.constants.RedisPrefix; 5 | import com.huangliang.api.entity.WebsocketMessage; 6 | import com.huangliang.api.entity.request.SendRequest; 7 | import com.huangliang.api.exception.ServiceException; 8 | import com.huangliang.api.util.RedisUtils; 9 | import com.huangliang.cloudpushportal.service.messagedispatch.MQDispatchServiceImpl; 10 | import com.huangliang.cloudpushportal.service.messagedispatch.MessageDispatchService; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.apache.rocketmq.common.message.Message; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.data.redis.core.RedisTemplate; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.util.CollectionUtils; 17 | 18 | import javax.annotation.Resource; 19 | import java.util.*; 20 | 21 | /** 22 | * 推送消息处理类 23 | */ 24 | @Slf4j 25 | @Service 26 | public class MessageService { 27 | 28 | @Autowired 29 | private RedisTemplate redisTemplate; 30 | @Resource(name = "MQDispatchServiceImpl") 31 | private MessageDispatchService messageDispatchService; 32 | 33 | /** 34 | * 返回不存在的客户端 35 | * @param request 36 | * @return 37 | */ 38 | public Set execute(SendRequest request) { 39 | checkServer(); 40 | //查询redis中所有的websocket服务 41 | Set set = redisTemplate.keys(RedisPrefix.PREFIX_SERVERCLIENTS + "*"); 42 | checkServerClients(set); 43 | //记录查询不存在的客户端 44 | Set notExist = new HashSet<>(); 45 | //<服务端地址,对应的客户端结果集> 46 | Map> hostClientsMap = new HashMap<>(set.size()); 47 | if (request.getSendToAll()) { 48 | //根据服务下的设备标识推送 49 | //2.消息分发给具体的websocket实例处理 50 | //serverKey => serverclients_10.9.217.160:9003 51 | for(String serverKey : set){ 52 | messageDispatchService.send(serverKey.split("\\_")[1],request); 53 | } 54 | }else{ 55 | //根据参数中的客户端标识,找出所在的服务器,先对应的服务器发起推送 56 | List requestClients = request.getTo(); 57 | //批量查询 58 | List pipeResult = redisTemplate.executePipelined(RedisUtils.getClientHostByClientFromRedis(requestClients)); 59 | for (int i=0;i clients = new LinkedList<>(); 75 | clients.add(channelId); 76 | hostClientsMap.put(host,clients); 77 | } 78 | } 79 | log.info("不存在的客户端[{}]", notExist); 80 | for(Map.Entry> entry: hostClientsMap.entrySet()){ 81 | request.setTo(entry.getValue()); 82 | messageDispatchService.send(entry.getKey(),request); 83 | } 84 | } 85 | 86 | return notExist; 87 | } 88 | 89 | private void checkServerClients(Set set) { 90 | if (CollectionUtils.isEmpty(set)) { 91 | throw new ServiceException("没有存在连接的websocket服务"); 92 | } 93 | } 94 | 95 | private void checkServer() { 96 | if(redisTemplate.opsForHash().size(RedisPrefix.WEBSOCKETSERVER)<=0){ 97 | throw new ServiceException("没有可用的websocket服务端"); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/service/messagedispatch/HttpDispatchServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.service.messagedispatch; 2 | 3 | import com.huangliang.api.entity.request.SendRequest; 4 | import com.huangliang.api.entity.response.Response; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | import java.util.concurrent.ExecutorService; 10 | import java.util.concurrent.Executors; 11 | 12 | /** 13 | * 基于http的形式,分发消息给对应客户端所在的websocket服务器 14 | */ 15 | @Service 16 | @Slf4j 17 | public class HttpDispatchServiceImpl implements MessageDispatchService { 18 | 19 | private static ExecutorService service = Executors.newCachedThreadPool(); 20 | 21 | @Override 22 | public void send(String instance, SendRequest request) { 23 | service.execute(() -> { 24 | try { 25 | String url = "http://"+instance+"/message/send"; 26 | RestTemplate restTemplate = new RestTemplate(); 27 | restTemplate.postForEntity(url,request, Response.class); 28 | }catch (Exception e){ 29 | log.error("发送失败,host="+instance,e); 30 | } 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/service/messagedispatch/MQDispatchServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.service.messagedispatch; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.huangliang.api.config.RocketMQConfig; 5 | import com.huangliang.api.constants.Constants; 6 | import com.huangliang.api.entity.WebsocketMessage; 7 | import com.huangliang.api.entity.request.SendRequest; 8 | import io.github.rhwayfun.springboot.rocketmq.starter.common.DefaultRocketMqProducer; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.rocketmq.common.message.Message; 11 | import org.springframework.stereotype.Service; 12 | 13 | import javax.annotation.Resource; 14 | 15 | /** 16 | * 基于MQ的形式,分发消息给对应客户端所在的websocket服务器所订阅的topic 17 | */ 18 | @Service 19 | @Slf4j 20 | public class MQDispatchServiceImpl implements MessageDispatchService { 21 | 22 | @Resource 23 | private DefaultRocketMqProducer producer; 24 | 25 | @Override 26 | public void send(String instants, SendRequest request) { 27 | producer.sendMsg(getInstants(instants, request)); 28 | } 29 | 30 | private Message getInstants(String topic, SendRequest msg) { 31 | //构建message消息体 32 | Message message = new Message(RocketMQConfig.getWebsocketTopic(topic), JSONObject.toJSONString(msg).getBytes()); 33 | //由调用接口的方式触发消息 34 | message.putUserProperty(Constants.Trigger, WebsocketMessage.Trigger.HTTP.code + ""); 35 | return message; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/service/messagedispatch/MessageDispatchService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.service.messagedispatch; 2 | 3 | import com.huangliang.api.entity.request.SendRequest; 4 | 5 | /** 6 | * 消息分发接口定义 7 | */ 8 | public interface MessageDispatchService { 9 | void send(String instants,SendRequest request); 10 | } 11 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/java/com/huangliang/cloudpushportal/templet/Redis.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal.templet; 2 | 3 | import com.huangliang.api.config.RedisComConfig; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.redis.connection.RedisConnectionFactory; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | 9 | @Configuration 10 | public class Redis { 11 | 12 | @Bean 13 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 14 | return RedisComConfig.getTemplate(factory); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8001 3 | spring: 4 | application: 5 | name: portal 6 | # 配置统一从配置中心nacos读取 7 | # rocketmq: 8 | # name-server: 10.9.217.161:9876 9 | # producer-send-msg-timeout: 3000 10 | # producer-group-name: ${spring.application.name}-127-0-0-1-${server.port} 11 | # redis: 12 | # host: 10.9.216.2 13 | # #host: 192.168.99.100 14 | # port: 6399 15 | # timeout: 1000ms 16 | # jedis: 17 | # pool: 18 | # max-active: 8 19 | # max-idle: 8 20 | # min-idle: 0 21 | eureka: 22 | instance: 23 | prefer-ip-address: true 24 | instance-id: ${spring.application.name}:${server.port} 25 | # client: 26 | # service-url: 27 | # defaultZone: http://localhost:8000/eureka/ 28 | 29 | -------------------------------------------------------------------------------- /cloudpush-portal/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/cloudpush-portal/src/main/resources/bootstrap.properties -------------------------------------------------------------------------------- /cloudpush-portal/src/test/java/com/huangliang/cloudpushportal/CloudpushPortalApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushportal; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | //@RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class CloudpushPortalApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | @Test 17 | public void a1(){ 18 | // String test = {hours=2019-08, device_code=wsd843292, temperature=25.10000000, humidity=45.60000000} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cloudpush-task/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | cloudpush 8 | com.huangliang 9 | 1.0-SNAPSHOT 10 | 11 | 12 | com.huangliang 13 | cloudpush-task 14 | 0.0.1-SNAPSHOT 15 | cloudpush-task 16 | Demo project for Spring Boot 17 | 18 | 19 | 20 | cloudpush-api 21 | com.huangliang 22 | 1.0-SNAPSHOT 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-data-redis 28 | 29 | 30 | 31 | org.springframework.cloud 32 | spring-cloud-starter-netflix-eureka-client 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-web 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | 44 | 45 | 46 | 47 | ${artifactId} 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | 54 | com.huangliang.cloudpushtask.CloudpushTaskApplication 55 | 56 | 57 | 58 | 59 | repackage 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /cloudpush-task/src/main/java/com/huangliang/cloudpushtask/CloudpushTaskApplication.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushtask; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CloudpushTaskApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CloudpushTaskApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /cloudpush-task/src/main/java/com/huangliang/cloudpushtask/task/CountWebsocketServerWeight.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushtask.task; 2 | 3 | import com.huangliang.api.constants.RedisPrefix; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.commons.lang.StringUtils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.scheduling.annotation.EnableScheduling; 9 | import org.springframework.scheduling.annotation.Scheduled; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * 根据各websocket实例的连接数量, 17 | * 来计算网关应该给各个websocket实例所分配的路由权重 18 | * 写入redis供网关获取 19 | */ 20 | @Component 21 | @EnableScheduling 22 | @Slf4j 23 | public class CountWebsocketServerWeight { 24 | 25 | private static final Integer TOTALWEIGHT = 100; 26 | 27 | @Autowired 28 | private RedisTemplate redisTemplate; 29 | 30 | @Scheduled(fixedRate=60000) 31 | private void execute(){ 32 | log.info("计算权重任务开始.."); 33 | //每个服务器上对应的websocket连接数量 34 | Map result = new HashMap<>(); 35 | int totalCount = 0; 36 | //连接数为0的服务数量 37 | int newServerCount = 0; 38 | //所有服务 39 | Map map = redisTemplate.opsForHash().entries(RedisPrefix.WEBSOCKETSERVER); 40 | for(Map.Entry entry : map.entrySet()){ 41 | if(StringUtils.isEmpty(entry.getValue())){ 42 | continue;//只返回设置好netty websocket端口的实例地址 43 | } 44 | int count = redisTemplate.opsForSet().size(RedisPrefix.PREFIX_SERVERCLIENTS+entry.getKey()).intValue(); 45 | if(count == 0){ 46 | newServerCount++; 47 | } 48 | result.put(entry.getKey(),count+""); 49 | totalCount = totalCount + count; 50 | } 51 | if(newServerCount>0){ 52 | result = getWeightWithNewServer(result,newServerCount); 53 | }else{ 54 | result = getWeightWithOutNewServer(result,totalCount); 55 | } 56 | //覆盖写入redis供网关使用 57 | redisTemplate.delete(RedisPrefix.WEBSOCKETWEIGHT); 58 | redisTemplate.opsForHash().putAll(RedisPrefix.WEBSOCKETWEIGHT,result); 59 | log.info("计算结果为:"+result); 60 | } 61 | 62 | /** 63 | * 计算存在新连上来的websocket实例权重 64 | * 权重全分配至新连上来的实例,其他的为0 65 | * @param map 66 | * @param newServerCount 67 | * @return 68 | */ 69 | private Map getWeightWithNewServer(Map map ,int newServerCount) { 70 | int weight = 1; 71 | for(Map.Entry entry : map.entrySet()){ 72 | Integer instantceCount = Integer.parseInt(entry.getValue()); 73 | if(instantceCount == 0){ 74 | weight = TOTALWEIGHT/newServerCount; 75 | }else{ 76 | weight = 0 ; 77 | } 78 | entry.setValue(weight+""); 79 | } 80 | return map; 81 | } 82 | 83 | /** 84 | * 计算不存在新连接上来的实例的所有权重 85 | * @param result 86 | * @param totalCount 87 | * @return 88 | */ 89 | private Map getWeightWithOutNewServer(Map result, int totalCount) { 90 | Map weight = new HashMap<>(result.size()); 91 | Double totalWeight = 0.0; 92 | for(Map.Entry entry : result.entrySet()){ 93 | Double itemWeight = ((double)totalCount/Double.parseDouble(entry.getValue())); 94 | totalWeight = totalWeight + itemWeight; 95 | weight.put(entry.getKey(), itemWeight); 96 | } 97 | //转换为百分比 98 | for(Map.Entry entry : weight.entrySet()){ 99 | result.put(entry.getKey(),(int)((entry.getValue()/totalWeight)*100)+""); 100 | } 101 | return result; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /cloudpush-task/src/main/java/com/huangliang/cloudpushtask/templet/Redis.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushtask.templet; 2 | 3 | import com.huangliang.api.config.RedisComConfig; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.redis.connection.RedisConnectionFactory; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | 9 | @Configuration 10 | public class Redis { 11 | 12 | @Bean 13 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 14 | return RedisComConfig.getTemplate(factory); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cloudpush-task/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8005 3 | spring: 4 | application: 5 | name: task 6 | eureka: 7 | instance: 8 | prefer-ip-address: true 9 | instance-id: ${spring.application.name}:${server.port} 10 | # client: 11 | # service-url: 12 | # defaultZone: http://localhost:8000/eureka/ 13 | -------------------------------------------------------------------------------- /cloudpush-task/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/cloudpush-task/src/main/resources/bootstrap.properties -------------------------------------------------------------------------------- /cloudpush-websocket/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | -------------------------------------------------------------------------------- /cloudpush-websocket/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cloudpush 7 | com.huangliang 8 | 1.0-SNAPSHOT 9 | 10 | com.huangliang 11 | cloudpush-websocket 12 | 0.0.1-SNAPSHOT 13 | cloudpush-websocket 14 | Demo project for Spring Boot 15 | 16 | 17 | 1.8 18 | 19 | 20 | 21 | 22 | cloudpush-api 23 | com.huangliang 24 | 1.0-SNAPSHOT 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-actuator 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-data-redis 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-starter-netflix-eureka-client 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | io.netty 50 | netty-all 51 | 4.1.29.Final 52 | 53 | 54 | io.github.rhwayfun 55 | spring-boot-rocketmq-starter 56 | 0.0.3.RELEASE 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.springframework.cloud 64 | spring-cloud-dependencies 65 | ${spring-cloud.version} 66 | pom 67 | import 68 | 69 | 70 | 71 | 72 | 73 | ${artifactId} 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-maven-plugin 78 | 79 | com.huangliang.cloudpushwebsocket.CloudpushWebsocketApplication 80 | 81 | 82 | 83 | 84 | repackage 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/CloudpushWebsocketApplication.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket; 2 | 3 | import com.huangliang.api.util.NetUtils; 4 | import com.huangliang.cloudpushwebsocket.netty.Server; 5 | import io.netty.channel.ChannelFuture; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.boot.CommandLineRunner; 11 | import org.springframework.boot.SpringApplication; 12 | import org.springframework.boot.autoconfigure.SpringBootApplication; 13 | 14 | @Slf4j 15 | @SpringBootApplication 16 | public class CloudpushWebsocketApplication implements CommandLineRunner { 17 | 18 | private static String yamlPath = "application.yml"; 19 | private static String yamlKey = "hostip"; 20 | 21 | @Value("${hostip}") 22 | private static String hostip; 23 | 24 | @Autowired 25 | private Server socketServer; 26 | 27 | public static void main(String[] args) { 28 | init(); 29 | SpringApplication.run(CloudpushWebsocketApplication.class, args); 30 | } 31 | 32 | @Override 33 | public void run(String... args){ 34 | 35 | ChannelFuture future = socketServer.start(); 36 | Runtime.getRuntime().addShutdownHook(new Thread(() -> socketServer.destroy())); 37 | future.channel().closeFuture().syncUninterruptibly(); 38 | } 39 | 40 | private static void init(){ 41 | //根据网卡动态设置ip(为了将netty的ip:port维护进redis) 42 | initIp(); 43 | } 44 | 45 | private static void initIp() { 46 | //优先获取jvm参数中指定的ip 47 | hostip = System.getProperty(yamlKey); 48 | if(StringUtils.isNotEmpty(hostip)){ 49 | log.info("jvm启动参中指定ip为[{}]",hostip); 50 | return ; 51 | }else{ 52 | String localIP = NetUtils.getLocalHost(); 53 | System.setProperty(yamlKey,localIP); 54 | log.info("自动获取ip为[{}]",localIP); 55 | } 56 | log.info("若此ip不是与网关gateway通信的内网ip,请尝试通过启动参数指定[{}]","java -jar -Dhostip=x.x.x.x"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/config/ComConfig.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @Data 9 | public class ComConfig { 10 | @Value("${hostip}") 11 | private String hostip; 12 | @Value("${eureka.instance.instance-id}") 13 | private String instanceId; 14 | @Value("${config.client.interval-time}") 15 | private Integer intervalTime; 16 | @Value("${config.client.expire-time}") 17 | private Integer expireTime; 18 | @Value("${config.client.interval-client-active-time}") 19 | private Integer intervalClientActiveTime; 20 | } 21 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/constants/AttrConstants.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.constants; 2 | 3 | import io.netty.util.AttributeKey; 4 | 5 | public interface AttrConstants { 6 | 7 | public final static AttributeKey channelId = AttributeKey.valueOf("channelId"); 8 | 9 | public final static AttributeKey activeTime = AttributeKey.valueOf("activeTime"); 10 | 11 | public final static AttributeKey sessionId = AttributeKey.valueOf("sessionId"); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/constants/MessageConstants.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.constants; 2 | 3 | public interface MessageConstants { 4 | 5 | public String ErrorChannelId = "找不到对应channelId的设备"; 6 | 7 | public String NoUser = "没有用户"; 8 | 9 | public String ShakeSuccess = "连接成功!客户端标识channelId=%s"; 10 | 11 | public String ParseError = "消息解析失败[%s]"; 12 | 13 | public String NoSendToError = "推送目的地为空[%s]"; 14 | } 15 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/controller/ChannelController.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.controller; 2 | 3 | import com.huangliang.cloudpushwebsocket.service.channel.ChannelService; 4 | import io.netty.channel.Channel; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import java.util.Map; 10 | 11 | @RestController 12 | public class ChannelController { 13 | 14 | @Autowired 15 | private ChannelService channelService; 16 | 17 | @RequestMapping(value="channels") 18 | public Map getChannels(){ 19 | return channelService.getAll(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/controller/MessageController.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.controller; 2 | 3 | import com.huangliang.api.annotation.LogOperate; 4 | import com.huangliang.api.constants.CommonConsts; 5 | import com.huangliang.api.entity.request.SendRequest; 6 | import com.huangliang.api.entity.response.Response; 7 | import com.huangliang.cloudpushwebsocket.service.MessageHttpService; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | 15 | @Slf4j 16 | @RestController 17 | public class MessageController { 18 | 19 | @Autowired 20 | private MessageHttpService messageHttpService; 21 | 22 | @RequestMapping("/message/send") 23 | public Response sendToAllClient(@RequestBody SendRequest request){ 24 | messageHttpService.send(request); 25 | return new Response(CommonConsts.SUCCESS,CommonConsts.REQUST_SUC); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/netty/HttpRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.netty; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.huangliang.api.constants.CommonConsts; 5 | import com.huangliang.api.constants.Constants; 6 | import com.huangliang.api.entity.WebsocketMessage; 7 | import com.huangliang.api.entity.response.Response; 8 | import com.huangliang.cloudpushwebsocket.constants.AttrConstants; 9 | import com.huangliang.cloudpushwebsocket.constants.MessageConstants; 10 | import com.huangliang.cloudpushwebsocket.service.HttpResponseService; 11 | import com.huangliang.cloudpushwebsocket.service.channel.ChannelService; 12 | import com.huangliang.cloudpushwebsocket.util.NettyUtil; 13 | import com.huangliang.cloudpushwebsocket.util.WebsocketMessageGenerateUtils; 14 | import io.netty.channel.Channel; 15 | import io.netty.channel.ChannelHandler; 16 | import io.netty.channel.ChannelHandlerContext; 17 | import io.netty.channel.SimpleChannelInboundHandler; 18 | import io.netty.handler.codec.http.FullHttpRequest; 19 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 20 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; 21 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; 22 | import lombok.extern.slf4j.Slf4j; 23 | import org.apache.commons.lang3.StringUtils; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.stereotype.Component; 26 | 27 | import java.util.Map; 28 | 29 | @ChannelHandler.Sharable 30 | @Component 31 | @Slf4j 32 | public class HttpRequestHandler extends SimpleChannelInboundHandler { 33 | 34 | @Autowired 35 | private HttpResponseService httpResponseService; 36 | 37 | @Autowired 38 | private ChannelService clientsService; 39 | 40 | private WebSocketServerHandshaker handshaker; 41 | 42 | @Override 43 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg){ 44 | try { 45 | shakeHandsHandler(ctx, msg); 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | } 49 | 50 | } 51 | 52 | @Override 53 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 54 | ctx.flush(); 55 | } 56 | 57 | @Override 58 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 59 | cause.printStackTrace(); 60 | } 61 | 62 | /** 63 | * 处理握手请求 64 | * @param ctx 65 | * @param req 66 | */ 67 | private void shakeHandsHandler(ChannelHandlerContext ctx, FullHttpRequest req){ 68 | //解析握手请求 69 | String channelId = ""; 70 | Channel channel = ctx.channel(); 71 | Map requestParam = NettyUtil.getRequestParams(req); 72 | if(!requestParam.containsKey(Constants.CHANNELID)||!StringUtils.isNotEmpty(requestParam.get(Constants.CHANNELID))) 73 | { 74 | httpResponseService.responseJson(ctx, new Response<>(CommonConsts.SUCCESS, MessageConstants.ErrorChannelId)); 75 | log.error("握手失败:缺少channelId"); 76 | return ; 77 | } 78 | channelId = requestParam.get(Constants.CHANNELID); 79 | WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://127.0.0.1"+req.getUri(), 80 | null, false); 81 | handshaker = wsFactory.newHandshaker(req); 82 | if (handshaker == null) { 83 | WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channel); 84 | } else { 85 | //处理握手,协议升级 86 | handshaker.handshake(ctx.channel(), req); 87 | //将客户端放入集合 88 | channel = clientsService.put(channelId,channel); 89 | //以websocket的形式将标识返回 90 | ctx.channel().writeAndFlush(getShakeHandsSuccessResponse(channel)); 91 | } 92 | } 93 | 94 | private TextWebSocketFrame getShakeHandsSuccessResponse(Channel channel){ 95 | return new TextWebSocketFrame(JSONObject.toJSONString(WebsocketMessageGenerateUtils.generateShakeHands(channel))); 96 | } 97 | } -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/netty/Server.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.netty; 2 | 3 | import com.huangliang.api.constants.RedisPrefix; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelFuture; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.EventLoopGroup; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.util.concurrent.Future; 12 | import io.netty.util.concurrent.GenericFutureListener; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.beans.factory.annotation.Value; 16 | import org.springframework.data.redis.core.RedisTemplate; 17 | import org.springframework.stereotype.Component; 18 | 19 | import javax.annotation.Resource; 20 | 21 | @Component 22 | @Slf4j 23 | public class Server { 24 | 25 | private static final int InitPort = 9003; 26 | private static final int tryMaxCount = 3; 27 | private static int tryCount = 0; 28 | private static String nettyPort = ""; 29 | 30 | private final EventLoopGroup bossGroup = new NioEventLoopGroup(); 31 | private final EventLoopGroup workerGroup = new NioEventLoopGroup(); 32 | 33 | @Value("${eureka.instance.instance-id}") 34 | public String instanceId; 35 | 36 | @Autowired 37 | private ServerInitializer serverInitializer; 38 | 39 | @Resource 40 | private RedisTemplate redisTemplate; 41 | 42 | private Channel channel; 43 | 44 | 45 | /** 46 | * 启动netty服务 47 | */ 48 | public ChannelFuture start () { 49 | ChannelFuture f = null; 50 | try { 51 | ServerBootstrap b = new ServerBootstrap(); 52 | b.group(bossGroup, workerGroup) 53 | .channel(NioServerSocketChannel.class) 54 | .childHandler(serverInitializer) 55 | .option(ChannelOption.SO_BACKLOG, 128) 56 | .childOption(ChannelOption.SO_KEEPALIVE, true); 57 | //启动netty服务绑定端口 58 | f = bind(b,InitPort); 59 | channel = f.channel(); 60 | //服务初始化 61 | init(); 62 | } catch (Exception e) { 63 | log.error("Netty start error:", e); 64 | } 65 | 66 | return f; 67 | } 68 | 69 | /** 70 | * 递归启动,从port端口开始,绑定不成功就+1 继续绑定 71 | * @param serverBootstrap 72 | * @param port 73 | * @return 74 | */ 75 | private static ChannelFuture bind(final ServerBootstrap serverBootstrap, final int port) { 76 | return serverBootstrap.bind(port).addListener(new GenericFutureListener>() { 77 | public void operationComplete(Future future) { 78 | if (future.isSuccess()) { 79 | log.info("netty端口在[" + port + "]启动成功!"); 80 | nettyPort = port+""; 81 | } else { 82 | log.info("netty端口在[" + port + "]启动失败,继续尝试启动..."); 83 | bind(serverBootstrap, port + 1); 84 | } 85 | } 86 | }); 87 | } 88 | 89 | private void init(){ 90 | try { 91 | //设置redis中记录的websocket服务地址的连接端口 92 | setRedisWebsocketPort(); 93 | }catch (Exception e){ 94 | log.error("初始化失败,程序退出...",e); 95 | System.exit(0); 96 | } 97 | } 98 | 99 | public void destroy() { 100 | if(channel != null) { channel.close();} 101 | workerGroup.shutdownGracefully(); 102 | bossGroup.shutdownGracefully(); 103 | } 104 | 105 | private void setRedisWebsocketPort() { 106 | if(redisTemplate.opsForHash().hasKey(RedisPrefix.WEBSOCKETSERVER,instanceId)){ 107 | redisTemplate.opsForHash().put(RedisPrefix.WEBSOCKETSERVER,instanceId,nettyPort); 108 | log.info("设置实例[{}]的netty端口为[{}].",instanceId,nettyPort); 109 | }else{ 110 | log.info("不存在[{}]的实例,netty初始化失败...",instanceId); 111 | tryCount++; 112 | if(tryCount<=tryMaxCount){ 113 | try { 114 | Thread.sleep(500); 115 | } catch (InterruptedException e) { 116 | log.error("启动失败,继续尝试",e); 117 | } 118 | setRedisWebsocketPort(); 119 | }else{ 120 | System.exit(0); 121 | } 122 | // throw new 123 | // try { 124 | // Thread.sleep(2000); 125 | // } catch (InterruptedException e) { 126 | // log.error(e.getMessage()); 127 | // } 128 | // setRedisWebsocketPort(); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/netty/ServerInitializer.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.netty; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.ChannelPipeline; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.http.HttpObjectAggregator; 8 | import io.netty.handler.codec.http.HttpServerCodec; 9 | import io.netty.handler.stream.ChunkedWriteHandler; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | @ChannelHandler.Sharable 15 | public class ServerInitializer extends ChannelInitializer { 16 | 17 | @Autowired 18 | private WebsocketRequestHandler websocketRequestHandler; 19 | @Autowired 20 | private HttpRequestHandler httpRequestHandler; 21 | 22 | @Override 23 | protected void initChannel(SocketChannel ch) throws Exception { 24 | ChannelPipeline pipeline = ch.pipeline(); 25 | pipeline.addLast(new HttpServerCodec()); 26 | pipeline.addLast(new HttpObjectAggregator(65536)); 27 | pipeline.addLast(new ChunkedWriteHandler()); 28 | // http处理握手 29 | pipeline.addLast(httpRequestHandler); 30 | //websocket处理websocket消息 31 | pipeline.addLast(websocketRequestHandler); 32 | } 33 | } -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/netty/WebsocketRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.netty; 2 | 3 | import com.huangliang.cloudpushwebsocket.service.HttpResponseService; 4 | import com.huangliang.cloudpushwebsocket.service.websocket.WebsocketRequestService; 5 | import com.huangliang.cloudpushwebsocket.service.websocket.WebsocketServiceStrategy; 6 | import io.netty.channel.ChannelHandler; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | import io.netty.handler.codec.http.websocketx.*; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.data.redis.core.RedisTemplate; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.web.HttpRequestHandler; 15 | 16 | @ChannelHandler.Sharable 17 | @Component 18 | @Slf4j 19 | public class WebsocketRequestHandler extends SimpleChannelInboundHandler { 20 | 21 | @Autowired 22 | private WebsocketRequestService websocketRequestService; 23 | 24 | @Override 25 | protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame){ 26 | try { 27 | websocketRequestService.handler(ctx,frame); 28 | } catch (Exception e) { 29 | log.error("请求异常",e); 30 | // e.printStackTrace(); 31 | // httpResponseHandler.responseFailed(ctx); 32 | } 33 | 34 | } 35 | 36 | @Override 37 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 38 | log.info("channelRegistered"); 39 | super.channelRegistered(ctx); 40 | } 41 | 42 | @Override 43 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 44 | log.info("channelUnregistered"); 45 | super.channelUnregistered(ctx); 46 | } 47 | 48 | @Override 49 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 50 | log.info("channelActive"); 51 | super.channelActive(ctx); 52 | } 53 | 54 | @Override 55 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 56 | log.info("channelInactive"); 57 | super.channelInactive(ctx); 58 | } 59 | 60 | @Override 61 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 62 | log.info("channelReadComplete"); 63 | ctx.flush(); 64 | } 65 | 66 | @Override 67 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 68 | cause.printStackTrace(); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/HttpResponseService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelFutureListener; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.http.*; 8 | import io.netty.util.CharsetUtil; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | public class HttpResponseService { 13 | 14 | /** 15 | * 以json形式返回 16 | * @param ctx 17 | * @param object 18 | */ 19 | public void responseJson(ChannelHandlerContext ctx, Object object) { 20 | String context = JSONObject.toJSONString(object); 21 | response(ctx,context); 22 | } 23 | 24 | /** 25 | * 发送的返回值 26 | * @param ctx 返回 27 | * @param context 消息 28 | */ 29 | public void response(ChannelHandlerContext ctx, String context) { 30 | FullHttpResponse res = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8)); 31 | res.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8"); 32 | ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE); 33 | } 34 | 35 | public void response404(ChannelHandlerContext ctx) { 36 | FullHttpResponse res = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND); 37 | ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE); 38 | } 39 | 40 | //返回错误信息 41 | // public void responseError(ChannelHandlerContext ctx,CommonException e){ 42 | // responseJson(ctx,new Message(CommonConsts.ERROR, e.getMessage())); 43 | // } 44 | 45 | //返回失败 46 | public void responseFailed(ChannelHandlerContext ctx){ 47 | FullHttpResponse res = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR); 48 | ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/MessageHttpService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service; 2 | 3 | import com.huangliang.api.entity.request.SendRequest; 4 | import com.huangliang.cloudpushwebsocket.service.channel.ChannelService; 5 | import com.huangliang.cloudpushwebsocket.service.message.MessageSendService; 6 | import com.huangliang.cloudpushwebsocket.util.WebsocketMessageGenerateUtils; 7 | import io.netty.channel.Channel; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.util.CollectionUtils; 12 | 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * 处理http的推送请求 18 | */ 19 | @Slf4j 20 | @Service 21 | public class MessageHttpService { 22 | 23 | @Autowired 24 | private ChannelService channelService; 25 | @Autowired 26 | private MessageSendService messageSendService; 27 | 28 | public void send(SendRequest request){ 29 | Map map = channelService.getAll(); 30 | if(request.getSendToAll()){ 31 | //遍历服务上的所有设备进行推送 32 | for(String channelId : map.keySet()){ 33 | messageSendService.sendMessage(channelId,WebsocketMessageGenerateUtils.generateWebsocketMessage(channelId,request.getRequestId(),request.getMsg())); 34 | } 35 | }else{ 36 | //根据标识进行推送 37 | List list = request.getTo(); 38 | if(CollectionUtils.isEmpty(list)){ 39 | return ; 40 | } 41 | for(String client : list){ 42 | messageSendService.sendMessage(client,WebsocketMessageGenerateUtils.generateWebsocketMessage(client,request.getRequestId(),request.getMsg())); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/channel/ChannelService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.channel; 2 | 3 | import com.huangliang.api.constants.RedisPrefix; 4 | import com.huangliang.api.entity.Client; 5 | import com.huangliang.api.util.DateUtils; 6 | import com.huangliang.api.util.ObjUtils; 7 | import com.huangliang.cloudpushwebsocket.config.ComConfig; 8 | import com.huangliang.cloudpushwebsocket.constants.AttrConstants; 9 | import com.huangliang.cloudpushwebsocket.task.UpdateRedisChannelActiveTimeTask; 10 | import io.netty.channel.Channel; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.dao.DataAccessException; 15 | import org.springframework.data.redis.connection.RedisConnection; 16 | import org.springframework.data.redis.core.RedisCallback; 17 | import org.springframework.data.redis.core.RedisTemplate; 18 | import org.springframework.stereotype.Service; 19 | 20 | import java.util.Date; 21 | import java.util.Map; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | /** 26 | * 维护客户端集合的操作类 27 | */ 28 | @Slf4j 29 | @Service 30 | public class ChannelService { 31 | 32 | @Autowired 33 | private RedisTemplate redisTemplate; 34 | @Autowired 35 | private ComConfig config; 36 | @Autowired 37 | private UpdateRedisChannelActiveTimeTask updateRedisChannelActiveTimeTask; 38 | 39 | //初始化1000容量,减少扩容 40 | private static Map channels = new ConcurrentHashMap(1000); 41 | 42 | /** 43 | * 根据channelId获取单个对象 44 | * @param channelId 45 | * @return 46 | */ 47 | public Channel get(String channelId){ 48 | return channels.get(channelId); 49 | } 50 | 51 | /** 52 | * 获取当前websocket节点所维护的所有客户端websocket连接对象 53 | * @return 54 | */ 55 | public Map getAll(){ 56 | return channels; 57 | } 58 | 59 | /** 60 | * 客户端websocket连接上服务器 61 | * @param channelId 62 | * @param channel 63 | * @return 64 | */ 65 | public Channel put(String channelId,Channel channel){ 66 | try { 67 | //缓存服务端与客户端关联信息 68 | redisTemplate.opsForSet().add(RedisPrefix.PREFIX_SERVERCLIENTS+config.getInstanceId(),channelId); 69 | //给channel对象绑定客户端channelId标识 70 | channel.attr(AttrConstants.channelId).set(channelId); 71 | //给channel对象绑定客户端sessionId会话标识,一次连接保持一致的值 72 | channel.attr(AttrConstants.sessionId).set(config.getInstanceId()+"_"+channelId+"_"+DateUtils.getCurrentDateTimeFormat()); 73 | //更新活跃时间 74 | channel.attr(AttrConstants.activeTime).set(System.currentTimeMillis()+""); 75 | 76 | redisTemplate.executePipelined(new RedisCallback() { 77 | @Override 78 | public Object doInRedis(RedisConnection redisConnection) throws DataAccessException { 79 | redisConnection.openPipeline(); 80 | //缓存客户端信息 81 | // redisTemplate.opsForHash().putAll(RedisPrefix.PREFIX_CLIENT+channelId, ObjUtils.ObjToMap(new Client(channelId,config.getInstanceId()))); 82 | redisConnection.hMSet((RedisPrefix.PREFIX_CLIENT+channelId).getBytes(), ObjUtils.ObjToByteMap(new Client(channelId,config.getInstanceId()))); 83 | // redisTemplate.expire(RedisPrefix.PREFIX_CLIENT+channelId,config.getExpireTime(),TimeUnit.SECONDS); 84 | redisConnection.expire((RedisPrefix.PREFIX_CLIENT+channelId).getBytes(),TimeUnit.SECONDS.toSeconds(config.getExpireTime())); 85 | //缓存服务端与客户端关联信息 86 | // redisTemplate.opsForSet().add(RedisPrefix.PREFIX_SERVERCLIENTS+config.getInstanceId(),channelId); 87 | redisConnection.sAdd((RedisPrefix.PREFIX_SERVERCLIENTS+config.getInstanceId()).getBytes(),channelId.getBytes()); 88 | return null; 89 | } 90 | }); 91 | log.info("加入了客户端:[{}]",channelId); 92 | channels.put(channelId,channel); 93 | return channel; 94 | }catch (Exception e){ 95 | log.error("加入客户端失败:["+channelId+"]",e); 96 | } 97 | return null; 98 | } 99 | 100 | /** 101 | * 移除客户端 102 | * @param channelId 103 | */ 104 | public void remove(String channelId){ 105 | if(!StringUtils.isNotEmpty(channelId)){return;} 106 | Channel channel = get(channelId); 107 | if(channel==null){return;} 108 | try { 109 | String dateTime = channel.attr(AttrConstants.activeTime).get(); 110 | //断开当前连接 111 | get(channelId).close(); 112 | //删除自己节点维护的客户端列表 113 | channels.remove(channelId); 114 | // channel.closeFuture().addListener() 115 | //删除redis中维护的客户端信息 116 | redisTemplate.delete(RedisPrefix.PREFIX_CLIENT+channelId); 117 | //删除redis中客户端与host的关联关系 118 | redisTemplate.opsForSet().remove(RedisPrefix.PREFIX_SERVERCLIENTS+config.getInstanceId(),channelId); 119 | log.info("移除了客户端[{}],上一次的活跃时间为[{}]", 120 | channelId, 121 | StringUtils.isNotEmpty(dateTime)?DateUtils.dateToDateTime(new Date(Long.parseLong(dateTime))):""); 122 | }catch (Exception e){ 123 | log.error("移除客户端失败["+channelId+"]",e); 124 | } 125 | } 126 | 127 | /** 128 | * 更新活跃时间 129 | * @param channel 130 | */ 131 | public void updateActiveTime(Channel channel){ 132 | //更新自己维护的信息 133 | channel.attr(AttrConstants.activeTime).set(System.currentTimeMillis()+""); 134 | //更新redis维护的信息 135 | //redisTemplate.opsForHash().put(RedisPrefix.PREFIX_CLIENT+channel.attr(AttrConstants.attrChannelId).get(),"lastActiveTime" ,DateUtils.dateToDateTime(now)); 136 | updateRedisChannelActiveTimeTask.addChannel(channel.attr(AttrConstants.channelId).get()); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/message/MessageSendService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.message; 2 | 3 | 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.huangliang.api.entity.WebsocketMessage; 6 | import com.huangliang.api.util.UUIDUtils; 7 | import com.huangliang.cloudpushwebsocket.service.channel.ChannelService; 8 | import io.netty.channel.Channel; 9 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 10 | import io.netty.util.Constant; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.UUID; 17 | 18 | /** 19 | * 消息发送类 20 | */ 21 | @Slf4j 22 | @Service 23 | public class MessageSendService { 24 | 25 | @Autowired 26 | private ChannelService channelService; 27 | 28 | /** 29 | * 具体的推送实现方法 30 | * @param channelId 推送的目标对象客户端标识 31 | * @param wsMessage 推送的具体内容 32 | */ 33 | public void sendMessage(String channelId, WebsocketMessage wsMessage){ 34 | Channel channel = channelService.get(channelId); 35 | //0.校验客户端合法性 36 | if(!checkClient(channel)){ 37 | log.info("客户端不可达:[{}]",channelId); 38 | return ; 39 | } 40 | //1.修改本地和redis中维护的客户端的活跃时间 41 | channelService.updateActiveTime(channel); 42 | //2.发起对客户端的推送(websocket消息) 43 | channel.writeAndFlush(generateMessage(wsMessage)); 44 | //3.记录推送日志 45 | 46 | } 47 | 48 | private TextWebSocketFrame generateMessage(WebsocketMessage websocketMessage){ 49 | //设置推送的消息id 50 | if (StringUtils.isEmpty(websocketMessage.getMessageId())){ 51 | websocketMessage.setMessageId(UUIDUtils.getUUID()); 52 | } 53 | return new TextWebSocketFrame(JSONObject.toJSONString(websocketMessage)); 54 | } 55 | 56 | private boolean checkClient(Channel channel){ 57 | if(channel==null){ 58 | log.info("找不到该设备[{}]."); 59 | return false; 60 | } 61 | if(!channel.isOpen()){ 62 | log.info("设备不可达[{}]."); 63 | return false; 64 | } 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/message/MqConsumerService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.message; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.huangliang.api.config.RocketMQConfig; 6 | import com.huangliang.api.constants.Constants; 7 | import com.huangliang.api.entity.WebsocketMessage; 8 | import com.huangliang.api.entity.request.SendRequest; 9 | import com.huangliang.api.util.UUIDUtils; 10 | import com.huangliang.cloudpushwebsocket.constants.AttrConstants; 11 | import com.huangliang.cloudpushwebsocket.service.channel.ChannelService; 12 | import io.github.rhwayfun.springboot.rocketmq.starter.common.AbstractRocketMqConsumer; 13 | import io.github.rhwayfun.springboot.rocketmq.starter.constants.RocketMqContent; 14 | import io.github.rhwayfun.springboot.rocketmq.starter.constants.RocketMqTopic; 15 | import io.netty.channel.Channel; 16 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.apache.rocketmq.common.message.MessageExt; 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.beans.factory.annotation.Value; 21 | import org.springframework.stereotype.Service; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.Set; 26 | 27 | /** 28 | * 处理MQ中的推送消息,对客户端发起推送 29 | */ 30 | @Service 31 | @Slf4j 32 | public class MqConsumerService extends AbstractRocketMqConsumer { 33 | 34 | @Value("${eureka.instance.instance-id}") 35 | private String instanceId; 36 | 37 | @Autowired 38 | private ChannelService channelService; 39 | 40 | @Autowired 41 | private MessageSendService messageSendService; 42 | 43 | @Override 44 | public boolean consumeMsg(RocketMqContent content, MessageExt msg) { 45 | try { 46 | String MqMessage = new String(msg.getBody()); 47 | log.info("=====消费消息:"+MqMessage); 48 | //消息内容 49 | SendRequest request = JSON.parseObject(MqMessage,SendRequest.class); 50 | if(request.getSendToAll()){ 51 | //遍历该服务上的所有客户端进行推送 52 | for(String channelId : channelService.getAll().keySet()){ 53 | messageSendService.sendMessage(channelId,getMessage(channelId,request,msg)); 54 | } 55 | return true; 56 | } 57 | //根据请求标识进行推送 58 | for(String channelId : request.getTo()){ 59 | messageSendService.sendMessage(channelId,getMessage(channelId,request,msg)); 60 | } 61 | return true; 62 | }catch (Exception e){ 63 | log.error("推送失败.",e); 64 | } 65 | return false; 66 | } 67 | 68 | //构造推送消息体 69 | private WebsocketMessage getMessage(String channelId,SendRequest request,MessageExt msg) { 70 | WebsocketMessage websocketMsg = new WebsocketMessage( 71 | request.getRequestId(), 72 | channelService.get(channelId).attr(AttrConstants.sessionId).get(), 73 | UUIDUtils.getUUID(), 74 | WebsocketMessage.MsgType.BUSSINESS.code, 75 | new String[]{channelId}, 76 | request.getMsg(), 77 | request.getFrom(), 78 | Integer.parseInt(msg.getUserProperty(Constants.Trigger)) 79 | ); 80 | return websocketMsg; 81 | } 82 | 83 | 84 | /** 85 | * 订阅该服务实例名的topic 86 | * @return 87 | */ 88 | @Override 89 | public Map> subscribeTopicTags() { 90 | Map> map = new HashMap<>(); 91 | map.put(RocketMQConfig.getWebsocketTopic(instanceId), null); 92 | return map; 93 | } 94 | 95 | /** 96 | * 消费组为服务实例名 97 | * @return 98 | */ 99 | @Override 100 | public String getConsumerGroup() { 101 | return RocketMQConfig.getWebsocketGroup(instanceId); 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/IWebSocketService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 5 | 6 | public interface IWebSocketService { 7 | public void handler(ChannelHandlerContext ctx,WebSocketFrame frame); 8 | } 9 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/WebsocketRequestService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket; 2 | 3 | import com.huangliang.cloudpushwebsocket.service.channel.ChannelService; 4 | import com.huangliang.cloudpushwebsocket.service.websocket.WebsocketServiceStrategy; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.http.websocketx.*; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | @Slf4j 13 | public class WebsocketRequestService { 14 | 15 | @Autowired 16 | private WebsocketServiceStrategy websocketServiceStrategy; 17 | 18 | /** 19 | * 20 | * 策略模式 21 | * 判断是否关闭链路的指令 22 | * 判断是否ping消息 23 | * 判断是否二进制消息 24 | * 判断是否文本消息 25 | * 26 | * @param ctx 27 | * @param frame 28 | */ 29 | public void handler(ChannelHandlerContext ctx, WebSocketFrame frame) { 30 | try { 31 | //处理消息 32 | websocketServiceStrategy.execute(ctx,frame); 33 | } catch (Exception e) { 34 | log.error("处理消息异常", e); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/WebsocketServiceStrategy.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket; 2 | 3 | import com.huangliang.cloudpushwebsocket.service.websocket.handlerClose.CloseWebSocketService; 4 | import com.huangliang.cloudpushwebsocket.service.websocket.handlerPing.PingWebSocketService; 5 | import com.huangliang.cloudpushwebsocket.service.websocket.handlerText.TextWebSocketService; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; 8 | import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; 9 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 10 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 11 | import org.apache.commons.collections.map.HashedMap; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.util.Map; 17 | 18 | @Service 19 | public class WebsocketServiceStrategy { 20 | 21 | @Autowired 22 | private PingWebSocketService pingWebSocketService; 23 | @Autowired 24 | private TextWebSocketService textWebSocketService; 25 | @Autowired 26 | private CloseWebSocketService closeWebSocketService; 27 | 28 | private static Map map = new HashedMap(); 29 | 30 | @PostConstruct 31 | private void init() { 32 | map.put(CloseWebSocketFrame.class,closeWebSocketService); 33 | map.put(PingWebSocketFrame.class,pingWebSocketService); 34 | map.put(TextWebSocketFrame.class,textWebSocketService); 35 | } 36 | 37 | private IWebSocketService getInstants(WebSocketFrame frame){ 38 | return map.get(frame.getClass()); 39 | } 40 | 41 | public void execute(ChannelHandlerContext ctx, WebSocketFrame frame){ 42 | getInstants(frame).handler(ctx,frame); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/handlerClose/CloseWebSocketService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket.handlerClose; 2 | 3 | import com.huangliang.cloudpushwebsocket.service.websocket.IWebSocketService; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class CloseWebSocketService implements IWebSocketService { 10 | @Override 11 | public void handler(ChannelHandlerContext ctx, WebSocketFrame frame) { 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/handlerPing/PingWebSocketService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket.handlerPing; 2 | 3 | import com.huangliang.cloudpushwebsocket.constants.AttrConstants; 4 | import com.huangliang.cloudpushwebsocket.service.channel.ChannelService; 5 | import com.huangliang.cloudpushwebsocket.service.websocket.IWebSocketService; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; 9 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Service; 13 | 14 | @Service 15 | @Slf4j 16 | public class PingWebSocketService implements IWebSocketService { 17 | 18 | @Autowired 19 | private ChannelService channelService; 20 | 21 | @Override 22 | public void handler(ChannelHandlerContext ctx,WebSocketFrame frame) { 23 | Channel channel = ctx.channel(); 24 | log.info("[{}]Ping来了。。。。",channel.attr(AttrConstants.channelId).get()); 25 | //写回pong响应 26 | ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/handlerText/BussinessService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket.handlerText; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.huangliang.api.constants.CommonConsts; 5 | import com.huangliang.api.entity.WebsocketMessage; 6 | import com.huangliang.api.util.RedisUtils; 7 | import com.huangliang.cloudpushwebsocket.config.ComConfig; 8 | import com.huangliang.cloudpushwebsocket.constants.AttrConstants; 9 | import com.huangliang.cloudpushwebsocket.constants.MessageConstants; 10 | import com.huangliang.cloudpushwebsocket.service.message.MessageSendService; 11 | import com.huangliang.cloudpushwebsocket.util.WebsocketMessageGenerateUtils; 12 | import io.netty.channel.Channel; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.data.redis.core.RedisTemplate; 17 | import org.springframework.stereotype.Service; 18 | 19 | import java.util.*; 20 | 21 | /** 22 | * 接收处理websocket消息的业务逻辑类 23 | */ 24 | @Service 25 | @Slf4j 26 | public class BussinessService implements IMessageService { 27 | 28 | @Autowired 29 | private RedisTemplate redisTemplate; 30 | @Autowired 31 | private MessageSendService messageSendService; 32 | @Autowired 33 | private ComConfig comConfig; 34 | 35 | @Override 36 | public void handler(Channel channel, WebsocketMessage websocketMessage) { 37 | if(!checkWsMessage(channel,websocketMessage)){ 38 | return ; 39 | } 40 | String arr[] = websocketMessage.getTo(); 41 | List toClients = Arrays.asList(arr); 42 | Map> hostClientsMap = new HashMap<>(); 43 | List pipeResult = redisTemplate.executePipelined(RedisUtils.getClientHostByClientFromRedis(toClients)); 44 | for (int i=0;i clients = new LinkedList<>(); 64 | clients.add(channelId); 65 | hostClientsMap.put(host, clients); 66 | } 67 | } 68 | } 69 | 70 | private boolean checkWsMessage(Channel channel,WebsocketMessage websocketMessage) { 71 | String[] to = websocketMessage.getTo(); 72 | if(to == null || to.length == 0){ 73 | channel.writeAndFlush( 74 | WebsocketMessageGenerateUtils.generateErrorWebsocketMessage( 75 | channel, 76 | MessageConstants.NoSendToError, 77 | JSONObject.toJSONString(websocketMessage))); 78 | log.info("目标对象为空"+websocketMessage); 79 | return false; 80 | } 81 | websocketMessage.setTrigger(WebsocketMessage.Trigger.WEBSOCKET.code); 82 | websocketMessage.setMsgType(WebsocketMessage.MsgType.BUSSINESS.code); 83 | websocketMessage.setFrom(channel.attr(AttrConstants.channelId).get()); 84 | return true; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/handlerText/HeartBeatService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket.handlerText; 2 | 3 | import com.huangliang.api.entity.WebsocketMessage; 4 | import com.huangliang.cloudpushwebsocket.constants.AttrConstants; 5 | import com.huangliang.cloudpushwebsocket.service.message.MessageSendService; 6 | import io.netty.channel.Channel; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.UUID; 11 | 12 | /** 13 | * 处理心跳websocket消息类型 14 | */ 15 | @Service 16 | public class HeartBeatService implements IMessageService { 17 | 18 | @Autowired 19 | private MessageSendService messageSendService; 20 | 21 | @Override 22 | public void handler(Channel channel, WebsocketMessage websocketMessage) { 23 | websocketMessage.setMessageId(UUID.randomUUID().toString()); 24 | //记录接收到的消息 25 | //给客户端发送心跳消息回执 26 | websocketMessage = getFrame(websocketMessage); 27 | messageSendService.sendMessage(channel.attr(AttrConstants.channelId).get(),websocketMessage); 28 | } 29 | 30 | /** 31 | * 构建心跳回执 32 | * @param websocketMessage 33 | * @return 34 | */ 35 | private WebsocketMessage getFrame(WebsocketMessage websocketMessage){ 36 | websocketMessage.setMsgType(WebsocketMessage.MsgType.HEARTBEAT_ACK.code); 37 | websocketMessage.setTrigger(WebsocketMessage.Trigger.WEBSOCKET.code); 38 | return websocketMessage; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/handlerText/IMessageService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket.handlerText; 2 | 3 | import com.huangliang.api.entity.WebsocketMessage; 4 | import io.netty.channel.Channel; 5 | 6 | /** 7 | * 各种消息类型对应的处理类 8 | */ 9 | public interface IMessageService { 10 | public void handler(Channel channel, WebsocketMessage websocketMessage); 11 | } 12 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/handlerText/MessageServiceStrategy.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket.handlerText; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.huangliang.api.entity.WebsocketMessage; 5 | import com.huangliang.cloudpushwebsocket.constants.MessageConstants; 6 | import com.huangliang.cloudpushwebsocket.util.WebsocketMessageGenerateUtils; 7 | import io.netty.channel.Channel; 8 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import javax.annotation.PostConstruct; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | @Service 18 | @Slf4j 19 | public class MessageServiceStrategy { 20 | 21 | @Autowired 22 | private HeartBeatService heartBeatService; 23 | @Autowired 24 | private BussinessService bussinessService; 25 | 26 | //处理消息策略 27 | private static Map map = new HashMap<>(); 28 | 29 | @PostConstruct 30 | public void init(){ 31 | //处理业务逻辑的消息 32 | map.put(WebsocketMessage.MsgType.BUSSINESS.code,bussinessService); 33 | //处理心跳的消息 34 | map.put(WebsocketMessage.MsgType.HEARTBEAT.code,heartBeatService); 35 | } 36 | 37 | public void handler(Channel channel, WebsocketMessage websocketMessage){ 38 | IMessageService service = map.get(websocketMessage.getMsgType()); 39 | if(service == null){ 40 | log.info("无法解析的消息类型:"+websocketMessage.toString()); 41 | channel.writeAndFlush(generate(channel,websocketMessage)); 42 | return; 43 | } 44 | service.handler(channel,websocketMessage); 45 | } 46 | 47 | private TextWebSocketFrame generate(Channel channel, WebsocketMessage str){ 48 | return WebsocketMessageGenerateUtils.generateResponse( 49 | WebsocketMessageGenerateUtils.generateErrorWebsocketMessage( 50 | channel, 51 | MessageConstants.ParseError, 52 | JSONObject.toJSONString(str))); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/service/websocket/handlerText/TextWebSocketService.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.service.websocket.handlerText; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.huangliang.api.entity.WebsocketMessage; 5 | import com.huangliang.cloudpushwebsocket.constants.AttrConstants; 6 | import com.huangliang.cloudpushwebsocket.constants.MessageConstants; 7 | import com.huangliang.cloudpushwebsocket.service.websocket.IWebSocketService; 8 | import com.huangliang.cloudpushwebsocket.util.WebsocketMessageGenerateUtils; 9 | import io.netty.channel.Channel; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 12 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Service; 17 | 18 | /** 19 | * 处理客户端发过来的推送消息 20 | */ 21 | @Service 22 | @Slf4j 23 | public class TextWebSocketService implements IWebSocketService { 24 | 25 | @Autowired 26 | private MessageServiceStrategy messageServiceStrategy; 27 | 28 | @Override 29 | public void handler(ChannelHandlerContext ctx, WebSocketFrame frame) { 30 | String str = ((TextWebSocketFrame) frame).text(); 31 | if (StringUtils.isEmpty(str)) { 32 | return ; 33 | } 34 | Channel channel = ctx.channel(); 35 | String channelId = channel.attr(AttrConstants.channelId).get(); 36 | String sessionId = channel.attr(AttrConstants.sessionId).get(); 37 | log.info("receive[{}]:" + str,channelId); 38 | //按规定规则解析消息 39 | WebsocketMessage msg = init(str,channelId,sessionId); 40 | if(msg == null){ 41 | ctx.channel().writeAndFlush(errorResponse(channel,str)); 42 | return ; 43 | } 44 | //处理消息 45 | messageServiceStrategy.handler(channel,msg); 46 | } 47 | 48 | /** 49 | * 初始化消息类型 50 | * @param str 51 | * @param channelId 52 | * @return 53 | */ 54 | private WebsocketMessage init(String str,String channelId,String sessionId) { 55 | try { 56 | WebsocketMessage msg = JSONObject.parseObject(str, WebsocketMessage.class); 57 | msg.setFrom(channelId);//设置消息来源 58 | msg.setSessionId(sessionId); 59 | msg.setTrigger(WebsocketMessage.Trigger.WEBSOCKET.code);//设置触发类型为websocket形式 60 | return msg; 61 | } catch (Exception e) { 62 | log.error("json解析失败,无法识别的消息:[{}]",str); 63 | return null; 64 | } 65 | } 66 | 67 | private TextWebSocketFrame errorResponse(Channel channel,String str){ 68 | return WebsocketMessageGenerateUtils.generateResponse( 69 | WebsocketMessageGenerateUtils.generateErrorWebsocketMessage( 70 | channel, 71 | MessageConstants.ParseError, 72 | str)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/task/ScanClientsNotOnline.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.task; 2 | 3 | import com.huangliang.api.constants.RedisPrefix; 4 | import com.huangliang.cloudpushwebsocket.config.ComConfig; 5 | import com.huangliang.cloudpushwebsocket.constants.AttrConstants; 6 | import com.huangliang.cloudpushwebsocket.service.channel.ChannelService; 7 | import io.netty.channel.Channel; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.scheduling.annotation.EnableScheduling; 12 | import org.springframework.scheduling.annotation.Scheduled; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.util.CollectionUtils; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | /** 22 | * 扫描不在线的用户 23 | */ 24 | @Component 25 | @EnableScheduling // 2.开启定时任务 26 | @Slf4j 27 | public class ScanClientsNotOnline { 28 | 29 | @Autowired 30 | private ChannelService channelService; 31 | @Autowired 32 | private RedisTemplate redisTemplate; 33 | @Autowired 34 | private ComConfig config; 35 | 36 | private static Long now = null; 37 | 38 | @Scheduled(fixedRate=60000) 39 | private void execute() { 40 | Long startTime = System.currentTimeMillis(); 41 | log.info("过期客户端扫描任务开启..."); 42 | now = System.currentTimeMillis(); 43 | Map channels = channelService.getAll(); 44 | if(CollectionUtils.isEmpty(channels)){ 45 | log.info("没有客户端连接."); 46 | return ; 47 | } 48 | Channel channel = null; 49 | //存储需要删除的客户端 50 | List delKeys = new ArrayList<>(); 51 | for(String key : channels.keySet()){ 52 | channel = channelService.get(key); 53 | //判断上一次的活跃时间 54 | if(outOfTime(channel)){ 55 | //如果在离线就删除 56 | delKeys.add(key); 57 | }else{ 58 | //如果在线就延长有效时间 59 | redisTemplate.expire(RedisPrefix.PREFIX_CLIENT+key,config.getExpireTime(),TimeUnit.SECONDS); 60 | } 61 | } 62 | //遍历key移除过期客户端连接 63 | for(String key : delKeys){ 64 | channelService.remove(key); 65 | } 66 | redisTemplate.expire(RedisPrefix.PREFIX_SERVERCLIENTS+config.getInstanceId(),120,TimeUnit.SECONDS); 67 | log.info("过期客户端扫描任务执行完毕,踢出[{}]个过期客户端,剩余[{}]个,耗时[{}]ms",delKeys.size(),channels.size(),System.currentTimeMillis()-startTime); 68 | } 69 | 70 | private boolean outOfTime(Channel channel) { 71 | String activeTime = channel.attr(AttrConstants.activeTime).get(); 72 | if(System.currentTimeMillis()-Long.parseLong(activeTime)>config.getIntervalTime()*1000){ 73 | return true; 74 | }else{ 75 | return false; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/task/UpdateRedisChannelActiveTimeTask.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.task; 2 | 3 | import com.huangliang.api.constants.RedisPrefix; 4 | import com.huangliang.api.util.DateUtils; 5 | import com.huangliang.cloudpushwebsocket.config.ComConfig; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.dao.DataAccessException; 9 | import org.springframework.data.redis.connection.RedisConnection; 10 | import org.springframework.data.redis.core.RedisCallback; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import org.springframework.stereotype.Component; 13 | 14 | import javax.annotation.PostConstruct; 15 | import java.util.Date; 16 | import java.util.Iterator; 17 | import java.util.Map; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | /** 22 | * 定时批量更新redis中的客户端活跃时间(减少实时单个修改带来的消耗) 23 | */ 24 | @Component 25 | @Slf4j 26 | public class UpdateRedisChannelActiveTimeTask{ 27 | 28 | //待更新至redis中的客户端标识与活跃时间 29 | private volatile Map channelActiveTime = new ConcurrentHashMap(); 30 | 31 | @Autowired 32 | private RedisTemplate redisTemplate; 33 | 34 | @Autowired 35 | private ComConfig config; 36 | 37 | public void addChannel(String channelId){ 38 | //重复就覆盖 保存最新的 39 | channelActiveTime.put(channelId,new Date()); 40 | } 41 | 42 | //批量更新redis中的客户端活跃时间 43 | @PostConstruct 44 | public void refresh(){ 45 | new Thread(() -> { 46 | while(true){ 47 | try { 48 | TimeUnit.SECONDS.sleep(config.getIntervalClientActiveTime()); 49 | Iterator> it = channelActiveTime.entrySet().iterator(); 50 | if(!it.hasNext()){ 51 | continue; 52 | } 53 | executePipelined(it); 54 | } catch (Exception e) { 55 | log.error("刷新redis中客户端活跃时间失败.",e); 56 | } 57 | } 58 | }).start(); 59 | } 60 | 61 | private void executePipelined(Iterator> it){ 62 | redisTemplate.executePipelined(new RedisCallback() { 63 | @Override 64 | public Object doInRedis(RedisConnection redisConnection) throws DataAccessException { 65 | redisConnection.openPipeline(); 66 | int count = 0; 67 | //1000条客户端活跃时间提交一次redis 68 | while(it.hasNext()){ 69 | Map.Entry entry = it.next(); 70 | //redisTemplate.opsForHash().put(RedisPrefix.PREFIX_CLIENT+channel.attr(Constants.attrChannelId).get(),"lastActiveTime" ,DateUtils.dateToDateTime(now)); 71 | redisConnection.hSet((RedisPrefix.PREFIX_CLIENT+entry.getKey()).getBytes(),"lastActiveTime".getBytes(),DateUtils.dateToDateTime(entry.getValue()).getBytes() ); 72 | it.remove(); 73 | count++; 74 | if(count>=1000){ 75 | break; 76 | } 77 | } 78 | return null; 79 | } 80 | }); 81 | if(it.hasNext()){ 82 | executePipelined(it); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/templet/Redis.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.templet; 2 | 3 | import com.huangliang.api.config.RedisComConfig; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.redis.connection.RedisConnectionFactory; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | 9 | @Configuration 10 | public class Redis { 11 | @Bean 12 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 13 | return RedisComConfig.getTemplate(factory); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/util/NettyUtil.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.util; 2 | 3 | import com.huangliang.api.constants.Constants; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | import io.netty.handler.codec.http.QueryStringDecoder; 6 | import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; 7 | import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; 8 | import io.netty.handler.codec.http.multipart.InterfaceHttpData; 9 | import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType; 10 | import io.netty.handler.codec.http.multipart.MemoryAttribute; 11 | 12 | import java.util.HashMap; 13 | import java.util.Iterator; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Map.Entry; 17 | 18 | public class NettyUtil { 19 | 20 | /** 21 | * 将请求中的参数与参数值转换为Map 22 | * @param req 23 | * @return 24 | */ 25 | public static Map getRequestParams(HttpRequest req) { 26 | Map requestParams = new HashMap(); 27 | if (req.getMethod().toString() == Constants.GET) { 28 | QueryStringDecoder decoder = new QueryStringDecoder(req.getUri()); 29 | Map> parame = decoder.parameters(); 30 | Iterator>> iterator = parame.entrySet().iterator(); 31 | while (iterator.hasNext()) { 32 | Entry> next = iterator.next(); 33 | requestParams.put(next.getKey(), next.getValue().get(0)); 34 | } 35 | } 36 | if (req.getMethod().toString() == Constants.POST) { 37 | HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), req); 38 | List postData = decoder.getBodyHttpDatas(); 39 | for (InterfaceHttpData data : postData) { 40 | if (data.getHttpDataType() == HttpDataType.Attribute) { 41 | MemoryAttribute attribute = (MemoryAttribute) data; 42 | requestParams.put(attribute.getName(), attribute.getValue()); 43 | } 44 | } 45 | } 46 | return requestParams; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/java/com/huangliang/cloudpushwebsocket/util/WebsocketMessageGenerateUtils.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.huangliang.api.constants.Constants; 5 | import com.huangliang.api.entity.WebsocketMessage; 6 | import com.huangliang.cloudpushwebsocket.constants.AttrConstants; 7 | import com.huangliang.cloudpushwebsocket.constants.MessageConstants; 8 | import io.netty.channel.Channel; 9 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 10 | 11 | /** 12 | * 消息对象构建类 13 | */ 14 | public class WebsocketMessageGenerateUtils { 15 | 16 | public static WebsocketMessage generateWebsocketMessage(String channelId, JSONObject msgJson){ 17 | return generateWebsocketMessage(channelId,null,null,msgJson); 18 | } 19 | 20 | public static WebsocketMessage generateWebsocketMessage(String channelId, String requestId, JSONObject msgJson){ 21 | return generateWebsocketMessage(channelId,requestId,null,msgJson); 22 | } 23 | 24 | //根据mq中的内容构建发送消息 25 | // public static WebsocketMessage generateWebsocketMessage(MessageExt msg){ 26 | // return generateWebsocketMessage(msg.getTags(),null,null,new String(msg.getBody())); 27 | // } 28 | 29 | //根据请求中的参数构建发送消息 30 | public static WebsocketMessage generateWebsocketMessage(String channelId,String requestId,String msgId,JSONObject msgJson){ 31 | WebsocketMessage websocketMsg = new WebsocketMessage( 32 | msgId, 33 | WebsocketMessage.MsgType.BUSSINESS.code, 34 | new String[]{channelId}, 35 | msgJson, 36 | Constants.SYSTEM, 37 | WebsocketMessage.Trigger.HTTP.code 38 | ); 39 | return websocketMsg; 40 | } 41 | 42 | //根据请求中的参数构建发送消息 43 | public static WebsocketMessage generateErrorWebsocketMessage(Channel channel,String format,Object... args){ 44 | WebsocketMessage websocketMsg = new WebsocketMessage(); 45 | websocketMsg.setResultMsg(String.format(format,args)); 46 | websocketMsg.setSessionId(channel.attr(AttrConstants.sessionId).get()); 47 | websocketMsg.setFrom(Constants.SYSTEM); 48 | websocketMsg.setTrigger(WebsocketMessage.Trigger.WEBSOCKET.code); 49 | websocketMsg.setMsgType(WebsocketMessage.MsgType.ERROR.code); 50 | return websocketMsg; 51 | } 52 | 53 | public static WebsocketMessage generateShakeHands(Channel channel){ 54 | WebsocketMessage websocketMsg = new WebsocketMessage(); 55 | websocketMsg.setResultMsg(String.format(MessageConstants.ShakeSuccess,channel.attr(AttrConstants.channelId).get())); 56 | websocketMsg.setSessionId(channel.attr(AttrConstants.sessionId).get()); 57 | websocketMsg.setFrom(Constants.SYSTEM); 58 | websocketMsg.setMsgType(WebsocketMessage.MsgType.CONNECTION.code); 59 | websocketMsg.setTrigger(WebsocketMessage.Trigger.WEBSOCKET.code); 60 | return websocketMsg; 61 | } 62 | 63 | public static TextWebSocketFrame generateResponse(WebsocketMessage ws){ 64 | return new TextWebSocketFrame(JSONObject.toJSONString(ws)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/resources/application-config.yml: -------------------------------------------------------------------------------- 1 | config: 2 | client: 3 | #客户端在redis中的有效时间(s) 4 | expire-time: 120 5 | #判断客户端的过期间隔时间(s) 6 | interval-time: 60 7 | #批量更新redis中客户端的活跃时间的间隔时间(s) 8 | interval-client-active-time: 5 9 | #redis分页提交数量(条) 10 | pipeline-page-size: 1000 -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | hostip: ${hostip} 2 | 3 | server: 4 | port: 9000 #内置tomcat启动的端口号(集群需不同名) 5 | 6 | eureka: 7 | instance: 8 | prefer-ip-address: true # 访问路径可以显示IP地址 9 | lease-renewal-interval-in-seconds: 5 # 心跳时间,即服务续约间隔时间(缺省为30s) 10 | lease-expiration-duration-in-seconds: 10 11 | instance-id: ${hostip}:${server.port} #显示服务名称(ip:port集群需不同名,如果重复会覆盖之前的实例) 12 | # client: 13 | # service-url: 14 | # defaultZone: http://10.9.217.160:8000/eureka/ 15 | 16 | spring: 17 | profiles: 18 | active: dev 19 | #引入config.yml 20 | include: config 21 | application: 22 | name: websocket 23 | # rocketmq: 24 | # name-server: 10.9.217.161:9876 25 | # producer-send-msg-timeout: 3000 26 | # producer-group-name: ${spring.application.name}-127-0-0-1-${server.port} 27 | # redis: 28 | # host: 10.9.216.2 29 | # #host: 192.168.99.100 30 | # port: 6399 31 | # timeout: 3000ms 32 | # jedis: 33 | # pool: 34 | # max-active: 20 35 | # max-idle: 8 36 | # min-idle: 0 37 | management: 38 | endpoints: 39 | jmx: 40 | exposure: 41 | include: health,info 42 | exclude: 43 | 44 | 45 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/cloudpush-websocket/src/main/resources/bootstrap.properties -------------------------------------------------------------------------------- /cloudpush-websocket/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{80} - %msg%n 12 | 13 | 14 | 15 | ${log_dir}/websocket_logback.log 16 | 17 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{80} - %msg%n 18 | 19 | true 20 | false 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ERROR 30 | ACCEPT 31 | DENY 32 | 33 | 34 | 35 | 36 | ${log_dir}/%d{yyyy-MM-dd}/error-log.log 37 | 39 | ${maxHistory} 40 | 41 | 42 | 48 | 52 | 53 | 54 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | WARN 65 | ACCEPT 66 | DENY 67 | 68 | 69 | 70 | ${log_dir}/%d{yyyy-MM-dd}/warn-log.log 71 | 72 | 73 | ${maxHistory} 74 | 75 | 76 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | INFO 88 | ACCEPT 89 | DENY 90 | 91 | 92 | 93 | ${log_dir}/%d{yyyy-MM-dd}/info-log.log 94 | 95 | 96 | ${maxHistory} 97 | 98 | 99 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | ERROR 111 | ACCEPT 112 | DENY 113 | 114 | 115 | 116 | ${log_dir}/%d{yyyy-MM-dd}/debug-log.log 117 | 118 | 119 | ${maxHistory} 120 | 121 | 122 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | ERROR 134 | ACCEPT 135 | DENY 136 | 137 | 138 | 139 | ${log_dir}/%d{yyyy-MM-dd}/trace-log.log 140 | 141 | 142 | ${maxHistory} 143 | 144 | 145 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /cloudpush-websocket/src/test/java/com/huangliang/cloudpushwebsocket/CloudpushWebsocketApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.huangliang.cloudpushwebsocket; 2 | 3 | import com.huangliang.api.constants.RedisPrefix; 4 | import com.netflix.discovery.converters.Auto; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest 17 | public class CloudpushWebsocketApplicationTests { 18 | 19 | @Autowired 20 | private RedisTemplate redisTemplate; 21 | 22 | @Test 23 | public void contextLoads() { 24 | } 25 | 26 | @Test 27 | public void a1(){ 28 | Map a = new HashMap(); 29 | a.put("a","b"); 30 | a.put("a2","b2"); 31 | a.put("a3","b3"); 32 | a.put("a4","b4"); 33 | a.put("a5","b5"); 34 | synchronized (a){ 35 | for(String key:a.keySet()){ 36 | if("a3".equals(key)){ 37 | a.remove(key); 38 | } 39 | } 40 | } 41 | System.out.println(a); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.huangliang 8 | cloudpush 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | 14 | cloudpush-api 15 | cloudpush-eureka 16 | cloudpush-portal 17 | cloudpush-websocket 18 | cloudpush-gateway 19 | cloudpush-task 20 | 21 | 22 | 23 | UTF-8 24 | 1.8 25 | 1.8 26 | 4.13.1 27 | 1.2.17 28 | 1.16.18 29 | Finchley.SR2 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-dependencies 38 | ${spring-cloud.version} 39 | pom 40 | import 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-parent 45 | 2.0.1.RELEASE 46 | pom 47 | import 48 | 49 | 50 | ch.qos.logback 51 | logback-core 52 | 1.2.3 53 | 54 | 55 | junit 56 | junit 57 | ${junit.version} 58 | test 59 | 60 | 61 | log4j 62 | log4j 63 | ${log4j.version} 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 2.0.0.RELEASE 74 | 75 | 76 | copy-artifact 77 | install 78 | 79 | 80 | 81 | ${project.groupId} 82 | ${project.artifactId} 83 | ${project.version} 84 | ${project.packaging} 85 | 86 | 87 | ${session.executionRootDirectory}/tools/ 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /tools/apache-jmeter-5.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/tools/apache-jmeter-5.0.zip -------------------------------------------------------------------------------- /tools/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 76 | 77 | 78 | 79 |
80 | 从服务端收到的消息: 81 |
82 |
83 |
    84 |
    85 |
    86 | 发送给服务端的消息: 87 |
    88 |
    89 | 90 | 91 | 92 | 93 |
    94 | 95 | 96 | 160 | -------------------------------------------------------------------------------- /tools/nacos-config.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangQAQ/cloudpush/c4c7e278b701c47de4476cd94935e6384b872f4a/tools/nacos-config.zip -------------------------------------------------------------------------------- /tools/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | java -jar -Xms1g -Xmx1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+CMSClassUnloadingEnabled -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 cloudpush-eureka.jar >> eureka.log & 3 | java -jar -Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+CMSClassUnloadingEnabled -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 cloudpush-websocket.jar >> websocket.log & 4 | java -jar -Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+CMSClassUnloadingEnabled -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 cloudpush-gateway.jar >> gateway.log & 5 | java -jar -Xms1g -Xmx1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+CMSClassUnloadingEnabled -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 cloudpush-portal.jar >> portal.log & --------------------------------------------------------------------------------