├── .gitignore
├── README.md
├── build.gradle
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── main
├── assemblies
└── plugin.xml
├── bin
├── consumer.sh
├── rpcServer.sh
└── tcpServer.sh
├── config
├── im.properties
├── logback.xml
└── spring
│ ├── router.xml
│ ├── rpc.xml
│ ├── spring.xml
│ └── tcp.xml
├── docs
├── IM-architecture.jpg
├── Im-Communication.jpg
├── Im-PullMessage.jpg
├── Im-SyncUnread.jpg
└── mysql.sql
└── java
└── com
└── linbox
└── im
├── exceptions
├── ConfigFailException.java
├── IMConsumerException.java
├── IMException.java
└── ServiceNotFoundException.java
├── interfaces
└── IIMService.java
├── message
├── AuthRequest.java
├── AuthResponse.java
├── ByteCreator.java
├── Message.java
├── MessageType.java
├── MessageWrapper.java
├── NewMessage.java
├── OfflineInfo.java
├── Ping.java
├── Pong.java
├── PullOldMsgRequest.java
├── PullOldMsgRequestType.java
├── PullOldMsgResponse.java
├── ReadAckRequest.java
├── ReadAckResponse.java
├── RequestResponseType.java
├── SendMsgRequest.java
├── SendMsgResponse.java
├── SyncUnreadRequest.java
├── SyncUnreadResponse.java
├── UnreadMsg.java
└── system
│ ├── JobNotificationMessage.java
│ ├── SyncSystemUnreadRequest.java
│ ├── SyncSystemUnreadResponse.java
│ ├── SystemMessage.java
│ ├── SystemMessageTypes.java
│ ├── SystemUnreadAckRequest.java
│ ├── SystemUnreadAckResponse.java
│ └── content
│ └── SystemUnreadContent.java
├── server
├── connector
│ ├── rpc
│ │ └── IMService.java
│ └── tcp
│ │ ├── ImTcpServer.java
│ │ ├── constant
│ │ └── HandlerName.java
│ │ └── handler
│ │ ├── AES.java
│ │ ├── AuthHandler.java
│ │ ├── IMChannelInitializer.java
│ │ ├── IMIdleStateHandler.java
│ │ ├── IMMessageDecoder.java
│ │ ├── IMMessageEncoder.java
│ │ └── IMMessageHandler.java
├── constant
│ ├── MessageTopic.java
│ ├── RedisKey.java
│ └── SpecialUsers.java
├── monitor
│ ├── IConnectorMonitor.java
│ └── impl
│ │ └── ConnectorMonitor.java
├── router
│ ├── ImRouterServer.java
│ └── handlers
│ │ ├── Handler.java
│ │ ├── PingHandler.java
│ │ ├── PullOldMsgHandler.java
│ │ ├── ReadAckHandler.java
│ │ ├── SendMsgHandler.java
│ │ ├── SyncSystemUnreadHandler.java
│ │ ├── SyncUnreadHandler.java
│ │ └── dispatcher
│ │ ├── DispatchToGroupHandler.java
│ │ ├── DispatchToSingleHandler.java
│ │ ├── SendDispatchMessage.java
│ │ └── SendDispatcher.java
├── service
│ ├── IInboxService.java
│ ├── IOutboxService.java
│ ├── IPushService.java
│ ├── StopcockService.java
│ └── impl
│ │ ├── InboxService.java
│ │ ├── OutboxService.java
│ │ └── PushService.java
└── storage
│ ├── UnreadLoopData.java
│ ├── dao
│ ├── IGroupDAO.java
│ ├── IGroupMessageDAO.java
│ ├── IMessageDAO.java
│ ├── IServerDAO.java
│ ├── ISessionMessageDAO.java
│ ├── IUserDAO.java
│ └── impl
│ │ ├── GroupDAO.java
│ │ ├── GroupMessageDao.java
│ │ ├── MessageDAO.java
│ │ ├── ServerDAO.java
│ │ ├── SessionMessageDAO.java
│ │ └── UserDAO.java
│ └── entity
│ ├── GroupEntity.java
│ ├── GroupMessageEntity.java
│ └── SessionMessageEntity.java
└── utils
├── AESUtils.java
├── IMUtils.java
└── IdGenerator.java
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Maven template
2 | target/
3 | pom.xml.tag
4 | pom.xml.releaseBackup
5 | pom.xml.versionsBackup
6 | pom.xml.next
7 | release.properties
8 | dependency-reduced-pom.xml
9 | buildNumber.properties
10 | .mvn/timing.properties
11 | ### Java template
12 | *.class
13 |
14 | # Mobile Tools for Java (J2ME)
15 | .mtj.tmp/
16 |
17 | # Package Files #
18 | *.jar
19 | *.war
20 | *.ear
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | ### JetBrains template
25 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
26 |
27 | *.iml
28 |
29 | ## Directory-based project format:
30 | .idea/
31 | # if you remove the above rule, at least ignore the following:
32 |
33 | # User-specific stuff:
34 | # .idea/workspace.xml
35 | # .idea/tasks.xml
36 | # .idea/dictionaries
37 |
38 | # Sensitive or high-churn files:
39 | # .idea/dataSources.ids
40 | # .idea/dataSources.xml
41 | # .idea/sqlDataSources.xml
42 | # .idea/dynamic.xml
43 | # .idea/uiDesigner.xml
44 |
45 | # Gradle:
46 | # .idea/gradle.xml
47 | # .idea/libraries
48 |
49 | # Mongo Explorer plugin:
50 | # .idea/mongoSettings.xml
51 |
52 | ## File-based project format:
53 | *.ipr
54 | *.iws
55 |
56 | ## Plugin-specific files:
57 |
58 | # IntelliJ
59 | /out/
60 |
61 | # mpeltonen/sbt-idea plugin
62 | .idea_modules/
63 |
64 | # JIRA plugin
65 | atlassian-ide-plugin.xml
66 |
67 | # Crashlytics plugin (for Android Studio and IntelliJ)
68 | com_crashlytics_export_strings.xml
69 | ### Gradle template
70 | .gradle
71 | build/
72 | gradle
73 |
74 | # Ignore Gradle GUI config
75 | gradle-app.setting
76 |
77 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
78 | !gradle-wrapper.jar
79 |
80 | crashlytics.properties
81 | crashlytics-build.properties
82 | ### OSX template
83 | .DS_Store
84 | .AppleDouble
85 | .LSOverride
86 |
87 | # Icon must end with two \r
88 | Icon
89 |
90 | # Thumbnails
91 | ._*
92 |
93 | # Files that might appear in the root of a volume
94 | .DocumentRevisions-V100
95 | .fseventsd
96 | .Spotlight-V100
97 | .TemporaryItems
98 | .Trashes
99 | .VolumeIcon.icns
100 |
101 | # Directories potentially created on remote AFP share
102 | .AppleDB
103 | .AppleDesktop
104 | Network Trash Folder
105 | Temporary Items
106 | .apdisk
107 |
108 | # Created by .ignore support plugin (hsz.mobi)
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # linbox_server
2 |
3 | LINBOX is a distributed IM system designed for www.medtree.cn.
4 |
5 | Download medtree app for iphone at [here](https://itunes.apple.com/cn/app/yi-shu/id933709180?mt=8)
6 |
7 | Download medtree app for android at [here](https://medtree.cn/release/android/4.1.1/medtree.apk)
8 |
9 | # Tech
10 | * Netty and tcp protocol
11 | * Kafka as center MQ
12 | * Dubbo as rpc framework
13 | * Zookeeper as config center
14 |
15 | # Usage
16 | Build the project:
17 | ```
18 | gradle zip
19 | ```
20 |
21 | distribution package is under
22 | ```
23 | build/distribution/linbox_server-1.0.zip
24 | ```
25 |
26 | Unzip it and start the server:
27 | ```
28 | # start the tcp client:
29 | sh tcpServer.sh
30 |
31 | # start the router:
32 | sh consumer.sh
33 |
34 | # start the rpc client
35 | sh rpcServer.sh
36 | ```
37 |
38 | # High availablity and scaliability
39 | Linbox is distributed by design.
40 | All of the components of linbox - tcp client, rpc client and router - are all distributed.
41 |
42 | Steps to prompt availability:
43 |
44 | * Use zookeeper cluster
45 | * Use Kafka cluster
46 | * Use tcp client cluster
47 | * [im.properties](src/main/config/im.properties) need tobe configured for server hosts and ports
48 | * Use rpc client cluster
49 | * [rpc.xml](src/main/config/spring/rpc.xml) need to be configured for pointed rpc port, or you can use dynamic ports generated by system.
50 | * Use router client cluster
51 |
52 | # Dependencies
53 | ## Java Version
54 | **JDK-1.8** is required to build and run it.
55 |
56 | ## Security Update For JRE
57 | **Be carefual:** as we use AES-256 encryption, you need to download packages to update your local jre environment.
58 | * Download package at [here](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html)
59 | * Unzip and copy the jars into ```${java.home}/jre/lib/security/```
60 |
61 | Click [this](http://stackoverflow.com/questions/6481627/java-security-illegal-key-size-or-default-parameters) for detail information for this problem.
62 |
63 | ## Mysql
64 | Mysql database is used as persistence storage for im messages.
65 |
66 | ### Mysql Versions
67 | There is no special requirement for mysql version.
68 | But if you want to use **emojis**, you need to use mysql version **5.6+**, and config the character set to *utf8mb4*.
69 |
70 | ### Predefined Tables
71 | You can find all mysql operations in package ```com.linbox.im.server.storage```
72 |
73 | There are 4 tables predefined in programs, sql script could be find in [mysql.sql](src/main/docs/mysql.sql)
74 |
75 | ### Configs
76 | Config mysql connection in [im.properties](src/main/config/im.properties)
77 |
78 | ## Redis
79 | Config redis connection in [im.properties](src/main/config/im.properties)
80 |
81 | ## Zookeeper
82 | Zookeeper is used as config center of the project. It is also a dependency of *dubbo* and *Kafka*.
83 | Config Zookeeper in [rpc.xml](src/main/config/spring/rpc.xml)
84 |
85 | ## Kafka
86 | Kafka is used as Center MQ.
87 | Kafka is configd in [im.properties](src/main/config/im.properties)
88 |
89 |
90 | # Architecture
91 | 
92 |
93 |
94 | # Communication Protocol
95 | Linbox use tcp protocal and JSON structure in communication.
96 | Data package structure is designed as below:
97 |
98 | |Version|Type|Length|Content|
99 | |-----|-----|----|------|
100 | |2 Byte|2 Byte|4 Byte|JSON String|
101 |
102 |
103 | |Name|Description|
104 | |:--|:--|
105 | |Version| A flag to show the protocol version of the package|
106 | |Type| Request / Response command type|
107 | |Length| The length of the content part|
108 | |Content| Encrypted Data content structured in json|
109 |
110 |
111 | # Main Communications
112 | ## Get history messages
113 |
114 | 
115 |
116 | ## Get unread count and messages
117 |
118 | 
119 |
120 | ## Send messages
121 |
122 | 
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'maven'
3 |
4 | defaultTasks 'zip'
5 |
6 | group = 'com.linbox'
7 | version = '1.0'
8 |
9 | description = 'linbox server'
10 |
11 | sourceCompatibility = 1.8
12 | targetCompatibility = 1.8
13 |
14 | repositories {
15 |
16 | mavenLocal()
17 |
18 | maven {
19 | url 'http://repo.mobimind.net/nexus/content/groups/mobimind-all'
20 | }
21 |
22 | maven {
23 | url 'http://repo1.maven.org/maven2'
24 | artifactUrls 'http://repository.jboss.org/nexus/content/groups/public-jboss'
25 | }
26 |
27 | }
28 | configurations.all {
29 | // check for updates every build
30 | resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
31 | }
32 |
33 | dependencies {
34 | compile 'commons-lang:commons-lang:2.6'
35 | compile 'redis.clients:jedis:2.7.2'
36 | compile 'com.alibaba:fastjson:1.2.4'
37 | compile 'joda-time:joda-time:2.8.1'
38 | compile 'com.google.guava:guava:18.0'
39 |
40 | //slf4j and logback
41 | compile 'ch.qos.logback:logback-classic:1.1.3'
42 |
43 | // spring
44 | compile 'org.springframework:spring-core:4.2.2.RELEASE'
45 | compile 'org.springframework:spring-context:4.2.2.RELEASE'
46 | compile 'org.springframework:spring-expression:4.2.2.RELEASE'
47 |
48 | // mysql
49 | compile 'com.zaxxer:HikariCP:2.4.3'
50 | compile 'org.sql2o:sql2o:1.5.4'
51 | compile 'mysql:mysql-connector-java:5.1.38'
52 |
53 | //kafka
54 | compile 'org.apache.kafka:kafka-clients:0.9.0.0'
55 |
56 | //dubbo
57 | compile group: "com.alibaba", name: "dubbo", version: "2.5.4-SNAPSHOT", changing: true, {
58 | exclude module: 'spring'
59 | }
60 | compile 'com.github.sgroschupf:zkclient:0.1'
61 |
62 | //netty
63 | compile 'io.netty:netty-all:4.0.29.Final'
64 |
65 |
66 | testCompile 'junit:junit:3.8.1'
67 | }
68 |
69 | task copyConfig(type: Copy, dependsOn: 'build') {
70 | from 'src/main/config'
71 | from 'src/main/resource'
72 |
73 | into 'build/target/config'
74 | }
75 |
76 | task copyBin(type: Copy, dependsOn: 'copyConfig') {
77 | from 'src/main/bin'
78 | into 'build/target/bin'
79 | }
80 |
81 | task copyLibs(type: Copy, dependsOn: 'copyBin') {
82 | from configurations.runtime
83 | from 'build/libs'
84 |
85 | into 'build/target/lib'
86 | }
87 |
88 | task zip(type: Zip, dependsOn: 'copyLibs') {
89 | outputs.upToDateWhen { false }
90 |
91 | from 'build/target'
92 | into 'linbox_server'
93 | }
94 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'linbox_server'
2 |
--------------------------------------------------------------------------------
/src/main/assemblies/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | im_server
4 |
5 | zip
6 |
7 | true
8 |
9 |
10 | /lib
11 | true
12 | true
13 |
14 |
15 |
16 |
17 | ${basedir}/src/main/bin
18 | /bin
19 | 0777
20 |
21 |
22 | ${basedir}/src/main/config/
23 | /config
24 | 0777
25 |
26 |
27 | ${basedir}/src/main/resource/
28 | /resource
29 | 0777
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/main/bin/consumer.sh:
--------------------------------------------------------------------------------
1 | #java -cp ".:../lib/*:../config" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006 -Xloggc:consumer.gc.log -verbose:gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M -XX:-PrintGCDetails -XX:HeapDumpPath=./consumer.hprof -XX:-HeapDumpOnOutOfMemoryError -Xms1g -Xmx3g com.medtree.im.server.router.ImRouterService
2 | java -cp ".:../lib/*:../config" -Xloggc:consumer.gc.log -verbose:gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M -XX:-PrintGCDetails -XX:HeapDumpPath=./consumer.hprof -XX:-HeapDumpOnOutOfMemoryError -Xms1g -Xmx3g com.linbox.im.server.router.ImRouterServer
--------------------------------------------------------------------------------
/src/main/bin/rpcServer.sh:
--------------------------------------------------------------------------------
1 | #java -cp ".:../lib/*:../config:../cert" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5008 -Xloggc:tcpServer.gc.log -verbose:gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M -XX:-PrintGCDetails -XX:HeapDumpPath=./tcp_server.hprof -XX:-HeapDumpOnOutOfMemoryError -Xms2g -Xmx2g -Ddubbo.application.logger=slf4j -Ddubbo.spring.config=classpath*:spring/*.xml -Dname=medtree-im-rpc-server com.alibaba.dubbo.container.Main spring logback
2 | java -cp ".:../lib/*:../config:../cert" -Xloggc:tcpServer.gc.log -verbose:gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M -XX:-PrintGCDetails -XX:HeapDumpPath=./tcp_server.hprof -XX:-HeapDumpOnOutOfMemoryError -Xms2g -Xmx2g -Ddubbo.application.logger=slf4j -Ddubbo.spring.config=classpath*:spring/rpc.xml -Dname=medtree-Im-rpc-server com.alibaba.dubbo.container.Main spring logback
--------------------------------------------------------------------------------
/src/main/bin/tcpServer.sh:
--------------------------------------------------------------------------------
1 | #java -cp ".:../lib/*:../config:" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -Xloggc:tcpServer.gc.log -verbose:gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M -XX:-PrintGCDetails -XX:HeapDumpPath=./tcp_server.hprof -XX:-HeapDumpOnOutOfMemoryError -Xms6g -Xmx10g com.medtree.im.server.connector.tcp.ImTcpServer
2 | java -cp ".:../lib/*:../config:" -Xloggc:tcpServer.gc.log -verbose:gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M -XX:-PrintGCDetails -XX:HeapDumpPath=./tcp_server.hprof -XX:-HeapDumpOnOutOfMemoryError -Xms6g -Xmx10g com.linbox.im.server.connector.tcp.ImTcpServer
--------------------------------------------------------------------------------
/src/main/config/im.properties:
--------------------------------------------------------------------------------
1 | # server information
2 | im.ip=127.0.0.1
3 | im.port=9502
4 |
5 | #mysql configs
6 | mysql.url=jdbc:mysql://localhost/linbox
7 | mysql.user=root
8 | mysql.password=root
9 | mysql.cachePrepStmts=true
10 | mysql.prepStmtCacheSize=300
11 | mysql.prepStmtCacheSqlLimit=2048
12 |
13 | #Kafka Configs
14 | bootstrap.servers=localhost:9092
15 | acks=all
16 | retries=0
17 | batch.size=16384
18 | linger.ms=1
19 | buffer.memory=33554432
20 | key.serializer=org.apache.kafka.common.serialization.StringSerializer
21 | value.serializer=org.apache.kafka.common.serialization.StringSerializer
22 | group.id=im
23 | enable.auto.commit=true
24 | auto.commit.interval.ms=1000
25 | session.timeout.ms=30000
26 | key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
27 | value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
28 |
29 | #redis config
30 | redis.host=127.0.0.1
31 | redis.port=6379
32 | redis.maxActive=1000
33 | redis.maxIdle=20
34 | redis.maxWaitMilli=3000
35 | redis.timeout=6000
36 |
37 | #monitor config
38 | monitor.duration.second=10
--------------------------------------------------------------------------------
/src/main/config/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
6 |
7 |
8 |
9 |
10 | true
11 |
12 | /tmp/logging/im-server/%d{yyyy-MM-dd}.%i.log
13 | 10
14 |
15 | 100MB
16 |
17 |
18 |
19 | %d{HH:mm:ss.SSS} %level [%thread] %logger.%class{0}#%method [%file:%line] %msg%n
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/config/spring/router.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/config/spring/rpc.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/config/spring/spring.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | classpath*:im.properties
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ${mysql.url}
29 | ${mysql.user}
30 | ${mysql.password}
31 | ${mysql.cachePrepStmts}
32 | ${mysql.prepStmtCacheSize}
33 | ${mysql.prepStmtCacheSqlLimit}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/main/config/spring/tcp.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/docs/IM-architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrsec/linbox_server/40ea82c52c7cd61ac4719e08b12602c6bee80245/src/main/docs/IM-architecture.jpg
--------------------------------------------------------------------------------
/src/main/docs/Im-Communication.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrsec/linbox_server/40ea82c52c7cd61ac4719e08b12602c6bee80245/src/main/docs/Im-Communication.jpg
--------------------------------------------------------------------------------
/src/main/docs/Im-PullMessage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrsec/linbox_server/40ea82c52c7cd61ac4719e08b12602c6bee80245/src/main/docs/Im-PullMessage.jpg
--------------------------------------------------------------------------------
/src/main/docs/Im-SyncUnread.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lrsec/linbox_server/40ea82c52c7cd61ac4719e08b12602c6bee80245/src/main/docs/Im-SyncUnread.jpg
--------------------------------------------------------------------------------
/src/main/docs/mysql.sql:
--------------------------------------------------------------------------------
1 |
2 | DROP TABLE IF EXISTS `im_session_message`;
3 |
4 | CREATE TABLE `im_session_message` (
5 | `RId` BIGINT(20) NOT NULL COMMENT '消息发送方的 rid',
6 | `SessionId` VARCHAR(32) NOT NULL COMMENT '对话 session id',
7 | `FromUserID` BIGINT(20) NOT NULL,
8 | `ToUserID` BIGINT(20) NOT NULL,
9 | `MsgID` BIGINT(20) NOT NULL COMMENT '消息编号',
10 | `MimeType` VARCHAR(64) NOT NULL COMMENT '多媒体文件类型',
11 | `Content` VARCHAR(500) DEFAULT NULL COMMENT '文件内容',
12 | `SendTime` BIGINT(20) NOT NULL COMMENT '服务器端接收到消息的时间,消息发送时间',
13 | `Created` BIGINT(20) NOT NULL
14 | COMMENT '记录创建时间',
15 | UNIQUE KEY `session_msg` (`SessionId`, `MsgID`)
16 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对话信息记录';
17 |
18 |
19 | DROP TABLE IF EXISTS `im_group_message`;
20 |
21 | CREATE TABLE `im_group_message` (
22 | `RId` BIGINT(20) NOT NULL COMMENT '消息发送方的 rid',
23 | `GroupId` VARCHAR(32) NOT NULL COMMENT '对话 session id',
24 | `FromUserID` BIGINT(20) NOT NULL,
25 | `MsgID` BIGINT(20) NOT NULL COMMENT '消息编号',
26 | `MimeType` VARCHAR(64) NOT NULL COMMENT '多媒体文件类型',
27 | `Content` VARCHAR(500) DEFAULT NULL COMMENT '文件内容',
28 | `SendTime` BIGINT(20) NOT NULL COMMENT '服务器端接收到消息的时间,消息发送时间',
29 | `Created` BIGINT(20) NOT NULL COMMENT '记录创建时间',
30 | UNIQUE KEY `group_msg` (`GroupId`, `MsgID`)
31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对话信息记录';
32 |
33 | DROP TABLE IF EXISTS `group_members`;
34 |
35 | CREATE TABLE `group_members` (
36 | `Id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id,用于表维护目的',
37 | `GroupId` bigint(20) unsigned NOT NULL COMMENT '群组id',
38 | `AccountId` bigint(20) unsigned NOT NULL COMMENT '系统用户 id',
39 | `Role` int(2) DEFAULT '0' COMMENT '用户在群组中的身份',
40 | `Visible` tinyint(1) DEFAULT '1' COMMENT '消息是否在用户列表可见',
41 | `Created` bigint(20) unsigned NOT NULL,
42 | `Updated` bigint(20) unsigned NOT NULL,
43 | PRIMARY KEY (`Id`),
44 | KEY `group_id` (`GroupId`),
45 | KEY `account_id` (`AccountId`)
46 | ) ENGINE=InnoDB AUTO_INCREMENT=555 DEFAULT CHARSET=utf8mb4;
47 |
48 | DROP TABLE IF EXISTS `profile`;
49 |
50 | CREATE TABLE `profile` (
51 | `AccountID` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '账户ID',
52 | `MDTCode` bigint(20) NOT NULL,
53 | `ChatID` varchar(32) COLLATE utf8_bin DEFAULT NULL,
54 | `Birthday` date DEFAULT NULL COMMENT '生日',
55 | `Profession` int(2) NOT NULL DEFAULT '0' COMMENT '职业:\n0-未知\n10-医生\n20-护士\n30-医学生\n90-医护工作者',
56 | `Signature` varchar(200) CHARACTER SET utf8mb4 DEFAULT NULL,
57 | `Phone` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '电话',
58 | `OftenPlace` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '出没地点',
59 | `Interest` varchar(200) CHARACTER SET utf8mb4 DEFAULT NULL,
60 | `ActiveState` int(1) NOT NULL DEFAULT '0' COMMENT '账户激活状态:\n0-未激活;\n1-已激活;\n9-停用;',
61 | `RealName` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL,
62 | `NickName` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL,
63 | `Age` varchar(45) COLLATE utf8_bin DEFAULT NULL,
64 | `Gender` int(2) DEFAULT '0' COMMENT 'Secrecy(0,"保密"), /** * 男性 */ Male(1,"男"), /** * 女性 */ Female(2,"女");',
65 | `Constellation` varchar(45) COLLATE utf8_bin DEFAULT NULL,
66 | `LastActive` int(11) DEFAULT NULL,
67 | `Status` int(2) DEFAULT '0' COMMENT '状态\n0-离线\n1-在线\n2-手术中\n3-门诊中\n4-工作中\n5-休假中',
68 | `Avatar` varchar(50) COLLATE utf8_bin DEFAULT NULL,
69 | `OrgName` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '机构名称',
70 | `OrgID` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '机构ID',
71 | `DeptName` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '部门名称',
72 | `DeptID` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '部门ID',
73 | `Title` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '学历或职位名称',
74 | `TitleType` int(1) DEFAULT '0' COMMENT '学历或职位类型(不同经历枚举含义不同):\n0-未设置\n\n学校:\n1-大专\n2-本科\n3-硕士\n4-博士\n5-其他\n\n医院',
75 | `Created` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
76 | `Updated` timestamp NULL DEFAULT NULL,
77 | `IsCertificated` int(1) DEFAULT '0' COMMENT '是否认证',
78 | `CertificateInfo` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '认证信息,由审核人员添加',
79 | `FirstDeptName` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '一级部门名称',
80 | `CreatedNew` int(11) DEFAULT '0',
81 | `UpdatedNew` int(11) DEFAULT '0',
82 | `PinYin` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '真实姓名拼音字段',
83 | `Province` varchar(50) CHARACTER SET utf8mb4 DEFAULT '0',
84 | `City` varchar(50) CHARACTER SET utf8mb4 DEFAULT '0',
85 | `Achievement` varchar(250) CHARACTER SET utf8mb4 DEFAULT NULL,
86 | `Sideline` varchar(130) CHARACTER SET utf8mb4 DEFAULT NULL,
87 | `Zone` int(2) DEFAULT '2',
88 | PRIMARY KEY (`AccountID`),
89 | UNIQUE KEY `MDTCode_UNIQUE123` (`MDTCode`),
90 | UNIQUE KEY `AccountID_UNIQUE` (`AccountID`)
91 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT COMMENT='用户信息表'
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/exceptions/ConfigFailException.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.exceptions;
2 |
3 | /**
4 | * Created by lrsec on 11/13/15.
5 | */
6 | public class ConfigFailException extends RuntimeException {
7 | public ConfigFailException (String message) {
8 | super(message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/exceptions/IMConsumerException.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.exceptions;
2 |
3 | /**
4 | * Created by lrsec on 12/15/15.
5 | */
6 | public class IMConsumerException extends RuntimeException {
7 | private String topicMessage;
8 |
9 | public IMConsumerException(Exception e, String topicMessage) {
10 | super(e);
11 | topicMessage = topicMessage;
12 | }
13 |
14 | public String getTopicMessage() {
15 | return topicMessage;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/exceptions/IMException.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.exceptions;
2 |
3 | /**
4 | * Created by lrsec on 11/13/15.
5 | */
6 | public class IMException extends RuntimeException {
7 | public IMException (String message) {
8 | super (message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/exceptions/ServiceNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.exceptions;
2 |
3 | /**
4 | * Created by lrsec on 11/13/15.
5 | */
6 | public class ServiceNotFoundException extends Exception {
7 | public ServiceNotFoundException(String message) {
8 | super(message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/interfaces/IIMService.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.interfaces;
2 |
3 | import java.util.Set;
4 |
5 | /**
6 | * Created by lrsec on 8/21/15.
7 | */
8 | public interface IIMService {
9 | Set getIMServerList();
10 | String getPassword(long userId);
11 |
12 | void sendMessage(String fromUserId, String toId, String mineType, String content, int messageType, String creatorId);
13 | void sendSystemMessage(String targetUserId, int systemType, String content);
14 |
15 | long generateGroupId();
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/AuthRequest.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 |
9 | public class AuthRequest extends ByteCreator{
10 | // 客户端 request id
11 | @JSONField(name = "r_id")
12 | public long rId;
13 |
14 | // user chat id
15 | @JSONField(name="user_id")
16 | public String userId;
17 |
18 | // user token
19 | @JSONField(name = "token")
20 | public String token;
21 |
22 | // user ip
23 | @JSONField(name = "ip")
24 | public String ip;
25 |
26 | // user port
27 | @JSONField(name = "port")
28 | public int port;
29 |
30 | // device type
31 | @JSONField(name = "device")
32 | public String device;
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/AuthResponse.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class AuthResponse extends ByteCreator {
9 | // 客户端 request id
10 | @JSONField(name = "r_id")
11 | public long rId;
12 |
13 | // user chat id
14 | @JSONField(name = "user_id")
15 | public String userId;
16 |
17 | // status code, 200 = success
18 | @JSONField(name = "status")
19 | public int status;
20 |
21 | // error message
22 | @JSONField(name = "err_msg")
23 | public String errMsg;
24 |
25 | // 发送该消息的服务器时间,毫秒
26 | @JSONField(name = "send_time")
27 | public long sendTime;
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/ByteCreator.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.linbox.im.exceptions.IMException;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | /**
9 | * Created by lrsec on 7/4/15.
10 | *
11 | * 提供了消息体自生成可用于 netty 发送的 byte[] 的共更能
12 | */
13 | public class ByteCreator {
14 | private static Logger logger = LoggerFactory.getLogger(ByteCreator.class);
15 |
16 | public MessageWrapper toWrapper() {
17 | MessageWrapper wrapper = new MessageWrapper();
18 |
19 | RequestResponseType type = RequestResponseType.parse(this);
20 |
21 | if (type == null) {
22 | logger.error("无法解析当前类型 {}", this.getClass().getName());
23 | throw new IMException("无法解析当前类型 " + this.getClass().getName());
24 | }
25 |
26 | wrapper.type = type;
27 | wrapper.content = this;
28 |
29 | return wrapper;
30 | }
31 |
32 | public String toWrapperJson() {
33 | return JSON.toJSONString(toWrapper());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/Message.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import com.linbox.im.server.storage.entity.GroupMessageEntity;
5 | import com.linbox.im.server.storage.entity.SessionMessageEntity;
6 |
7 | /**
8 | * Created by lrsec on 6/29/15.
9 | */
10 | public class Message {
11 |
12 | // 消息发送时的 rId
13 | @JSONField(name = "r_id")
14 | public long rId;
15 |
16 | // 发送方 id
17 | @JSONField(name = "from_user_id")
18 | public String fromUserId;
19 |
20 | // 目的方 id
21 | @JSONField(name = "to_user_id")
22 | public String toUserId;
23 |
24 | // 群组 id
25 | @JSONField(name = "group_id")
26 | public String groupId;
27 |
28 | // message id
29 | @JSONField(name = "msg_id")
30 | public long msgId;
31 |
32 | // 消息体类型
33 | @JSONField(name = "mime_type")
34 | public String mimeType;
35 |
36 | // 消息体内容
37 | @JSONField(name = "content")
38 | public String content;
39 |
40 | // 服务器端接收到消息的时间
41 | @JSONField(name = "send_time")
42 | public long sendTime;
43 |
44 | // 消息的类型
45 | @JSONField(name = "type")
46 | public int type;
47 |
48 | public static Message convertToMsg(SessionMessageEntity dao, String userId) {
49 | Message m = new Message();
50 | m.rId = dao.RId;
51 | m.fromUserId = Long.toString(dao.FromUserID);
52 | m.toUserId = Long.toString(dao.ToUserID);
53 | m.msgId = dao.MsgID;
54 | m.mimeType = dao.MimeType;
55 | m.content = dao.Content;
56 | m.sendTime = dao.SendTime;
57 | m.type = MessageType.Session.getValue();
58 |
59 | return m;
60 | }
61 |
62 | public static Message convertToMsg(GroupMessageEntity dao, String fromUserId, String groupId) {
63 | Message m = new Message();
64 | m.rId = dao.RId;
65 | m.fromUserId = fromUserId;
66 | m.groupId = groupId;
67 | m.msgId = dao.MsgID;
68 | m.mimeType = dao.MimeType;
69 | m.content = dao.Content;
70 | m.sendTime = dao.SendTime;
71 | m.type = MessageType.Group.getValue();
72 |
73 | return m;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/MessageType.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | /**
4 | * Created by lrsec on 7/3/15.
5 | */
6 | public enum MessageType {
7 | All(1, "all"),
8 | Session(2, "session"),
9 | Group(3, "group");
10 |
11 | private int value;
12 | private String name;
13 |
14 | private MessageType(int value, String name) {
15 | this.value = value;
16 | this.name = name;
17 | }
18 |
19 | public int getValue() {
20 | return value;
21 | }
22 |
23 | public String getName() {
24 | return name;
25 | }
26 |
27 | public static MessageType parse(int v) {
28 | MessageType type = null;
29 |
30 | for(MessageType t : MessageType.values()) {
31 | if (t.getValue() == v) {
32 | type = t;
33 | }
34 | }
35 |
36 | return type;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/MessageWrapper.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | /**
4 | * Created by lrsec on 7/10/15.
5 | */
6 | public class MessageWrapper {
7 | public RequestResponseType type;
8 | public Object content;
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/NewMessage.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class NewMessage extends ByteCreator{
9 | @JSONField(name = "user_id")
10 | public String userId;
11 |
12 | @JSONField(name = "remote_id")
13 | public String remoteId;
14 |
15 | @JSONField(name = "group_id")
16 | public String groupId;
17 |
18 | @JSONField(name = "type")
19 | public int type;
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/OfflineInfo.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 7/9/15.
7 | */
8 | public class OfflineInfo extends ByteCreator{
9 | // user chat id
10 | @JSONField(name="user_id")
11 | public String userId;
12 |
13 | // 返回信息,客户端显示给用户下线原因
14 | @JSONField(name = "reason")
15 | public String reason;
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/Ping.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 7/6/15.
7 | */
8 | public class Ping extends ByteCreator{
9 | // 客户端 request id
10 | @JSONField(name = "r_id")
11 | public long rId;
12 |
13 | // user chat id
14 | @JSONField(name="user_id")
15 | public String userId;
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/Pong.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 7/6/15.
7 | */
8 | public class Pong extends ByteCreator{
9 | // 客户端 request id
10 | @JSONField(name = "r_id")
11 | public long rId;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/PullOldMsgRequest.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class PullOldMsgRequest extends ByteCreator{
9 |
10 | public static final long MAX = -1l;
11 |
12 | // 客户端 request id
13 | @JSONField(name = "r_id")
14 | public long rId;
15 |
16 | // user id
17 | @JSONField(name = "user_id")
18 | public String userId;
19 |
20 | // remote id
21 | @JSONField(name = "remote_id")
22 | public String remoteId;
23 |
24 | @JSONField(name = "group_id")
25 | public String groupId;
26 |
27 | // max message id want to pull
28 | @JSONField(name = "max_msg_id")
29 | public long maxMsgId;
30 |
31 | // min message id for this pulling
32 | @JSONField(name = "min_msg_id")
33 | public long minMsgId;
34 |
35 | // pull page size
36 | @JSONField(name = "limit")
37 | public int limit;
38 |
39 | // 消息类型
40 | @JSONField(name = "type")
41 | public int type;
42 |
43 | // request 中携带的 max message id 信息,冗余仅用于简化客户端操作
44 | @JSONField(name = "max_msg_id_in_request")
45 | public long maxMsgIdInRequest;
46 |
47 | // request type ,冗余,仅用于简化客户端操作
48 | @JSONField(name = "request_type")
49 | public int requestType;
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/PullOldMsgRequestType.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | /**
4 | * Created by lrsec on 8/4/15.
5 | */
6 |
7 | import com.alibaba.fastjson.JSON;
8 |
9 | /**
10 | * 冗余类型,仅用于简化客户端操作
11 | */
12 | public enum PullOldMsgRequestType {
13 | LATEST(1, "最新数据"),
14 | OLD(2, "历史数据"),
15 | POINTED(3,"指定数据")
16 | ;
17 |
18 | private int value;
19 | private String name;
20 |
21 | private PullOldMsgRequestType(int value, String name) {
22 | this.value = value;
23 | this.name = name;
24 | }
25 |
26 | public int getValue() {
27 | return value;
28 | }
29 |
30 | public String getName() {
31 | return name;
32 | }
33 |
34 | public int valueOf() {
35 | return value;
36 | }
37 |
38 | public static void main(String[] args) {
39 | class A {
40 | public PullOldMsgRequestType type = LATEST;
41 | }
42 |
43 | A a = new A();
44 |
45 | String json = JSON.toJSONString(a);
46 |
47 | A b = JSON.parseObject(json, A.class);
48 |
49 | System.out.println(json);
50 | System.out.println(b.type.getName());
51 | }
52 | }
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/PullOldMsgResponse.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class PullOldMsgResponse extends ByteCreator {
9 | // client request id
10 | @JSONField(name = "r_id")
11 | public long rId;
12 |
13 | // user chat id
14 | @JSONField(name = "user_id")
15 | public String userId;
16 |
17 | // remote chatter id
18 | @JSONField(name = "remote_id")
19 | public String remoteId;
20 |
21 | @JSONField(name = "group_id")
22 | public String groupId;
23 |
24 | // 消息
25 | @JSONField(name = "msgs")
26 | public Message[] msgs;
27 |
28 | // 状态码, 200 = success
29 | @JSONField(name = "status")
30 | public int status;
31 |
32 | // 错误信息
33 | @JSONField(name = "err_msg")
34 | public String errMsg;
35 |
36 | // 消息类型
37 | @JSONField(name = "type")
38 | public int type;
39 |
40 | // request 中携带的 max message id 信息,冗余仅用于简化客户端操作
41 | @JSONField(name = "max_msg_id_in_request")
42 | public long maxMsgIdInRequest;
43 |
44 | // request type ,冗余,仅用于简化客户端操作
45 | @JSONField(name = "request_type")
46 | public int requestType;
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/ReadAckRequest.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class ReadAckRequest extends ByteCreator{
9 |
10 | @JSONField(name = "r_id")
11 | public long rId;
12 |
13 | @JSONField(name = "user_id")
14 | public String userId;
15 |
16 | @JSONField(name = "remote_id")
17 | public String remoteId;
18 |
19 | @JSONField(name = "group_id")
20 | public String groupId;
21 |
22 | @JSONField(name = "msg_id")
23 | public long msgId;
24 |
25 | // 消息类型
26 | @JSONField(name = "type")
27 | public int type;
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/ReadAckResponse.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class ReadAckResponse extends ByteCreator {
9 |
10 | @JSONField(name = "r_id")
11 | public long rId;
12 |
13 | @JSONField(name = "user_id")
14 | public String userId;
15 |
16 | @JSONField(name = "remote_id")
17 | public String remoteId;
18 |
19 | @JSONField(name = "group_id")
20 | public String groupId;
21 |
22 | // 消息类型
23 | @JSONField(name = "type")
24 | public int type;
25 |
26 | @JSONField(name = "status")
27 | public int status;
28 |
29 | @JSONField(name = "err_code")
30 | public String errCode;
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/RequestResponseType.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.linbox.im.message.system.*;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public enum RequestResponseType {
9 |
10 | // 客户端鉴权,同时用于连接建立后试探网络环境
11 | // 鉴权信息必须是网络联通后客户端发送的第一个信息,否则连接被关闭
12 | AuthRequestMsg(1, "AuthRequest", AuthRequest.class),
13 | AuthResponseMsg(2, "AuthResponse", AuthResponse.class),
14 |
15 | // 客户端同步未读信息
16 | SyncUnreadRequestMsg(3, "SyncUnreadRequest", SyncUnreadRequest.class),
17 | SyncUnreadResponseMsg(4, "SyncUnreadResponse", SyncUnreadResponse.class),
18 |
19 | // 客户端确认未读信息已被读取
20 | ReadAckRequestMsg(5, "ReadAckRequest", ReadAckRequest.class),
21 | ReadAckResponseMsg(6, "ReadAckResponse", ReadAckResponse.class),
22 |
23 | // 客户端以反序分页拉取信息
24 | PullOldMsgRequestMsg(7, "PullOldMsgRequest", PullOldMsgRequest.class),
25 | PullOldMsgResponseMsg(8, "PullOldMsgResponse", PullOldMsgResponse.class),
26 |
27 | // 客户端发送信息
28 | SendMsgRequestMsg(9, "SendMsgRequest", SendMsgRequest.class),
29 | SendMsgResponseMsg(10, "SendMsgResponse", SendMsgResponse.class),
30 |
31 | // 新消息通知
32 | NewMsgInfo(11, "NewMessage", NewMessage.class),
33 | OfflineInfo(12, "OfflineInfo", OfflineInfo.class),
34 |
35 | // Heartbeat
36 | Ping(13, "Ping", Ping.class),
37 | Pong(14, "Pong", Pong.class),
38 |
39 | // 系统消息
40 | SystemMsgInfo(15, "SystemMessage", SystemMessage.class),
41 | SyncSystemUnreadRequestMsg(16, "SyncSystemUnreadRequest", SyncSystemUnreadRequest.class),
42 | SyncSystemUnreadResponseMsg(17, "SyncSystemUnreadResponse", SyncSystemUnreadResponse.class),
43 | SystemUnreadAckRequestMsg(18, "SystemUnreadAckRequest", SystemUnreadAckRequest.class),
44 | SystemUnreadAckResponseMsg(19, "SystemUnreadAckResponse", SystemUnreadAckResponse.class)
45 |
46 | ;
47 |
48 | private int value;
49 | private String name;
50 | private Class clazz ;
51 |
52 | private RequestResponseType(int value, String name, Class clazz) {
53 | this.value = value;
54 | this.clazz = clazz;
55 | this.name = name;
56 | }
57 |
58 | public int getValue() {
59 | return value;
60 | }
61 |
62 | public Class getClazz() {
63 | return clazz;
64 | }
65 |
66 | public String getName() {
67 | return name;
68 | }
69 |
70 | public static RequestResponseType parse(int value) {
71 | RequestResponseType type = null;
72 |
73 | for(RequestResponseType t : RequestResponseType.values()) {
74 | if(t.getValue() == value) {
75 | type = t;
76 | break;
77 | }
78 | }
79 |
80 | return type;
81 |
82 | }
83 |
84 | public static RequestResponseType parse(Object o) {
85 | RequestResponseType type = null;
86 |
87 | for (RequestResponseType t : RequestResponseType.values()) {
88 | if (t.clazz.isInstance(o)) {
89 | type = t;
90 | break;
91 | }
92 | }
93 |
94 | return type;
95 | }
96 |
97 | public static boolean isRequest(RequestResponseType t) {
98 | return t==AuthRequestMsg
99 | || t==SyncUnreadRequestMsg
100 | || t==ReadAckRequestMsg
101 | || t==PullOldMsgRequestMsg
102 | || t==SendMsgRequestMsg
103 | || t==Ping;
104 | }
105 |
106 | public static boolean isResponse(RequestResponseType t) {
107 | return t==AuthResponseMsg
108 | || t==SyncUnreadResponseMsg
109 | || t==ReadAckResponseMsg
110 | || t==PullOldMsgResponseMsg
111 | || t==SendMsgResponseMsg
112 | || t==NewMsgInfo
113 | || t==OfflineInfo
114 | || t==Pong;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/SendMsgRequest.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class SendMsgRequest extends ByteCreator{
9 | @JSONField(name = "r_id")
10 | public long rId;
11 |
12 | @JSONField(name = "user_id")
13 | public String userId;
14 |
15 | @JSONField(name = "remote_id")
16 | public String remoteId;
17 |
18 | @JSONField(name = "group_id")
19 | public String groupId;
20 |
21 | @JSONField(name = "msg")
22 | public Message msg;
23 |
24 | // 消息类型
25 | @JSONField(name = "type")
26 | public int type;
27 | }
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/SendMsgResponse.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class SendMsgResponse extends ByteCreator {
9 | @JSONField(name = "r_id")
10 | public long rId;
11 |
12 | @JSONField(name = "msg_r_id")
13 | public long msgRId;
14 |
15 | @JSONField(name = "user_id")
16 | public String userId;
17 |
18 | @JSONField(name = "remote_id")
19 | public String remoteId;
20 |
21 | @JSONField(name = "group_id")
22 | public String groupId;
23 |
24 | @JSONField(name = "msg_id")
25 | public long msgId;
26 |
27 | @JSONField(name = "send_time")
28 | public long sendTime;
29 |
30 | // 消息类型
31 | @JSONField(name = "type")
32 | public int type;
33 |
34 | @JSONField(name = "status")
35 | public int status;
36 |
37 | @JSONField(name = "err_msg")
38 | public String errMsg;
39 | }
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/SyncUnreadRequest.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class SyncUnreadRequest extends ByteCreator{
9 | // request id
10 | @JSONField(name="r_id")
11 | public long rId;
12 |
13 | // user chat id
14 | @JSONField(name="user_id")
15 | public String userId;
16 |
17 | // remote chatter id
18 | // 非必填项,填写则指定拉取一组对话的未读数信息。不填则拉取所有未读数信息
19 | @JSONField(name = "remote_id")
20 | public String remoteId;
21 |
22 | @JSONField(name = "group_id")
23 | public String groupId;
24 |
25 | // 分页数据,本次分页起始偏移量
26 | @JSONField(name = "offset")
27 | public long offset;
28 |
29 | // 分页数据,分业内数据量
30 | @JSONField(name = "limit")
31 | public int limit;
32 |
33 | // 消息类型
34 | // 非必填项,不填写则拉取所有类型的信息
35 | // 如果填写,则必须包含对应类型的 remoteChatId 或者 groupChatId
36 | @JSONField(name = "type")
37 | public int type = 1;
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/SyncUnreadResponse.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class SyncUnreadResponse extends ByteCreator {
9 | // request id
10 | @JSONField(name="r_id")
11 | public long rId;
12 |
13 | // user chat id
14 | @JSONField(name="user_id")
15 | public String userId;
16 |
17 | // remote chatter id
18 | // 非必填项,填写则指定拉取一组对话的未读数信息。不填则拉取所有未读数信息
19 | @JSONField(name = "remote_id")
20 | public String remoteId;
21 |
22 | @JSONField(name = "group_id")
23 | public String groupId;
24 |
25 | @JSONField(name = "type")
26 | public int type = 1;
27 |
28 | // unread messages
29 | @JSONField(name="unreads")
30 | public UnreadMsg[] unreads;
31 |
32 | // 返回本次所取数据的起始 offset
33 | @JSONField(name = "offset")
34 | public long offset;
35 |
36 | // status. 200 = success
37 | @JSONField(name="status")
38 | public int status;
39 |
40 | // error message
41 | @JSONField(name="err_msg")
42 | public String errMsg;
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/UnreadMsg.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 6/29/15.
7 | */
8 | public class UnreadMsg {
9 | // user chat id
10 | @JSONField(name = "user_id")
11 | public String userId;
12 |
13 | // remote chatter id
14 | @JSONField(name = "remote_id")
15 | public String remoteId;
16 |
17 | @JSONField(name = "group_id")
18 | public String groupId;
19 |
20 | // 消息类型
21 | @JSONField(name = "type")
22 | public int type;
23 |
24 | // last message id
25 | @JSONField(name = "msg_id")
26 | public long msgId;
27 |
28 | // unread count
29 | @JSONField(name = "count")
30 | public long count;
31 |
32 | // last message
33 | @JSONField(name = "msg")
34 | public Message msg;
35 | }
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/system/JobNotificationMessage.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message.system;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 11/9/15.
7 | */
8 | public class JobNotificationMessage {
9 | @JSONField(name = "unread")
10 | public int unreadCount;
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/system/SyncSystemUnreadRequest.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message.system;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import com.linbox.im.message.ByteCreator;
5 |
6 | /**
7 | * Created by lrsec on 9/23/15.
8 | */
9 | public class SyncSystemUnreadRequest extends ByteCreator {
10 | // 客户端 request id
11 | @JSONField(name = "r_id")
12 | public long rId;
13 |
14 | /** 要拉取未读数的用户 id */
15 | @JSONField(name = "user_id")
16 | public String userId;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/system/SyncSystemUnreadResponse.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message.system;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import com.linbox.im.message.ByteCreator;
5 |
6 | /**
7 | * Created by lrsec on 9/23/15.
8 | */
9 | public class SyncSystemUnreadResponse extends ByteCreator {
10 | // 客户端 request id
11 | @JSONField(name = "r_id")
12 | public long rId;
13 |
14 | /** 用户 id */
15 | @JSONField(name = "user_id")
16 | public String userId;
17 |
18 | /** 系统未读数消息,直接复用了 SystemMessage */
19 | @JSONField(name = "unreads")
20 | public SystemMessage[] unreads;
21 |
22 | // status. 200 = success
23 | @JSONField(name="status")
24 | public int status;
25 |
26 | // error message
27 | @JSONField(name="err_msg")
28 | public String errMsg;
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/system/SystemMessage.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message.system;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import com.linbox.im.message.ByteCreator;
5 |
6 | /**
7 | * Created by lrsec on 9/7/15.
8 | */
9 | public class SystemMessage extends ByteCreator {
10 |
11 | @JSONField(name = "system_type")
12 | public int systemType;
13 |
14 | @JSONField(name = "content")
15 | public String content;
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/system/SystemMessageTypes.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message.system;
2 |
3 | /**
4 | * Created by lrsec on 9/23/15.
5 | */
6 | public enum SystemMessageTypes {
7 |
8 | /** 有添加好友信息时,通知客户端,客户端更新新朋友未读数 */
9 | NewFriend(10, "新朋友"),
10 | /** 有新消息时,通知客户端,用于消息中心中,新通知未读数的更新 */
11 | NoticeNotify(30, "新通知"),
12 | /** 添加或者删除好友时,通知对方,刷新好友列表,通知客户端,用于人脉关系刷新 */
13 | RelationChange(100, "人脉关系变动"),
14 | /** 身份认证状态发生变化时,发出通知 */
15 | Certification(110, "身份认证信息"),
16 | /** 动态有更新 */
17 | FeedNotify(120, "动态更新"),
18 | /** 活动信息有更新 */
19 | ActivityNotify(130, "活动更新"),
20 | /** 招聘信息相关系统消息类型 */
21 | JobNotifify(140, "招聘信息更新");
22 |
23 |
24 | private int value;
25 | private String name;
26 |
27 | private SystemMessageTypes(int v, String n) {
28 | this.value = v;
29 | this.name = n;
30 | }
31 |
32 | public int getValue() {
33 | return value;
34 | }
35 |
36 | public String getName() {
37 | return name;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/system/SystemUnreadAckRequest.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message.system;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 9/23/15.
7 | */
8 | public class SystemUnreadAckRequest {
9 | /** 消息发送时的 rId */
10 | @JSONField(name = "r_id")
11 | public long rId;
12 |
13 | /** 用户 id */
14 | @JSONField(name = "user_id")
15 | public String userId;
16 |
17 | /** 未读数类型 */
18 | @JSONField(name = "type")
19 | public int type;
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/system/SystemUnreadAckResponse.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message.system;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 9/23/15.
7 | */
8 | public class SystemUnreadAckResponse {
9 | // 消息发送时的 rId
10 | @JSONField(name = "r_id")
11 | public long rId;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/message/system/content/SystemUnreadContent.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.message.system.content;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 |
5 | /**
6 | * Created by lrsec on 9/23/15.
7 | */
8 | public class SystemUnreadContent {
9 |
10 | /** 未读数 */
11 | @JSONField(name = "unread")
12 | public long unread;
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/server/connector/rpc/IMService.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.server.connector.rpc;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.google.common.base.Strings;
5 | import com.linbox.im.interfaces.IIMService;
6 | import com.linbox.im.message.Message;
7 | import com.linbox.im.message.MessageType;
8 | import com.linbox.im.message.MessageWrapper;
9 | import com.linbox.im.message.SendMsgRequest;
10 | import com.linbox.im.message.system.SystemMessage;
11 | import com.linbox.im.server.constant.MessageTopic;
12 | import com.linbox.im.server.service.IOutboxService;
13 | import com.linbox.im.server.storage.dao.IGroupDAO;
14 | import com.linbox.im.server.storage.dao.IServerDAO;
15 | import org.apache.kafka.clients.producer.KafkaProducer;
16 | import org.apache.kafka.clients.producer.ProducerRecord;
17 | import org.slf4j.Logger;
18 | import org.slf4j.LoggerFactory;
19 | import org.springframework.beans.factory.annotation.Autowired;
20 |
21 | import java.util.Set;
22 |
23 | /**
24 | * Created by lrsec on 7/27/15.
25 | */
26 | public class IMService implements IIMService {
27 | private static Logger logger = LoggerFactory.getLogger(IMService.class);
28 |
29 | @Autowired
30 | private KafkaProducer kafkaProducer;
31 |
32 | @Autowired
33 | private IOutboxService outboxService;
34 |
35 | @Autowired
36 | private IServerDAO serverDAO;
37 |
38 | @Autowired
39 | private IGroupDAO groupDAO;
40 |
41 | @Override
42 | public Set getIMServerList() {
43 | return serverDAO.getServers();
44 | }
45 |
46 | @Override
47 | public String getPassword(long userId) {
48 | return serverDAO.generatePassword(userId);
49 | }
50 |
51 | @Override
52 | public void sendMessage(String fromUserId, String toId, String mineType, String content, int messageType, String creatorId) {
53 | logger.debug("Received message from rpc call. From: {}. To: {}. MimeType: {}. Content: {}. MessageType: {}. CreatorID: {}", fromUserId, toId, mineType, content, messageType, creatorId);
54 |
55 | if (Strings.isNullOrEmpty(fromUserId)) {
56 | logger.error("Sender user id could not be blank");
57 | return;
58 | }
59 |
60 | if (Strings.isNullOrEmpty(fromUserId)) {
61 | logger.error("Target user id could not be blank");
62 | return;
63 | }
64 |
65 | if (!isLegalSender(fromUserId)) {
66 | logger.warn("User {} is not allowed to send message through im rpc service", fromUserId);
67 | return;
68 | }
69 |
70 | long current = System.currentTimeMillis();
71 |
72 | String toUserId = null;
73 | String toGroupId = null;
74 |
75 | MessageType type = MessageType.parse(messageType);
76 | if (type == null) {
77 | logger.warn("Unknown message type {} for user {}. Stop sending messages", messageType, fromUserId);
78 | return;
79 | }
80 |
81 | switch (type) {
82 | case Session:
83 | toUserId = toId;
84 | break;
85 | case Group:
86 | toGroupId = toId;
87 | break;
88 | default:
89 | logger.warn("Unknown message type {} in sending message through rpc service.", type.getName());
90 | return;
91 | }
92 |
93 | Message msg = new Message();
94 | msg.rId = current;
95 | msg.fromUserId = fromUserId;
96 | msg.toUserId = toUserId;
97 | msg.groupId = toGroupId;
98 | msg.msgId = -1;
99 | msg.mimeType = mineType;
100 | msg.content = content;
101 | msg.sendTime = -1;
102 | msg.type = messageType;
103 |
104 | SendMsgRequest request = new SendMsgRequest();
105 | request.rId = current;
106 | request.userId = fromUserId;
107 | request.remoteId = toUserId;
108 | request.groupId = toGroupId;
109 | request.msg = msg;
110 | request.type = messageType;
111 |
112 | MessageWrapper wrapper = request.toWrapper();
113 |
114 | String json = JSON.toJSONString(wrapper);
115 |
116 | kafkaProducer.send(new ProducerRecord(MessageTopic.TOPIC_SEND_MSG, json));
117 | }
118 |
119 | // only allow users in white list to send message
120 | private boolean isLegalSender(String userId) {
121 | return true;
122 | }
123 |
124 | @Override
125 | public long generateGroupId() {
126 | return groupDAO.generateGroupId();
127 | }
128 |
129 | @Override
130 | public void sendSystemMessage(String targetUserId, int systemType, String content) {
131 | SystemMessage systemMessage = new SystemMessage();
132 | systemMessage.systemType = systemType;
133 | systemMessage.content = content;
134 |
135 | outboxService.put(targetUserId, systemMessage.toWrapperJson());
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/server/connector/tcp/ImTcpServer.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.server.connector.tcp;
2 |
3 | import com.linbox.im.server.connector.tcp.handler.IMChannelInitializer;
4 | import com.linbox.im.server.monitor.IConnectorMonitor;
5 | import com.linbox.im.server.storage.dao.IServerDAO;
6 | import io.netty.bootstrap.ServerBootstrap;
7 | import io.netty.channel.ChannelFuture;
8 | import io.netty.channel.ChannelOption;
9 | import io.netty.channel.EventLoopGroup;
10 | import io.netty.channel.nio.NioEventLoopGroup;
11 | import io.netty.channel.socket.nio.NioServerSocketChannel;
12 | import io.netty.util.internal.SystemPropertyUtil;
13 | import org.joda.time.DateTime;
14 | import org.slf4j.Logger;
15 | import org.slf4j.LoggerFactory;
16 | import org.springframework.beans.factory.annotation.Autowired;
17 | import org.springframework.beans.factory.annotation.Value;
18 | import org.springframework.context.support.ClassPathXmlApplicationContext;
19 |
20 | import java.util.concurrent.Executors;
21 | import java.util.concurrent.ScheduledExecutorService;
22 |
23 | /**
24 | * Created by lrsec on 6/25/15.
25 | */
26 | public class ImTcpServer {
27 |
28 | private static final int EXECUTOR_CORE_SIZE =
29 | Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
30 | private static final int CONNECT_TIMEOUT_MILLIS = 3 * 1000;
31 | private static final int SO_TIMEOUT_MILLIS = 3000;
32 |
33 | private static Logger logger = LoggerFactory.getLogger(ImTcpServer.class);
34 |
35 | @Autowired
36 | private IServerDAO serverDAO;
37 |
38 | @Value("${im.port}")
39 | private int port;
40 |
41 | public void run(ClassPathXmlApplicationContext appContext) throws Exception{
42 | logger.info("Im Tcp Server Started at {}", DateTime.now());
43 |
44 | EventLoopGroup bossGroup = new NioEventLoopGroup(1);
45 | EventLoopGroup workerGroup = new NioEventLoopGroup();
46 | ScheduledExecutorService executorService = Executors.newScheduledThreadPool(EXECUTOR_CORE_SIZE);
47 |
48 | try {
49 | ServerBootstrap bootstrap = new ServerBootstrap();
50 |
51 | bootstrap.group(bossGroup, workerGroup)
52 | .channel(NioServerSocketChannel.class)
53 | .childHandler(new IMChannelInitializer(appContext, executorService))
54 | .option(ChannelOption.SO_BACKLOG, 128)
55 | .childOption(ChannelOption.SO_KEEPALIVE, true)
56 | // .childOption(ChannelOption.SO_TIMEOUT, SO_TIMEOUT_MILLIS)
57 | .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT_MILLIS)
58 | .childOption(ChannelOption.TCP_NODELAY, true);
59 |
60 | ChannelFuture future = bootstrap.bind(port).sync();
61 | serverDAO.registerServer();
62 | future.channel().closeFuture().sync();
63 | } catch (Exception e) {
64 | logger.error("Error in Tcp Server", e);
65 | bossGroup.shutdownGracefully();
66 | workerGroup.shutdownGracefully();
67 | }
68 |
69 | logger.info("Im Tcp Server Stopped at {}", DateTime.now());
70 | }
71 |
72 | public static void main(String[] args) {
73 | ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("spring/tcp.xml");
74 |
75 | try {
76 | IConnectorMonitor connectorMonitorService = (IConnectorMonitor)appContext.getBean("connectorMonitor");
77 | connectorMonitorService.start();
78 |
79 | ImTcpServer server = (ImTcpServer)appContext.getBean("imTcpServer");
80 | server.run(appContext);
81 | } catch (Exception e) {
82 | logger.error("Exception in starting ImTcpServer", e);
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/server/connector/tcp/constant/HandlerName.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.server.connector.tcp.constant;
2 |
3 | /**
4 | * Created by lrsec on 7/9/15.
5 | */
6 | public interface HandlerName {
7 | String HANDLER_DECODER = "im_msg_decoder";
8 | String HANDLER_MSG = "im_msg_handler";
9 | String HANDLER_IDLE = "im_idle_handler";
10 | String HANDLER_AUTH = "im_auth_handler";
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/server/connector/tcp/handler/AES.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.server.connector.tcp.handler;
2 |
3 |
4 | import com.linbox.im.exceptions.IMException;
5 | import org.apache.commons.lang.StringUtils;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import javax.crypto.Cipher;
10 | import javax.crypto.spec.IvParameterSpec;
11 | import javax.crypto.spec.SecretKeySpec;
12 | import java.util.Base64;
13 |
14 | /**
15 | * Created by lrsec on 7/10/15.
16 | */
17 | public class AES {
18 | private static Logger logger = LoggerFactory.getLogger(AES.class);
19 |
20 | // private static final String DEFAULT_PASSWORD = "medtree-im-passwmedtree-im-passw";
21 | private static final String DEFAULT_PASSWORD = "medtree-im-passw";
22 | public static final String CIPHER_ALGORITHM="AES/CBC/PKCS5Padding";
23 | public static final String KEY_ALGORITHM="AES";
24 | protected static final String STRING_PATTERN = "UTF-8";
25 |
26 | private SecretKeySpec keypSpec;
27 | private Cipher cipher;
28 | private byte[] password;
29 | private byte[] iv;
30 |
31 | private Base64.Decoder base64Decoder = Base64.getDecoder();
32 |
33 | public AES() {
34 | password = DEFAULT_PASSWORD.getBytes();
35 | iv = generateIV(DEFAULT_PASSWORD);
36 | try {
37 | keypSpec = new SecretKeySpec(password ,KEY_ALGORITHM);
38 | cipher = Cipher.getInstance(CIPHER_ALGORITHM);
39 | } catch (Exception e) {
40 | logger.error("init AES fail", e);
41 | throw new IMException("init AES fail");
42 | }
43 | }
44 |
45 | public void resetPassword(String pw) {
46 | if (StringUtils.isBlank(pw)) {
47 | logger.error("Get a empty password in resetPassword");
48 | return;
49 | }
50 |
51 | this.password = base64Decoder.decode(pw);
52 | this.iv = generateIV(pw);
53 | this.keypSpec = new SecretKeySpec(this.password, KEY_ALGORITHM);
54 |
55 | logger.debug("Reset password: {}. Password size: {}", pw, this.password.length * 8);
56 | }
57 |
58 | public byte[] encrypt(String content) {
59 | byte[] result = null;
60 |
61 | try {
62 | byte[] byteContent = content.getBytes(STRING_PATTERN);
63 | cipher.init(Cipher.ENCRYPT_MODE, keypSpec, new IvParameterSpec(iv));
64 | result = cipher.doFinal(byteContent);
65 | } catch (Exception e) {
66 | logger.error("encrypt AES fail", e);
67 | throw new IMException("encrypt AES fail");
68 | }
69 |
70 | return result;
71 | }
72 |
73 | public String decrypt(byte[] content) {
74 | String result = null;
75 |
76 | try {
77 | cipher.init(Cipher.DECRYPT_MODE, keypSpec, new IvParameterSpec(iv));
78 | result = new String(cipher.doFinal(content), STRING_PATTERN);
79 | } catch (Exception e) {
80 | logger.error("decrypt AES fail", e);
81 | throw new IMException("decrypt AES fail");
82 | }
83 |
84 | return result;
85 | }
86 |
87 | private byte[] generateIV(String pw) {
88 | byte[] result = null;
89 |
90 | try {
91 | result = pw.substring(0, 16).getBytes(STRING_PATTERN);
92 | } catch (Exception e) {
93 | logger.error("Create AES IV error ", e);
94 | throw new IMException("Create AES IV error");
95 | }
96 |
97 | return result;
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/server/connector/tcp/handler/AuthHandler.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.server.connector.tcp.handler;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.linbox.im.message.*;
5 | import com.linbox.im.server.connector.tcp.constant.HandlerName;
6 | import com.linbox.im.server.service.IOutboxService;
7 | import com.linbox.im.server.storage.dao.IServerDAO;
8 | import com.linbox.im.server.storage.dao.IUserDAO;
9 | import io.netty.channel.Channel;
10 | import io.netty.channel.ChannelFuture;
11 | import io.netty.channel.ChannelHandlerContext;
12 | import io.netty.channel.ChannelInboundHandlerAdapter;
13 | import io.netty.util.concurrent.Future;
14 | import io.netty.util.concurrent.GenericFutureListener;
15 | import org.apache.commons.lang.StringUtils;
16 | import org.slf4j.Logger;
17 | import org.slf4j.LoggerFactory;
18 | import org.springframework.context.support.ClassPathXmlApplicationContext;
19 |
20 | import java.net.InetSocketAddress;
21 | import java.util.Base64;
22 | import java.util.concurrent.ScheduledExecutorService;
23 | import java.util.concurrent.ScheduledFuture;
24 | import java.util.concurrent.TimeUnit;
25 |
26 | /**
27 | * Created by lrsec on 6/28/15.
28 | */
29 | public class AuthHandler extends ChannelInboundHandlerAdapter {
30 |
31 | private static Logger logger = LoggerFactory.getLogger(AuthHandler.class);
32 |
33 | private ScheduledExecutorService executor;
34 | private int loopRatio = 500;
35 | private int maxHandleTimeInMills = 5 * 1000;
36 | private ScheduledFuture task;
37 |
38 | private long rId = -1;
39 | private String userId = null;
40 | private AES aes = null;
41 | private Base64.Encoder base64Encoder = null;
42 |
43 | private IOutboxService outboxService;
44 | private IUserDAO userDAO;
45 | private IServerDAO serverDAO;
46 |
47 | public AuthHandler(ClassPathXmlApplicationContext applicationContext, ScheduledExecutorService executor, int loopRatio, int maxHandleTimeInMills, AES aes) {
48 | this.executor = executor;
49 | this.loopRatio = loopRatio;
50 | this.maxHandleTimeInMills = maxHandleTimeInMills;
51 | this.aes = aes;
52 | this.outboxService = (IOutboxService)applicationContext.getBean("outboxService");
53 | this.userDAO = (IUserDAO) applicationContext.getBean("userDAO");
54 | this.serverDAO = (IServerDAO) applicationContext.getBean("serverDAO");
55 | this.base64Encoder = Base64.getEncoder();
56 | }
57 |
58 | @Override
59 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
60 | logger.error("Exception in AuthHandler. Close the connection for user: " + StringUtils.trimToEmpty(userId), cause);
61 | if (rId >= 0) {
62 | sendFailResponse(ctx, 500, cause.getMessage());
63 | }
64 |
65 | ctx.close();
66 | }
67 |
68 | @Override
69 | public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
70 | logger.debug("Received AuthRequest. Message: {}", JSON.toJSONString(msg));
71 |
72 | MessageWrapper wrapper = (MessageWrapper) msg;
73 |
74 | if (wrapper == null) {
75 | logger.error("Get an null message wrapper in message handler");
76 | ctx.close();
77 | return;
78 | }
79 |
80 | RequestResponseType type = wrapper.type;
81 |
82 | if (type == null || type != RequestResponseType.AuthRequestMsg) {
83 | logger.error("The first message from client: {} is not AuthRequest. Close the connection. Message type is {}", ctx.channel().remoteAddress(), msg.getClass().getName());
84 | ctx.close();
85 | return;
86 | }
87 |
88 | AuthRequest authRequest = (AuthRequest) wrapper.content;
89 | rId = authRequest.rId;
90 | boolean authenticated = isCertified(authRequest);
91 |
92 | if (authenticated) {
93 | logger.debug("User {} authenticated.", userId);
94 |
95 | InetSocketAddress address = (InetSocketAddress)ctx.channel().localAddress();
96 | String addressRecord = address.toString() + Long.toString(System.currentTimeMillis());
97 | serverDAO.registerConnection(userId, addressRecord);
98 | logger.debug("Create connection for user: {}. Remote address: {}.", userId, ctx.channel().remoteAddress());
99 |
100 | //TODO 动态密码的生成策略,在测试时关闭
101 | // resetPassword(ctx);
102 |
103 | task = executor.scheduleAtFixedRate(new SendMessageChecker(ctx.channel(), addressRecord), 0, loopRatio, TimeUnit.MILLISECONDS);
104 |
105 | IMMessageHandler imMsgHandler = (IMMessageHandler) ctx.pipeline().get(HandlerName.HANDLER_MSG);
106 | imMsgHandler.setUserId(userId);
107 | IMIdleStateHandler imIdleStateHandler = (IMIdleStateHandler) ctx.pipeline().get(HandlerName.HANDLER_IDLE);
108 | imIdleStateHandler.setUserId(userId);
109 |
110 | ctx.pipeline().remove(this);
111 |
112 | sendSuccessResponse(ctx);
113 |
114 | return;
115 | } else {
116 | logger.error("User {} is not certified. Close the connection.", userId);
117 | sendFailResponse(ctx, 401, "User is not certified");
118 |
119 | ctx.close();
120 | return;
121 | }
122 | }
123 |
124 | private boolean isCertified(AuthRequest request) {
125 | userId = request.userId;
126 | return userDAO.isUserValid(request.userId, request.token);
127 | }
128 |
129 | private void resetPassword(final ChannelHandlerContext ctx) {
130 | String password = serverDAO.getPassword(Long.parseLong(userId));
131 |
132 | if (StringUtils.isBlank(password)) {
133 | logger.error("Can not get im password for user: {}", userId);
134 | sendFailResponse(ctx, 400, "Can not find user im password");
135 | return;
136 | } else {
137 | aes.resetPassword(password);
138 | }
139 | }
140 |
141 | private class SendMessageChecker implements Runnable {
142 | private Logger logger = LoggerFactory.getLogger(SendMessageChecker.class);
143 |
144 | private volatile boolean isConnectionClosed = false;
145 | private Channel ch;
146 | private String linkRecord;
147 |
148 | public SendMessageChecker(Channel ch, String linkRecord) {
149 | this.ch = ch;
150 | this.linkRecord = linkRecord;
151 | }
152 |
153 | public void run() {
154 | long start = System.currentTimeMillis();
155 | while ((System.currentTimeMillis() - start <= maxHandleTimeInMills)) {
156 | try {
157 | if(shouldClose()) {
158 | terminate();
159 | return;
160 | }
161 |
162 | final String msg = outboxService.get(userId);
163 |
164 | if (StringUtils.isBlank(msg)) {
165 | return;
166 | }
167 |
168 | final MessageWrapper wrapper = JSON.parseObject(msg, MessageWrapper.class);
169 |
170 | ChannelFuture future = ch.writeAndFlush(wrapper);
171 |
172 | logger.debug("Tcp sender send message for {}. Message type: {}. Message body: {}", userId, wrapper.type, msg);
173 |
174 | future.addListener(new GenericFutureListener() {
175 | public void operationComplete(Future future) throws Exception {
176 | if (!future.isSuccess()) {
177 | logger.info("Network I/O write fail. Should close connection for user {}.", userId);
178 | isConnectionClosed = true;
179 |
180 | Throwable t = future.cause();
181 | if (t != null) {
182 | logger.info("Send message fail", t);
183 | }
184 | } else {
185 | logger.debug("Tcp sender send message success. user: {}. Message type: {}. Message body: {}", userId, wrapper.type, msg);
186 | }
187 | }
188 | });
189 |
190 | } catch (Exception e) {
191 | logger.error("Exception in sending task for user: " + userId + ". But the sending loop will continue to work", e);
192 | return;
193 | }
194 | }
195 | }
196 |
197 | private boolean shouldClose() {
198 | if (!ch.isActive()) {
199 | logger.warn("Connection for user {} is inactive, should terminate the sending task.", userId);
200 | return true;
201 | }
202 |
203 | if(isConnectionClosed) {
204 | logger.warn("Connection for user {} is closed, should terminate the sending task.", userId);
205 | return true;
206 | }
207 |
208 | String record = serverDAO.getConnection(userId);
209 |
210 | if(!StringUtils.equals(record, linkRecord)) {
211 | logger.warn("Connection is updated, should terminate the sending task for user {}. Local address {}. New connection address: {}", userId, StringUtils.trimToEmpty(linkRecord), StringUtils.trimToEmpty(record));
212 |
213 | sendOfflineInfo();
214 | return true;
215 | }
216 |
217 | return false;
218 | }
219 |
220 | private void terminate() {
221 | logger.info("Terminate sending task for user {}", userId);
222 |
223 | task.cancel(false);
224 |
225 | try {
226 | ch.close();
227 | } catch (Exception e) {
228 | logger.error("Exception when terminate sending task", e);
229 | }
230 | }
231 |
232 | private void sendOfflineInfo() {
233 | OfflineInfo offlineInfo = new OfflineInfo();
234 | offlineInfo.userId = userId;
235 |
236 | MessageWrapper wrapper = offlineInfo.toWrapper();
237 | ch.writeAndFlush(wrapper);
238 | }
239 | }
240 |
241 | private void sendSuccessResponse(ChannelHandlerContext ctx) {
242 | AuthResponse response = new AuthResponse();
243 | response.rId = rId;
244 | response.userId = userId;
245 | response.status = 200;
246 | response.sendTime = System.currentTimeMillis();
247 |
248 | MessageWrapper responseWrapper = response.toWrapper();
249 | ctx.channel().writeAndFlush(responseWrapper);
250 | }
251 |
252 | private void sendFailResponse(ChannelHandlerContext ctx, int status, String errCode) {
253 |
254 | AuthResponse response = new AuthResponse();
255 | response.rId = rId;
256 | response.userId = userId;
257 | response.status = status;
258 | response.errMsg = errCode;
259 | response.sendTime = System.currentTimeMillis();
260 |
261 | MessageWrapper responseWrapper = response.toWrapper();
262 | ctx.channel().writeAndFlush(responseWrapper);
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/server/connector/tcp/handler/IMChannelInitializer.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.server.connector.tcp.handler;
2 |
3 | import com.linbox.im.server.connector.tcp.constant.HandlerName;
4 | import io.netty.channel.ChannelInitializer;
5 | import io.netty.channel.ChannelPipeline;
6 | import io.netty.channel.socket.SocketChannel;
7 | import io.netty.handler.timeout.IdleStateHandler;
8 | import org.springframework.context.support.ClassPathXmlApplicationContext;
9 |
10 | import java.util.concurrent.ScheduledExecutorService;
11 |
12 | /**
13 | * Created by lrsec on 7/19/15.
14 | */
15 | public class IMChannelInitializer extends ChannelInitializer {
16 | private static final int SENDER_LOOP_RATIO_MILLIS = 500;
17 | private static final int MAX_HANDLE_TIME_MILLIS = 5 * 1000;
18 | private static final int IDLE_LOOP_IN_SEC = 60;
19 |
20 | private ClassPathXmlApplicationContext appContext;
21 | private ScheduledExecutorService executorService;
22 |
23 | public IMChannelInitializer (ClassPathXmlApplicationContext appContext, ScheduledExecutorService executorService) {
24 | this.appContext = appContext;
25 | this.executorService = executorService;
26 | }
27 |
28 | @Override
29 | protected void initChannel(SocketChannel sc) throws Exception {
30 | AES aes = new AES();
31 |
32 | ChannelPipeline p = sc.pipeline();
33 |
34 | p.addLast(HandlerName.HANDLER_DECODER, new IMMessageDecoder(aes));
35 |
36 | p.addLast(HandlerName.HANDLER_AUTH, new AuthHandler(appContext,executorService, SENDER_LOOP_RATIO_MILLIS, MAX_HANDLE_TIME_MILLIS, aes));
37 | p.addLast(HandlerName.HANDLER_MSG, new IMMessageHandler(appContext));
38 |
39 | p.addLast(new IdleStateHandler(0, 0, IDLE_LOOP_IN_SEC));
40 | p.addLast(HandlerName.HANDLER_IDLE, new IMIdleStateHandler());
41 |
42 | p.addLast(new IMMessageEncoder(aes));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/server/connector/tcp/handler/IMIdleStateHandler.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.server.connector.tcp.handler;
2 |
3 | import io.netty.channel.ChannelDuplexHandler;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.handler.timeout.IdleStateEvent;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | /**
10 | * Created by lrsec on 7/8/15.
11 | */
12 | public class IMIdleStateHandler extends ChannelDuplexHandler {
13 |
14 | private static Logger logger = LoggerFactory.getLogger(IMIdleStateHandler.class);
15 | private String userId = null;
16 |
17 | @Override
18 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
19 | if (evt instanceof IdleStateEvent) {
20 | IdleStateEvent e = (IdleStateEvent) evt;
21 |
22 | logger.info("Can not receive heartbeat from user {}. Close the connection.", userId);
23 |
24 | ctx.close();
25 | }
26 | }
27 |
28 | void setUserId(String u) {
29 | userId = u;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/linbox/im/server/connector/tcp/handler/IMMessageDecoder.java:
--------------------------------------------------------------------------------
1 | package com.linbox.im.server.connector.tcp.handler;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.linbox.im.message.MessageWrapper;
5 | import com.linbox.im.message.RequestResponseType;
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.channel.ChannelHandlerContext;
8 | import io.netty.handler.codec.ByteToMessageDecoder;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 |
12 | import java.util.List;
13 |
14 | /**
15 | * Created by lrsec on 6/29/15.
16 | */
17 | public class IMMessageDecoder extends ByteToMessageDecoder{
18 | private static Logger logger = LoggerFactory.getLogger(IMMessageDecoder.class);
19 |
20 | private short version = -1;
21 | private RequestResponseType requestResponseType = null;
22 | private int contentSize = -1;
23 |
24 | private AES aes;
25 |
26 | public IMMessageDecoder(AES aes) {
27 | this.aes = aes;
28 | }
29 |
30 | @Override
31 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List