版本升级到4.0.0

参见更新日志
This commit is contained in:
远方夕阳 2021-05-09 10:41:44 +08:00
parent 57ae7a2426
commit 756ff96b8a
278 changed files with 5485 additions and 19796 deletions

View File

@ -1,5 +1,9 @@
#### 项目介绍
CIM项目是基于mina或者netty框架下的推送系统我们平常使用第三方的推送SDK如极光推送百度推送小米推送以及腾讯信鸽等来支撑自己的移动端的业务或许有一些用户自己实现即时通讯系统的需求那么CIM为您提供了一个解决方案或者思路目前CIM支持 websocketandroidios桌面应用系统应用等多端接入支持目前CIM服务端使用springboot搭建仅仅拥有消息推送的功能关于数据缓存与持久化都需要使用者自己开发但是配备了比较完整的使用文档。最后希望CIM能为您带来一些价值。
CIM是一套完善的消息推送框架可应用于信令推送即时聊天移动设备指令推送等领域。开发者可沉浸于业务开发不用关心消息通道链接消息编解码协议等繁杂处理。
CIM采用业内主流开源技术构建易于扩展和使用并完美支持集群部署支持海量链接目前支持websocketandroidios桌面应用系统应用等多端接入持,可应用于移动应用物联网智能家居嵌入式开发桌面应用WEB应用即时消服务。
用时7年 基于CIM的项目已经运行在全国各个地方包括上司公司各地政务系统警务系统等服务于上百家客户希望CIM也能为您带来价值如果您也希望加入项目成为贡献者请联系我。
#### 在线文档
## [https://www.yuque.com/yuanfangxiyang/ma4ytb](https://www.yuque.com/yuanfangxiyang/ma4ytb)
---
## 以下是基于CIM开发的2款产品并不开源!!!
@ -20,18 +24,6 @@ CIM项目是基于mina或者netty框架下的推送系统我们平常使用
---
#### 目录说明
1.cim-use-examples是各个客户端使用示例
2.cim-client-sdk 是各个客户端的SDK源码
3.cim-server-sdk 是服务端SDK源码,分为 mina和netty 两个版本,二者任选其一
4.cim-boot-server是springboot服务端工程源码,使用Idea工具开发
其中所有的sdk均为IntelliJ IDEA工程Maven打包成jar导出引入到对应的客户端或服务端工程
#### 功能预览
@ -94,4 +86,22 @@ CIM项目是基于mina或者netty框架下的推送系统我们平常使用
4.所有sdk均使用maven构建idea工具开发发现多处代码单词拼写错误使用阿里语法检测组件优化了部分代码
5.同步修改了文档
-------------------------------------------------------------------------------------------
版本:4.0.0/时间:2021-04-30
1.websocket支持心跳机制
2.删除mina版本服务端sdk 、删除java版本客户端sdk
3.cim-boot-server重写、加入了推送集群实现。cim-android-client重写演示了更丰富的功能
4.客户端上行数据参数名修改
account > uid
device > deviceName
CR > PONG
5.文档放到语雀在线文档
6.其他30多处多处代码优化

View File

@ -13,6 +13,16 @@
</sourceRoots>
</configuration>
</facet>
<facet type="jpa" name="JPA">
<configuration>
<setting name="validation-enabled" value="true" />
<setting name="provider-name" value="Hibernate" />
<datasource-mapping>
<factory-entry name="entityManagerFactory" />
</datasource-mapping>
<naming-strategy-map />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
@ -25,63 +35,114 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.2.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.4.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.4.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.4.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.4.4" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.12.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.12.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.30" level="project" />
<orderEntry type="library" name="Maven: jakarta.annotation:jakarta.annotation-api:1.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.2.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.2.4.RELEASE" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.25" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.10.2" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.10.2" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.10.2" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.10.2" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.2" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.10.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:9.0.31" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:9.0.31" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.31" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-validation:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: jakarta.validation:jakarta.validation-api:2.0.2" level="project" />
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.0.18.Final" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.27" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.4.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.4.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.11.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.11.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.11.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.11.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.4.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:9.0.44" level="project" />
<orderEntry type="library" name="Maven: org.glassfish:jakarta.el:3.0.3" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.44" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-freemarker:2.4.4" level="project" />
<orderEntry type="library" name="Maven: org.freemarker:freemarker:2.3.31" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context-support:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.4.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-redis:2.4.6" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-keyvalue:2.4.6" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-tx:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-oxm:5.3.5" level="project" />
<orderEntry type="library" name="Maven: io.lettuce:lettuce-core:6.0.3.RELEASE" level="project" />
<orderEntry type="library" name="Maven: io.projectreactor:reactor-core:3.4.4" level="project" />
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.3" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-jpa:2.4.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-aop:2.4.4" level="project" />
<orderEntry type="library" name="Maven: org.aspectj:aspectjweaver:1.9.6" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.4.4" level="project" />
<orderEntry type="library" name="Maven: com.zaxxer:HikariCP:3.4.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jdbc:5.3.5" level="project" />
<orderEntry type="library" name="Maven: jakarta.transaction:jakarta.transaction-api:1.3.3" level="project" />
<orderEntry type="library" name="Maven: jakarta.persistence:jakarta.persistence-api:2.2.3" level="project" />
<orderEntry type="library" name="Maven: org.hibernate:hibernate-core:5.4.29.Final" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.4.1.Final" level="project" />
<orderEntry type="library" name="Maven: org.javassist:javassist:3.27.0-GA" level="project" />
<orderEntry type="library" name="Maven: net.bytebuddy:byte-buddy:1.10.22" level="project" />
<orderEntry type="library" name="Maven: antlr:antlr:2.7.7" level="project" />
<orderEntry type="library" name="Maven: org.jboss:jandex:2.2.3.Final" level="project" />
<orderEntry type="library" name="Maven: org.dom4j:dom4j:2.1.3" level="project" />
<orderEntry type="library" name="Maven: org.hibernate.common:hibernate-commons-annotations:5.1.2.Final" level="project" />
<orderEntry type="library" name="Maven: org.glassfish.jaxb:jaxb-runtime:2.3.3" level="project" />
<orderEntry type="library" name="Maven: jakarta.xml.bind:jakarta.xml.bind-api:2.3.3" level="project" />
<orderEntry type="library" name="Maven: org.glassfish.jaxb:txw2:2.3.3" level="project" />
<orderEntry type="library" name="Maven: com.sun.istack:istack-commons-runtime:3.0.11" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: com.sun.activation:jakarta.activation:1.2.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-jpa:2.4.6" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:2.4.6" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-orm:5.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aspects:5.3.5" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-boot-starter:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-oas:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.swagger.core.v3:swagger-annotations:2.1.2" level="project" />
<orderEntry type="library" name="Maven: io.swagger.core.v3:swagger-models:2.1.2" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-spi:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-schema:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-core:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-spring-web:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.github.classgraph:classgraph:4.8.83" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-spring-webmvc:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-spring-webflux:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-swagger-common:3.0.0" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.mapstruct:mapstruct:1.3.1.Final" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-data-rest:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-bean-validators:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-swagger2:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.swagger:swagger-annotations:1.5.20" level="project" />
<orderEntry type="library" name="Maven: io.swagger:swagger-models:1.5.20" level="project" />
<orderEntry type="library" name="Maven: io.springfox:springfox-swagger-ui:3.0.0" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.5.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.2.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.2.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.2.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.2.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.2.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-freemarker:2.2.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.freemarker:freemarker:2.3.29" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context-support:5.2.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" />
<orderEntry type="library" name="Maven: org.springframework.plugin:spring-plugin-core:2.0.0.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.plugin:spring-plugin-metadata:2.0.0.RELEASE" level="project" />
<orderEntry type="module-library">
<library name="Maven: com.farsunset:cim-server-sdk:3.8.0">
<library name="Maven: com.farsunset:cim-server-sdk:4.0.0">
<CLASSES>
<root url="jar://$MODULE_DIR$/libs/cim-server-sdk-netty-3.8.0.jar!/" />
<root url="jar://$MODULE_DIR$/libs/cim-server-sdk-netty-4.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="library" name="Maven: org.apache.mina:mina-core:2.1.3" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-handler:4.1.35.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-buffer:4.1.35.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-codec:4.1.35.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-codec-http:4.1.35.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-common:4.1.35.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-transport:4.1.35.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-resolver:4.1.35.Final" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.8.0" level="project" />
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:8.0.22" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-handler:4.1.62.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-resolver:4.1.62.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-buffer:4.1.62.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-codec:4.1.62.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-codec-http:4.1.62.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-common:4.1.62.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-transport:4.1.62.Final" level="project" />
<orderEntry type="library" name="Maven: com.google.protobuf:protobuf-java:3.11.1" level="project" />
<orderEntry type="library" name="Maven: cn.teaey.apns4j:apns4j:1.1.4" level="project" />
<orderEntry type="library" name="Maven: commons-io:commons-io:2.6" level="project" />

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,235 @@
<component name="AnalysisUIOptions">
<option name="GROUP_BY_SEVERITY" value="true" />
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="bed47126-03b3-4370-a6c1-08503492974f" name="Default Changelist" comment="" />
<list default="true" id="bed47126-03b3-4370-a6c1-08503492974f" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/jar.bat" afterDir="false" />
<change afterPath="$PROJECT_DIR$/jar.sh" afterDir="false" />
<change afterPath="$PROJECT_DIR$/libs/cim-server-sdk-netty-4.0.0.jar" afterDir="false" />
<change afterPath="$PROJECT_DIR$/run.bat" afterDir="false" />
<change afterPath="$PROJECT_DIR$/run.sh" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/BootApplication.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/handler/BindHandler.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/handler/annotation/CIMHandler.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/message/BindMessageListener.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/message/PushMessageListener.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/redis/KeyValueRedisTemplate.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/redis/SignalRedisTemplate.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/config/RedisConfig.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/config/SwaggerConfig.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/mvc/controller/api/APNsController.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/service/SessionService.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/service/impl/SessionServiceImpl.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/resources/apns/app.p12" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/resources/logback-spring.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../3.8.0.VERSION" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../README.md" beforeDir="false" afterPath="$PROJECT_DIR$/../README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cim-boot-server.iml" beforeDir="false" afterPath="$PROJECT_DIR$/cim-boot-server.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cim-boot-server.ipr" beforeDir="false" afterPath="$PROJECT_DIR$/cim-boot-server.ipr" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cim-boot-server.iws" beforeDir="false" afterPath="$PROJECT_DIR$/cim-boot-server.iws" afterDir="false" />
<change beforePath="$PROJECT_DIR$/libs/cim-server-sdk-mina-3.8.0.jar" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/libs/cim-server-sdk-netty-3.8.0.jar" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/pom.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/ServerLauncher.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/admin/controller/NavigationController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/mvc/controller/admin/NavigationController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/admin/controller/SessionController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/mvc/controller/admin/SessionController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/api/controller/MessageController.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/mvc/controller/api/MessageController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/config/CIMConfig.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/config/CIMConfig.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/handler/BindHandler.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/handler/SessionClosedHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/handler/ClosedHandler.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/push/CIMMessagePusher.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/push/CIMMessagePusher.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/push/DefaultMessagePusher.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/push/DefaultMessagePusher.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/repository/SessionRepository.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/repository/SessionRepository.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/service/ApnsService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/service/APNsService.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/service/CIMSessionService.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/constants/Constants.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/service/impl/CIMSessionServiceImpl.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/service/impl/JavaApnsServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/farsunset/cim/service/impl/APNsServiceImpl.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/application.properties" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/application.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/i18n/messages.properties" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/i18n/messages.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/page/console/nav.html" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/page/console/nav.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/page/console/session/manage.html" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/page/console/session/manage.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/page/console/webclient/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/page/console/webclient/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/resources/static/js/cim/cim.web.sdk.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/resources/static/js/cim/cim.web.sdk.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk-libs/android/cim-android-sdk-3.8.2.jar" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk-libs/java/cim-java-sdk-3.8.0.jar" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk-libs/js/cim.web.sdk.js" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk-libs/js/cim.web.sdk.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/pom.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMCacheManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMCacheManager.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMConnectorManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMConnectorManager.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMEventBroadcastReceiver.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMEventBroadcastReceiver.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMEventListener.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMEventListener.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMListenerManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMListenerManager.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMPushManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/CIMPushManager.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/coder/ClientMessageDecoder.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/coder/ClientMessageDecoder.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/constant/CIMConstant.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/constant/CIMConstant.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/logger/CIMLogger.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/logger/CIMLogger.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/Message.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/Message.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/Ping.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/Ping.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/Pong.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/Pong.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/ReplyBody.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/ReplyBody.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/SentBody.java" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-android-sdk/src/main/java/com/farsunset/cim/sdk/android/model/SentBody.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-ios-sdk/CIMKit/CIMKit/Handler/CIMSendMessageData.m" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-client-sdk/cim-ios-sdk/CIMKit/CIMKit/Handler/CIMSendMessageData.m" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/cim-java-sdk.iml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/jar.bat" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/jar.sh" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/pom.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/CIMCacheManager.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/CIMConnectorManager.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/CIMEventBroadcastReceiver.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/CIMEventListener.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/CIMListenerManager.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/CIMPushManager.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/CIMPushService.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/coder/CIMLogger.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/coder/ClientMessageDecoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/coder/ClientMessageEncoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/constant/CIMConstant.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/model/HeartbeatRequest.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/model/HeartbeatResponse.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/model/Intent.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/model/Message.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/model/Protobufable.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/model/ReplyBody.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/client/model/SentBody.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/model/proto/Message.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/model/proto/MessageProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/model/proto/ReplyBody.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/model/proto/ReplyBodyProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/model/proto/SentBody.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-sdk/cim-java-sdk/src/main/java/com/farsunset/cim/sdk/model/proto/SentBodyProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-vue/Auto.vue" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-vue/websocket/cim.web.sdk.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-vue/websocket/message.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-vue/websocket/replybody.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-client-vue/websocket/sentbody.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/cim-server-sdk-mina.iml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/jar.bat" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/jar.sh" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/pom.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/coder/AppMessageCodecFactory.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/coder/AppMessageDecoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/coder/AppMessageEncoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/coder/WebMessageCodecFactory.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/coder/WebMessageDecoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/coder/WebMessageEncoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/constant/CIMConstant.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/handler/CIMNioSocketAcceptor.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/handler/CIMRequestHandler.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/handler/WebsocketHandler.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/CIMSession.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/HandshakerResponse.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/HeartbeatRequest.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/HeartbeatResponse.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/Message.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/ReplyBody.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/SentBody.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/Transportable.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/proto/Message.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/proto/MessageProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/proto/ReplyBody.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/proto/ReplyBodyProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/proto/SentBody.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/proto/SentBodyProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/proto/Session.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-mina/src/main/java/com/farsunset/cim/sdk/server/model/proto/SessionProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/cim-server-sdk-netty.iml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/jar.bat" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/jar.sh" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/pom.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/coder/AppMessageDecoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/coder/AppMessageEncoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/coder/WebMessageDecoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/coder/WebMessageEncoder.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/constant/CIMConstant.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/handler/CIMNioSocketAcceptor.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/handler/CIMRequestHandler.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/handler/HeartbeatHandler.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/CIMSession.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/HeartbeatRequest.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/HeartbeatResponse.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/Message.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/ReplyBody.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/SentBody.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/Transportable.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/proto/Message.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/proto/MessageProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/proto/ReplyBody.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/proto/ReplyBodyProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/proto/SentBody.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/proto/SentBodyProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/proto/Session.proto" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-server-sdk/cim-server-sdk-netty/src/main/java/com/farsunset/cim/sdk/server/model/proto/SessionProto.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/caches/build_file_checksums.ser" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/caches/build_file_checksums.ser" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/caches/gradle_models.ser" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/encodings.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/gradle.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/gradle.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/modules.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/modules.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/runConfigurations.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/.idea/vcs.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/build.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/build.gradle" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/libs/cim-android-sdk-3.8.2.jar" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/proguard-rules.pro" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/proguard-rules.pro" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/AndroidManifest.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/AndroidManifest.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/java/com/farsunset/ichat/example/adapter/MessageTimeDescComparator.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/java/com/farsunset/ichat/example/adapter/SystemMsgListViewAdapter.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/java/com/farsunset/ichat/example/app/CIMMonitorActivity.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/java/com/farsunset/ichat/example/app/Constant.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/java/com/farsunset/ichat/example/receiver/CIMPushManagerReceiver.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/java/com/farsunset/ichat/example/ui/LoginActivity.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/java/com/farsunset/ichat/example/ui/SplashActivity.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/java/com/farsunset/ichat/example/ui/SystemMessageActivity.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/color/login_text_button_selector.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/icon.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/login_bg.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/login_default_avatar.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/login_input.9.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/skin_common_btn_blue_bg_pressed.9.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/skin_common_btn_blue_unpressed.9.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/skin_header_bar_bg.9.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/skin_header_btn_back_normal.9.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/skin_header_btn_back_press.9.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/skin_msgbox_bg_nor.9.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/skin_msgbox_bg_pressed.9.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/skin_msgbox_bg_top.9.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable-hdpi/splash_bg.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable/common_button_blue.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable/common_msgbox_bg.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/drawable/top_back_left_selector.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/layout/activity_login.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/layout/activity_login.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/layout/activity_splansh.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/layout/activity_system_chat.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/layout/item_chat_sysmsg.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/layout/layout_global_top_header.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/values/colors.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/values/colors.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/values/strings.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/values/strings.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/app/src/main/res/values/styles.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/build.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/build.gradle" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/gradle.properties" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/gradle.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/gradle/wrapper/gradle-wrapper.properties" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/gradle/wrapper/gradle-wrapper.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/gradlew" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/gradlew" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/gradlew.bat" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/gradlew.bat" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/settings.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-android/settings.gradle" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-java/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-java/cim-client-java.iml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-java/cim-java-client.iml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-java/libs/cim-java-sdk-3.8.0.jar" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-java/pom.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-java/src/main/java/CIMJavaClient.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-web/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-web/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../cim-use-examples/cim-client-web/js/cim/cim.web.sdk.js" beforeDir="false" afterPath="$PROJECT_DIR$/../cim-use-examples/cim-client-web/js/cim/cim.web.sdk.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../doc/AndroidSDK使用文档.doc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../doc/IOS或PC客户端等SDK开发文档.doc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../doc/JavaSDK使用文档.doc" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@ -20,7 +247,21 @@
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="GitSEFilterConfiguration">
<file-type-list>
<filtered-out-file-type name="LOCAL_BRANCH" />
<filtered-out-file-type name="REMOTE_BRANCH" />
<filtered-out-file-type name="TAG" />
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
</file-type-list>
</component>
<component name="ProjectId" id="1PxWQToGq56jcmMz176UjXWLSaU" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
@ -30,6 +271,7 @@
<property name="RequestMappingsPanelOrder1" value="1" />
<property name="RequestMappingsPanelWidth0" value="75" />
<property name="RequestMappingsPanelWidth1" value="75" />
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="aspect.path.notification.shown" value="true" />
@ -42,19 +284,42 @@
<property name="node.js.selected.package.tslint" value="(autodetect)" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
<property name="nodejs_package_manager_path" value="npm" />
<property name="settings.editor.selected.configurable" value="configurable.group.appearance" />
<property name="show.migrate.to.gradle.popup" value="false" />
</component>
<component name="ReactorSettings">
<option name="notificationShown" value="true" />
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\Administrator\Documents\gitee-cim\cim-boot-server\lib" />
<recent name="$PROJECT_DIR$/lib" />
<recent name="C:\Users\Administrator\Documents\cim\cim-boot-server\cim-boot-server\cim-boot-server" />
<recent name="C:\Users\Administrator\Documents\cim\cim-boot-server\cim-boot-server\cim-boot-server\src" />
<recent name="$PROJECT_DIR$" />
<recent name="$PROJECT_DIR$/libs" />
<recent name="$PROJECT_DIR$/src/main/resources" />
<recent name="$PROJECT_DIR$/src/main/java/com/farsunset/cim/component" />
<recent name="$PROJECT_DIR$/src/main/java/com/farsunset/cim/handler" />
</key>
<key name="CopyClassDialog.RECENTS_KEY">
<recent name="com.farsunset.cim.config" />
<recent name="com.farsunset.cim.mvc.controller.admin" />
<recent name="com.farsunset.cim.constants" />
<recent name="com.farsunset.cim.component.redis" />
<recent name="com.farsunset.cim.service.impl" />
</key>
</component>
<component name="RunManager">
<configuration name="ServerLauncher" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" temporary="true" nameIsGenerated="true">
<configuration default="true" type="ArquillianJUnit" factoryName="" nameIsGenerated="true">
<option name="arquillianRunConfiguration">
<value>
<option name="containerStateName" value="" />
</value>
</option>
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration name="BootApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" temporary="true" nameIsGenerated="true">
<module name="cim-boot-server" />
<extension name="coverage">
<pattern>
@ -62,17 +327,18 @@
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="SPRING_BOOT_MAIN_CLASS" value="com.farsunset.cim.ServerLauncher" />
<option name="SPRING_BOOT_MAIN_CLASS" value="com.farsunset.cim.BootApplication" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="Spring Boot.ServerLauncher" />
<item itemvalue="Spring Boot.BootApplication" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="SvnConfiguration">
<configuration />
</component>
@ -104,99 +370,16 @@
<workItem from="1579226829168" duration="8852000" />
<workItem from="1583472574255" duration="526000" />
<workItem from="1583473153031" duration="15009000" />
<workItem from="1618489559220" duration="4137000" />
<workItem from="1619013470791" duration="41000" />
<workItem from="1619014134954" duration="548000" />
<workItem from="1619185841584" duration="14228000" />
<workItem from="1619619256370" duration="6445000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="2" />
</component>
<component name="WindowStateProjectService">
<state x="609" y="345" key="#com.intellij.fileTypes.FileTypeChooser" timestamp="1583567210548">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state x="609" y="345" key="#com.intellij.fileTypes.FileTypeChooser/0.23.1920.1057@0.23.1920.1057" timestamp="1583567210548" />
<state x="645" y="287" key="#com.intellij.openapi.updateSettings.impl.PluginUpdateInfoDialog" timestamp="1583472646530">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state x="645" y="287" key="#com.intellij.openapi.updateSettings.impl.PluginUpdateInfoDialog/0.23.1920.1057@0.23.1920.1057" timestamp="1583472646530" />
<state x="627" y="209" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog" timestamp="1579149988674">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="627" y="209" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog/0.0.1920.1040@0.0.1920.1040" timestamp="1579149988674" />
<state x="690" y="218" key="FileChooserDialogImpl" timestamp="1579230010253">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="690" y="218" key="FileChooserDialogImpl/0.0.1920.1040@0.0.1920.1040" timestamp="1579230010253" />
<state width="1870" height="403" key="GridCell.Tab.0.bottom" timestamp="1583567568644">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state width="1867" height="615" key="GridCell.Tab.0.bottom/0.0.1920.1040@0.0.1920.1040" timestamp="1579243696749" />
<state width="1870" height="403" key="GridCell.Tab.0.bottom/0.23.1920.1057@0.23.1920.1057" timestamp="1583567568644" />
<state width="1870" height="403" key="GridCell.Tab.0.center" timestamp="1583567568644">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state width="1867" height="615" key="GridCell.Tab.0.center/0.0.1920.1040@0.0.1920.1040" timestamp="1579243696749" />
<state width="1870" height="403" key="GridCell.Tab.0.center/0.23.1920.1057@0.23.1920.1057" timestamp="1583567568644" />
<state width="1870" height="403" key="GridCell.Tab.0.left" timestamp="1583567568643">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state width="1867" height="615" key="GridCell.Tab.0.left/0.0.1920.1040@0.0.1920.1040" timestamp="1579243696749" />
<state width="1870" height="403" key="GridCell.Tab.0.left/0.23.1920.1057@0.23.1920.1057" timestamp="1583567568643" />
<state width="1870" height="403" key="GridCell.Tab.0.right" timestamp="1583567568644">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state width="1867" height="615" key="GridCell.Tab.0.right/0.0.1920.1040@0.0.1920.1040" timestamp="1579243696749" />
<state width="1870" height="403" key="GridCell.Tab.0.right/0.23.1920.1057@0.23.1920.1057" timestamp="1583567568644" />
<state width="1870" height="565" key="GridCell.Tab.1.bottom" timestamp="1583567566487">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state width="1867" height="615" key="GridCell.Tab.1.bottom/0.0.1920.1040@0.0.1920.1040" timestamp="1579243696751" />
<state width="1870" height="565" key="GridCell.Tab.1.bottom/0.23.1920.1057@0.23.1920.1057" timestamp="1583567566487" />
<state width="1870" height="565" key="GridCell.Tab.1.center" timestamp="1583567566486">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state width="1867" height="615" key="GridCell.Tab.1.center/0.0.1920.1040@0.0.1920.1040" timestamp="1579243696750" />
<state width="1870" height="565" key="GridCell.Tab.1.center/0.23.1920.1057@0.23.1920.1057" timestamp="1583567566486" />
<state width="1870" height="565" key="GridCell.Tab.1.left" timestamp="1583567566486">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state width="1867" height="615" key="GridCell.Tab.1.left/0.0.1920.1040@0.0.1920.1040" timestamp="1579243696749" />
<state width="1870" height="565" key="GridCell.Tab.1.left/0.23.1920.1057@0.23.1920.1057" timestamp="1583567566486" />
<state width="1870" height="565" key="GridCell.Tab.1.right" timestamp="1583567566486">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state width="1867" height="615" key="GridCell.Tab.1.right/0.0.1920.1040@0.0.1920.1040" timestamp="1579243696751" />
<state width="1870" height="565" key="GridCell.Tab.1.right/0.23.1920.1057@0.23.1920.1057" timestamp="1583567566486" />
<state width="1867" height="386" key="GridCell.Tab.2.bottom" timestamp="1579156159339">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1867" height="386" key="GridCell.Tab.2.bottom/0.0.1920.1040@0.0.1920.1040" timestamp="1579156159339" />
<state width="1867" height="386" key="GridCell.Tab.2.center" timestamp="1579156159339">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1867" height="386" key="GridCell.Tab.2.center/0.0.1920.1040@0.0.1920.1040" timestamp="1579156159339" />
<state width="1867" height="386" key="GridCell.Tab.2.left" timestamp="1579156159339">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1867" height="386" key="GridCell.Tab.2.left/0.0.1920.1040@0.0.1920.1040" timestamp="1579156159339" />
<state width="1867" height="386" key="GridCell.Tab.2.right" timestamp="1579156159339">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1867" height="386" key="GridCell.Tab.2.right/0.0.1920.1040@0.0.1920.1040" timestamp="1579156159339" />
<state x="574" y="219" width="771" height="670" key="find.popup" timestamp="1583473246286">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state x="574" y="193" width="771" height="654" key="find.popup/0.0.1920.1040@0.0.1920.1040" timestamp="1579242964438" />
<state x="574" y="219" width="771" height="670" key="find.popup/0.23.1920.1057@0.23.1920.1057" timestamp="1583473246286" />
<state x="161" y="163" key="new project wizard" timestamp="1579242569030">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="161" y="163" key="new project wizard/0.0.1920.1040@0.0.1920.1040" timestamp="1579242569030" />
<state x="539" y="28" width="840" height="1051" key="search.everywhere.popup" timestamp="1583567214095">
<screen x="0" y="23" width="1920" height="1057" />
</state>
<state x="539" y="5" width="840" height="1034" key="search.everywhere.popup/0.0.1920.1040@0.0.1920.1040" timestamp="1579235575382" />
<state x="539" y="28" width="840" height="1051" key="search.everywhere.popup/0.23.1920.1057@0.23.1920.1057" timestamp="1583567214095" />
<option name="version" value="3" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
@ -212,9 +395,9 @@
<option name="timeStamp" value="10" />
</line-breakpoint>
<line-breakpoint enabled="true" type="java-line">
<url>file://$PROJECT_DIR$/src/main/java/com/farsunset/cim/handler/SessionClosedHandler.java</url>
<line>48</line>
<option name="timeStamp" value="12" />
<url>file://$PROJECT_DIR$/src/main/java/com/farsunset/cim/component/message/PushMessageListener.java</url>
<line>24</line>
<option name="timeStamp" value="16" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>

View File

Binary file not shown.

View File

@ -11,15 +11,17 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<version>2.4.4</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<netty.version>4.1.35.Final</netty.version>
<mina.version>2.1.3</mina.version>
<netty.version>4.1.62.Final</netty.version>
<protobuf.version>3.11.1</protobuf.version>
<mysql.jdbc.version>8.0.22</mysql.jdbc.version>
<common.pool.version>2.8.0</common.pool.version>
<swagger.version>3.0.0</swagger.version>
</properties>
<dependencies>
<dependency>
@ -36,26 +38,40 @@
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>com.farsunset</groupId>
<artifactId>cim-server-sdk</artifactId>
<version>3.8.0</version>
<version>4.0.0</version>
<scope>system</scope>
<!-- mina 、netty版本 sdk任选其一 -->
<systemPath>${project.basedir}/libs/cim-server-sdk-netty-3.8.0.jar</systemPath>
<systemPath>${project.basedir}/libs/cim-server-sdk-netty-4.0.0.jar</systemPath>
</dependency>
<!--- ##################使用mina版本SDK时的配置 start ##################-->
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>${mina.version}</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${common.pool.version}</version>
</dependency>
<!--- ##################使用mina版本SDK时的配置 end ##################-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.jdbc.version}</version>
</dependency>
<!--- ##################使用netty本SDK时的配置 start ##################-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
@ -113,7 +129,6 @@
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
</dependencies>

1
cim-boot-server/run.bat Normal file
View File

@ -0,0 +1 @@
java -Dcom.sun.akuma.Daemon=daemonized -Dspring.profiles.active=pro -jar ./cim-boot-server-4.0.0.jar

3
cim-boot-server/run.sh Executable file
View File

@ -0,0 +1,3 @@
#! /bin/bash
java -Dcom.sun.akuma.Daemon=daemonized -Dspring.profiles.active=pro -jar ./cim-boot-server-4.0.0.jar &

View File

@ -19,21 +19,14 @@
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.server.model;
package com.farsunset.cim;
/**
* 需要向另一端发送的结构体
*/
public interface Transportable {
/**
* 消息体字节数组
* @return
*/
byte[] getBody();
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 消息类型
* @return
*/
byte getType();
}
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
import org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration;
import org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration;
import org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration;
import org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration;
@SpringBootApplication
public class ServerLauncher {
public static void main(String[] args) {
SpringApplication.run(ServerLauncher.class, args);
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.component.handler;
import com.farsunset.cim.component.handler.annotation.CIMHandler;
import com.farsunset.cim.component.redis.SignalRedisTemplate;
import com.farsunset.cim.entity.Session;
import com.farsunset.cim.sdk.server.constant.ChannelAttr;
import com.farsunset.cim.sdk.server.group.SessionGroup;
import com.farsunset.cim.sdk.server.handler.CIMRequestHandler;
import com.farsunset.cim.sdk.server.model.ReplyBody;
import com.farsunset.cim.sdk.server.model.SentBody;
import com.farsunset.cim.service.SessionService;
import io.netty.channel.Channel;
import org.springframework.http.HttpStatus;
import javax.annotation.Resource;
/**
* 客户长连接 账户绑定实现
*/
@CIMHandler(key = "client_bind")
public class BindHandler implements CIMRequestHandler {
@Resource
private SessionService sessionService;
@Resource
private SessionGroup sessionGroup;
@Resource
private SignalRedisTemplate signalRedisTemplate;
@Override
public void process(Channel channel, SentBody body) {
ReplyBody reply = new ReplyBody();
reply.setKey(body.getKey());
reply.setCode(HttpStatus.OK.value());
reply.setTimestamp(System.currentTimeMillis());
String uid = body.get("uid");
Session session = new Session();
session.setUid(uid);
session.setNid(channel.attr(ChannelAttr.ID).get());
session.setDeviceId(body.get("deviceId"));
session.setChannel(body.get("channel"));
session.setDeviceName(body.get("deviceName"));
session.setAppVersion(body.get("appVersion"));
session.setOsVersion(body.get("osVersion"));
channel.attr(ChannelAttr.UID).set(uid);
channel.attr(ChannelAttr.CHANNEL).set(session.getChannel());
channel.attr(ChannelAttr.DEVICE_ID).set(session.getDeviceId());
/*
*存储到数据库
*/
sessionService.add(session);
/*
* 添加到内存管理
*/
sessionGroup.add(channel);
/*
*向客户端发送bind响应
*/
channel.writeAndFlush(reply);
/*
* 发送上线事件到集群中的其他实例控制其他设备下线
*/
signalRedisTemplate.bind(session);
}
}

View File

@ -19,44 +19,47 @@
* *
***************************************************************************************
*/
package com.farsunset.cim.handler;
package com.farsunset.cim.component.handler;
import com.farsunset.cim.sdk.server.constant.CIMConstant;
import com.farsunset.cim.component.handler.annotation.CIMHandler;
import com.farsunset.cim.entity.Session;
import com.farsunset.cim.sdk.server.constant.ChannelAttr;
import com.farsunset.cim.sdk.server.group.SessionGroup;
import com.farsunset.cim.sdk.server.handler.CIMRequestHandler;
import com.farsunset.cim.sdk.server.model.CIMSession;
import com.farsunset.cim.sdk.server.model.SentBody;
import com.farsunset.cim.service.CIMSessionService;
import org.springframework.stereotype.Component;
import com.farsunset.cim.service.SessionService;
import io.netty.channel.Channel;
import javax.annotation.Resource;
import java.util.Objects;
/*
* 断开连接清除session
*
/**
* 连接断开时更新用户相关状态
*/
@Component
public class SessionClosedHandler implements CIMRequestHandler {
@CIMHandler(key = "client_closed")
public class ClosedHandler implements CIMRequestHandler {
@Resource
private CIMSessionService cimSessionService;
private SessionService sessionService;
@Resource
private SessionGroup sessionGroup;
@Override
public void process(CIMSession ios, SentBody message) {
Object quietly = ios.getAttribute(CIMConstant.KEY_QUIETLY_CLOSE);
if (Objects.equals(quietly, true)) {
public void process(Channel channel, SentBody message) {
String uid = channel.attr(ChannelAttr.UID).get();
String nid = channel.attr(ChannelAttr.ID).get();
sessionGroup.remove(channel);
if (!Objects.equals(channel.attr(ChannelAttr.CHANNEL).get(), Session.CHANNEL_IOS)){
sessionService.delete(uid,nid);
return;
}
Object account = ios.getAttribute(CIMConstant.KEY_ACCOUNT);
if (account == null) {
return;
}
cimSessionService.remove(account.toString());
sessionService.updateState(uid,nid, Session.STATE_INACTIVE);
}
}

View File

@ -19,14 +19,17 @@
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.model;
package com.farsunset.cim.component.handler.annotation;
/**
* 需要向另一端发送的结构体
*/
public interface Protobufable {
import org.springframework.stereotype.Component;
byte[] getByteArray();
import java.lang.annotation.*;
byte getType();
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface CIMHandler {
String key();
}

View File

@ -0,0 +1,78 @@
package com.farsunset.cim.component.message;
import com.farsunset.cim.entity.Session;
import com.farsunset.cim.sdk.server.constant.ChannelAttr;
import com.farsunset.cim.sdk.server.group.SessionGroup;
import com.farsunset.cim.sdk.server.model.Message;
import com.farsunset.cim.util.JSONUtils;
import io.netty.channel.Channel;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 集群环境下监控多设备登录情况控制是否其余终端下线的逻辑
*/
@Component
public class BindMessageListener implements MessageListener {
private static final String FORCE_OFFLINE_ACTION = "999";
private static final String SYSTEM_ID = "0";
/*
一个账号只能在同一个类型的终端登录
: 多个android或ios不能同时在线
一个android或ios可以和web桌面同时在线
*/
private final Map<String,String[]> conflictMap = new HashMap<>();
@Resource
private SessionGroup sessionGroup;
public BindMessageListener(){
conflictMap.put(Session.CHANNEL_ANDROID,new String[]{Session.CHANNEL_ANDROID,Session.CHANNEL_IOS});
conflictMap.put(Session.CHANNEL_IOS,new String[]{Session.CHANNEL_ANDROID,Session.CHANNEL_IOS});
conflictMap.put(Session.CHANNEL_WINDOWS,new String[]{Session.CHANNEL_WINDOWS,Session.CHANNEL_WEB,Session.CHANNEL_MAC});
conflictMap.put(Session.CHANNEL_WEB,new String[]{Session.CHANNEL_WINDOWS,Session.CHANNEL_WEB,Session.CHANNEL_MAC});
conflictMap.put(Session.CHANNEL_MAC,new String[]{Session.CHANNEL_WINDOWS,Session.CHANNEL_WEB,Session.CHANNEL_MAC});
}
@Override
public void onMessage(org.springframework.data.redis.connection.Message redisMessage, byte[] bytes) {
Session session = JSONUtils.fromJson(redisMessage.getBody(), Session.class);
String uid = session.getUid();
String[] conflictChannels = conflictMap.get(session.getChannel());
Collection<Channel> channelList = sessionGroup.find(uid,conflictChannels);
channelList.removeIf(channel -> session.getNid().equals(channel.attr(ChannelAttr.ID).get()));
/*
* 获取到其他在线的终端连接提示账号再其他终端登录
*/
channelList.forEach(channel -> {
if (Objects.equals(session.getDeviceId(),channel.attr(ChannelAttr.DEVICE_ID).get())){
channel.close();
return;
}
Message message = new Message();
message.setAction(FORCE_OFFLINE_ACTION);
message.setReceiver(uid);
message.setSender(SYSTEM_ID);
message.setContent(session.getDeviceName());
channel.writeAndFlush(message);
channel.close();
});
}
}

View File

@ -0,0 +1,32 @@
package com.farsunset.cim.component.message;
import com.farsunset.cim.sdk.server.group.SessionGroup;
import com.farsunset.cim.sdk.server.model.Message;
import com.farsunset.cim.util.JSONUtils;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 集群环境下监听redis队列广播消息到每个实例进行推送
* 如果使用MQ的情况也最好替换为MQ消息队列
*/
@Component
public class PushMessageListener implements MessageListener {
@Resource
private SessionGroup sessionGroup;
@Override
public void onMessage(org.springframework.data.redis.connection.Message redisMessage, byte[] bytes) {
Message message = JSONUtils.fromJson(redisMessage.getBody(), Message.class);
String uid = message.getReceiver();
sessionGroup.write(uid,message);
}
}

View File

@ -19,7 +19,7 @@
* *
***************************************************************************************
*/
package com.farsunset.cim.push;
package com.farsunset.cim.component.push;
import com.farsunset.cim.sdk.server.model.Message;
@ -34,6 +34,6 @@ public interface CIMMessagePusher {
*
* @param msg
*/
public void push(Message msg);
void push(Message msg);
}

View File

@ -19,34 +19,56 @@
* *
***************************************************************************************
*/
package com.farsunset.cim.api.controller;
package com.farsunset.cim.component.push;
import com.farsunset.cim.push.DefaultMessagePusher;
import com.farsunset.cim.component.redis.KeyValueRedisTemplate;
import com.farsunset.cim.component.redis.SignalRedisTemplate;
import com.farsunset.cim.sdk.server.model.Message;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.farsunset.cim.service.APNsService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@RestController
@RequestMapping("/api/message")
public class MessageController {
/*
* 消息发送实现类
*
*/
@Component
public class DefaultMessagePusher implements CIMMessagePusher {
@Resource
private DefaultMessagePusher defaultMessagePusher;
private APNsService apnsService;
@Resource
private SignalRedisTemplate signalRedisTemplate;
@PostMapping(value = "/send")
public ResponseEntity<Long> send(Message message) {
@Resource
private KeyValueRedisTemplate keyValueRedisTemplate;
message.setId(System.currentTimeMillis());
/**
* 向用户发送消息
*
* @param message
*/
public final void push(Message message) {
defaultMessagePusher.push(message);
String uid = message.getReceiver();
/*
* 说明iOS客户端开启了apns
*/
String deviceToken = keyValueRedisTemplate.getDeviceToken(uid);
if(deviceToken != null) {
apnsService.push(message,deviceToken);
return;
}
/*
* 通过发送redis广播到集群中的每台实例获得当前UID绑定了连接并推送
* @see com.farsunset.hoxin.component.message.PushMessageListener
*/
signalRedisTemplate.push(message);
return ResponseEntity.ok(message.getId());
}
}

View File

@ -19,52 +19,40 @@
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.model;
package com.farsunset.cim.component.redis;
import java.io.Serializable;
import java.util.HashMap;
import com.farsunset.cim.constants.Constants;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
/**
* java |android 客户端请求结构
*
*/
public class Intent implements Serializable {
private static final long serialVersionUID = 1L;
@Component
public class KeyValueRedisTemplate extends StringRedisTemplate {
private String action;
private final HashMap<String, Object> data = new HashMap<String, Object>();
public Intent() {
public KeyValueRedisTemplate(RedisConnectionFactory connectionFactory) {
super(connectionFactory);
}
public Intent(String action) {
this.action = action;
public void set(String key ,String value) {
super.boundValueOps(key).set(value);
}
public String getAction() {
return action;
public String get(String key) {
return super.boundValueOps(key).get();
}
public void setAction(String action) {
this.action = action;
public String getDeviceToken(String uid){
return super.boundValueOps(String.format(Constants.APNS_DEVICE_TOKEN,uid)).get();
}
public void putExtra(String key, Object value) {
data.put(key, value);
public void openApns(String uid,String deviceToken){
super.boundValueOps(String.format(Constants.APNS_DEVICE_TOKEN,uid)).set(deviceToken);
}
public Object getExtra(String key) {
return data.get(key);
public void closeApns(String uid){
super.delete(String.format(Constants.APNS_DEVICE_TOKEN,uid));
}
public long getLongExtra(String key, long defValue) {
Object v = getExtra(key);
try {
return Long.parseLong(v.toString());
} catch (Exception e) {
return defValue;
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.component.redis;
import com.farsunset.cim.constants.Constants;
import com.farsunset.cim.entity.Session;
import com.farsunset.cim.sdk.server.model.Message;
import com.farsunset.cim.util.JSONUtils;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class SignalRedisTemplate extends StringRedisTemplate {
public SignalRedisTemplate(LettuceConnectionFactory connectionFactory) {
super(connectionFactory);
connectionFactory.setValidateConnection(true);
}
/**
* 消息发送到 集群中的每个实例获取对应长连接进行消息写入
* @param message
*/
public void push(Message message) {
super.convertAndSend(Constants.PUSH_MESSAGE_INNER_QUEUE, JSONUtils.toJSONString(message));
}
/**
* 消息发送到 集群中的每个实例解决多终端在线冲突问题
* @param session
*/
public void bind(Session session) {
super.convertAndSend(Constants.BIND_MESSAGE_INNER_QUEUE, JSONUtils.toJSONString(session));
}
}

View File

@ -1,11 +1,13 @@
package com.farsunset.cim.config;
import com.farsunset.cim.handler.BindHandler;
import com.farsunset.cim.handler.SessionClosedHandler;
import com.farsunset.cim.component.handler.annotation.CIMHandler;
import com.farsunset.cim.sdk.server.group.SessionGroup;
import com.farsunset.cim.sdk.server.group.TagSessionGroup;
import com.farsunset.cim.sdk.server.handler.CIMNioSocketAcceptor;
import com.farsunset.cim.sdk.server.handler.CIMRequestHandler;
import com.farsunset.cim.sdk.server.model.CIMSession;
import com.farsunset.cim.sdk.server.model.SentBody;
import com.farsunset.cim.service.SessionService;
import io.netty.channel.Channel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
@ -15,6 +17,7 @@ import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class CIMConfig implements CIMRequestHandler, ApplicationListener<ApplicationStartedEvent> {
@ -22,7 +25,20 @@ public class CIMConfig implements CIMRequestHandler, ApplicationListener<Applica
@Resource
private ApplicationContext applicationContext;
private final HashMap<String,CIMRequestHandler> appHandlerMap = new HashMap<>();
@Resource
private SessionService sessionService;
private final HashMap<String,CIMRequestHandler> handlerMap = new HashMap<>();
@Bean
public SessionGroup sessionGroup() {
return new SessionGroup();
}
@Bean
public TagSessionGroup tagSessionGroup() {
return new TagSessionGroup();
}
@Bean(destroyMethod = "destroy")
@ -38,13 +54,13 @@ public class CIMConfig implements CIMRequestHandler, ApplicationListener<Applica
}
@Override
public void process(CIMSession session, SentBody body) {
public void process(Channel channel, SentBody body) {
CIMRequestHandler handler = appHandlerMap.get(body.getKey());
CIMRequestHandler handler = handlerMap.get(body.getKey());
if(handler == null) {return ;}
handler.process(session, body);
handler.process(channel, body);
}
/*
@ -53,9 +69,22 @@ public class CIMConfig implements CIMRequestHandler, ApplicationListener<Applica
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
appHandlerMap.put("client_bind",applicationContext.getBean(BindHandler.class));
appHandlerMap.put("client_closed",applicationContext.getBean(SessionClosedHandler.class));
Map<String, CIMRequestHandler> beans = applicationContext.getBeansOfType(CIMRequestHandler.class);
for (Map.Entry<String, CIMRequestHandler> entry : beans.entrySet()) {
CIMRequestHandler handler = entry.getValue();
CIMHandler annotation = handler.getClass().getAnnotation(CIMHandler.class);
if (annotation != null){
handlerMap.put(annotation.key(),handler);
}
}
applicationContext.getBean(CIMNioSocketAcceptor.class).bind();
sessionService.deleteLocalhost();
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.config;
import com.farsunset.cim.constants.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import java.util.Objects;
@Configuration
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig {
@Autowired
public RedisConfig(LettuceConnectionFactory connectionFactory, @Value("${spring.profiles.active}") String profile){
if (Objects.equals("dev",profile)){
connectionFactory.setValidateConnection(true);
}
}
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory,
MessageListener pushMessageListener,
MessageListener bindMessageListener){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(pushMessageListener,new ChannelTopic(Constants.PUSH_MESSAGE_INNER_QUEUE));
container.addMessageListener(bindMessageListener,new ChannelTopic(Constants.BIND_MESSAGE_INNER_QUEUE));
return container;
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@EnableOpenApi
@Configuration
public class SwaggerConfig {
@Bean
public Docket userApiDocket(ApiInfo apiInfo) {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo)
.groupName("APP接口")
.select()
.apis(RequestHandlerSelectors.basePackage("com.farsunset.cim.mvc.controller.api"))
.build();
}
@Bean
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("CIM Push Service APIs.")
.description("CIM客户端接口文档")
.version("2.0")
.build();
}
}

View File

@ -19,18 +19,15 @@
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.server.handler;
package com.farsunset.cim.constants;
public interface Constants {
import com.farsunset.cim.sdk.server.model.CIMSession;
import com.farsunset.cim.sdk.server.model.SentBody;
String PUSH_MESSAGE_INNER_QUEUE = "signal/channel/PUSH_MESSAGE_INNER_QUEUE";
public interface CIMRequestHandler {
String BIND_MESSAGE_INNER_QUEUE = "signal/channel/BIND_MESSAGE_INNER_QUEUE";
/**
* 处理长连接发送的请求
* @param session
* @param message
*/
void process(CIMSession session, SentBody message);
String PING_MESSAGE_INNER_QUEUE = "signal/channel/PING_MESSAGE_INNER_QUEUE";
String APNS_DEVICE_TOKEN = "APNS_OPEN_%s";
}

View File

@ -0,0 +1,240 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.entity;
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name = "t_hoxin_session")
public class Session implements Serializable {
private static final transient long serialVersionUID = 1L;
public static final transient int STATE_ACTIVE = 0;
public static final transient int STATE_APNS = 1;
public static final transient int STATE_INACTIVE = 2;
public static final transient String CHANNEL_IOS = "ios";
public static final transient String CHANNEL_ANDROID = "android";
public static final transient String CHANNEL_WINDOWS = "windows";
public static final transient String CHANNEL_MAC = "mac";
public static final transient String CHANNEL_WEB = "web";
/**
* 数据库主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
/**
* session绑定的用户账号
*/
@Column(name = "uid")
private String uid;
/**
* session在本台服务器上的ID
*/
@Column(name = "nid",length = 32,nullable = false)
private String nid;
/**
* 客户端ID (设备号码+应用包名),ios为deviceToken
*/
@Column(name = "device_id",length = 64,nullable = false)
private String deviceId;
/**
* 终端设备型号
*/
@Column(name = "device_name")
private String deviceName;
/**
* session绑定的服务器IP
*/
@Column(name = "host",length = 15,nullable = false)
private String host;
/**
* 终端设备类型
*/
@Column(name = "channel",length = 10,nullable = false)
private String channel;
/**
* 终端应用版本
*/
@Column(name = "app_version")
private String appVersion;
/**
* 终端系统版本
*/
@Column(name = "os_version")
private String osVersion;
/**
* 登录时间
*/
@Column(name = "bind_time")
private Long bindTime;
/**
* 经度
*/
@Column(name = "longitude")
private Double longitude;
/**
* 维度
*/
@Column(name = "latitude")
private Double latitude;
/**
* 位置
*/
@Column(name = "location")
private String location;
/**
* 状态
*/
private int state;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getNid() {
return nid;
}
public void setNid(String nid) {
this.nid = nid;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getAppVersion() {
return appVersion;
}
public void setAppVersion(String appVersion) {
this.appVersion = appVersion;
}
public String getOsVersion() {
return osVersion;
}
public void setOsVersion(String osVersion) {
this.osVersion = osVersion;
}
public Long getBindTime() {
return bindTime;
}
public void setBindTime(Long bindTime) {
this.bindTime = bindTime;
}
public Double getLongitude() {
return longitude;
}
public void setLongitude(Double longitude) {
this.longitude = longitude;
}
public Double getLatitude() {
return latitude;
}
public void setLatitude(Double latitude) {
this.latitude = latitude;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}

View File

@ -1,141 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.handler;
import com.farsunset.cim.push.CIMMessagePusher;
import com.farsunset.cim.sdk.server.constant.CIMConstant;
import com.farsunset.cim.sdk.server.handler.CIMRequestHandler;
import com.farsunset.cim.sdk.server.model.CIMSession;
import com.farsunset.cim.sdk.server.model.Message;
import com.farsunset.cim.sdk.server.model.ReplyBody;
import com.farsunset.cim.sdk.server.model.SentBody;
import com.farsunset.cim.service.CIMSessionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
/*
* 账号绑定实现
*
*/
@Component
public class BindHandler implements CIMRequestHandler {
private final Logger logger = LoggerFactory.getLogger(BindHandler.class);
@Resource
private CIMSessionService cimSessionService;
@Value("${server.host}")
private String host;
@Resource
private CIMMessagePusher defaultMessagePusher;
@Override
public void process(CIMSession newSession, SentBody body) {
ReplyBody reply = new ReplyBody();
reply.setKey(body.getKey());
reply.setCode(HttpStatus.OK.value());
reply.setTimestamp(System.currentTimeMillis());
try {
String account = body.get("account");
newSession.setAccount(account);
newSession.setDeviceId(body.get("deviceId"));
newSession.setHost(host);
newSession.setChannel(body.get("channel"));
newSession.setDeviceModel(body.get("device"));
newSession.setClientVersion(body.get("appVersion"));
newSession.setSystemVersion(body.get("osVersion"));
newSession.setBindTime(System.currentTimeMillis());
/*
* 由于客户端断线服务端可能会无法获知的情况客户端重连时需要关闭旧的连接
*/
CIMSession oldSession = cimSessionService.get(account);
/*
* 如果是账号已经在另一台终端登录则让另一个终端下线
*/
if (oldSession != null && fromOtherDevice(newSession,oldSession) && oldSession.isConnected()) {
sendForceOfflineMessage(oldSession, account, newSession.getDeviceModel());
}
/*
* 有可能是同一个设备重复连接则关闭旧的链接这种情况一般是客户端断网联网又重新链接上来之前的旧链接没有来得及通过心跳机制关闭在这里手动关闭
* 条件1连接来自是同一个设备
* 条件2.2个连接都是同一台服务器
*/
if (oldSession != null && !fromOtherDevice(newSession,oldSession) && Objects.equals(oldSession.getHost(),host)) {
closeQuietly(oldSession);
}
cimSessionService.save(newSession);
} catch (Exception exception) {
reply.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
logger.error("Bind has error",exception);
}
newSession.write(reply);
}
private boolean fromOtherDevice(CIMSession oldSession ,CIMSession newSession) {
return !Objects.equals(oldSession.getDeviceId(), newSession.getDeviceId());
}
private void sendForceOfflineMessage(CIMSession oldSession, String account, String deviceModel) {
Message msg = new Message();
msg.setAction(CIMConstant.MessageAction.ACTION_OFFLINE);
msg.setReceiver(account);
msg.setSender("system");
msg.setContent(deviceModel);
msg.setId(System.currentTimeMillis());
defaultMessagePusher.push(msg);
closeQuietly(oldSession);
}
private void closeQuietly(CIMSession oldSession) {
if (oldSession.isConnected() && Objects.equals(host, oldSession.getHost())) {
oldSession.setAttribute(CIMConstant.KEY_QUIETLY_CLOSE,true);
oldSession.closeOnFlush();
}
}
}

View File

@ -19,7 +19,7 @@
* *
***************************************************************************************
*/
package com.farsunset.cim.admin.controller;
package com.farsunset.cim.mvc.controller.admin;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

View File

@ -19,9 +19,9 @@
* *
***************************************************************************************
*/
package com.farsunset.cim.admin.controller;
package com.farsunset.cim.mvc.controller.admin;
import com.farsunset.cim.service.CIMSessionService;
import com.farsunset.cim.service.SessionService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@ -34,11 +34,13 @@ import javax.annotation.Resource;
public class SessionController {
@Resource
private CIMSessionService cimSessionService;
private SessionService sessionService;
@GetMapping(value = "/list")
public String list(Model model) {
model.addAttribute("sessionList", cimSessionService.list());
model.addAttribute("sessionList", sessionService.findAll());
return "console/session/manage";
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.mvc.controller.api;
import com.farsunset.cim.service.SessionService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/apns")
@Api(produces = "application/json", tags = "APNs推送相关")
public class APNsController {
@ApiOperation(httpMethod = "POST", value = "开启apns")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceToken", value = "APNs的deviceToken", paramType = "query", dataTypeClass = String.class, required = true, example = ""),
@ApiImplicitParam(name = "uid", value = "用户ID", paramType = "query", dataTypeClass = String.class,example = "0")
})
@PostMapping(value = "/open")
public ResponseEntity<Void> open(@RequestParam String uid , @RequestParam String deviceToken) {
sessionService.openApns(uid,deviceToken);
return ResponseEntity.ok().build();
}
@Resource
private SessionService sessionService;
@ApiOperation(httpMethod = "POST", value = "关闭apns")
@ApiImplicitParam(name = "uid", value = "用户ID", paramType = "query", dataTypeClass = String.class,example = "0")
@PostMapping(value = "/close")
public ResponseEntity<Void> close(@RequestParam String uid) {
sessionService.closeApns(uid);
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.mvc.controller.api;
import com.farsunset.cim.component.push.DefaultMessagePusher;
import com.farsunset.cim.sdk.server.model.Message;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/api/message")
@Api(produces = "application/json", tags = "消息相关接口" )
public class MessageController {
@Resource
private DefaultMessagePusher defaultMessagePusher;
@ApiOperation(httpMethod = "POST", value = "发送消息")
@ApiImplicitParams({
@ApiImplicitParam(name = "sender", value = "发送者UID", paramType = "query", dataTypeClass = String.class, required = true, example = ""),
@ApiImplicitParam(name = "receiver", value = "接收者UID", paramType = "query", dataTypeClass = String.class, required = true, example = ""),
@ApiImplicitParam(name = "action", value = "消息动作", paramType = "query", dataTypeClass = String.class, required = true, example = ""),
@ApiImplicitParam(name = "title", value = "消息标题", paramType = "query", dataTypeClass = String.class, example = ""),
@ApiImplicitParam(name = "content", value = "消息内容", paramType = "query", dataTypeClass = String.class, example = ""),
@ApiImplicitParam(name = "format", value = "消息格式", paramType = "query", dataTypeClass = String.class, example = ""),
@ApiImplicitParam(name = "extra", value = "扩展字段", paramType = "query", dataTypeClass = String.class, example = ""),
})
@PostMapping(value = "/send")
public ResponseEntity<Long> send(@RequestParam String sender ,
@RequestParam String receiver ,
@RequestParam String action ,
@RequestParam(required = false) String title ,
@RequestParam(required = false) String content ,
@RequestParam(required = false) String format ,
@RequestParam(required = false) String extra) {
Message message = new Message();
message.setSender(sender);
message.setReceiver(receiver);
message.setAction(action);
message.setContent(content);
message.setFormat(format);
message.setTitle(title);
message.setExtra(extra);
message.setId(System.currentTimeMillis());
defaultMessagePusher.push(message);
return ResponseEntity.ok(message.getId());
}
}

View File

@ -1,96 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.push;
import com.farsunset.cim.sdk.server.model.CIMSession;
import com.farsunset.cim.sdk.server.model.Message;
import com.farsunset.cim.service.ApnsService;
import com.farsunset.cim.service.CIMSessionService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
/*
* 消息发送实现类
*
*/
@Component
public class DefaultMessagePusher implements CIMMessagePusher {
@Value("${server.host}")
private String host;
@Resource
private CIMSessionService cimSessionService;
@Resource
private ApnsService apnsService;
/*
* 向用户发送消息
*
* @param message
*/
@Override
public void push(Message message) {
CIMSession session = cimSessionService.get(message.getReceiver());
if(session == null) {
return;
}
/*
* IOS设备如果开启了apns则使用apns推送
*/
if (session.isIOSChannel() && session.isApnsEnable()) {
apnsService.push(message, session.getDeviceId());
return;
}
/*
* 服务器集群时判断当前session是否连接于本台服务器
* 如果连接到了其他服务器则转发请求到目标服务器
*/
if (session.isConnected() && !Objects.equals(host, session.getHost())) {
/*
* @TODO
* 在此调用目标服务器接口来发送如session.host = 123.123.123.123
* 调用目标服务器的消息发送接口http://123.123.123.123:8080/message/send
*/
return;
}
/*
* 如果是Android浏览器或者windows客户端则直接发送
*/
if (session.isConnected() && Objects.equals(host, session.getHost())) {
session.write(message);
}
}
}

View File

@ -1,34 +1,55 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.repository;
import com.farsunset.cim.sdk.server.model.CIMSession;
import com.farsunset.cim.entity.Session;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/*
* 正式场景下使用redis或者数据库来存储session信息
*/
@Repository
public class SessionRepository {
@Transactional(rollbackFor = Exception.class)
public interface SessionRepository extends JpaRepository<Session, Long> {
private ConcurrentHashMap<String, CIMSession> map = new ConcurrentHashMap<>();
@Modifying
@Query("delete from Session where uid = ?1 and nid = ?2")
void delete(String uid,String nid);
@Modifying
@Query("delete from Session where host = ?1 ")
void deleteAll(String host);
public void save(CIMSession session){
map.put(session.getAccount(),session);
}
@Modifying
@Query("update Session set state = ?3 where uid = ?1 and nid = ?2")
void updateState(String uid,String nid,int state);
public CIMSession get(String account){
return map.get(account);
}
@Modifying
@Query("update Session set state = " + Session.STATE_APNS + " where uid = ?1 and channel = ?2")
void openApns(String uid,String channel);
public void remove(String account){
map.remove(account);
}
public List<CIMSession> findAll(){
return new LinkedList<>(map.values());
}
@Modifying
@Query("update Session set state = " + Session.STATE_ACTIVE + " where uid = ?1 and channel = ?2")
void closeApns(String uid,String channel);
}

View File

@ -23,7 +23,7 @@ package com.farsunset.cim.service;
import com.farsunset.cim.sdk.server.model.Message;
public interface ApnsService {
public interface APNsService {
void push(Message message, String deviceToken);
}

View File

@ -21,23 +21,30 @@
*/
package com.farsunset.cim.service;
import com.farsunset.cim.sdk.server.model.CIMSession;
import com.farsunset.cim.entity.Session;
import java.util.List;
/*
* 集群 session管理实现示例 各位可以自行实现 AbstractSessionManager接口来实现自己的 session管理 服务器集群时
* 须要将CIMSession 信息存入数据库或者redis中 第三方存储空间中便于所有服务器都可以访问
/**
* 存储连接信息便于查看用户的链接信息
*/
public interface CIMSessionService {
public interface SessionService {
void save(CIMSession session);
void add(Session session);
CIMSession get(String account);
void delete(String uid,String nid);
List<CIMSession> list();
void remove(String account);
/**
* 删除本机的连接记录
*/
void deleteLocalhost();
void updateState(String uid,String nid,int state);
void openApns(String uid,String deviceToken);
void closeApns(String uid);
List<Session> findAll();
}

View File

@ -25,29 +25,38 @@ import cn.teaey.apns4j.Apns4j;
import cn.teaey.apns4j.network.ApnsChannel;
import cn.teaey.apns4j.network.ApnsChannelFactory;
import cn.teaey.apns4j.network.ApnsGateway;
import cn.teaey.apns4j.protocol.ApnsPayload;
import com.farsunset.cim.sdk.server.model.Message;
import com.farsunset.cim.service.ApnsService;
import com.farsunset.cim.util.ApnsPayloadCompat;
import com.farsunset.cim.service.APNsService;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.InputStream;
@Service
public class JavaApnsServiceImpl implements ApnsService {
public class APNsServiceImpl implements APNsService {
private static final Logger LOGGER = LoggerFactory.getLogger(JavaApnsServiceImpl.class);
private static final Logger LOGGER = LoggerFactory.getLogger(APNsServiceImpl.class);
private final ApnsChannelFactory apnsChannelFactory;
@Autowired
public APNsServiceImpl(@Value("${apple.apns.p12.file}") String p12File,
@Value("${apple.apns.p12.password}") String password,
@Value("${apple.apns.debug}") boolean isDebug){
InputStream stream = getClass().getResourceAsStream(p12File);
apnsChannelFactory = Apns4j.newChannelFactoryBuilder()
.keyStoreMeta(stream)
.keyStorePwd(password)
.apnsGateway(isDebug ? ApnsGateway.DEVELOPMENT : ApnsGateway.PRODUCTION)
.build();
}
@Value("${apple.apns.p12.password}")
private String password;
@Value("${apple.apns.p12.file}")
private String p12Path;
@Value("${apple.apns.debug}")
private boolean isDebug;
@SuppressWarnings("deprecation")
@Override
@ -56,31 +65,30 @@ public class JavaApnsServiceImpl implements ApnsService {
if(StringUtils.isBlank(deviceToken)) {
return ;
}
InputStream stream = getClass().getResourceAsStream(p12Path);
ApnsChannelFactory apnsChannelFactory = Apns4j.newChannelFactoryBuilder()
.keyStoreMeta(stream)
.keyStorePwd(password)
.apnsGateway(isDebug ? ApnsGateway.DEVELOPMENT : ApnsGateway.PRODUCTION)
.build();
ApnsChannel apnsChannel = apnsChannelFactory.newChannel();
ApnsPayload apnsPayload = new ApnsPayload();
apnsPayload.alert("您有一条新的消息");
apnsPayload.sound("default");
apnsPayload.badge(1);
apnsPayload.extend("id",message.getId());
apnsPayload.extend("action",message.getAction());
apnsPayload.extend("content",message.getContent());
apnsPayload.extend("sender",message.getSender());
apnsPayload.extend("receiver",message.getReceiver());
apnsPayload.extend("format",message.getFormat());
apnsPayload.extend("extra",message.getExtra());
apnsPayload.extend("timestamp",message.getTimestamp());
try {
ApnsPayloadCompat apnsPayload = new ApnsPayloadCompat();
apnsPayload.setAction(message.getAction());
apnsPayload.setContent(message.getContent());
apnsPayload.setSender(message.getSender());
apnsPayload.setFormat(message.getFormat());
apnsPayload.setReceiver(message.getReceiver());
apnsChannel.send(deviceToken, apnsPayload);
LOGGER.info(deviceToken +"\r\ndata: {}",apnsPayload.toJsonString());
LOGGER.info("APNs push done.\ndeviceToken : {} \napnsPayload : {}",deviceToken,apnsPayload.toJsonString());
}catch(Exception exception) {
LOGGER.error("Apns has error",exception);
LOGGER.error("APNs push failed",exception);
}finally {
apnsChannel.close();
IOUtils.closeQuietly(stream);
IOUtils.closeQuietly(apnsChannel);
}
}
}

View File

@ -1,76 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.service.impl;
import com.farsunset.cim.repository.SessionRepository;
import com.farsunset.cim.sdk.server.handler.CIMNioSocketAcceptor;
import com.farsunset.cim.sdk.server.model.CIMSession;
import com.farsunset.cim.service.CIMSessionService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class CIMSessionServiceImpl implements CIMSessionService {
@Resource
private CIMNioSocketAcceptor nioSocketAcceptor;
@Resource
private SessionRepository sessionRepository;
@Override
public void save(CIMSession session) {
sessionRepository.save(session);
}
/*
*
* @param account 用户id
* @return
*/
@Override
public CIMSession get(String account) {
CIMSession session = sessionRepository.get(account);
if (session != null){
session.setSession(nioSocketAcceptor.getManagedSession(session.getNid()));
}
return session;
}
@Override
public void remove(String account) {
sessionRepository.remove(account);
}
@Override
public List<CIMSession> list() {
return sessionRepository.findAll();
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.service.impl;
import com.farsunset.cim.component.redis.KeyValueRedisTemplate;
import com.farsunset.cim.entity.Session;
import com.farsunset.cim.repository.SessionRepository;
import com.farsunset.cim.service.SessionService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
@Service
public class SessionServiceImpl implements SessionService {
@Resource
private SessionRepository sessionRepository;
@Resource
private KeyValueRedisTemplate keyValueRedisTemplate;
private final String host;
public SessionServiceImpl() throws UnknownHostException {
host = InetAddress.getLocalHost().getHostAddress();
}
@Override
public void add(Session session) {
session.setBindTime(System.currentTimeMillis());
session.setHost(host);
sessionRepository.save(session);
}
@Override
public void delete(String uid, String nid) {
sessionRepository.delete(uid,nid);
}
@Override
public void deleteLocalhost() {
sessionRepository.deleteAll(host);
}
@Override
public void updateState(String uid, String nid, int state) {
sessionRepository.updateState(uid,nid,state);
}
@Override
public void openApns(String uid,String deviceToken) {
keyValueRedisTemplate.openApns(uid,deviceToken);
sessionRepository.openApns(uid,Session.CHANNEL_IOS);
}
@Override
public void closeApns(String uid) {
keyValueRedisTemplate.closeApns(uid);
sessionRepository.closeApns(uid,Session.CHANNEL_IOS);
}
@Override
public List<Session> findAll() {
return sessionRepository.findAll();
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.util;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.json.JsonMapper;
import java.io.IOException;
import java.util.List;
public final class JSONUtils {
private static final JsonMapper OBJECT_MAPPER = JsonMapper.builder()
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
.enable(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
public static String toJSONString(Object data) {
try {
return OBJECT_MAPPER.writeValueAsString(data);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
public static <T> T fromJson(String str, Class<T> clazz) {
try {
return OBJECT_MAPPER.readValue(str, clazz);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static <T> T fromJson(byte[] data, Class<T> clazz) {
try {
return OBJECT_MAPPER.readValue(data, clazz);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static <T> List<T> parseList(String str, Class<T> clazz) {
JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructCollectionLikeType(List.class,clazz);
try {
return OBJECT_MAPPER.readValue(str, javaType);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
}

Binary file not shown.

View File

@ -1,6 +1,46 @@
server.port=8080
server.host=127.0.0.1
spring.profiles.active=dev
##################################################################
# JDBC Config #
##################################################################
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/cim?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username = cim
spring.datasource.password = cimv587!
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=MASTER_HIKARI_POOL
spring.datasource.hikari.max-lifetime=120000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.validation-timeout=600000
##################################################################
# JPA Config #
##################################################################
spring.jpa.database = MYSQL
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update
spring.jpa.open-in-view = false
spring.jpa.hibernate.naming.implicit-strategy= org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
spring.jpa.hibernate.naming.physical-strategy= org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
##################################################################
# Redis Config #
##################################################################
spring.redis.host=127.0.0.1
spring.redis.password=RDSV587
spring.redis.database=12
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-wait= 10s
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=1
spring.redis.timeout=10s
##################################################################
# Freemarker Config #
@ -22,5 +62,5 @@ cim.app.port=23456
cim.websocket.port=34567
apple.apns.debug=false
apple.apns.p12.password= your p12 password
apple.apns.p12.file= /apns/lvxin.p12
apple.apns.p12.password=123
apple.apns.p12.file= /apns/app.p12

View File

@ -57,7 +57,7 @@ module.console.cimsession.channel = 终端
module.console.cimsession.app.version =应用版本
module.console.cimsession.os.version =系统版本
module.console.cimsession.deviceid =设备编号
module.console.cimsession.device.model =终端型号
module.console.cimsession.device.name =终端型号
module.console.cimsession.online.time =在线时长(秒)
module.console.cimsession.time.format ={0}秒

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>true</withJansi>
<encoder>
<pattern>[%thread] %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %cyan(%logger{32}) - %msg %n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console"/>
</root>
</configuration>

View File

@ -3,9 +3,14 @@
<ul class="ui-nav-inner">
<li style="height: 50px;text-align: center;margin-top: 10px;">
<a type="button" target="_blank" href="/webclient" class="btn btn-danger" >
<span class="glyphicon glyphicon-globe"></span> WEB版本
<span class="glyphicon glyphicon-globe"></span> 网页终端
</a>
</li>
<li style="height: 50px;text-align: center;margin-top: 10px;">
<a type="button" target="_blank" href="/swagger-ui/index.html" class="btn btn-success" >
<span class="glyphicon glyphicon-leaf"></span> 接口文档
</a>
</li>
<li style="border-bottom: 1px solid #D1D6DA;"></li>
<li class="ui-item" id="sessionMenu">
<a href="/console/session/list">

View File

@ -20,8 +20,7 @@ function showMessageDialog(account){
function doSendMessage(){
var message = $('#message').val();
var account = $('#Saccount').val();
if($.trim(message)=='')
{
if($.trim(message)===''){
return;
}
showProcess("<@spring.message 'module.console.cimsession.sending'/>");
@ -49,35 +48,33 @@ function doSendMessage(){
<table style="width: 100%" class="utable">
<thead>
<tr class="tableHeader">
<th width="8%"><@spring.message 'module.console.cimsession.account'/></th>
<th width="6%"> <@spring.message 'module.console.cimsession.nid'/></th>
<th width="6%"><@spring.message 'module.console.cimsession.channel'/></th>
<th width="14%"><@spring.message 'module.console.cimsession.deviceid'/></th>
<th width="10%"><@spring.message 'module.console.cimsession.device.model'/></th>
<th width="6%"><@spring.message 'module.console.cimsession.app.version'/></th>
<th width="6%"><@spring.message 'module.console.cimsession.os.version'/></th>
<th width="6%"><@spring.message 'module.console.cimsession.online.time'/></th>
<th width="25%"><@spring.message 'module.common.location'/></th>
<th width="8%"><@spring.message "module.common.operation"/></th>
<th width="10%"><@spring.message 'module.console.cimsession.account'/></th>
<th width="10%"><@spring.message 'module.console.cimsession.nid'/></th>
<th width="10%"><@spring.message 'module.console.cimsession.channel'/></th>
<th width="20%"><@spring.message 'module.console.cimsession.deviceid'/></th>
<th width="20%"><@spring.message 'module.console.cimsession.device.name'/></th>
<th width="10%"><@spring.message 'module.console.cimsession.app.version'/></th>
<th width="10%"><@spring.message 'module.console.cimsession.os.version'/></th>
<th width="10%"><@spring.message 'module.console.cimsession.online.time'/></th>
<th width="10%"><@spring.message "module.common.operation"/></th>
</tr>
</thead>
<tbody>
<#list sessionList as cimsession>
<tr style="height: 50px;">
<td>${cimsession.account! }</td>
<td>${cimsession.uid! }</td>
<td><#if cimsession.nid??>${cimsession.nid}</#if></td>
<td>${cimsession.channel! }</td>
<td>${cimsession.deviceId! }</td>
<td>${cimsession.deviceModel! }</td>
<td>${cimsession.clientVersion! }</td>
<td>${cimsession.systemVersion! }</td>
<td>${cimsession.deviceName! }</td>
<td>${cimsession.appVersion! }</td>
<td>${cimsession.osVersion! }</td>
<td>
<@spring.messageArgs "module.console.cimsession.time.format",[((.now?long - cimsession.bindTime)/1000)?round?c] />
</td>
<td>${cimsession.location!}</td>
<td>
<div class="btn-group btn-group-xs">
<button type="button" class="btn btn-primary" style="padding: 5px;" onclick="showMessageDialog('${cimsession.account!}')">
<button type="button" class="btn btn-primary" style="padding: 5px;" onclick="showMessageDialog('${cimsession.uid!}')">
<span class="glyphicon glyphicon-send" style="top:2px;"></span>
<@spring.message 'module.console.cimsession.send.message'/>
</button>

View File

@ -22,14 +22,14 @@
/** 当socket连接成功回调 **/
function onConnectFinished(){
CIMPushManager.bindAccount($('#account').val());
CIMPushManager.bind($('#account').val());
}
/** 当收到请求回复时候回调 **/
function onReplyReceived(reply)
{
console.log(reply);
if(reply.key=='client_bind' && reply.code==200)
if(reply.key==='client_bind' && reply.code === "200" )
{
hideProcess();
@ -48,7 +48,10 @@
function onMessageReceived(message)
{
console.log(message);
if(message.action == ACTION_999){
/*
账户在其他地方登录了
*/
if(message.action === ACTION_999){
$('#MessageDialog').fadeOut();
$('#LoginDialog').fadeIn();
$('#LoginDialog').addClass("in");
@ -56,9 +59,8 @@
}
showNotification(message.content);
$("#messageList").append("<h6 style='text-align: center;'>时间:"+new Date(message.timestamp).toLocaleString()+"</h6>");
$("#messageList").append("<div class='alert alert-info' >"+message.content+"</div>");
var time = new Date(message.timestamp).toLocaleString();
$("#messageList").prepend("<div class='alert alert-info' >"+time+"</p></p>"+message.content+"</div>");
}
@ -83,8 +85,9 @@
$(document).ready(function(){
$('#LoginDialog').fadeIn();
$('#LoginDialog').addClass("in");
initNotification();
$('#host').val(CIM_HOST);
$('#port').val(CIM_PORT);
initNotification();
});
@ -130,12 +133,28 @@
</div>
<div class="input-group" style="margin-top: 30px;margin-left:10px;margin-right:10px;margin-bottom:30px;">
<span class="input-group-addon"><span class="glyphicon glyphicon-user" aria-hidden="true"></span></span>
<span class="input-group-addon">账号</span>
<input type="text" class="form-control" id="account" maxlength="32" placeholder="帐号(数字或者英文字母)"
style="display: inline; width: 100%; height: 50px;" />
</div>
</div>
</div>
<div class="alert alert-success" role="alert" style="margin: 0 10px;">
cim.web.sdk.js中设置cim服务的IP(域名)和端口
</div>
<div class="input-group" style="margin-top: 30px;margin-left:10px;margin-right:10px;margin-bottom:30px;">
<span class="input-group-addon">host</span>
<input type="text" class="form-control" id="host" maxlength="32" readonly = "readonly"
style="display: inline; width: 100%; height: 50px;" />
</div>
<div class="input-group" style="margin-top: 30px;margin-left:10px;margin-right:10px;margin-bottom:30px;">
<span class="input-group-addon">port</span>
<input type="text" class="form-control" id="port" maxlength="32" readonly = "readonly"
style="display: inline; width: 100%; height: 50px;" />
</div>
</div>
<div class="modal-footer" style="text-align: center;">
<a type="button" class="btn btn-success btn-lg" onclick="doLogin()"
style="width: 300px;">登录</a>
@ -146,10 +165,13 @@
<!-- 消息提示页面 -->
<div class="modal fade" data-backdrop="static" id="MessageDialog" tabindex="-1" role="dialog" >
<div class="modal-dialog" style="width: 600px;margin: 30px auto;">
<div class="alert alert-success" role="alert">
通过<a href="/console/session/list" class="alert-link" target="_blank" >控制台</a>或者<a href="/swagger-ui/index.html" target="_blank" class="alert-link">调用接口</a>发送消息
</div>
<div class="modal-dialog" style="width: 720px;margin: 30px auto;">
<div class="modal-content" >
<div class="modal-header" style="text-align: center;">
<span style="float: left;">请在管理页面推送一条消息</span>
<span style="float: left;">消息显示面板</span>
<span style="float: right;color: #4caf50;">当前帐号:<span id="current_account"></span></span>
</div>
<div class="modal-body" id="messageList" style="min-height: 600px;" >

View File

@ -1,5 +1,5 @@
/*CIM服务器IP*/
const CIM_HOST = "127.0.0.1";
const CIM_HOST = window.location.hostname;
/*
*服务端 websocket端口
*/
@ -7,7 +7,7 @@ const CIM_PORT = 34567;
const CIM_URI = "ws://" + CIM_HOST + ":" + CIM_PORT;
const APP_VERSION = "1.0.0";
const APP_CHANNEL = "browser";
const APP_CHANNEL = "web";
const APP_PACKAGE = "com.farsunset.cim";
/*
@ -18,6 +18,15 @@ const DATA_HEADER_LENGTH = 1;
const MESSAGE = 2;
const REPLY_BODY = 4;
const SENT_BODY = 3;
const PING = 1;
const PONG = 0;
/**
* PONG字符串转换后
* @type {Uint8Array}
*/
const PONG_BODY = new Uint8Array([80,79,78,71]);
let socket;
let manualStop = false;
@ -33,12 +42,12 @@ CIMPushManager.connect = function () {
socket.onclose = CIMPushManager.innerOnConnectionClosed;
};
CIMPushManager.bindAccount = function (account) {
CIMPushManager.bind = function (account) {
window.localStorage.account = account;
let deviceId = window.localStorage.deviceIddeviceId;
if (deviceId == '' || deviceId == undefined) {
let deviceId = window.localStorage.deviceId;
if (deviceId === '' || deviceId === undefined) {
deviceId = generateUUID();
window.localStorage.deviceId = deviceId;
}
@ -47,13 +56,13 @@ CIMPushManager.bindAccount = function (account) {
let body = new proto.com.farsunset.cim.sdk.web.model.SentBody();
body.setKey("client_bind");
body.setTimestamp(new Date().getTime());
body.getDataMap().set("account", account);
body.getDataMap().set("uid", account);
body.getDataMap().set("channel", APP_CHANNEL);
body.getDataMap().set("appVersion", APP_VERSION);
body.getDataMap().set("osVersion", browser.version);
body.getDataMap().set("packageName", APP_PACKAGE);
body.getDataMap().set("deviceId", deviceId);
body.getDataMap().set("device", browser.name);
body.getDataMap().set("deviceName", browser.name);
CIMPushManager.sendRequest(body);
};
@ -83,13 +92,18 @@ CIMPushManager.innerOnMessageReceived = function (e) {
let type = data[0];
let body = data.subarray(DATA_HEADER_LENGTH, data.length);
if (type == MESSAGE) {
if (type === PING) {
CIMPushManager.pong();
return;
}
if (type === MESSAGE) {
let message = proto.com.farsunset.cim.sdk.web.model.Message.deserializeBinary(body);
onInterceptMessageReceived(message.toObject(false));
return;
}
if (type == REPLY_BODY) {
if (type === REPLY_BODY) {
let message = proto.com.farsunset.cim.sdk.web.model.ReplyBody.deserializeBinary(body);
/**
* 将proto对象转换成json对象去除无用信息
@ -123,16 +137,24 @@ CIMPushManager.innerOnConnectionClosed = function (e) {
CIMPushManager.sendRequest = function (body) {
let data = body.serializeBinary();
let protobuf = new Uint8Array(data.length);
protobuf.set(data, 0);
let protobuf = new Uint8Array(data.length + 1);
protobuf[0] = SENT_BODY;
protobuf.set(data, 1);
socket.send(protobuf);
};
CIMPushManager.pong = function () {
let pong = new Uint8Array(PONG_BODY.byteLength + 1);
pong[0] = PONG;
pong.set(PONG_BODY,1);
socket.send(pong);
};
function onInterceptMessageReceived(message) {
/*
*被强制下线之后不再继续连接服务端
*/
if (message.action == ACTION_999) {
if (message.action === ACTION_999) {
manualStop = true;
}
/*
@ -174,7 +196,7 @@ function generateUUID() {
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid.replace(/-/g, '');
}

View File

@ -1,5 +1,5 @@
/*CIM服务器IP*/
const CIM_HOST = "127.0.0.1";
const CIM_HOST = window.location.hostname;
/*
*服务端 websocket端口
*/
@ -7,7 +7,7 @@ const CIM_PORT = 34567;
const CIM_URI = "ws://" + CIM_HOST + ":" + CIM_PORT;
const APP_VERSION = "1.0.0";
const APP_CHANNEL = "browser";
const APP_CHANNEL = "web";
const APP_PACKAGE = "com.farsunset.cim";
/*
@ -18,6 +18,15 @@ const DATA_HEADER_LENGTH = 1;
const MESSAGE = 2;
const REPLY_BODY = 4;
const SENT_BODY = 3;
const PING = 1;
const PONG = 0;
/**
* PONG字符串转换后
* @type {Uint8Array}
*/
const PONG_BODY = new Uint8Array([80,79,78,71]);
let socket;
let manualStop = false;
@ -33,12 +42,12 @@ CIMPushManager.connect = function () {
socket.onclose = CIMPushManager.innerOnConnectionClosed;
};
CIMPushManager.bindAccount = function (account) {
CIMPushManager.bind = function (account) {
window.localStorage.account = account;
let deviceId = window.localStorage.deviceIddeviceId;
if (deviceId == '' || deviceId == undefined) {
let deviceId = window.localStorage.deviceId;
if (deviceId === '' || deviceId === undefined) {
deviceId = generateUUID();
window.localStorage.deviceId = deviceId;
}
@ -47,13 +56,13 @@ CIMPushManager.bindAccount = function (account) {
let body = new proto.com.farsunset.cim.sdk.web.model.SentBody();
body.setKey("client_bind");
body.setTimestamp(new Date().getTime());
body.getDataMap().set("account", account);
body.getDataMap().set("uid", account);
body.getDataMap().set("channel", APP_CHANNEL);
body.getDataMap().set("appVersion", APP_VERSION);
body.getDataMap().set("osVersion", browser.version);
body.getDataMap().set("packageName", APP_PACKAGE);
body.getDataMap().set("deviceId", deviceId);
body.getDataMap().set("device", browser.name);
body.getDataMap().set("deviceName", browser.name);
CIMPushManager.sendRequest(body);
};
@ -83,13 +92,18 @@ CIMPushManager.innerOnMessageReceived = function (e) {
let type = data[0];
let body = data.subarray(DATA_HEADER_LENGTH, data.length);
if (type == MESSAGE) {
if (type === PING) {
CIMPushManager.pong();
return;
}
if (type === MESSAGE) {
let message = proto.com.farsunset.cim.sdk.web.model.Message.deserializeBinary(body);
onInterceptMessageReceived(message.toObject(false));
return;
}
if (type == REPLY_BODY) {
if (type === REPLY_BODY) {
let message = proto.com.farsunset.cim.sdk.web.model.ReplyBody.deserializeBinary(body);
/**
* 将proto对象转换成json对象去除无用信息
@ -123,16 +137,24 @@ CIMPushManager.innerOnConnectionClosed = function (e) {
CIMPushManager.sendRequest = function (body) {
let data = body.serializeBinary();
let protobuf = new Uint8Array(data.length);
protobuf.set(data, 0);
let protobuf = new Uint8Array(data.length + 1);
protobuf[0] = SENT_BODY;
protobuf.set(data, 1);
socket.send(protobuf);
};
CIMPushManager.pong = function () {
let pong = new Uint8Array(PONG_BODY.byteLength + 1);
pong[0] = PONG;
pong.set(PONG_BODY,1);
socket.send(pong);
};
function onInterceptMessageReceived(message) {
/*
*被强制下线之后不再继续连接服务端
*/
if (message.action == ACTION_999) {
if (message.action === ACTION_999) {
manualStop = true;
}
/*
@ -174,7 +196,7 @@ function generateUUID() {
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid.replace(/-/g, '');
}

View File

@ -6,7 +6,7 @@
<groupId>com.farsunset</groupId>
<artifactId>cim-android-sdk</artifactId>
<version>3.8.2</version>
<version>4.0.0</version>
<packaging>jar</packaging>
<properties>

View File

@ -29,7 +29,7 @@ import android.net.Uri;
class CIMCacheManager {
public static final String KEY_ACCOUNT = "KEY_ACCOUNT";
public static final String KEY_UID = "KEY_UID";
public static final String KEY_DEVICE_ID = "KEY_DEVICE_ID";
@ -100,5 +100,4 @@ class CIMCacheManager {
String value = getString(context, key);
return value == null ? 0 : Integer.parseInt(value);
}
}

View File

@ -58,13 +58,11 @@ class CIMConnectorManager {
/*
服务端在连接写空闲120秒的时候发送心跳请求给客户端所以客户端在空闲150秒后都没有收到任何数据则关闭链接并重新创建
*/
private static final int CONNECT_ALIVE_TIME_OUT = 150 * 1000;
private static final int CONNECT_ALIVE_TIME_OUT = 120 * 1000;
private static final CIMLogger LOGGER = CIMLogger.getLogger();
private static final HandlerThread IDLE_HANDLER_THREAD = new HandlerThread("READ-IDLE", Process.THREAD_PRIORITY_BACKGROUND);
private volatile SocketChannel socketChannel;
private SocketChannel socketChannel;
private final Context context;
@ -77,15 +75,11 @@ class CIMConnectorManager {
private final ClientMessageEncoder messageEncoder = new ClientMessageEncoder();
private final ClientMessageDecoder messageDecoder = new ClientMessageDecoder();
static {
IDLE_HANDLER_THREAD.start();
}
private CIMConnectorManager(Context context) {
this.context = context;
}
public synchronized static CIMConnectorManager getManager(Context context) {
public static synchronized CIMConnectorManager getManager(Context context) {
if (manager == null) {
manager = new CIMConnectorManager(context);
@ -272,10 +266,10 @@ class CIMConnectorManager {
}
}
private final Handler idleHandler = new Handler(IDLE_HANDLER_THREAD.getLooper()) {
private final Handler idleHandler = new Handler() {
@Override
public void handleMessage(android.os.Message m) {
onSessionIdle();
workerExecutor.execute(() -> onSessionIdle());
}
};

View File

@ -184,7 +184,6 @@ public abstract class CIMEventBroadcastReceiver extends BroadcastReceiver {
return CIMConstant.MessageAction.ACTION_999.equals(action);
}
/**
* 接收消息实现方法
*

View File

@ -82,4 +82,5 @@ public interface CIMEventListener {
* @return 排序 值越大优先级越高
*/
int getEventDispatchOrder();
}

View File

@ -30,14 +30,13 @@ import com.farsunset.cim.sdk.android.model.SentBody;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
/**
* CIM 消息监听器管理
*/
public class CIMListenerManager {
private static final ArrayList<CIMEventListener> cimListeners = new ArrayList<CIMEventListener>();
private static final ArrayList<CIMEventListener> cimListeners = new ArrayList<>();
private static final ReceiveComparator comparator = new ReceiveComparator();
private CIMListenerManager() {
@ -48,70 +47,57 @@ public class CIMListenerManager {
if (!cimListeners.contains(listener)) {
cimListeners.add(listener);
Collections.sort(cimListeners, comparator);
Collections.sort(cimListeners,comparator);
}
}
public static void removeMessageListener(CIMEventListener listener) {
Iterator<CIMEventListener> iterable = cimListeners.iterator();
while (iterable.hasNext()) {
CIMEventListener target = iterable.next();
if (listener.getClass() == target.getClass()) {
iterable.remove();
}
}
cimListeners.remove(listener);
}
public static void notifyOnNetworkChanged(NetworkInfo info) {
Iterator<CIMEventListener> iterable = cimListeners.iterator();
while (iterable.hasNext()) {
iterable.next().onNetworkChanged(info);
for (CIMEventListener cimListener : cimListeners) {
cimListener.onNetworkChanged(info);
}
}
public static void notifyOnConnectFinished(boolean hasAutoBind) {
Iterator<CIMEventListener> iterable = cimListeners.iterator();
while (iterable.hasNext()) {
iterable.next().onConnectFinished(hasAutoBind);
for (CIMEventListener cimListener : cimListeners) {
cimListener.onConnectFinished(hasAutoBind);
}
}
public static void notifyOnMessageReceived(Message message) {
Iterator<CIMEventListener> iterable = cimListeners.iterator();
while (iterable.hasNext()) {
iterable.next().onMessageReceived(message);
for (CIMEventListener cimListener : cimListeners) {
cimListener.onMessageReceived(message);
}
}
public static void notifyOnConnectionClosed() {
Iterator<CIMEventListener> iterable = cimListeners.iterator();
while (iterable.hasNext()) {
iterable.next().onConnectionClosed();
for (CIMEventListener cimListener : cimListeners) {
cimListener.onConnectionClosed();
}
}
public static void notifyOnConnectFailed() {
Iterator<CIMEventListener> iterable = cimListeners.iterator();
while (iterable.hasNext()) {
iterable.next().onConnectFailed();
for (CIMEventListener cimListener : cimListeners) {
cimListener.onConnectFailed();
}
}
public static void notifyOnReplyReceived(ReplyBody body) {
Iterator<CIMEventListener> iterable = cimListeners.iterator();
while (iterable.hasNext()) {
iterable.next().onReplyReceived(body);
for (CIMEventListener cimListener : cimListeners) {
cimListener.onReplyReceived(body);
}
}
public static void notifyOnSendFinished(SentBody body) {
Iterator<CIMEventListener> iterable = cimListeners.iterator();
while (iterable.hasNext()) {
iterable.next().onSendFinished(body);
for (CIMEventListener cimListener : cimListeners) {
cimListener.onSendFinished(body);
}
}
@ -120,9 +106,8 @@ public class CIMListenerManager {
}
public static void logListenersName() {
Iterator<CIMEventListener> iterable = cimListeners.iterator();
while (iterable.hasNext()) {
Log.i(CIMEventListener.class.getSimpleName(), "#######" + iterable.next().getClass().getName() + "#######");
for (CIMEventListener cimListener : cimListeners) {
Log.i(CIMEventListener.class.getSimpleName(), "#######" + cimListener.getClass().getName() + "#######");
}
}
@ -133,7 +118,6 @@ public class CIMListenerManager {
@Override
public int compare(CIMEventListener arg1, CIMEventListener arg2) {
int order1 = arg1.getEventDispatchOrder();
int order2 = arg2.getEventDispatchOrder();
return Integer.compare(order2, order1);

View File

@ -69,14 +69,11 @@ public class CIMPushManager {
return;
}
CIMCacheManager.putString(context, CIMCacheManager.KEY_CIM_SERVER_HOST, host);
CIMCacheManager.putInt(context, CIMCacheManager.KEY_CIM_SERVER_PORT, port);
CIMCacheManager.putBoolean(context, CIMCacheManager.KEY_CIM_DESTROYED, false);
CIMCacheManager.putBoolean(context, CIMCacheManager.KEY_MANUAL_STOP, false);
CIMCacheManager.remove(context, CIMCacheManager.KEY_ACCOUNT);
CIMCacheManager.remove(context, CIMCacheManager.KEY_UID);
Intent serviceIntent = new Intent(context, CIMPushService.class);
@ -110,13 +107,33 @@ public class CIMPushManager {
/**
* 设置一个账号登录到服务端
*/
public static void bindAccount(Context context, String account) {
if (isDestroyed(context) || account == null || account.trim().length() == 0) {
public static void bind(Context context, long uid) {
bind(context,String.valueOf(uid));
}
public static void bind(Context context, String uid) {
if (isDestroyed(context)) {
return;
}
sendBindRequest(context, account);
sendBindRequest(context, uid);
}
public static void setTag(Context context, String tag) {
SentBody sent = new SentBody();
sent.setKey(CIMConstant.RequestKey.CLIENT_SET_TAG);
sent.put("tag", tag);
sendRequest(context, sent);
}
public static void removeTag(Context context) {
SentBody sent = new SentBody();
sent.setKey(CIMConstant.RequestKey.CLIENT_REMOVE_TAG);
sendRequest(context, sent);
}
@ -130,18 +147,17 @@ public class CIMPushManager {
startService(context, serviceIntent);
}
private static void sendBindRequest(Context context, String account) {
private static void sendBindRequest(Context context, String uid) {
CIMCacheManager.putBoolean(context, CIMCacheManager.KEY_MANUAL_STOP, false);
CIMCacheManager.putString(context, CIMCacheManager.KEY_ACCOUNT, account);
CIMCacheManager.putString(context, CIMCacheManager.KEY_UID, uid);
SentBody sent = new SentBody();
sent.setKey(CIMConstant.RequestKey.CLIENT_BIND);
sent.put("account", account);
sent.put("deviceId", getDeviceId(context));
sent.put("uid", String.valueOf(uid));
sent.put("channel", "android");
sent.put("device", Build.MODEL);
sent.put("deviceId", getDeviceId(context));
sent.put("deviceName", Build.MODEL);
sent.put("appVersion", getVersionName(context));
sent.put("osVersion", Build.VERSION.RELEASE);
sent.put("packageName", context.getPackageName());
@ -151,12 +167,13 @@ public class CIMPushManager {
protected static boolean autoBindAccount(Context context) {
String account = CIMCacheManager.getString(context, CIMCacheManager.KEY_ACCOUNT);
if (account == null || account.trim().length() == 0 || isDestroyed(context)) {
String uid = CIMCacheManager.getString(context, CIMCacheManager.KEY_UID);
if (uid == null || isDestroyed(context)) {
return false;
}
sendBindRequest(context, account);
sendBindRequest(context, uid);
return true;
}
@ -200,7 +217,7 @@ public class CIMPushManager {
public static void destroy(Context context) {
CIMCacheManager.putBoolean(context, CIMCacheManager.KEY_CIM_DESTROYED, true);
CIMCacheManager.putString(context, CIMCacheManager.KEY_ACCOUNT, null);
CIMCacheManager.remove(context, CIMCacheManager.KEY_UID);
Intent serviceIntent = new Intent(context, CIMPushService.class);
serviceIntent.setAction(ACTION_DESTROY_CIM_SERVICE);

View File

@ -72,7 +72,7 @@ public class ClientMessageDecoder {
/*
消息读取完成后通过type来解析成对应的消息体
*/
if (CIMConstant.ProtobufType.S_H_RQ == type) {
if (CIMConstant.ProtobufType.PING == type) {
return Ping.getInstance();
}

View File

@ -35,12 +35,12 @@ public interface CIMConstant {
/*
客户端->服务端 发送的心跳响应
*/
byte C_H_RS = 0;
byte PONG = 0;
/*
服务端->客户端 发送的心跳请求
*/
byte S_H_RQ = 1;
byte PING = 1;
byte MESSAGE = 2;
@ -53,6 +53,10 @@ public interface CIMConstant {
String CLIENT_BIND = "client_bind";
String CLIENT_SET_TAG = "client_set_tag";
String CLIENT_REMOVE_TAG = "client_remove_tag";
}
interface MessageAction {

View File

@ -30,7 +30,7 @@ import java.nio.channels.SocketChannel;
* 日志打印添加session 的id和ip address
*/
public class CIMLogger {
private final static String TAG = "CIM";
private static final String TAG = "CIM";
private boolean debug = true;
public static CIMLogger getLogger() {
@ -51,13 +51,13 @@ public class CIMLogger {
public void messageReceived(SocketChannel session, Object message) {
if (debug) {
Log.i(TAG, String.format("[RECEIVED]" + getSessionInfo(session) + "\n%s", message));
Log.i(TAG, "[RECEIVED]" + getSessionInfo(session) + "\n" + message);
}
}
public void messageSent(SocketChannel session, Object message) {
if (debug) {
Log.i(TAG, String.format("[ SENT ]" + getSessionInfo(session) + "\n%s", message));
Log.i(TAG,"[ SENT ]" + getSessionInfo(session) + "\n" + message);
}
}
@ -115,16 +115,13 @@ public class CIMLogger {
if (session.socket().getLocalAddress() != null) {
builder.append(" L:").append(session.socket().getLocalAddress()).append(":").append(session.socket().getLocalPort());
}
} catch (Exception ignore) {
}
} catch (Exception ignore) {}
try {
if (session.socket().getRemoteSocketAddress() != null) {
builder.append(" R:").append(session.socket().getRemoteSocketAddress().toString());
}
} catch (Exception ignore) {
}
} catch (Exception ignore) {}
builder.append("]");
return builder.toString();
}

View File

@ -73,6 +73,15 @@ public class Message implements Serializable {
timestamp = System.currentTimeMillis();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getTimestamp() {
return timestamp;
}
@ -154,17 +163,4 @@ public class Message implements Serializable {
return buffer.toString();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public boolean isNotEmpty(String txt) {
return txt != null && txt.trim().length() != 0;
}
}

View File

@ -21,42 +21,29 @@
*/
package com.farsunset.cim.sdk.android.model;
import com.farsunset.cim.sdk.android.constant.CIMConstant;
import java.io.Serializable;
/**
* 服务端心跳请求
*/
public class Ping implements Serializable, BinaryBody {
public class Ping implements Serializable {
private static final long serialVersionUID = 1L;
private static final String TAG = "SERVER_HEARTBEAT_REQUEST";
private static final String CMD_HEARTBEAT_REQUEST = "SR";
private static final String TAG = "PING";
private static final Ping object = new Ping();
private Ping() {
}
public static Ping getInstance() {
return object;
}
@Override
public byte[] getByteArray() {
return CMD_HEARTBEAT_REQUEST.getBytes();
}
@Override
public String toString() {
return TAG;
}
@Override
public byte getType() {
return CIMConstant.ProtobufType.S_H_RQ;
}
}

View File

@ -31,8 +31,8 @@ import java.io.Serializable;
public class Pong implements Serializable, BinaryBody {
private static final long serialVersionUID = 1L;
private static final String TAG = "CLIENT_HEARTBEAT_RESPONSE";
private static final String CMD_HEARTBEAT_RESPONSE = "CR";
private static final String TAG = "PONG";
private static final String CMD_PONG = "PONG";
private static final Pong object = new Pong();
@ -46,7 +46,7 @@ public class Pong implements Serializable, BinaryBody {
@Override
public byte[] getByteArray() {
return CMD_HEARTBEAT_RESPONSE.getBytes();
return CMD_PONG.getBytes();
}
@Override
@ -56,7 +56,7 @@ public class Pong implements Serializable, BinaryBody {
@Override
public byte getType() {
return CIMConstant.ProtobufType.C_H_RS;
return CIMConstant.ProtobufType.PONG;
}
}

View File

@ -21,8 +21,10 @@
*/
package com.farsunset.cim.sdk.android.model;
import android.util.ArrayMap;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -53,7 +55,7 @@ public class ReplyBody implements Serializable {
/**
* 返回数据集合
*/
private final Hashtable<String, String> data = new Hashtable<String, String>();
private final Map<String, String> data = new HashMap<>();
public long getTimestamp() {
return timestamp;

View File

@ -21,11 +21,13 @@
*/
package com.farsunset.cim.sdk.android.model;
import android.util.ArrayMap;
import com.farsunset.cim.sdk.android.constant.CIMConstant;
import com.farsunset.cim.sdk.android.model.proto.SentBodyProto;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
@ -37,7 +39,7 @@ public class SentBody implements Serializable, BinaryBody {
private String key;
private final Hashtable<String, String> data = new Hashtable<String, String>();
private final Map<String, String> data = new HashMap<>();
private long timestamp;

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>BundleSDK.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>CIMKit.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>CimKit.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>

View File

@ -15,7 +15,7 @@
///
+(NSData *)initHeartbeatData{
NSData * model = [@"CR" convertBytesStringToData];
NSData * model = [@"PONG" convertBytesStringToData];
NSInteger lenght = model.length;
Byte type = 0;
Byte head[3] ;
@ -35,27 +35,7 @@
SentBodyModel * body = [SentBodyModel new];
body.key = @"client_bind";
body.timestamp = (int64_t)[NSDate timeIntervalSinceReferenceDate] *1000;
body.data_p[@"account"] = userId;
body.data_p[@"deviceId"] = [[self class] deviceId];
body.data_p[@"channel"] = @"ios";
NSData *modeData = body.data;
NSInteger lenght = modeData.length;
Byte type = 3;
Byte head[3] ;
head[0] = type;
head[1] = lenght & 0xff;
head[2] = (lenght >> 8) & 0xff;
NSMutableData * sendData = [[NSMutableData alloc] initWithBytes:head length:3];
[sendData appendData:modeData];
return sendData;
}
+(NSData *)initMessageData:(NSString *)userId{
SentBodyModel * body = [SentBodyModel new];
body.key = @"client_bind";
body.timestamp = (int64_t)[NSDate timeIntervalSinceReferenceDate] *1000;
body.data_p[@"account"] = userId;
body.data_p[@"uid"] = userId;
body.data_p[@"deviceId"] = [[self class] deviceId];
body.data_p[@"channel"] = @"ios";
NSData *modeData = body.data;

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: com.google.protobuf:protobuf-java:3.11.1" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" />
</component>
</module>

View File

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.farsunset</groupId>
<artifactId>cim-java-sdk</artifactId>
<version>3.8.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<protobuf.version>3.11.1</protobuf.version>
<slf4j.version>1.7.30</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,80 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client;
import java.util.HashMap;
class CIMCacheManager {
private static final HashMap<String, String> CIM_CONFIG_INFO = new HashMap<String, String>();
public static final String KEY_MANUAL_STOP = "KEY_MANUAL_STOP";
public static final String KEY_CIM_DESTROYED = "KEY_CIM_DESTROYED";
public static final String KEY_CIM_SERVER_HOST = "KEY_CIM_SERVER_HOST";
public static final String KEY_CIM_SERVER_PORT = "KEY_CIM_SERVER_PORT";
public static final String KEY_CIM_CONNECTION_STATE = "KEY_CIM_CONNECTION_STATE";
static CIMCacheManager toolkit;
public static CIMCacheManager getInstance() {
if (toolkit == null) {
toolkit = new CIMCacheManager();
}
return toolkit;
}
public void remove(String key) {
CIM_CONFIG_INFO.remove(key);
}
public void putString(String key, String value) {
CIM_CONFIG_INFO.put(key, value);
}
public String getString(String key) {
return CIM_CONFIG_INFO.get(key);
}
public void putBoolean(String key, boolean value) {
putString(key, Boolean.toString(value));
}
public boolean getBoolean(String key) {
String value = getString(key);
return Boolean.parseBoolean(value);
}
public void putInt(String key, int value) {
putString(key, String.valueOf(value));
}
public int getInt(String key) {
String value = getString(key);
return value == null ? 0 : Integer.parseInt(value);
}
}

View File

@ -1,294 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.farsunset.cim.sdk.client.coder.CIMLogger;
import com.farsunset.cim.sdk.client.coder.ClientMessageDecoder;
import com.farsunset.cim.sdk.client.coder.ClientMessageEncoder;
import com.farsunset.cim.sdk.client.constant.CIMConstant;
import com.farsunset.cim.sdk.client.model.HeartbeatRequest;
import com.farsunset.cim.sdk.client.model.HeartbeatResponse;
import com.farsunset.cim.sdk.client.model.Intent;
import com.farsunset.cim.sdk.client.model.Message;
import com.farsunset.cim.sdk.client.model.Protobufable;
import com.farsunset.cim.sdk.client.model.ReplyBody;
import com.farsunset.cim.sdk.client.model.SentBody;
/**
* 连接服务端管理cim核心处理类管理连接以及消息处理
*
* @author 3979434@qq.com
*/
class CIMConnectorManager {
private static CIMConnectorManager manager;
private final int READ_BUFFER_SIZE = 2048;
private final int WRITE_BUFFER_SIZE = 1024;
private final int CONNECT_TIME_OUT = 10 * 1000;
private final CIMLogger LOGGER = CIMLogger.getLogger();
private SocketChannel socketChannel ;
private ByteBuffer readBuffer = ByteBuffer.allocate(READ_BUFFER_SIZE);
private final ExecutorService workerExecutor = Executors.newFixedThreadPool(1, r -> new Thread(r,"worker-"));
private final ExecutorService bossExecutor = Executors.newFixedThreadPool(1, r -> new Thread(r,"boss-"));
private final ExecutorService eventExecutor = Executors.newFixedThreadPool(1, r -> new Thread(r,"event-"));
private final ClientMessageEncoder messageEncoder = new ClientMessageEncoder();
private final ClientMessageDecoder messageDecoder = new ClientMessageDecoder();
private final ByteBuffer headerBuffer = ByteBuffer.allocate(CIMConstant.DATA_HEADER_LENGTH);
public synchronized static CIMConnectorManager getManager() {
if (manager == null) {
manager = new CIMConnectorManager();
}
return manager;
}
public void connect(final String host, final int port) {
if (isConnected()) {
return;
}
bossExecutor.execute(() -> {
if (isConnected()) {
return;
}
LOGGER.startConnect(host, port);
CIMCacheManager.getInstance().putBoolean(CIMCacheManager.KEY_CIM_CONNECTION_STATE, false);
try {
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(true);
socketChannel.socket().setTcpNoDelay(true);
socketChannel.socket().setKeepAlive(true);
socketChannel.socket().setReceiveBufferSize(READ_BUFFER_SIZE);
socketChannel.socket().setSendBufferSize(WRITE_BUFFER_SIZE);
socketChannel.socket().connect(new InetSocketAddress(host, port),CONNECT_TIME_OUT);
handleConnectedEvent();
/*
*开始读取来自服务端的消息先读取3个字节的消息头
*/
while (socketChannel.read(headerBuffer) > 0) {
handleSocketReadEvent();
}
/*
*read 返回 <= 0的情况发生了意外需要断开重链
*/
closeSession();
}catch(ConnectException | SocketTimeoutException ignore){
handleConnectAbortedEvent();
} catch(IOException ignore) {
handleDisconnectedEvent();
}
});
}
private void handleDisconnectedEvent() {
closeSession();
}
private void handleConnectAbortedEvent() {
long interval = CIMConstant.RECONNECT_INTERVAL_TIME - (5 * 1000 - new Random().nextInt(15 * 1000));
LOGGER.connectFailure(interval);
Intent intent = new Intent();
intent.setAction(CIMConstant.IntentAction.ACTION_CONNECT_FAILED);
intent.putExtra("interval", interval);
sendBroadcast(intent);
}
private void handleConnectedEvent() {
sessionCreated();
}
private void handleSocketReadEvent() throws IOException {
Object message = messageDecoder.doDecode(headerBuffer,socketChannel);
LOGGER.messageReceived(socketChannel, message);
if (isHeartbeatRequest(message)) {
send(HeartbeatResponse.getInstance());
return;
}
this.messageReceived(message);
}
public void send(final Protobufable body) {
if(!isConnected()) {
return;
}
workerExecutor.execute(() -> {
int result = 0;
try {
ByteBuffer buffer = messageEncoder.encode(body);
while(buffer.hasRemaining()){
result += socketChannel.write(buffer);
}
} catch (Exception e) {
result = -1;
}finally {
if(result <= 0) {
closeSession();
}else {
messageSent(body);
}
}
});
}
public void sessionCreated() {
LOGGER.sessionCreated(socketChannel);
Intent intent = new Intent();
intent.setAction(CIMConstant.IntentAction.ACTION_CONNECT_FINISHED);
sendBroadcast(intent);
}
public void sessionClosed() {
LOGGER.sessionClosed(socketChannel);
readBuffer.clear();
if(readBuffer.capacity() > READ_BUFFER_SIZE) {
readBuffer = ByteBuffer.allocate(READ_BUFFER_SIZE);
}
Intent intent = new Intent();
intent.setAction(CIMConstant.IntentAction.ACTION_CONNECTION_CLOSED);
sendBroadcast(intent);
}
public void messageReceived(Object data) {
if (data instanceof Message) {
Intent intent = new Intent();
intent.setAction(CIMConstant.IntentAction.ACTION_MESSAGE_RECEIVED);
intent.putExtra(Message.class.getName(), data);
sendBroadcast(intent);
}
if (data instanceof ReplyBody) {
Intent intent = new Intent();
intent.setAction(CIMConstant.IntentAction.ACTION_REPLY_RECEIVED);
intent.putExtra(ReplyBody.class.getName(), data);
sendBroadcast(intent);
}
}
public void messageSent(Object data) {
LOGGER.messageSent(socketChannel, data);
if (data instanceof SentBody) {
Intent intent = new Intent();
intent.setAction(CIMConstant.IntentAction.ACTION_SEND_FINISHED);
intent.putExtra(SentBody.class.getName(), data);
sendBroadcast(intent);
}
}
public boolean isHeartbeatRequest(Object data) {
return data instanceof HeartbeatRequest;
}
public void destroy() {
closeSession();
}
public boolean isConnected() {
return socketChannel != null && socketChannel.isConnected();
}
public void closeSession() {
if(!isConnected()) {
return;
}
try {
socketChannel.close();
} catch (IOException ignore) {
}finally {
this.sessionClosed();
}
}
private void sendBroadcast(final Intent intent) {
eventExecutor.execute(() -> CIMEventBroadcastReceiver.getInstance().onReceive(intent));
}
}

View File

@ -1,155 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.farsunset.cim.sdk.client.constant.CIMConstant;
import com.farsunset.cim.sdk.client.model.Intent;
import com.farsunset.cim.sdk.client.model.Message;
import com.farsunset.cim.sdk.client.model.ReplyBody;
import com.farsunset.cim.sdk.client.model.SentBody;
/**
* 消息入口所有消息都会经过这里
*/
public class CIMEventBroadcastReceiver {
private static CIMEventBroadcastReceiver receiver;
private CIMEventListener listener;
private final ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, r -> {
Thread thread = new Thread(r);
thread.setName("cim-reconnect-");
return thread;
});
private CIMEventBroadcastReceiver(){
}
public static CIMEventBroadcastReceiver getInstance() {
if (receiver == null) {
receiver = new CIMEventBroadcastReceiver();
}
return receiver;
}
public void setGlobalCIMEventListener(CIMEventListener ls) {
listener = ls;
}
public void onReceive(Intent intent) {
/*
* cim断开服务器事件
*/
if (intent.getAction().equals(CIMConstant.IntentAction.ACTION_CONNECTION_CLOSED)) {
onInnerConnectionClosed();
}
/*
* cim连接服务器失败事件
*/
if (intent.getAction().equals(CIMConstant.IntentAction.ACTION_CONNECT_FAILED)) {
long interval = intent.getLongExtra("interval", CIMConstant.RECONNECT_INTERVAL_TIME);
onInnerConnectFailed(interval);
}
/*
* cim连接服务器成功事件
*/
if (intent.getAction().equals(CIMConstant.IntentAction.ACTION_CONNECT_FINISHED)) {
onInnerConnectFinished();
}
/*
* 收到推送消息事件
*/
if (intent.getAction().equals(CIMConstant.IntentAction.ACTION_MESSAGE_RECEIVED)) {
onInnerMessageReceived((Message) intent.getExtra(Message.class.getName()));
}
/*
* 获取收到replyBody成功事件
*/
if (intent.getAction().equals(CIMConstant.IntentAction.ACTION_REPLY_RECEIVED)) {
listener.onReplyReceived((ReplyBody) intent.getExtra(ReplyBody.class.getName()));
}
/*
* 获取sendBody发送成功事件
*/
if (intent.getAction().equals(CIMConstant.IntentAction.ACTION_SEND_FINISHED)) {
onInnerSendFinished((SentBody) intent.getExtra(SentBody.class.getName()));
}
/*
* 重新连接如果断开的话
*/
if (intent.getAction().equals(CIMConstant.IntentAction.ACTION_CONNECTION_RECOVERY)) {
CIMPushManager.connect();
}
}
private void onInnerConnectionClosed() {
listener.onConnectionClosed();
CIMCacheManager.getInstance().putBoolean(CIMCacheManager.KEY_CIM_CONNECTION_STATE, false);
CIMPushManager.connect();
}
private void onInnerConnectFailed(long interval) {
executorService.schedule((Runnable) CIMPushManager::connect,interval, TimeUnit.MICROSECONDS);
listener.onConnectFailed();
}
private void onInnerConnectFinished() {
CIMCacheManager.getInstance().putBoolean(CIMCacheManager.KEY_CIM_CONNECTION_STATE, true);
boolean autoBind = CIMPushManager.autoBindDeviceId();
listener.onConnectFinished(autoBind);
}
private void onInnerMessageReceived(Message message) {
if (isForceOfflineMessage(message.getAction())) {
CIMPushManager.stop();
}
listener.onMessageReceived(message);
}
private boolean isForceOfflineMessage(String action) {
return CIMConstant.MessageAction.ACTION_999.equals(action);
}
private void onInnerSendFinished(SentBody body) {
listener.onSendFinished(body);
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client;
import com.farsunset.cim.sdk.client.model.Message;
import com.farsunset.cim.sdk.client.model.ReplyBody;
import com.farsunset.cim.sdk.client.model.SentBody;
/**
* CIM 主要事件接口
*/
public interface CIMEventListener {
/**
* 当收到服务端推送过来的消息时调用
*
* @param message
*/
void onMessageReceived(Message message);
/**
* 当调用CIMPushManager.sendRequest()向服务端发送请求获得相应时调用
*
* @param body
*/
void onReplyReceived(ReplyBody body);
/**
* 当调用CIMPushManager.sendRequest()向服务端发送请求成功
* @param body
*/
void onSendFinished(SentBody body);
/**
* 当连接服务器成功时回调
*
* @param hasAutoBind
* : true 已经自动绑定账号到服务器了不需要再手动调用bindAccount
*/
void onConnectFinished(boolean hasAutoBind);
/**
* 当断开服务器连接的时候回调
*/
void onConnectionClosed();
/**
* 当服务器连接失败的时候回调
*
*/
void onConnectFailed();
/**
* 监听器在容器里面的排序值越大则越先接收
* @return 值越大优先级越高
*/
int getEventDispatchOrder();
}

View File

@ -1,114 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.farsunset.cim.sdk.client.model.Message;
import com.farsunset.cim.sdk.client.model.ReplyBody;
/**
* CIM 消息监听器管理
*/
public class CIMListenerManager {
private static final ArrayList<CIMEventListener> cimListeners = new ArrayList<CIMEventListener>();
private static final CIMMessageReceiveComparator comparator = new CIMMessageReceiveComparator();
private static final Logger LOGGER = LoggerFactory.getLogger(CIMListenerManager.class);
public static void registerMessageListener(CIMEventListener listener) {
if (!cimListeners.contains(listener)) {
cimListeners.add(listener);
Collections.sort(cimListeners, comparator);
}
}
public static void removeMessageListener(CIMEventListener listener) {
for (int i = 0; i < cimListeners.size(); i++) {
if (listener.getClass() == cimListeners.get(i).getClass()) {
cimListeners.remove(i);
}
}
}
public static void notifyOnConnectFinished(boolean autoBind) {
for (CIMEventListener listener : cimListeners) {
listener.onConnectFinished(autoBind);
}
}
public static void notifyOnMessageReceived(Message message) {
for (CIMEventListener listener : cimListeners) {
listener.onMessageReceived(message);
}
}
public static void notifyOnConnectionClosed() {
for (CIMEventListener listener : cimListeners) {
listener.onConnectionClosed();
}
}
public static void notifyOnReplyReceived(ReplyBody body) {
for (CIMEventListener listener : cimListeners) {
listener.onReplyReceived(body);
}
}
public static void notifyOnConnectFailed() {
for (CIMEventListener listener : cimListeners) {
listener.onConnectFailed();
}
}
public static void destroy() {
cimListeners.clear();
}
public static void logListenersName() {
for (CIMEventListener listener : cimListeners) {
LOGGER.debug("#######" + listener.getClass().getName() + "#######");
}
}
/**
* 消息接收activity的接收顺序排序CIM_RECEIVE_ORDER倒序
*/
private static class CIMMessageReceiveComparator implements Comparator<CIMEventListener> {
@Override
public int compare(CIMEventListener arg1, CIMEventListener arg2) {
int order1 = arg1.getEventDispatchOrder();
int order2 = arg2.getEventDispatchOrder();
return order2 - order1;
}
}
}

View File

@ -1,259 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client;
import java.util.Properties;
import java.util.UUID;
import com.farsunset.cim.sdk.client.constant.CIMConstant;
import com.farsunset.cim.sdk.client.model.Intent;
import com.farsunset.cim.sdk.client.model.SentBody;
/**
* CIM 功能接口
*/
public class CIMPushManager {
static String ACTION_ACTIVATE_PUSH_SERVICE = "ACTION_ACTIVATE_PUSH_SERVICE";
static String ACTION_CREATE_CIM_CONNECTION = "ACTION_CREATE_CIM_CONNECTION";
static String ACTION_SEND_REQUEST_BODY = "ACTION_SEND_REQUEST_BODY";
static String ACTION_CLOSE_CIM_CONNECTION = "ACTION_CLOSE_CIM_CONNECTION";
static String ACTION_DESTROY = "ACTION_DESTROY";
/**
* 销毁的
*/
public static final int STATE_DESTROYED = 0x0000DE;
/**
* 停止推送
*/
public static final int STATE_STOPPED = 0x0000EE;
/**
* 正常
*/
public static final int STATE_NORMAL = 0x000000;
/**
* 初始化,连接服务端在程序启动页或者 在Application里调用
*
* @param ip
* @param port
*/
public static void connect(String ip, int port) {
CIMCacheManager.getInstance().putBoolean(CIMCacheManager.KEY_CIM_DESTROYED, false);
CIMCacheManager.getInstance().putBoolean(CIMCacheManager.KEY_MANUAL_STOP, false);
CIMCacheManager.getInstance().putString(CIMCacheManager.KEY_CIM_SERVER_HOST, ip);
CIMCacheManager.getInstance().putInt(CIMCacheManager.KEY_CIM_SERVER_PORT, port);
Intent serviceIntent = new Intent();
serviceIntent.putExtra(CIMCacheManager.KEY_CIM_SERVER_HOST, ip);
serviceIntent.putExtra(CIMCacheManager.KEY_CIM_SERVER_PORT, port);
serviceIntent.setAction(ACTION_CREATE_CIM_CONNECTION);
startService(serviceIntent);
}
private static void startService(Intent intent) {
CIMPushService.getInstance().onStartCommand(intent);
}
protected static void connect() {
boolean isManualStopped = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_MANUAL_STOP);
boolean isManualDestroyed = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_CIM_DESTROYED);
if (isManualStopped || isManualDestroyed) {
return;
}
String host = CIMCacheManager.getInstance().getString(CIMCacheManager.KEY_CIM_SERVER_HOST);
int port = CIMCacheManager.getInstance().getInt(CIMCacheManager.KEY_CIM_SERVER_PORT);
connect(host, port);
}
private static void sendBindRequest(String account) {
CIMCacheManager.getInstance().putBoolean(CIMCacheManager.KEY_MANUAL_STOP, false);
SentBody sent = new SentBody();
Properties sysPro = System.getProperties();
sent.setKey(CIMConstant.RequestKey.CLIENT_BIND);
sent.put("account", account);
sent.put("deviceId", getDeviceId());
sent.put("channel", "java");
sent.put("device", sysPro.getProperty("os.name"));
sent.put("appVersion", getClientVersion());
sent.put("osVersion", sysPro.getProperty("os.version"));
sendRequest(sent);
}
/**
* 设置一个账号登录到服务端
*
* @param account
* 用户唯一ID
*/
public static void bindAccount(String account) {
boolean isManualDestroyed = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_CIM_DESTROYED);
if (isManualDestroyed || account == null || account.trim().length() == 0) {
return;
}
sendBindRequest(account);
}
protected static boolean autoBindDeviceId() {
String account = getAccount();
boolean isManualDestroyed = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_CIM_DESTROYED);
boolean isManualStopped = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_MANUAL_STOP);
if (isManualStopped || account == null || account.trim().length() == 0 || isManualDestroyed) {
return false;
}
sendBindRequest(account);
return true;
}
/**
* 发送一个CIM请求
*
* @body
*/
public static void sendRequest(SentBody body) {
boolean isManualStopped = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_MANUAL_STOP);
boolean isManualDestroyed = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_CIM_DESTROYED);
if (isManualStopped || isManualDestroyed) {
return;
}
Intent serviceIntent = new Intent();
serviceIntent.putExtra(SentBody.class.getName(), body);
serviceIntent.setAction(ACTION_SEND_REQUEST_BODY);
startService(serviceIntent);
}
/**
* 停止接受推送将会退出当前账号登录端口与服务端的连接
*
*/
public static void stop() {
boolean isManualDestroyed = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_CIM_DESTROYED);
if (isManualDestroyed) {
return;
}
CIMCacheManager.getInstance().putBoolean(CIMCacheManager.KEY_MANUAL_STOP, true);
startService(new Intent(ACTION_CLOSE_CIM_CONNECTION));
}
/**
* 完全销毁CIM一般用于完全退出程序调用resume将不能恢复
*/
public static void destroy() {
CIMCacheManager.getInstance().putBoolean(CIMCacheManager.KEY_CIM_DESTROYED, true);
Intent serviceIntent = new Intent();
serviceIntent.setAction(ACTION_DESTROY);
startService(serviceIntent);
}
/**
* 重新恢复接收推送重新连接服务端并登录当前账号如果autoBind == true
*/
public static void resume() {
boolean isManualDestroyed = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_CIM_DESTROYED);
if (isManualDestroyed) {
return;
}
autoBindDeviceId();
}
public static boolean isConnected() {
return CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_CIM_CONNECTION_STATE);
}
public static int getState() {
boolean isManualDestroyed = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_CIM_DESTROYED);
if (isManualDestroyed) {
return STATE_DESTROYED;
}
boolean isManualStopped = CIMCacheManager.getInstance().getBoolean(CIMCacheManager.KEY_MANUAL_STOP);
if (isManualStopped) {
return STATE_STOPPED;
}
return STATE_NORMAL;
}
public static String getClientVersion() {
return System.getProperties().getProperty(CIMConstant.ConfigKey.CLIENT_VERSION);
}
public static String getAccount() {
return System.getProperties().getProperty(CIMConstant.ConfigKey.CLIENT_ACCOUNT);
}
public static void setAccount(String account) {
System.getProperties().put(CIMConstant.ConfigKey.CLIENT_ACCOUNT, account);
}
public static void setClientVersion(String version) {
System.getProperties().put(CIMConstant.ConfigKey.CLIENT_VERSION, version);
}
private static String getDeviceId() {
String currDeviceId = System.getProperties().getProperty(CIMConstant.ConfigKey.CLIENT_DEVICE_ID);
if(currDeviceId != null) {
return currDeviceId;
}
String deviceId = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
System.getProperties().put(CIMConstant.ConfigKey.CLIENT_DEVICE_ID, deviceId);
return deviceId;
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client;
import com.farsunset.cim.sdk.client.model.Intent;
import com.farsunset.cim.sdk.client.model.SentBody;
/**
* 与服务端连接服务
*/
public class CIMPushService {
private final CIMConnectorManager manager;
private static CIMPushService service;
public static CIMPushService getInstance() {
if (service == null) {
service = new CIMPushService();
}
return service;
}
public CIMPushService() {
manager = CIMConnectorManager.getManager();
}
public void onStartCommand(Intent intent) {
String action = intent == null ? CIMPushManager.ACTION_ACTIVATE_PUSH_SERVICE : intent.getAction();
if (CIMPushManager.ACTION_CREATE_CIM_CONNECTION.equals(action)) {
String host = CIMCacheManager.getInstance().getString(CIMCacheManager.KEY_CIM_SERVER_HOST);
int port = CIMCacheManager.getInstance().getInt(CIMCacheManager.KEY_CIM_SERVER_PORT);
manager.connect(host, port);
}
if (CIMPushManager.ACTION_SEND_REQUEST_BODY.equals(action)) {
manager.send((SentBody) intent.getExtra(SentBody.class.getName()));
}
if (CIMPushManager.ACTION_CLOSE_CIM_CONNECTION.equals(action)) {
manager.closeSession();
}
if (CIMPushManager.ACTION_DESTROY.equals(action)) {
manager.destroy();
}
if (CIMPushManager.ACTION_ACTIVATE_PUSH_SERVICE.equals(action) && !manager.isConnected()) {
String host = CIMCacheManager.getInstance().getString(CIMCacheManager.KEY_CIM_SERVER_HOST);
int port = CIMCacheManager.getInstance().getInt(CIMCacheManager.KEY_CIM_SERVER_PORT);
manager.connect(host, port);
}
}
}

View File

@ -1,114 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.coder;
import java.nio.channels.SocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 日志打印添加session 的id和ip address
*/
public class CIMLogger {
private static final Logger LOGGER = LoggerFactory.getLogger(CIMLogger.class);
public static CIMLogger getLogger() {
return LoggerHolder.logger;
}
private CIMLogger() {
}
private static class LoggerHolder{
private static final CIMLogger logger = new CIMLogger();
}
public void messageReceived(SocketChannel session, Object message) {
LOGGER.info(String.format("RECEIVED" + getSessionInfo(session) + "\n%s", message));
}
public void messageSent(SocketChannel session, Object message) {
LOGGER.info(String.format("SENT" + getSessionInfo(session) + "\n%s", message));
}
public void sessionCreated( SocketChannel session) {
LOGGER.info("OPENED" + getSessionInfo(session));
}
public void sessionIdle( SocketChannel session) {
LOGGER.debug("IDLE READ" + getSessionInfo(session));
}
public void sessionClosed( SocketChannel session) {
LOGGER.warn("CLOSED ID = " + session.hashCode());
}
public void connectFailure(long interval) {
LOGGER.debug("CONNECT FAILURE TRY RECONNECT AFTER " + interval +"ms");
}
public void startConnect(String host , int port) {
LOGGER.info("START CONNECT REMOTE HOST:" + host + " PORT:" + port);
}
public void connectState(boolean isConnected) {
LOGGER.debug("CONNECTED:" + isConnected);
}
public void connectState(boolean isConnected,boolean isManualStop,boolean isDestroyed) {
LOGGER.debug("CONNECTED:" + isConnected + " STOPPED:"+isManualStop+ " DESTROYED:"+isDestroyed);
}
private String getSessionInfo(SocketChannel session) {
StringBuilder builder = new StringBuilder();
if (session == null) {
return "";
}
builder.append(" [");
builder.append("id:").append(session.hashCode());
try {
if (session.socket().getLocalAddress() != null) {
builder.append(" L:").append(session.socket().getLocalAddress()+":"+session.socket().getLocalPort());
}
} catch (Exception ignore) {
}
try {
if (session.socket().getRemoteSocketAddress() != null) {
builder.append(" R:").append(session.socket().getRemoteSocketAddress().toString());
}
} catch (Exception ignore) {
}
builder.append("]");
return builder.toString();
}
}

View File

@ -1,118 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.coder;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import com.farsunset.cim.sdk.client.constant.CIMConstant;
import com.farsunset.cim.sdk.client.model.HeartbeatRequest;
import com.farsunset.cim.sdk.client.model.Message;
import com.farsunset.cim.sdk.client.model.ReplyBody;
import com.farsunset.cim.sdk.model.proto.MessageProto;
import com.farsunset.cim.sdk.model.proto.ReplyBodyProto;
/**
* 客户端消息解码
*/
public class ClientMessageDecoder {
/**
*
* @param headerBuffer 读取到的消息头
* @param socketChannel
* @return
*/
public Object doDecode(ByteBuffer headerBuffer , SocketChannel socketChannel) throws IOException {
headerBuffer.position(0);
byte type = headerBuffer.get();
byte lv = headerBuffer.get();
byte hv = headerBuffer.get();
headerBuffer.clear();
/*
* 先通过消息头拿到消息的长度然后进行定长读取
* 解决消息的断包和粘包情况
*/
int dataLength = getContentLength(lv, hv);
ByteBuffer bodyBuffer = ByteBuffer.allocate(dataLength);
/*
* 如果读取的消息长度不够则进行等待后续消息到来
* 当读取的消息长度(bodyBuffer.position() == dataLength)时意味着一个完整的消息已经接收完成
*/
do {
socketChannel.read(bodyBuffer);
} while (bodyBuffer.position() != dataLength);
/*
消息读取完成后通过type来解析成对应的消息体
*/
if (CIMConstant.ProtobufType.S_H_RQ == type) {
return HeartbeatRequest.getInstance();
}
if (CIMConstant.ProtobufType.REPLY_BODY == type) {
ReplyBodyProto.Model bodyProto = ReplyBodyProto.Model.parseFrom(bodyBuffer.array());
ReplyBody body = new ReplyBody();
body.setKey(bodyProto.getKey());
body.setTimestamp(bodyProto.getTimestamp());
body.putAll(bodyProto.getDataMap());
body.setCode(bodyProto.getCode());
body.setMessage(bodyProto.getMessage());
return body;
}
MessageProto.Model bodyProto = MessageProto.Model.parseFrom(bodyBuffer.array());
Message message = new Message();
message.setId(bodyProto.getId());
message.setAction(bodyProto.getAction());
message.setContent(bodyProto.getContent());
message.setSender(bodyProto.getSender());
message.setReceiver(bodyProto.getReceiver());
message.setTitle(bodyProto.getTitle());
message.setExtra(bodyProto.getExtra());
message.setTimestamp(bodyProto.getTimestamp());
message.setFormat(bodyProto.getFormat());
return message;
}
/**
* 解析消息体长度
*/
private int getContentLength(byte lv, byte hv) {
int l = (lv & 0xff);
int h = (hv & 0xff);
return (l | h << 8);
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.coder;
import java.nio.ByteBuffer;
import com.farsunset.cim.sdk.client.constant.CIMConstant;
import com.farsunset.cim.sdk.client.model.Protobufable;
/**
* 客户端消息发送前进行编码
*/
public class ClientMessageEncoder {
public ByteBuffer encode(Protobufable body) {
byte[] data = body.getByteArray();
ByteBuffer ioBuffer = ByteBuffer.allocate(data.length + CIMConstant.DATA_HEADER_LENGTH);
ioBuffer.put(createHeader(body.getType(), data.length));
ioBuffer.put(data);
ioBuffer.flip();
return ioBuffer;
}
/**
* 消息体最大为65535
*
* @param type
* @param length
* @return
*/
private byte[] createHeader(byte type, int length) {
byte[] header = new byte[CIMConstant.DATA_HEADER_LENGTH];
header[0] = type;
header[1] = (byte) (length & 0xff);
header[2] = (byte) ((length >> 8) & 0xff);
return header;
}
}

View File

@ -1,104 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.constant;
/**
* 常量
*/
public interface CIMConstant {
long RECONNECT_INTERVAL_TIME = 30 * 1000;
/*
* 消息头长度为3个字节第一个字节为消息类型第二第三字节 转换int后为消息长度
*/
int DATA_HEADER_LENGTH = 3;
interface ConfigKey {
String DEVICE_MODEL = "client.model";
String CLIENT_VERSION = "client.version";
String CLIENT_ACCOUNT = "client.account";
String CLIENT_DEVICE_ID = "client.deviceId";
}
interface ProtobufType {
byte C_H_RS = 0;
byte S_H_RQ = 1;
byte MESSAGE = 2;
byte SENT_BODY = 3;
byte REPLY_BODY = 4;
}
interface RequestKey {
String CLIENT_BIND = "client_bind";
}
interface MessageAction {
/*
被其他设备登录挤下线消息
*/
String ACTION_999 = "999";
}
interface IntentAction {
/*
消息广播action
*/
String ACTION_MESSAGE_RECEIVED = "com.farsunset.cim.ACTION_MESSAGE_RECEIVED";
/*
发送sendBody成功广播
*/
String ACTION_SEND_FINISHED = "com.farsunset.cim.ACTION_SEND_FINISHED";
/*
链接意外关闭广播
*/
String ACTION_CONNECTION_CLOSED = "com.farsunset.cim.ACTION_CONNECTION_CLOSED";
/*
链接失败广播
*/
String ACTION_CONNECT_FAILED = "com.farsunset.cim.ACTION_CONNECT_FAILED";
/*
链接成功广播
*/
String ACTION_CONNECT_FINISHED = "com.farsunset.cim.ACTION_CONNECT_FINISHED";
/*
发送sendBody成功后获得replyBody回应广播
*/
String ACTION_REPLY_RECEIVED = "com.farsunset.cim.ACTION_REPLY_RECEIVED";
/*
重试连接
*/
String ACTION_CONNECTION_RECOVERY = "com.farsunset.cim.CONNECTION_RECOVERY";
}
}

View File

@ -1,63 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.model;
import java.io.Serializable;
import com.farsunset.cim.sdk.client.constant.CIMConstant;
/**
* 服务端心跳请求
*
*/
public class HeartbeatRequest implements Serializable, Protobufable {
private static final long serialVersionUID = 1L;
private static final String TAG = "SERVER_HEARTBEAT_REQUEST";
private static final String CMD_HEARTBEAT_RESPONSE = "SR";
private static final HeartbeatRequest object = new HeartbeatRequest();
private HeartbeatRequest() {
}
public static HeartbeatRequest getInstance() {
return object;
}
@Override
public byte[] getByteArray() {
return CMD_HEARTBEAT_RESPONSE.getBytes();
}
@Override
public String toString() {
return TAG;
}
@Override
public byte getType() {
return CIMConstant.ProtobufType.S_H_RQ;
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.model;
import java.io.Serializable;
import com.farsunset.cim.sdk.client.constant.CIMConstant;
/**
* 客户端心跳响应
*/
public class HeartbeatResponse implements Serializable, Protobufable {
private static final long serialVersionUID = 1L;
private static final String TAG = "CLIENT_HEARTBEAT_RESPONSE";
private static final String CMD_HEARTBEAT_RESPONSE = "CR";
private static final HeartbeatResponse object = new HeartbeatResponse();
private HeartbeatResponse() {
}
public static HeartbeatResponse getInstance() {
return object;
}
@Override
public byte[] getByteArray() {
return CMD_HEARTBEAT_RESPONSE.getBytes();
}
@Override
public String toString() {
return TAG;
}
@Override
public byte getType() {
return CIMConstant.ProtobufType.C_H_RS;
}
}

View File

@ -1,176 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.model;
import java.io.Serializable;
/**
* 消息对象
*/
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 消息类型用户自定义消息类别
*/
private long id;
/**
* 消息类型用户自定义消息类别
*/
private String action;
/**
* 消息标题
*/
private String title;
/**
* 消息类容于action 组合为任何类型消息content 根据 format 可表示为 text,json ,xml数据格式
*/
private String content;
/**
* 消息发送者账号
*/
private String sender;
/**
* 消息发送者接收者
*/
private String receiver;
/**
* content 内容格式
*/
private String format;
/**
* 附加内容 内容
*/
private String extra;
private long timestamp;
public Message() {
timestamp = System.currentTimeMillis();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getExtra() {
return extra;
}
public void setExtra(String extra) {
this.extra = extra;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("#Message#").append("\n");
buffer.append("id:").append(id).append("\n");
buffer.append("action:").append(action).append("\n");
buffer.append("title:").append(title).append("\n");
buffer.append("content:").append(content).append("\n");
buffer.append("extra:").append(extra).append("\n");
buffer.append("sender:").append(sender).append("\n");
buffer.append("receiver:").append(receiver).append("\n");
buffer.append("format:").append(format).append("\n");
buffer.append("timestamp:").append(timestamp);
return buffer.toString();
}
public boolean isNotEmpty(String txt) {
return txt != null && txt.trim().length() != 0;
}
}

View File

@ -1,134 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.model;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 请求应答对象
*
*/
public class ReplyBody implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 请求key
*/
private String key;
/**
* 返回码
*/
private String code;
/**
* 返回说明
*/
private String message;
/**
* 返回数据集合
*/
private HashMap<String, String> data;
private long timestamp;
public ReplyBody() {
data = new HashMap<>();
timestamp = System.currentTimeMillis();
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public void put(String k, String v) {
if (v != null && k != null) {
data.put(k, v);
}
}
public String get(String k) {
return data.get(k);
}
public void remove(String k) {
data.remove(k);
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void putAll(Map<String, String> map) {
data.putAll(map);
}
public Set<String> getKeySet() {
return data.keySet();
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("#ReplyBody#").append("\n");
buffer.append("key:").append(this.getKey()).append("\n");
buffer.append("timestamp:").append(timestamp).append("\n");
buffer.append("code:").append(code).append("\n");
buffer.append("data{").append("\n");
for (String key : getKeySet()) {
buffer.append(key).append(":").append(this.get(key)).append("\n");
}
buffer.append("}");
return buffer.toString();
}
}

View File

@ -1,118 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.client.model;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.farsunset.cim.sdk.client.constant.CIMConstant;
import com.farsunset.cim.sdk.model.proto.SentBodyProto;
/**
* java |android 客户端请求结构
*
*/
public class SentBody implements Serializable, Protobufable {
private static final long serialVersionUID = 1L;
private String key;
private HashMap<String, String> data = new HashMap<>();;
private long timestamp;
public SentBody() {
timestamp = System.currentTimeMillis();
}
public String getKey() {
return key;
}
public String get(String k) {
return data.get(k);
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public void setKey(String key) {
this.key = key;
}
public void put(String k, String v) {
if (v != null && k != null) {
data.put(k, v);
}
}
public void putAll(Map<String, String> map) {
data.putAll(map);
}
public Set<String> getKeySet() {
return data.keySet();
}
public void remove(String k) {
data.remove(k);
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("#SentBody#").append("\n");
buffer.append("key:").append(key).append("\n");
buffer.append("timestamp:").append(timestamp).append("\n");
buffer.append("data{").append("\n");
for (String key : getKeySet()) {
buffer.append(key).append(":").append(this.get(key)).append("\n");
}
buffer.append("}");
return buffer.toString();
}
@Override
public byte[] getByteArray() {
SentBodyProto.Model.Builder builder = SentBodyProto.Model.newBuilder();
builder.setKey(key);
builder.setTimestamp(timestamp);
if (!data.isEmpty()) {
builder.putAllData(data);
}
return builder.build().toByteArray();
}
@Override
public byte getType() {
return CIMConstant.ProtobufType.SENT_BODY;
}
}

View File

@ -1,15 +0,0 @@
syntax = "proto3";
package com.farsunset.cim.sdk.model.proto;
option java_outer_classname="MessageProto";
message Model {
int64 id = 1;
string action = 2;
string content = 3;
string sender = 4;
string receiver = 5;
string extra = 6;
string title = 7;
string format = 8;
int64 timestamp = 9;
}

View File

@ -1,13 +0,0 @@
syntax = "proto3";
package com.farsunset.cim.sdk.model.proto;
option java_outer_classname="ReplyBodyProto";
message Model {
string key = 1;
string code = 2;
string message = 3;
int64 timestamp =4;
map<string,string> data =5;
}

View File

@ -1,11 +0,0 @@
syntax = "proto3";
package com.farsunset.cim.sdk.model.proto;
option java_outer_classname="SentBodyProto";
message Model {
string key = 1;
int64 timestamp =2;
map<string,string> data =3;
}

View File

@ -1,2 +0,0 @@
target/
.idea/

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4" />

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.farsunset</groupId>
<artifactId>cim-server-sdk-mina</artifactId>
<version>3.8.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<protobuf.java.version>3.11.1</protobuf.java.version>
<mina.version>2.1.3</mina.version>
<slf4j.version>1.7.30</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>${mina.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.java.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,53 +0,0 @@
/*
* Copyright 2013-2019 Xia Jun(3979434@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************************
* *
* Website : http://www.farsunset.com *
* *
***************************************************************************************
*/
package com.farsunset.cim.sdk.server.coder;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;
/**
* 服务端消息 编码解码器
*/
public class AppMessageCodecFactory implements ProtocolCodecFactory {
private final AppMessageEncoder encoder;
private final AppMessageDecoder decoder;
public AppMessageCodecFactory() {
encoder = new AppMessageEncoder();
decoder = new AppMessageDecoder();
}
@Override
public ProtocolEncoder getEncoder(IoSession session) {
return encoder;
}
@Override
public ProtocolDecoder getDecoder(IoSession session) {
return decoder;
}
}

Some files were not shown because too many files have changed in this diff Show More