更新netty版本websocket相关用protubuf替换josn实现方式,以及服务端由原来的spring Struts2 改为springboot
@ -9,6 +9,6 @@
|
|||||||
<classpathentry kind="lib" path="libs/netty-transport-4.1.9.Final.jar"/>
|
<classpathentry kind="lib" path="libs/netty-transport-4.1.9.Final.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/netty-common-4.1.9.Final.jar"/>
|
<classpathentry kind="lib" path="libs/netty-common-4.1.9.Final.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/netty-handler-4.1.9.Final.jar"/>
|
<classpathentry kind="lib" path="libs/netty-handler-4.1.9.Final.jar"/>
|
||||||
<classpathentry kind="lib" path="D:/devtools/android-sdk-windows/platforms/android-21/android.jar"/>
|
<classpathentry kind="lib" path="D:/dev/Android-SDK-Windows/platforms/android-25/android.jar"/>
|
||||||
<classpathentry kind="output" path="bin"/>
|
<classpathentry kind="output" path="bin"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
19
cim_for_netty/cim-boot-server/.classpath
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="bin/main" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="main"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="main,test"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="bin/main" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="main"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="main,test"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||||
|
<classpathentry combineaccessrules="false" kind="src" path="/cim-server-sdk"/>
|
||||||
|
<classpathentry kind="output" path="bin/default"/>
|
||||||
|
</classpath>
|
1
cim_for_netty/cim-boot-server/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/.gradle/
|
23
cim_for_netty/cim-boot-server/.project
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>cim-boot-server</name>
|
||||||
|
<comment>Project cim-boot-server created by Buildship.</comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
@ -0,0 +1,2 @@
|
|||||||
|
connection.project.dir=
|
||||||
|
eclipse.preferences.version=1
|
63
cim_for_netty/cim-boot-server/build.gradle
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.4.RELEASE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'org.springframework.boot'
|
||||||
|
|
||||||
|
//生成的jar包包名和版本
|
||||||
|
jar {
|
||||||
|
baseName = 'lvxin-server'
|
||||||
|
version = '1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://repo.spring.io/libs-milestone'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configurations {
|
||||||
|
//all*.exclude group: 'com.fasterxml.jackson.core' , module: 'jackson-databind'
|
||||||
|
all*.exclude group: 'com.fasterxml.jackson.datatype'
|
||||||
|
all*.exclude group: 'com.fasterxml.jackson.module'
|
||||||
|
|
||||||
|
all*.exclude group: 'org.yaml'
|
||||||
|
all*.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||||
|
all*.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-jdbc'
|
||||||
|
|
||||||
|
all*.exclude group: 'org.apache.tomcat'
|
||||||
|
all*.exclude group: 'io.undertow', module: 'undertow-websockets-jsr'
|
||||||
|
all*.exclude group: 'org.jboss.spec.javax.websocket'
|
||||||
|
all*.exclude group: 'org.jboss.spec.javax.transaction'
|
||||||
|
all*.exclude group: 'org.glassfish'
|
||||||
|
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
compile fileTree(dir: 'libs', include: '*.jar')
|
||||||
|
compile 'org.springframework.boot:spring-boot-starter-web:2.0.4.RELEASE'
|
||||||
|
compile 'org.springframework.boot:spring-boot-starter-undertow:2.0.4.RELEASE'
|
||||||
|
compile 'org.springframework.boot:spring-boot-starter-freemarker:2.0.4.RELEASE'
|
||||||
|
compile 'com.google.protobuf:protobuf-java:3.6.0'
|
||||||
|
compile 'com.google.code.gson:gson:2.8.5'
|
||||||
|
compile 'commons-io:commons-io:2.6'
|
||||||
|
compile 'org.apache.commons:commons-lang3:3.5'
|
||||||
|
compile 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||||
|
compile 'cn.teaey.apns4j:apns4j:1.1.4'
|
||||||
|
|
||||||
|
compile 'io.netty:netty-handler:4.1.28.Final'
|
||||||
|
compile 'io.netty:netty-buffer:4.1.28.Final'
|
||||||
|
compile 'io.netty:netty-codec:4.1.28.Final'
|
||||||
|
compile 'io.netty:netty-codec-http:4.1.28.Final'
|
||||||
|
compile 'io.netty:netty-common:4.1.28.Final'
|
||||||
|
compile 'io.netty:netty-transport:4.1.28.Final'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
BIN
cim_for_netty/cim-boot-server/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
cim_for_netty/cim-boot-server/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-bin.zip
|
172
cim_for_netty/cim-boot-server/gradlew
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
84
cim_for_netty/cim-boot-server/gradlew.bat
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
10
cim_for_netty/cim-boot-server/settings.gradle
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* This file was generated by the Gradle 'init' task.
|
||||||
|
*
|
||||||
|
* The settings file is used to specify which projects to include in your build.
|
||||||
|
*
|
||||||
|
* Detailed information about configuring a multi-project build in Gradle can be found
|
||||||
|
* in the user guide at https://docs.gradle.org/4.5/userguide/multi_project_builds.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
rootProject.name = 'cim-boot-server'
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.farsunset.cim;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.farsunset.cim.handler.BindHandler;
|
||||||
|
import com.farsunset.cim.handler.SessionClosedHandler;
|
||||||
|
import com.farsunset.cim.sdk.server.handler.CIMNioSocketAcceptor;
|
||||||
|
import com.farsunset.cim.sdk.server.handler.CIMRequestHandler;
|
||||||
|
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||||
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CIMConfig implements CIMRequestHandler {
|
||||||
|
@Value("${cim.server.port}")
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BindHandler bindHandler;
|
||||||
|
@Autowired
|
||||||
|
private SessionClosedHandler closedHandler;
|
||||||
|
|
||||||
|
private HashMap<String,CIMRequestHandler> appHandlerMap = new HashMap<>();
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
private void initHandler() {
|
||||||
|
/*
|
||||||
|
* 账号绑定handler
|
||||||
|
*/
|
||||||
|
appHandlerMap.put("client_bind", bindHandler);
|
||||||
|
/*
|
||||||
|
* 连接关闭handler
|
||||||
|
*/
|
||||||
|
appHandlerMap.put("client_closed", closedHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CIMNioSocketAcceptor getNioSocketAcceptor() throws IOException {
|
||||||
|
CIMNioSocketAcceptor nioSocketAcceptor = new CIMNioSocketAcceptor();
|
||||||
|
nioSocketAcceptor.setPort(port);
|
||||||
|
nioSocketAcceptor.setAppSentBodyHandler(this);
|
||||||
|
nioSocketAcceptor.bind();
|
||||||
|
return nioSocketAcceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(CIMSession session, SentBody body) {
|
||||||
|
|
||||||
|
CIMRequestHandler handler = appHandlerMap.get(body.getKey());
|
||||||
|
|
||||||
|
if(handler == null) {return ;}
|
||||||
|
|
||||||
|
handler.process(session, body);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2023 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 java.lang.reflect.Modifier;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
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;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 其中@SpringBootApplication申明让spring boot自动给程序进行必要的配置,
|
||||||
|
* 等价于以默认属性使用@Configuration,@EnableAutoConfiguration和@ComponentScan
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableAutoConfiguration(exclude = { WebSocketMessagingAutoConfiguration.class,
|
||||||
|
WebSocketReactiveAutoConfiguration.class,
|
||||||
|
WebSocketServletAutoConfiguration.class,
|
||||||
|
WebServicesAutoConfiguration.class,
|
||||||
|
JmxAutoConfiguration.class,
|
||||||
|
DataSourceAutoConfiguration.class,
|
||||||
|
ValidationAutoConfiguration.class,
|
||||||
|
WebClientAutoConfiguration.class,
|
||||||
|
JacksonAutoConfiguration.class })
|
||||||
|
|
||||||
|
public class ServerLauncher {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ServerLauncher.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
|
||||||
|
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
|
||||||
|
gson = new GsonBuilder()
|
||||||
|
.excludeFieldsWithModifiers(Modifier.PROTECTED | Modifier.TRANSIENT | Modifier.FINAL | Modifier.STATIC)
|
||||||
|
.create();
|
||||||
|
converter.setGson(gson);
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2033 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.admin.controller;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class NavigationController {
|
||||||
|
|
||||||
|
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||||
|
public ModelAndView index(ModelAndView model) {
|
||||||
|
model.setViewName("console/index");
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/webclient/main.action", method = RequestMethod.GET)
|
||||||
|
public ModelAndView webclient(ModelAndView model) {
|
||||||
|
model.setViewName("console/webclient/main");
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,34 +19,28 @@
|
|||||||
* *
|
* *
|
||||||
***************************************************************************************
|
***************************************************************************************
|
||||||
*/
|
*/
|
||||||
package com.farsunset.cim.admin.action;
|
package com.farsunset.cim.admin.controller;
|
||||||
|
|
||||||
import java.io.IOException;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
import org.apache.struts2.ServletActionContext;
|
import com.farsunset.cim.service.impl.CIMSessionServiceImpl;
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/console/session")
|
||||||
|
public class SessionController {
|
||||||
|
|
||||||
import com.farsunset.cim.push.SystemMessagePusher;
|
@Autowired
|
||||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
private CIMSessionServiceImpl sessionManager;
|
||||||
import com.farsunset.cim.sdk.server.model.Message;
|
|
||||||
import com.farsunset.cim.sdk.server.session.DefaultSessionManager;
|
|
||||||
import com.farsunset.cim.util.ContextHolder;
|
|
||||||
import com.opensymphony.xwork2.ActionSupport;
|
|
||||||
|
|
||||||
public class SessionAction extends ActionSupport {
|
@RequestMapping(value = "/list.action")
|
||||||
|
public String list(Model model) {
|
||||||
/**
|
model.addAttribute("sessionList", sessionManager.queryAll());
|
||||||
*
|
return "console/session/manage";
|
||||||
*/
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
public String list() {
|
|
||||||
ServletActionContext.getRequest().setAttribute("sessionList",
|
|
||||||
((DefaultSessionManager) ContextHolder.getBean("CIMSessionManager")).queryAll());
|
|
||||||
|
|
||||||
return "list";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void offline() throws IOException {
|
/*public void offline() throws IOException {
|
||||||
|
|
||||||
String account = ServletActionContext.getRequest().getParameter("account");
|
String account = ServletActionContext.getRequest().getParameter("account");
|
||||||
Message msg = new Message();
|
Message msg = new Message();
|
||||||
@ -56,5 +50,5 @@ public class SessionAction extends ActionSupport {
|
|||||||
// 向客户端 发送消息
|
// 向客户端 发送消息
|
||||||
ContextHolder.getBean(SystemMessagePusher.class).push(msg);
|
ContextHolder.getBean(SystemMessagePusher.class).push(msg);
|
||||||
|
|
||||||
}
|
}*/
|
||||||
}
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2023 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.api.controller;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.farsunset.cim.api.controller.dto.MessageResult;
|
||||||
|
import com.farsunset.cim.push.DefaultMessagePusher;
|
||||||
|
import com.farsunset.cim.push.SystemMessagePusher;
|
||||||
|
import com.farsunset.cim.sdk.server.model.Message;
|
||||||
|
import com.farsunset.cim.util.Constants;
|
||||||
|
import com.farsunset.cim.util.StringUtil;
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/message")
|
||||||
|
public class MessageController {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemMessagePusher systemMessagePusher;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DefaultMessagePusher defaultMessagePusher;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 此方法仅仅在集群时,通过服务器调用
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@RequestMapping(value = "/dispatch",method=RequestMethod.POST)
|
||||||
|
public MessageResult dispatchSend(Message message) {
|
||||||
|
return send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@RequestMapping(value = "/send",method=RequestMethod.POST)
|
||||||
|
public MessageResult send(Message message) {
|
||||||
|
|
||||||
|
MessageResult result = new MessageResult();
|
||||||
|
|
||||||
|
message.setMid(StringUtil.getUUID());
|
||||||
|
if (Constants.MessageType.TYPE_2.equals(message.getAction())) {
|
||||||
|
systemMessagePusher.push(message);
|
||||||
|
} else {
|
||||||
|
defaultMessagePusher.push(message);
|
||||||
|
}
|
||||||
|
result.id = message.getMid();
|
||||||
|
result.timestamp = message.getTimestamp();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
* Copyright 2013-2033 Xia Jun(3979434@qq.com).
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,22 +19,16 @@
|
|||||||
* *
|
* *
|
||||||
***************************************************************************************
|
***************************************************************************************
|
||||||
*/
|
*/
|
||||||
package com.farsunset.cim.sdk.server.handler;
|
package com.farsunset.cim.api.controller.dto;
|
||||||
|
|
||||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
import java.util.List;
|
||||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
|
||||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
|
||||||
|
|
||||||
/**
|
import org.springframework.http.HttpStatus;
|
||||||
* 记录心跳实现
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class HeartbeatHandler implements CIMRequestHandler {
|
|
||||||
|
|
||||||
public ReplyBody process(CIMSession session, SentBody message) {
|
|
||||||
// 收到心跳响应,设置心跳时间
|
|
||||||
session.setHeartbeat(System.currentTimeMillis());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
public class BaseResult {
|
||||||
|
public int code = HttpStatus.OK.value();
|
||||||
|
public String message;
|
||||||
|
public Object data;
|
||||||
|
public List<?> dataList;
|
||||||
}
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2033 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.api.controller.dto;
|
||||||
|
|
||||||
|
public class MessageResult extends BaseResult {
|
||||||
|
public long timestamp;
|
||||||
|
public String id;
|
||||||
|
}
|
@ -21,32 +21,45 @@
|
|||||||
*/
|
*/
|
||||||
package com.farsunset.cim.handler;
|
package com.farsunset.cim.handler;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.util.Objects;
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
|
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.Component;
|
||||||
|
|
||||||
import com.farsunset.cim.push.SystemMessagePusher;
|
|
||||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||||
import com.farsunset.cim.sdk.server.handler.CIMRequestHandler;
|
import com.farsunset.cim.sdk.server.handler.CIMRequestHandler;
|
||||||
import com.farsunset.cim.sdk.server.model.Message;
|
import com.farsunset.cim.sdk.server.model.Message;
|
||||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
||||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
import com.farsunset.cim.sdk.server.session.DefaultSessionManager;
|
import com.farsunset.cim.service.impl.CIMSessionServiceImpl;
|
||||||
import com.farsunset.cim.util.ContextHolder;
|
|
||||||
import com.farsunset.cim.util.StringUtil;
|
import com.farsunset.cim.util.StringUtil;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 账号绑定实现
|
* 账号绑定实现
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@Component
|
||||||
public class BindHandler implements CIMRequestHandler {
|
public class BindHandler implements CIMRequestHandler {
|
||||||
|
|
||||||
protected final Logger logger = Logger.getLogger(BindHandler.class);
|
protected final Logger logger = LoggerFactory.getLogger(BindHandler.class);
|
||||||
|
|
||||||
public ReplyBody process(CIMSession newSession, SentBody message) {
|
@Autowired
|
||||||
DefaultSessionManager sessionManager = ((DefaultSessionManager) ContextHolder.getBean("CIMSessionManager"));
|
private CIMSessionServiceImpl sessionManager;
|
||||||
|
|
||||||
|
@Value("${server.host}")
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
|
||||||
|
public void process(CIMSession newSession, SentBody message) {
|
||||||
|
|
||||||
ReplyBody reply = new ReplyBody();
|
ReplyBody reply = new ReplyBody();
|
||||||
|
reply.setKey(message.getKey());
|
||||||
reply.setCode(CIMConstant.ReturnCode.CODE_200);
|
reply.setCode(CIMConstant.ReturnCode.CODE_200);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@ -54,13 +67,14 @@ public class BindHandler implements CIMRequestHandler {
|
|||||||
newSession.setGid(StringUtil.getUUID());
|
newSession.setGid(StringUtil.getUUID());
|
||||||
newSession.setAccount(account);
|
newSession.setAccount(account);
|
||||||
newSession.setDeviceId(message.get("deviceId"));
|
newSession.setDeviceId(message.get("deviceId"));
|
||||||
newSession.setHost(InetAddress.getLocalHost().getHostAddress());
|
newSession.setHost(host);
|
||||||
newSession.setChannel(message.get("channel"));
|
newSession.setChannel(message.get("channel"));
|
||||||
newSession.setDeviceModel(message.get("device"));
|
newSession.setDeviceModel(message.get("device"));
|
||||||
newSession.setClientVersion(message.get("version"));
|
newSession.setClientVersion(message.get("version"));
|
||||||
newSession.setSystemVersion(message.get("osVersion"));
|
newSession.setSystemVersion(message.get("osVersion"));
|
||||||
newSession.setBindTime(System.currentTimeMillis());
|
newSession.setBindTime(System.currentTimeMillis());
|
||||||
newSession.setPackageName(message.get("packageName"));
|
newSession.setPackageName(message.get("packageName"));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 由于客户端断线服务端可能会无法获知的情况,客户端重连时,需要关闭旧的连接
|
* 由于客户端断线服务端可能会无法获知的情况,客户端重连时,需要关闭旧的连接
|
||||||
*/
|
*/
|
||||||
@ -68,20 +82,13 @@ public class BindHandler implements CIMRequestHandler {
|
|||||||
|
|
||||||
// 如果是账号已经在另一台终端登录。则让另一个终端下线
|
// 如果是账号已经在另一台终端登录。则让另一个终端下线
|
||||||
|
|
||||||
if (newSession.fromOtherDevice(oldSession)) {
|
if (oldSession != null && fromOtherDevice(newSession,oldSession) && oldSession.isConnected()) {
|
||||||
sendForceOfflineMessage(oldSession, account, newSession.getDeviceModel());
|
sendForceOfflineMessage(oldSession, account, newSession.getDeviceModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是重复连接,则直接返回
|
// 第一次设置心跳时间为登录时间
|
||||||
if (newSession.equals(oldSession)) {
|
newSession.setBindTime(System.currentTimeMillis());
|
||||||
|
|
||||||
oldSession.setStatus(CIMSession.STATUS_ENABLED);
|
|
||||||
sessionManager.update(oldSession);
|
|
||||||
reply.put("sid", oldSession.getGid());
|
|
||||||
return reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
closeQuietly(oldSession);
|
|
||||||
|
|
||||||
// 第一次设置心跳时间为登录时间
|
// 第一次设置心跳时间为登录时间
|
||||||
newSession.setBindTime(System.currentTimeMillis());
|
newSession.setBindTime(System.currentTimeMillis());
|
||||||
@ -91,39 +98,32 @@ public class BindHandler implements CIMRequestHandler {
|
|||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
reply.setCode(CIMConstant.ReturnCode.CODE_500);
|
reply.setCode(CIMConstant.ReturnCode.CODE_500);
|
||||||
e.printStackTrace();
|
logger.error(e.getMessage());
|
||||||
}
|
}
|
||||||
logger.debug("bind :account:" + message.get("account") + "-----------------------------" + reply.getCode());
|
|
||||||
reply.put("sid", newSession.getGid());
|
newSession.write(reply);
|
||||||
return reply;
|
}
|
||||||
|
|
||||||
|
private boolean fromOtherDevice(CIMSession oldSession ,CIMSession newSession) {
|
||||||
|
|
||||||
|
return !Objects.equals(oldSession.getDeviceId(), newSession.getDeviceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendForceOfflineMessage(CIMSession oldSession, String account, String deviceModel) {
|
private void sendForceOfflineMessage(CIMSession oldSession, String account, String deviceModel) {
|
||||||
|
|
||||||
Message msg = new Message();
|
Message msg = new Message();
|
||||||
msg.setMid(String.valueOf(System.currentTimeMillis()));
|
|
||||||
msg.setAction(CIMConstant.MessageAction.ACTION_999);// 强行下线消息类型
|
msg.setAction(CIMConstant.MessageAction.ACTION_999);// 强行下线消息类型
|
||||||
msg.setReceiver(account);
|
msg.setReceiver(account);
|
||||||
|
msg.setSender("system");
|
||||||
msg.setContent(deviceModel);
|
msg.setContent(deviceModel);
|
||||||
|
msg.setMid(StringUtil.getUUID());
|
||||||
closeQuietly(oldSession,msg);
|
closeQuietly(oldSession,msg);
|
||||||
|
|
||||||
ContextHolder.getBean(SystemMessagePusher.class).push(msg);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同一设备切换网络时关闭旧的连接
|
|
||||||
private void closeQuietly(CIMSession oldSession) {
|
|
||||||
if (oldSession != null && oldSession.isConnected()) {
|
|
||||||
oldSession.removeAttribute(CIMConstant.SESSION_KEY);
|
|
||||||
oldSession.closeNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不同设备同一账号登录时关闭旧的连接
|
// 不同设备同一账号登录时关闭旧的连接
|
||||||
private void closeQuietly(CIMSession oldSession,Message msg) {
|
private void closeQuietly(CIMSession oldSession,Message msg) {
|
||||||
if (oldSession.isConnected()) {
|
if (oldSession.isConnected() && Objects.equals(host, oldSession.getHost())) {
|
||||||
oldSession.write(msg);
|
oldSession.write(msg);
|
||||||
oldSession.removeAttribute(CIMConstant.SESSION_KEY);
|
oldSession.removeAttribute(CIMConstant.SESSION_KEY);
|
||||||
oldSession.closeNow();
|
oldSession.closeNow();
|
@ -21,36 +21,41 @@
|
|||||||
*/
|
*/
|
||||||
package com.farsunset.cim.handler;
|
package com.farsunset.cim.handler;
|
||||||
|
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||||
import com.farsunset.cim.sdk.server.handler.CIMRequestHandler;
|
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.sdk.server.model.SentBody;
|
||||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
import com.farsunset.cim.sdk.server.session.DefaultSessionManager;
|
import com.farsunset.cim.service.impl.CIMSessionServiceImpl;
|
||||||
import com.farsunset.cim.util.ContextHolder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 断开连接,清除session
|
* 断开连接,清除session
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@Component
|
||||||
public class SessionClosedHandler implements CIMRequestHandler {
|
public class SessionClosedHandler implements CIMRequestHandler {
|
||||||
|
|
||||||
protected final Logger logger = Logger.getLogger(SessionClosedHandler.class);
|
protected final Logger logger = LoggerFactory.getLogger(SessionClosedHandler.class);
|
||||||
|
|
||||||
public ReplyBody process(CIMSession ios, SentBody message) {
|
@Autowired
|
||||||
|
private CIMSessionServiceImpl sessionManager;
|
||||||
|
|
||||||
|
public void process(CIMSession ios, SentBody message) {
|
||||||
|
|
||||||
Object account = ios.getAttribute(CIMConstant.SESSION_KEY);
|
Object account = ios.getAttribute(CIMConstant.SESSION_KEY);
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
DefaultSessionManager sessionManager = ((DefaultSessionManager) ContextHolder.getBean("CIMSessionManager"));
|
|
||||||
|
|
||||||
ios.removeAttribute(CIMConstant.SESSION_KEY);
|
ios.removeAttribute(CIMConstant.SESSION_KEY);
|
||||||
sessionManager.remove(account.toString());
|
sessionManager.remove(account.toString());
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -21,70 +21,70 @@
|
|||||||
*/
|
*/
|
||||||
package com.farsunset.cim.push;
|
package com.farsunset.cim.push;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.farsunset.cim.sdk.server.model.Message;
|
import com.farsunset.cim.sdk.server.model.Message;
|
||||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
import com.farsunset.cim.sdk.server.session.DefaultSessionManager;
|
import com.farsunset.cim.sdk.server.session.DefaultSessionManager;
|
||||||
|
import com.farsunset.cim.service.ApnsService;
|
||||||
|
import com.farsunset.cim.service.impl.MessageDispatcherImpl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息发送实现类
|
* 消息发送实现类
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@Component
|
||||||
public class DefaultMessagePusher implements CIMMessagePusher {
|
public class DefaultMessagePusher implements CIMMessagePusher {
|
||||||
|
|
||||||
|
@Value("${server.host}")
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
private DefaultSessionManager sessionManager;
|
private DefaultSessionManager sessionManager;
|
||||||
|
|
||||||
public void setSessionManager(DefaultSessionManager sessionManager) {
|
@Autowired
|
||||||
this.sessionManager = sessionManager;
|
private MessageDispatcherImpl messageDispatcher;
|
||||||
}
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApnsService apnsService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向用户发送消息
|
* 向用户发送消息
|
||||||
*
|
*
|
||||||
* @param msg
|
* @param msg
|
||||||
*/
|
*/
|
||||||
public void push(Message msg) {
|
public void push(Message message) {
|
||||||
CIMSession session = sessionManager.get(msg.getReceiver());
|
CIMSession session = sessionManager.get(message.getReceiver());
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* 服务器集群时,可以在此 判断当前session是否连接于本台服务器,如果是,继续往下走,如果不是,将此消息发往当前session连接的服务器并
|
* 服务器集群时,可以在此 判断当前session是否连接于本台服务器,如果是,继续往下走,如果不是,将此消息发往当前session连接的服务器并
|
||||||
* return if(session!=null&&!session.isLocalhost()){//判断当前session是否连接于本台服务器,如不是
|
|
||||||
* //发往目标服务器处理
|
|
||||||
* MessageDispatcher.execute(MessageUtil.transform(msg),session.getHost());
|
|
||||||
* return; }
|
|
||||||
*/
|
*/
|
||||||
|
if (session.isConnected() && !Objects.equals(host, session.getHost())) {
|
||||||
if (session != null && session.isConnected()) {
|
messageDispatcher.forward(message, session.getHost());
|
||||||
|
|
||||||
session.write(msg);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果用户标示了APNS ON 说明这是ios设备需要使用apns发送
|
/**
|
||||||
if (session != null && session.getApnsAble() == CIMSession.APNS_ON) {
|
* 如果是在当前服务器则直接推送
|
||||||
|
*/
|
||||||
apnsPush(1, msg.getContent(), session.getDeviceId());
|
if (session.isConnected() && Objects.equals(host, session.getHost())) {
|
||||||
}
|
session.write(message);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 引入javaapns相关jar
|
* ios设备流程特别处理,如果长链接断开了,并且ApnsAble为打开状态的话优走apns
|
||||||
*
|
|
||||||
* @param badge
|
|
||||||
* @param content
|
|
||||||
* @param token
|
|
||||||
*/
|
|
||||||
private void apnsPush(int badge, String content, String token) {
|
|
||||||
/*
|
|
||||||
* String password = "password"; String keystore = "p12 文件 绝对路径"; boolean
|
|
||||||
* isDebug = true;
|
|
||||||
*
|
|
||||||
* PushNotificationPayload payload = PushNotificationPayload.complex(); try {
|
|
||||||
* payload.addAlert(content); payload.addBadge(1); payload.addSound("default");
|
|
||||||
* Push.payload(payload, keystore, password, isDebug, token);
|
|
||||||
*
|
|
||||||
* } catch (Exception e) { e.printStackTrace(); }
|
|
||||||
*/
|
*/
|
||||||
|
if (Objects.equals(session.getChannel(), CIMSession.CHANNEL_IOS) && Objects.equals(session.getApnsAble(), CIMSession.APNS_ON)) {
|
||||||
|
apnsService.push(message, session.getDeviceId());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -21,8 +21,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.farsunset.cim.push;
|
package com.farsunset.cim.push;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.farsunset.cim.sdk.server.model.Message;
|
import com.farsunset.cim.sdk.server.model.Message;
|
||||||
|
|
||||||
|
@Component
|
||||||
public class SystemMessagePusher extends DefaultMessagePusher {
|
public class SystemMessagePusher extends DefaultMessagePusher {
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2013-2023 Xia Jun(3979434@qq.com).
|
* Copyright 2013-2033 Xia Jun(3979434@qq.com).
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,27 +19,11 @@
|
|||||||
* *
|
* *
|
||||||
***************************************************************************************
|
***************************************************************************************
|
||||||
*/
|
*/
|
||||||
package com.farsunset.cim.util;
|
package com.farsunset.cim.service;
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import com.farsunset.cim.sdk.server.model.Message;
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
|
|
||||||
public class ContextHolder implements ApplicationContextAware {
|
public interface ApnsService {
|
||||||
|
|
||||||
private static ApplicationContext context;
|
void push( Message message, String deviceToken);
|
||||||
|
|
||||||
public static Object getBean(String name) {
|
|
||||||
return context.getBean(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T getBean(Class<T> c) {
|
|
||||||
|
|
||||||
return context.getBean(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
|
||||||
context = applicationContext;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2033 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;
|
||||||
|
|
||||||
|
import com.farsunset.cim.sdk.server.model.Message;
|
||||||
|
|
||||||
|
public interface MessageDispatcher {
|
||||||
|
void forward(final Message msg, final String ip);
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2033 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 org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.farsunset.cim.sdk.server.session.DefaultSessionManager;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CIMSessionServiceImpl extends DefaultSessionManager {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2033 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 java.io.InputStream;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.farsunset.cim.sdk.server.model.Message;
|
||||||
|
import com.farsunset.cim.service.ApnsService;
|
||||||
|
import com.farsunset.cim.util.ApnsPayloadCompat;
|
||||||
|
|
||||||
|
import cn.teaey.apns4j.Apns4j;
|
||||||
|
import cn.teaey.apns4j.network.ApnsChannel;
|
||||||
|
import cn.teaey.apns4j.network.ApnsChannelFactory;
|
||||||
|
import cn.teaey.apns4j.network.ApnsGateway;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class JavaApnsServiceImpl implements ApnsService {
|
||||||
|
private final Logger logger = Logger.getLogger(JavaApnsServiceImpl.class.getName());
|
||||||
|
@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
|
||||||
|
public void push(Message message, String deviceToken) {
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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());
|
||||||
|
}catch(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
logger.severe(e.getLocalizedMessage());
|
||||||
|
}finally {
|
||||||
|
apnsChannel.close();
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2023 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 java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.farsunset.cim.sdk.server.model.Message;
|
||||||
|
import com.farsunset.cim.service.MessageDispatcher;
|
||||||
|
|
||||||
|
import okhttp3.FormBody;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MessageDispatcherImpl implements MessageDispatcher{
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(MessageDispatcherImpl.class.getName());
|
||||||
|
|
||||||
|
@Value("${sys.message.dispatch.url}")
|
||||||
|
private String dispatchUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forward(final Message msg, final String ip) {
|
||||||
|
String apiUrl = String.format(dispatchUrl, ip);
|
||||||
|
try {
|
||||||
|
String response = httpPost(apiUrl, msg);
|
||||||
|
logger.info("消息转发目标服务器{" + ip + "},结果:" + response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
logger.severe("消息转发目标服务器" + apiUrl + "message:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private String httpPost(String url, Message msg) throws Exception {
|
||||||
|
|
||||||
|
OkHttpClient httpclient = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).build();
|
||||||
|
FormBody.Builder build = new FormBody.Builder();
|
||||||
|
|
||||||
|
build.add("mid", String.valueOf(msg.getMid()));
|
||||||
|
build.add("action", msg.getAction());
|
||||||
|
build.add("title", msg.getTitle());
|
||||||
|
build.add("content", msg.getContent());
|
||||||
|
build.add("sender", msg.getSender());
|
||||||
|
build.add("receiver", msg.getReceiver());
|
||||||
|
build.add("format", msg.getFormat());
|
||||||
|
build.add("extra", msg.getExtra());
|
||||||
|
build.add("timestamp", String.valueOf(msg.getTimestamp()));
|
||||||
|
Request request = new Request.Builder().url(url).post(build.build()).build();
|
||||||
|
|
||||||
|
Response response = httpclient.newCall(request).execute();
|
||||||
|
String data = response.body().string();
|
||||||
|
IOUtils.closeQuietly(response);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2033 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 cn.teaey.apns4j.protocol.ApnsPayload;
|
||||||
|
|
||||||
|
public class ApnsPayloadCompat extends ApnsPayload {
|
||||||
|
|
||||||
|
public static final String DATA = "{\"aps\": {\"message\": {\"action\":\"%s\",\"content\":\"%s\",\"sender\":\"%s\",\"receiver\":\"%s\",\"format\":\"%s\"},\"content-available\": 1}}";
|
||||||
|
|
||||||
|
private String action;
|
||||||
|
private String content;
|
||||||
|
private String sender;
|
||||||
|
private String format;
|
||||||
|
private String receiver;
|
||||||
|
|
||||||
|
|
||||||
|
public String getReceiver() {
|
||||||
|
return receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReceiver(String receiver) {
|
||||||
|
this.receiver = receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(String action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getFormat() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFormat(String format) {
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toJsonString() {
|
||||||
|
return String.format(DATA, action, content, sender,receiver,format);
|
||||||
|
}
|
||||||
|
}
|
@ -63,7 +63,6 @@ public class StringUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getUUID() {
|
public static String getUUID() {
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return UUID.randomUUID().toString().replaceAll("-", "");
|
return UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
|||||||
|
server.port=8080
|
||||||
|
server.host=192.168.1.100
|
||||||
|
##################################################################
|
||||||
|
# Freemarker Config #
|
||||||
|
##################################################################
|
||||||
|
spring.freemarker.suffix=.html
|
||||||
|
spring.freemarker.charset=utf-8
|
||||||
|
spring.freemarker.content-type=text/html
|
||||||
|
spring.freemarker.cache=false
|
||||||
|
spring.freemarker.templateLoaderPath=classpath:/page/
|
||||||
|
spring.freemarker.settings.auto_import = /ftl/spring.ftl as spring
|
||||||
|
spring.messages.encoding=UTF-8
|
||||||
|
spring.messages.basename=i18n/messages
|
||||||
|
##################################################################
|
||||||
|
# CIM Config #
|
||||||
|
##################################################################
|
||||||
|
cim.server.port =23456
|
||||||
|
apple.apns.debug=false
|
||||||
|
apple.apns.p12.password= your p12 password
|
||||||
|
apple.apns.p12.file= /apns/lvxin.p12
|
||||||
|
##################################################################
|
||||||
|
# Undertow Config #
|
||||||
|
##################################################################
|
||||||
|
server.undertow.accesslog.enabled=false
|
||||||
|
server.undertow.worker-threads=16
|
||||||
|
server.undertow.io-threads=8
|
||||||
|
server.undertow.buffer-size=1024
|
||||||
|
server.undertow.direct-buffers=true
|
||||||
|
|
||||||
|
|
||||||
|
##################################################################
|
||||||
|
# System Config #
|
||||||
|
##################################################################
|
||||||
|
sys.message.dispatch.url = http://%1$s:${server.port}/api/message/dispatch
|
@ -0,0 +1,67 @@
|
|||||||
|
module.common.html.title = CIM\u7BA1\u7406\u7CFB\u7EDF
|
||||||
|
module.common.account = \u5e10\u53f7
|
||||||
|
module.common.password = \u5bc6\u7801
|
||||||
|
module.common.save = \u4fdd\u5b58
|
||||||
|
module.common.id = ID
|
||||||
|
module.common.type =\u7c7b\u578b
|
||||||
|
module.common.name =\u540d\u79f0
|
||||||
|
module.common.time =\u65f6\u95f4
|
||||||
|
module.common.operation =\u64cd\u4f5c
|
||||||
|
module.common.query =\u67e5\u8be2
|
||||||
|
module.common.look =\u67e5\u770b
|
||||||
|
module.common.preview =\u9884\u89c8
|
||||||
|
module.common.send =\u53d1\u9001
|
||||||
|
module.common.saveing = \u6b63\u5728\u4fdd\u5b58,\u8bf7\u7a0d\u5019......
|
||||||
|
module.common.add = \u6dfb\u52a0
|
||||||
|
module.common.update = \u4fee\u6539
|
||||||
|
module.common.delete = \u5220\u9664
|
||||||
|
module.common.save.success = \u4fdd\u5b58\u6210\u529f
|
||||||
|
module.common.delete.success = \u5220\u9664\u6210\u529f
|
||||||
|
module.common.delete.loading =\u6b63\u5728\u5220\u9664,\u8bf7\u7a0d\u5019......
|
||||||
|
module.common.loading = \u52a0\u8f7d\u4e2d,\u8bf7\u7a0d\u5019......
|
||||||
|
module.common.description = \u8bf4\u660e
|
||||||
|
module.common.state = \u72b6\u6001
|
||||||
|
module.common.content = \u5185\u5bb9
|
||||||
|
module.common.location = \u4f4d\u7f6e
|
||||||
|
module.common.longitude = \u7ecf\u5ea6
|
||||||
|
module.common.latitude = \u7eac\u5ea6
|
||||||
|
module.common.sort = \u6392\u5e8f
|
||||||
|
module.common.code = \u7f16\u53f7
|
||||||
|
module.common.import = \u5bfc\u5165
|
||||||
|
module.common.headlogo = \u5934\u50cf
|
||||||
|
module.common.homepage = \u4e3b\u9875
|
||||||
|
module.common.website = \u7f51\u5740
|
||||||
|
module.common.text = \u6587\u5b57
|
||||||
|
|
||||||
|
module.global.error.500.hint = \u670d\u52a1\u7a0b\u5e8f\u53d1\u751f\u5185\u90e8\u9519\u8bef
|
||||||
|
module.global.error.400.hint = \u8bf7\u6c42\u53c2\u6570\u7c7b\u578b\u4e0d\u6b63\u786e
|
||||||
|
module.global.error.404.hint = \u8bbf\u95ee\u7684\u8d44\u6e90\u4e0d\u5b58\u5728
|
||||||
|
module.global.pager.next = \u4e0b\u4e00\u9875
|
||||||
|
module.global.pager.previou = \u4e0a\u4e00\u9875
|
||||||
|
module.global.pager.description = \u5171{0}\u6761\u8bb0\u5f55,{1}\u9875
|
||||||
|
|
||||||
|
module.console.about = \u5173\u4e8e
|
||||||
|
module.console.about.version = v3.5.0
|
||||||
|
module.console.about.author = \u4f5c\u8005: \u8fdc\u65b9\u5915\u9633
|
||||||
|
module.console.about.author.wechat = \u5fae\u4fe1: 3979434
|
||||||
|
module.console.about.author.qq = Q Q: 3979434
|
||||||
|
module.console.about.copyright = \u57FA\u4E8EJava\u670D\u52A1\u7AEF\u7684\u5373\u65F6\u901A\u4FE1\u89E3\u51B3\u65B9\u6848,\u4E0Eandroid \u5BA2\u6237\u7AEF\u5B8C\u7F8E\u7ED3\u5408\uFF0C\u540C\u65F6\u652F\u6301\u5176\u4ED6\u8BED\u8A00\u7684\u79FB\u52A8\u5E94\u7528\uFF0C\u7269\u8054\u7F51\uFF0C\u667A\u80FD\u5BB6\u5C45\uFF0C\u684C\u9762\u5E94\u7528\uFF0CWEB\u5E94\u7528\u4EE5\u53CA\u540E\u53F0\u7CFB\u7EDF\u4E4B\u95F4\u7684\u5373\u65F6\u6D88\u4EA4\u4E92\uFF0C\u4E3A\u4F60\u89E3\u51B3\u4E86\u957F\u8FDE\u63A5\u5404\u79CD\u6D88\u606F\u4E8B\u4EF6\uFF0C\u65AD\u7EBF\u91CD\u8FDE\u7B49\u7E41\u7410\u7684\u5904\u7406\uFF0C\u4F7F\u7528\u65B9\u4FBF\u6613\u4E8E\u96C6\u6210\u3002<br/><a target="_blank" href="https://gitee.com/farsunset/cim">https://gitee.com/farsunset/cim</a>
|
||||||
|
module.console.menu.onlineuser = \u5728\u7ebf\u7528\u6237
|
||||||
|
|
||||||
|
module.console.cimsession.sending = \u6b63\u5728\u53d1\u9001\uff0c\u8bf7\u7a0d\u5019......
|
||||||
|
module.console.cimsession.send.success = \u53d1\u9001\u6210\u529f
|
||||||
|
module.console.cimsession.logo =\u5934\u50cf
|
||||||
|
module.console.cimsession.account =\u5e10\u53f7
|
||||||
|
module.console.cimsession.nid = \u8fde\u63a5ID
|
||||||
|
module.console.cimsession.channel = \u7ec8\u7aef
|
||||||
|
module.console.cimsession.app.version =\u5e94\u7528\u7248\u672c
|
||||||
|
module.console.cimsession.os.version =\u7cfb\u7edf\u7248\u672c
|
||||||
|
module.console.cimsession.deviceid =\u8bbe\u5907\u7f16\u53f7
|
||||||
|
module.console.cimsession.device.model =\u7ec8\u7aef\u578b\u53f7
|
||||||
|
module.console.cimsession.online.time =\u5728\u7ebf\u65f6\u957f(\u79d2)
|
||||||
|
module.console.cimsession.time.format ={0}\u79d2
|
||||||
|
|
||||||
|
module.console.cimsession.send.message =\u53d1\u9001\u6d88\u606f
|
||||||
|
module.console.cimsession.receiver = \u63a5\u6536\u5e10\u53f7
|
||||||
|
module.console.cimsession.message = \u6d88\u606f\u5185\u5bb9
|
||||||
|
|
@ -0,0 +1,44 @@
|
|||||||
|
<!-- header -->
|
||||||
|
<div id="_main_header_banner" class="header">
|
||||||
|
<h3 style="float:left;color:#ffffff;line-height: 60px;margin-left: 30px;font-family: 楷体;font-size: 32px">CIM体验页面</h3>
|
||||||
|
<div class="btn-group" style=" float: right;margin-top: 33px;margin-right:20px;">
|
||||||
|
<button class="btn btn-info" onclick="doShowDialog('aboutDialog')">
|
||||||
|
<span class="glyphicon glyphicon-info-sign"></span> <@spring.message "module.console.about"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="header_liner"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="aboutDialog" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" style="width: 420px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header" >
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title"><@spring.message "module.common.html.title"/></h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div style="text-align: center;border:none;height: 150px;">
|
||||||
|
<img src="/image/icon.png" width="100px" height="100px" style="box-shadow: 0px 0px 7px 2px #d1d3d6;border: 1px solid #dfdfe0;padding: 10px;border-radius: 100px;margin-top: 20px;"/>
|
||||||
|
<h4 style="margin-top: 15px;"><@spring.message "module.console.about.version"/></h4>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group" style="margin-top: 20px;">
|
||||||
|
<li class="list-group-item" style="border-radius: 0px;">
|
||||||
|
<@spring.message "module.console.about.copyright"/>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item" style="border-radius: 0px;">
|
||||||
|
<@spring.message "module.console.about.author"/>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item" style="border-radius: 0px;">
|
||||||
|
<@spring.message "module.console.about.author.qq"/>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item" style="border-radius: 0px;">
|
||||||
|
<@spring.message "module.console.about.author.wechat"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="global_mask" style="display: none; position: absolute; top: 0px; left: 0px; z-index: 9999; background-color: rgb(20, 20, 20); opacity: 0.5; width: 100%; height: 100%; overflow: hidden; background-position: initial initial; background-repeat: initial initial;"></div>
|
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||||
|
<title><@spring.message "module.common.html.title"/></title>
|
||||||
|
<link rel="shortcut icon" href="/image/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="/bootstrap-3.3.7-dist/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="/css/common.css" />
|
||||||
|
<script type="text/javascript" src="/js/jquery-3.3.1.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/common.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="web-app ui-selectable">
|
||||||
|
|
||||||
|
<#include "/console/header.html">
|
||||||
|
<#include "/console/nav.html">
|
||||||
|
<div id="mainWrapper" style="padding-top: 25px;" >
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$('#indexMenu').addClass('current');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
<div id="_main_nav" class="ui-vnav">
|
||||||
|
<ul class="ui-nav-inner">
|
||||||
|
<li style="height: 50px;text-align: center;margin-top: 10px;">
|
||||||
|
<a type="button" target="_blank" href="/webclient/main.action" class="btn btn-danger" >
|
||||||
|
<span class="glyphicon glyphicon-globe"></span> WEB版本
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li style="border-bottom: 1px solid #D1D6DA;"></li>
|
||||||
|
<li class="ui-item" id="sessionMenu">
|
||||||
|
<a href="/console/session/list.action">
|
||||||
|
<img src="/image/icon/online.svg" style="margin-top:-2px;" width="24px" height="24px"/>
|
||||||
|
<span class="ui-text"><@spring.message "module.console.menu.onlineuser"/></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
@ -0,0 +1,129 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||||
|
<title><@spring.message "module.common.html.title"/></title>
|
||||||
|
<link rel="shortcut icon" href="/image/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="/bootstrap-3.3.7-dist/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="/css/common.css" />
|
||||||
|
<script type="text/javascript" src="/js/jquery-3.3.1.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/common.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/jquery-ui.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showMessageDialog(account){
|
||||||
|
$('#messageDialog').modal('show');
|
||||||
|
$('#Saccount').val(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSendMessage(){
|
||||||
|
var message = $('#message').val();
|
||||||
|
var account = $('#Saccount').val();
|
||||||
|
if($.trim(message)=='')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showProcess("<@spring.message 'module.console.cimsession.sending'/>");
|
||||||
|
$.post("/api/message/send", {content:message,action:2,sender:'system',receiver:account,format:'0'},
|
||||||
|
function(data){
|
||||||
|
hideProcess();
|
||||||
|
showSTip("<@spring.message 'module.console.cimsession.send.success'/>");
|
||||||
|
doHideDialog("messageDialog");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="web-app ui-selectable">
|
||||||
|
|
||||||
|
<#include "/console/header.html">
|
||||||
|
<#include "/console/nav.html">
|
||||||
|
|
||||||
|
<div id="mainWrapper">
|
||||||
|
|
||||||
|
<div class="lay-main-toolbar"></div>
|
||||||
|
<div>
|
||||||
|
<form action="/console/session/list.action" method="post" id="searchForm" >
|
||||||
|
<input type="hidden" name="currentPage" id="currentPage" value="0"/>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<#list sessionList as cimsession>
|
||||||
|
<tr style="height: 50px;">
|
||||||
|
<td>${cimsession.account! }</td>
|
||||||
|
<td>${cimsession.nid!}</td>
|
||||||
|
<td>${cimsession.channel! }</td>
|
||||||
|
<td>${cimsession.deviceId! }</td>
|
||||||
|
<td>${cimsession.deviceModel! }</td>
|
||||||
|
<td>${cimsession.clientVersion! }</td>
|
||||||
|
<td>${cimsession.systemVersion! }</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!}')">
|
||||||
|
<span class="glyphicon glyphicon-send" style="top:2px;"></span>
|
||||||
|
<@spring.message 'module.console.cimsession.send.message'/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</#list>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" id="messageDialog" tabindex="-1" role="dialog" >
|
||||||
|
<div class="modal-dialog" style="width: 420px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title"><@spring.message 'module.console.cimsession.send.message'/></h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-groupBuy">
|
||||||
|
<label for="Amobile">
|
||||||
|
<@spring.message 'module.console.cimsession.receiver'/>
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="Saccount" name="account"
|
||||||
|
style="width: 100%; font-size: 20px; font-weight: bold;height:40px;"
|
||||||
|
disabled="disabled" />
|
||||||
|
</div>
|
||||||
|
<div class="form-groupBuy" style="margin-top: 20px;">
|
||||||
|
<label for="exampleInputFile">
|
||||||
|
<@spring.message 'module.console.cimsession.message'/>
|
||||||
|
</label>
|
||||||
|
<textarea rows="10" style="width: 100%; height: 200px;" id="message" name="message" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer" style="padding: 5px 10px; text-align: center;">
|
||||||
|
<button type="button" class="btn btn-success btn-lg" style="width: 200px;" onclick="doSendMessage()">
|
||||||
|
<span class="glyphicon glyphicon-send" style="top:2px;"></span><@spring.message 'module.common.send'/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$('#sessionMenu').addClass('current');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,16 +1,7 @@
|
|||||||
<%@ page language="java" pageEncoding="utf-8"%>
|
|
||||||
<%
|
|
||||||
String loginBasePath = request.getScheme() + "://"
|
|
||||||
+ request.getServerName() + ":" + request.getServerPort()
|
|
||||||
+ request.getContextPath();
|
|
||||||
|
|
||||||
%>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function doLogin()
|
function doLogin()
|
||||||
{
|
{
|
||||||
ACCOUNT = $('#account').val();
|
if($.trim($('#account').val()) =='' ){
|
||||||
if($.trim(ACCOUNT)=='' )
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showProcess('正在接入请稍后......');
|
showProcess('正在接入请稍后......');
|
||||||
@ -20,17 +11,17 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="modal fade" id="LoginDialog" tabindex="-1" role="dialog" data-backdrop="static">
|
<div class="modal fade" id="LoginDialog" tabindex="-1" role="dialog" data-backdrop="static">
|
||||||
<div class="modal-dialog" style="width: 300px;margin: 30px auto;">
|
<div class="modal-dialog" style="width: 400px;margin: 64px auto;">
|
||||||
<div class="modal-content" >
|
<div class="modal-content" >
|
||||||
<div class="modal-body" style="padding:0px;" >
|
<div class="modal-body" style="padding:0px;" >
|
||||||
<div style="height:150px;text-align: center; background: #5FA0D3; color: #ffffff; border: 0px; border-top-left-radius: 4px; border-top-right-radius: 4px;">
|
<div style="height:150px;text-align: center; background: #5FA0D3; color: #ffffff; border: 0px; border-top-left-radius: 4px; border-top-right-radius: 4px;">
|
||||||
<img src="<%=loginBasePath %>/resource/img/icon.png" style="height: 60px;width: 60px;margin-top:30px;"/>
|
<img src="/image/icon.png" style="height: 60px;width: 60px;margin-top:30px;"/>
|
||||||
<div style="margin-top: 20px; color: #ffffff;font-size: 16px;">使用用户帐号登录</div>
|
<div style="margin-top: 20px; color: #ffffff;font-size: 16px;">请输入一个帐号用于登录,随后接收推送消息</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group" style="margin-top: 20px;margin-left:10px;margin-right:10px;margin-bottom:20px;">
|
<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 class="glyphicon glyphicon-user" aria-hidden="true"></span></span>
|
||||||
<input type="text" class="form-control" id="account" maxlength="32" placeholder="帐号"
|
<input type="text" class="form-control" id="account" maxlength="32" placeholder="帐号(数字或者英文字母)"
|
||||||
style="display: inline; width: 100%; height: 50px;" />
|
style="display: inline; width: 100%; height: 50px;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -0,0 +1,103 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>CIM Webclient </title>
|
||||||
|
<link rel="shortcut icon" href="/image/favicon.ico" type="image/x-icon">
|
||||||
|
<link charset="utf-8" rel="stylesheet" href="/bootstrap-3.3.7-dist/css/bootstrap.min.css" />
|
||||||
|
<link charset="utf-8" rel="stylesheet" href="/css/common.css" />
|
||||||
|
<script type="text/javascript" src="/js/jquery-3.3.1.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/common.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/cim/message.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/cim/replybody.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/cim/sentbody.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/cim/cim.web.sdk.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
|
||||||
|
/** 当socket连接成功回调 **/
|
||||||
|
function onConnectionSuccessed(){
|
||||||
|
CIMWebBridge.bindAccount($('#account').val());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当收到请求回复时候回调 **/
|
||||||
|
function onReplyReceived(reply)
|
||||||
|
{
|
||||||
|
console.log(reply);
|
||||||
|
if(reply.key=='client_bind' && reply.code==200)
|
||||||
|
{
|
||||||
|
hideProcess();
|
||||||
|
|
||||||
|
$('#LoginDialog').fadeOut();
|
||||||
|
|
||||||
|
|
||||||
|
$('#MessageDialog').fadeIn();
|
||||||
|
$('#MessageDialog').addClass("in");
|
||||||
|
$("#current_account").text($('#account').val());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当收到消息时候回调 **/
|
||||||
|
|
||||||
|
function onMessageReceived(message)
|
||||||
|
{
|
||||||
|
console.log(message);
|
||||||
|
if(message.action == ACTION_999){
|
||||||
|
$('#MessageDialog').fadeOut();
|
||||||
|
$('#LoginDialog').fadeIn();
|
||||||
|
$('#LoginDialog').addClass("in");
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#LoginDialog').fadeIn();
|
||||||
|
$('#LoginDialog').addClass("in");
|
||||||
|
initNotification();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function initNotification(){
|
||||||
|
//判断浏览器是否支持桌面通知
|
||||||
|
if (window.Notification) {
|
||||||
|
var notification = window.Notification;
|
||||||
|
if (notification.permission == "default") {
|
||||||
|
notification.requestPermission(function(permission) {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNotification(msg){
|
||||||
|
var notify = new Notification("系统消息", {
|
||||||
|
body: msg,
|
||||||
|
icon: '/image/icon.png',
|
||||||
|
tag: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
notify.onshow = function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
notify.close();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body style="width: 600px;">
|
||||||
|
<#include "loginDialog.html">
|
||||||
|
<#include "messageDialog.html">
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,16 +1,12 @@
|
|||||||
<%@ page language="java" pageEncoding="utf-8"%>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="modal fade" data-backdrop="static" id="MessageDialog" tabindex="-1" role="dialog" >
|
<div class="modal fade" data-backdrop="static" id="MessageDialog" tabindex="-1" role="dialog" >
|
||||||
<div class="modal-dialog" style="width: 500px;margin: 30px auto;">
|
<div class="modal-dialog" style="width: 600px;margin: 30px auto;">
|
||||||
<div class="modal-content" >
|
<div class="modal-content" >
|
||||||
<div class="modal-header" style="text-align: center;">
|
<div class="modal-header" style="text-align: center;">
|
||||||
<h4>消息列表</h4>
|
|
||||||
<span style="float: left;">请在管理页面推送一条消息</span>
|
<span style="float: left;">请在管理页面推送一条消息</span>
|
||||||
<span style="float: right;color: #4caf50;">当前帐号:<span id="current_account">10000</span></span>
|
<span style="float: right;color: #4caf50;">当前帐号:<span id="current_account"></span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" id="messageList" style="min-height: 600px;" >
|
<div class="modal-body" id="messageList" style="min-height: 600px;" >
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -0,0 +1,384 @@
|
|||||||
|
<#ftl strip_whitespace=true>
|
||||||
|
<#--
|
||||||
|
* spring.ftl
|
||||||
|
*
|
||||||
|
* This file consists of a collection of FreeMarker macros aimed at easing
|
||||||
|
* some of the common requirements of web applications - in particular
|
||||||
|
* handling of forms.
|
||||||
|
*
|
||||||
|
* Spring's FreeMarker support will automatically make this file and therefore
|
||||||
|
* all macros within it available to any application using Spring's
|
||||||
|
* FreeMarkerConfigurer.
|
||||||
|
*
|
||||||
|
* To take advantage of these macros, the "exposeSpringMacroHelpers" property
|
||||||
|
* of the FreeMarker class needs to be set to "true". This will expose a
|
||||||
|
* RequestContext under the name "springMacroRequestContext", as needed by
|
||||||
|
* the macros in this library.
|
||||||
|
*
|
||||||
|
* @author Darren Davison
|
||||||
|
* @author Juergen Hoeller
|
||||||
|
* @since 1.1
|
||||||
|
-->
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* message
|
||||||
|
*
|
||||||
|
* Macro to translate a message code into a message.
|
||||||
|
-->
|
||||||
|
<#macro message code>${springMacroRequestContext.getMessage(code)}</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* messageText
|
||||||
|
*
|
||||||
|
* Macro to translate a message code into a message,
|
||||||
|
* using the given default text if no message found.
|
||||||
|
-->
|
||||||
|
<#macro messageText code, text>${springMacroRequestContext.getMessage(code, text)}</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* messageArgs
|
||||||
|
*
|
||||||
|
* Macro to translate a message code with arguments into a message.
|
||||||
|
-->
|
||||||
|
<#macro messageArgs code, args>${springMacroRequestContext.getMessage(code, args)}</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* messageArgsText
|
||||||
|
*
|
||||||
|
* Macro to translate a message code with arguments into a message,
|
||||||
|
* using the given default text if no message found.
|
||||||
|
-->
|
||||||
|
<#macro messageArgsText code, args, text>${springMacroRequestContext.getMessage(code, args, text)}</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* theme
|
||||||
|
*
|
||||||
|
* Macro to translate a theme message code into a message.
|
||||||
|
-->
|
||||||
|
<#macro theme code>${springMacroRequestContext.getThemeMessage(code)}</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* themeText
|
||||||
|
*
|
||||||
|
* Macro to translate a theme message code into a message,
|
||||||
|
* using the given default text if no message found.
|
||||||
|
-->
|
||||||
|
<#macro themeText code, text>${springMacroRequestContext.getThemeMessage(code, text)}</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* themeArgs
|
||||||
|
*
|
||||||
|
* Macro to translate a theme message code with arguments into a message.
|
||||||
|
-->
|
||||||
|
<#macro themeArgs code, args>${springMacroRequestContext.getThemeMessage(code, args)}</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* themeArgsText
|
||||||
|
*
|
||||||
|
* Macro to translate a theme message code with arguments into a message,
|
||||||
|
* using the given default text if no message found.
|
||||||
|
-->
|
||||||
|
<#macro themeArgsText code, args, text>${springMacroRequestContext.getThemeMessage(code, args, text)}</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* url
|
||||||
|
*
|
||||||
|
* Takes a relative URL and makes it absolute from the server root by
|
||||||
|
* adding the context root for the web application.
|
||||||
|
-->
|
||||||
|
<#macro url relativeUrl extra...><#if extra?? && extra?size!=0>${springMacroRequestContext.getContextUrl(relativeUrl,extra)}<#else>${springMacroRequestContext.getContextUrl(relativeUrl)}</#if></#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* bind
|
||||||
|
*
|
||||||
|
* Exposes a BindStatus object for the given bind path, which can be
|
||||||
|
* a bean (e.g. "person") to get global errors, or a bean property
|
||||||
|
* (e.g. "person.name") to get field errors. Can be called multiple times
|
||||||
|
* within a form to bind to multiple command objects and/or field names.
|
||||||
|
*
|
||||||
|
* This macro will participate in the default HTML escape setting for the given
|
||||||
|
* RequestContext. This can be customized by calling "setDefaultHtmlEscape"
|
||||||
|
* on the "springMacroRequestContext" context variable, or via the
|
||||||
|
* "defaultHtmlEscape" context-param in web.xml (same as for the JSP bind tag).
|
||||||
|
* Also regards a "htmlEscape" variable in the namespace of this library.
|
||||||
|
*
|
||||||
|
* Producing no output, the following context variable will be available
|
||||||
|
* each time this macro is referenced (assuming you import this library in
|
||||||
|
* your templates with the namespace 'spring'):
|
||||||
|
*
|
||||||
|
* spring.status : a BindStatus instance holding the command object name,
|
||||||
|
* expression, value, and error messages and codes for the path supplied
|
||||||
|
*
|
||||||
|
* @param path : the path (string value) of the value required to bind to.
|
||||||
|
* Spring defaults to a command name of "command" but this can be overridden
|
||||||
|
* by user config.
|
||||||
|
-->
|
||||||
|
<#macro bind path>
|
||||||
|
<#if htmlEscape?exists>
|
||||||
|
<#assign status = springMacroRequestContext.getBindStatus(path, htmlEscape)>
|
||||||
|
<#else>
|
||||||
|
<#assign status = springMacroRequestContext.getBindStatus(path)>
|
||||||
|
</#if>
|
||||||
|
<#-- assign a temporary value, forcing a string representation for any
|
||||||
|
kind of variable. This temp value is only used in this macro lib -->
|
||||||
|
<#if status.value?exists && status.value?is_boolean>
|
||||||
|
<#assign stringStatusValue=status.value?string>
|
||||||
|
<#else>
|
||||||
|
<#assign stringStatusValue=status.value?default("")>
|
||||||
|
</#if>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* bindEscaped
|
||||||
|
*
|
||||||
|
* Similar to spring:bind, but takes an explicit HTML escape flag rather
|
||||||
|
* than relying on the default HTML escape setting.
|
||||||
|
-->
|
||||||
|
<#macro bindEscaped path, htmlEscape>
|
||||||
|
<#assign status = springMacroRequestContext.getBindStatus(path, htmlEscape)>
|
||||||
|
<#-- assign a temporary value, forcing a string representation for any
|
||||||
|
kind of variable. This temp value is only used in this macro lib -->
|
||||||
|
<#if status.value?exists && status.value?is_boolean>
|
||||||
|
<#assign stringStatusValue=status.value?string>
|
||||||
|
<#else>
|
||||||
|
<#assign stringStatusValue=status.value?default("")>
|
||||||
|
</#if>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* formInput
|
||||||
|
*
|
||||||
|
* Display a form input field of type 'text' and bind it to an attribute
|
||||||
|
* of a command or bean.
|
||||||
|
*
|
||||||
|
* @param path the name of the field to bind to
|
||||||
|
* @param attributes any additional attributes for the element (such as class
|
||||||
|
* or CSS styles or size
|
||||||
|
-->
|
||||||
|
<#macro formInput path attributes="" fieldType="text">
|
||||||
|
<@bind path/>
|
||||||
|
<input type="${fieldType}" id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" value="<#if fieldType!="password">${stringStatusValue}</#if>" ${attributes}<@closeTag/>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* formPasswordInput
|
||||||
|
*
|
||||||
|
* Display a form input field of type 'password' and bind it to an attribute
|
||||||
|
* of a command or bean. No value will ever be displayed. This functionality
|
||||||
|
* can also be obtained by calling the formInput macro with a 'type' parameter
|
||||||
|
* of 'password'.
|
||||||
|
*
|
||||||
|
* @param path the name of the field to bind to
|
||||||
|
* @param attributes any additional attributes for the element (such as class
|
||||||
|
* or CSS styles or size
|
||||||
|
-->
|
||||||
|
<#macro formPasswordInput path attributes="">
|
||||||
|
<@formInput path, attributes, "password"/>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* formHiddenInput
|
||||||
|
*
|
||||||
|
* Generate a form input field of type 'hidden' and bind it to an attribute
|
||||||
|
* of a command or bean. This functionality can also be obtained by calling
|
||||||
|
* the formInput macro with a 'type' parameter of 'hidden'.
|
||||||
|
*
|
||||||
|
* @param path the name of the field to bind to
|
||||||
|
* @param attributes any additional attributes for the element (such as class
|
||||||
|
* or CSS styles or size
|
||||||
|
-->
|
||||||
|
<#macro formHiddenInput path attributes="">
|
||||||
|
<@formInput path, attributes, "hidden"/>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* formTextarea
|
||||||
|
*
|
||||||
|
* Display a text area and bind it to an attribute of a command or bean.
|
||||||
|
*
|
||||||
|
* @param path the name of the field to bind to
|
||||||
|
* @param attributes any additional attributes for the element (such as class
|
||||||
|
* or CSS styles or size
|
||||||
|
-->
|
||||||
|
<#macro formTextarea path attributes="">
|
||||||
|
<@bind path/>
|
||||||
|
<textarea id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" ${attributes}>
|
||||||
|
${stringStatusValue}</textarea>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* formSingleSelect
|
||||||
|
*
|
||||||
|
* Show a selectbox (dropdown) input element allowing a single value to be chosen
|
||||||
|
* from a list of options.
|
||||||
|
*
|
||||||
|
* @param path the name of the field to bind to
|
||||||
|
* @param options a map (value=label) of all the available options
|
||||||
|
* @param attributes any additional attributes for the element (such as class
|
||||||
|
* or CSS styles or size
|
||||||
|
-->
|
||||||
|
<#macro formSingleSelect path options attributes="">
|
||||||
|
<@bind path/>
|
||||||
|
<select id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" ${attributes}>
|
||||||
|
<#if options?is_hash>
|
||||||
|
<#list options?keys as value>
|
||||||
|
<option value="${value?html}"<@checkSelected value/>>${options[value]?html}</option>
|
||||||
|
</#list>
|
||||||
|
<#else>
|
||||||
|
<#list options as value>
|
||||||
|
<option value="${value?html}"<@checkSelected value/>>${value?html}</option>
|
||||||
|
</#list>
|
||||||
|
</#if>
|
||||||
|
</select>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* formMultiSelect
|
||||||
|
*
|
||||||
|
* Show a listbox of options allowing the user to make 0 or more choices from
|
||||||
|
* the list of options.
|
||||||
|
*
|
||||||
|
* @param path the name of the field to bind to
|
||||||
|
* @param options a map (value=label) of all the available options
|
||||||
|
* @param attributes any additional attributes for the element (such as class
|
||||||
|
* or CSS styles or size
|
||||||
|
-->
|
||||||
|
<#macro formMultiSelect path options attributes="">
|
||||||
|
<@bind path/>
|
||||||
|
<select multiple="multiple" id="${status.expression?replace('[','')?replace(']','')}" name="${status.expression}" ${attributes}>
|
||||||
|
<#list options?keys as value>
|
||||||
|
<#assign isSelected = contains(status.actualValue?default([""]), value)>
|
||||||
|
<option value="${value?html}"<#if isSelected> selected="selected"</#if>>${options[value]?html}</option>
|
||||||
|
</#list>
|
||||||
|
</select>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* formRadioButtons
|
||||||
|
*
|
||||||
|
* Show radio buttons.
|
||||||
|
*
|
||||||
|
* @param path the name of the field to bind to
|
||||||
|
* @param options a map (value=label) of all the available options
|
||||||
|
* @param separator the html tag or other character list that should be used to
|
||||||
|
* separate each option. Typically ' ' or '<br>'
|
||||||
|
* @param attributes any additional attributes for the element (such as class
|
||||||
|
* or CSS styles or size
|
||||||
|
-->
|
||||||
|
<#macro formRadioButtons path options separator attributes="">
|
||||||
|
<@bind path/>
|
||||||
|
<#list options?keys as value>
|
||||||
|
<#assign id="${status.expression?replace('[','')?replace(']','')}${value_index}">
|
||||||
|
<input type="radio" id="${id}" name="${status.expression}" value="${value?html}"<#if stringStatusValue == value> checked="checked"</#if> ${attributes}<@closeTag/>
|
||||||
|
<label for="${id}">${options[value]?html}</label>${separator}
|
||||||
|
</#list>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* formCheckboxes
|
||||||
|
*
|
||||||
|
* Show checkboxes.
|
||||||
|
*
|
||||||
|
* @param path the name of the field to bind to
|
||||||
|
* @param options a map (value=label) of all the available options
|
||||||
|
* @param separator the html tag or other character list that should be used to
|
||||||
|
* separate each option. Typically ' ' or '<br>'
|
||||||
|
* @param attributes any additional attributes for the element (such as class
|
||||||
|
* or CSS styles or size
|
||||||
|
-->
|
||||||
|
<#macro formCheckboxes path options separator attributes="">
|
||||||
|
<@bind path/>
|
||||||
|
<#list options?keys as value>
|
||||||
|
<#assign id="${status.expression?replace('[','')?replace(']','')}${value_index}">
|
||||||
|
<#assign isSelected = contains(status.actualValue?default([""]), value)>
|
||||||
|
<input type="checkbox" id="${id}" name="${status.expression}" value="${value?html}"<#if isSelected> checked="checked"</#if> ${attributes}<@closeTag/>
|
||||||
|
<label for="${id}">${options[value]?html}</label>${separator}
|
||||||
|
</#list>
|
||||||
|
<input type="hidden" name="_${status.expression}" value="on"/>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* formCheckbox
|
||||||
|
*
|
||||||
|
* Show a single checkbox.
|
||||||
|
*
|
||||||
|
* @param path the name of the field to bind to
|
||||||
|
* @param attributes any additional attributes for the element (such as class
|
||||||
|
* or CSS styles or size
|
||||||
|
-->
|
||||||
|
<#macro formCheckbox path attributes="">
|
||||||
|
<@bind path />
|
||||||
|
<#assign id="${status.expression?replace('[','')?replace(']','')}">
|
||||||
|
<#assign isSelected = status.value?? && status.value?string=="true">
|
||||||
|
<input type="hidden" name="_${status.expression}" value="on"/>
|
||||||
|
<input type="checkbox" id="${id}" name="${status.expression}"<#if isSelected> checked="checked"</#if> ${attributes}/>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* showErrors
|
||||||
|
*
|
||||||
|
* Show validation errors for the currently bound field, with
|
||||||
|
* optional style attributes.
|
||||||
|
*
|
||||||
|
* @param separator the html tag or other character list that should be used to
|
||||||
|
* separate each option. Typically '<br>'.
|
||||||
|
* @param classOrStyle either the name of a CSS class element (which is defined in
|
||||||
|
* the template or an external CSS file) or an inline style. If the value passed in here
|
||||||
|
* contains a colon (:) then a 'style=' attribute will be used, else a 'class=' attribute
|
||||||
|
* will be used.
|
||||||
|
-->
|
||||||
|
<#macro showErrors separator classOrStyle="">
|
||||||
|
<#list status.errorMessages as error>
|
||||||
|
<#if classOrStyle == "">
|
||||||
|
<b>${error}</b>
|
||||||
|
<#else>
|
||||||
|
<#if classOrStyle?index_of(":") == -1><#assign attr="class"><#else><#assign attr="style"></#if>
|
||||||
|
<span ${attr}="${classOrStyle}">${error}</span>
|
||||||
|
</#if>
|
||||||
|
<#if error_has_next>${separator}</#if>
|
||||||
|
</#list>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* checkSelected
|
||||||
|
*
|
||||||
|
* Check a value in a list to see if it is the currently selected value.
|
||||||
|
* If so, add the 'selected="selected"' text to the output.
|
||||||
|
* Handles values of numeric and string types.
|
||||||
|
* This function is used internally but can be accessed by user code if required.
|
||||||
|
*
|
||||||
|
* @param value the current value in a list iteration
|
||||||
|
-->
|
||||||
|
<#macro checkSelected value>
|
||||||
|
<#if stringStatusValue?is_number && stringStatusValue == value?number>selected="selected"</#if>
|
||||||
|
<#if stringStatusValue?is_string && stringStatusValue == value>selected="selected"</#if>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* contains
|
||||||
|
*
|
||||||
|
* Macro to return true if the list contains the scalar, false if not.
|
||||||
|
* Surprisingly not a FreeMarker builtin.
|
||||||
|
* This function is used internally but can be accessed by user code if required.
|
||||||
|
*
|
||||||
|
* @param list the list to search for the item
|
||||||
|
* @param item the item to search for in the list
|
||||||
|
* @return true if item is found in the list, false otherwise
|
||||||
|
-->
|
||||||
|
<#function contains list item>
|
||||||
|
<#list list as nextInList>
|
||||||
|
<#if nextInList == item><#return true></#if>
|
||||||
|
</#list>
|
||||||
|
<#return false>
|
||||||
|
</#function>
|
||||||
|
|
||||||
|
<#--
|
||||||
|
* closeTag
|
||||||
|
*
|
||||||
|
* Simple macro to close an HTML tag that has no body with '>' or '/>',
|
||||||
|
* depending on the value of a 'xhtmlCompliant' variable in the namespace
|
||||||
|
* of this library.
|
||||||
|
-->
|
||||||
|
<#macro closeTag>
|
||||||
|
<#if xhtmlCompliant?exists && xhtmlCompliant>/><#else>></#if>
|
||||||
|
</#macro>
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
13
cim_for_netty/cim-boot-server/src/main/resources/static/bootstrap-3.3.7-dist/js/npm.js
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
|
||||||
|
require('../../js/transition.js');
|
||||||
|
require('../../js/alert.js');
|
||||||
|
require('../../js/button.js');
|
||||||
|
require('../../js/carousel.js');
|
||||||
|
require('../../js/collapse.js');
|
||||||
|
require('../../js/dropdown.js');
|
||||||
|
require('../../js/modal.js');
|
||||||
|
require('../../js/tooltip.js');
|
||||||
|
require('../../js/popover.js');
|
||||||
|
require('../../js/scrollspy.js');
|
||||||
|
require('../../js/tab.js');
|
||||||
|
require('../../js/affix.js');
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 5.5 KiB |
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1516610432242" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="36739" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M160 12.8h665.6c19.2 0 38.4 19.2 38.4 38.4v921.6c0 19.2-19.2 38.4-38.4 38.4H160c-19.2 0-38.4-19.2-38.4-38.4V51.2c0-25.6 19.2-38.4 38.4-38.4zM377.6 448c-6.4 12.8-19.2 12.8-32 6.4-6.4-6.4-6.4-25.6 0-32 19.2-25.6 38.4-44.8 64-57.6 25.6-12.8 51.2-19.2 83.2-19.2s57.6 6.4 83.2 19.2c25.6 12.8 51.2 32 64 57.6 12.8 6.4 12.8 25.6 0 32-12.8 6.4-25.6 6.4-32-6.4-12.8-19.2-32-32-51.2-44.8-19.2-12.8-38.4-12.8-64-12.8s-44.8 6.4-64 12.8c-19.2 12.8-32 25.6-51.2 44.8z m115.2 0c25.6 0 44.8 6.4 57.6 25.6 12.8 12.8 25.6 38.4 25.6 57.6 0 25.6-6.4 44.8-25.6 57.6-12.8 19.2-32 25.6-57.6 25.6-19.2 0-44.8-6.4-57.6-19.2-19.2-19.2-25.6-38.4-25.6-64s6.4-44.8 25.6-57.6c12.8-19.2 38.4-25.6 57.6-25.6z m32 57.6c-6.4-6.4-19.2-12.8-32-12.8s-19.2 6.4-25.6 12.8c-6.4 6.4-12.8 12.8-12.8 25.6s6.4 19.2 12.8 25.6c6.4 6.4 19.2 12.8 25.6 12.8 12.8 0 19.2-6.4 25.6-12.8 6.4-6.4 12.8-19.2 12.8-25.6 0-12.8 0-19.2-6.4-25.6z m-217.6-128c-6.4 6.4-25.6 12.8-32 0-12.8-6.4-12.8-19.2 0-32 25.6-32 64-57.6 102.4-76.8 76.8-38.4 166.4-38.4 243.2 0 38.4 19.2 70.4 44.8 102.4 76.8 6.4 6.4 6.4 25.6 0 32-19.2 12.8-32 6.4-38.4 0-25.6-25.6-51.2-51.2-83.2-64-32-12.8-64-25.6-102.4-25.6-38.4 0-70.4 6.4-102.4 25.6-38.4 12.8-64 38.4-89.6 64z m-51.2-256c1587.2 0-544 0 0 0z m108.8 748.8c-12.8 0-25.6-12.8-25.6-25.6s12.8-25.6 25.6-25.6h256c12.8 0 25.6 12.8 25.6 25.6s-12.8 25.6-25.6 25.6h-256z m422.4-780.8H198.4v844.8h588.8V89.6z" fill="#707070" p-id="36740"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 511.998 511.998" style="enable-background:new 0 0 511.998 511.998;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<path style="fill:#9BF57D;" d="M405.976,166.184c-3.258-0.555-6.474-0.644-9.639-0.467c-1.216,2.928-2.196,5.992-2.752,9.25
|
||||||
|
c-3.985,23.389,11.745,45.581,35.135,49.566c3.258,0.555,6.474,0.644,9.639,0.467c1.216-2.928,2.196-5.992,2.752-9.25
|
||||||
|
C445.096,192.359,429.366,170.169,405.976,166.184z"/>
|
||||||
|
<path style="fill:#9BF57D;" d="M499.501,182.118c3.258,0.555,6.322,1.536,9.25,2.752c0.178,3.166,0.088,6.381-0.467,9.639
|
||||||
|
c-3.985,23.389-26.176,39.12-49.566,35.135c-3.258-0.555-6.322-1.536-9.25-2.752c-0.178-3.166-0.088-6.381,0.467-9.639
|
||||||
|
C453.919,193.863,476.111,178.133,499.501,182.118z"/>
|
||||||
|
<path style="fill:#9BF57D;" d="M433.953,95.384c-2.479-0.827-4.981-1.283-7.476-1.526c-1.302,2.141-2.438,4.417-3.265,6.896
|
||||||
|
c-5.932,17.794,3.684,37.027,21.478,42.958c2.479,0.827,4.981,1.283,7.476,1.526c1.302-2.141,2.438-4.417,3.265-6.896
|
||||||
|
C461.364,120.549,451.747,101.316,433.953,95.384z"/>
|
||||||
|
<path style="fill:#9BF57D;" d="M505.102,119.101c2.479,0.827,4.755,1.962,6.896,3.265c-0.243,2.495-0.699,4.996-1.526,7.476
|
||||||
|
c-5.932,17.794-25.164,27.41-42.958,21.478c-2.479-0.827-4.755-1.962-6.896-3.265c0.243-2.495,0.699-4.996,1.526-7.476
|
||||||
|
C468.076,122.786,487.308,113.171,505.102,119.101z"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:#0096DC;" d="M396.392,258.169l0.062,38.105l38.306-12.963c4.92-1.665,5.052-8.575,0.199-10.427L396.392,258.169z"
|
||||||
|
/>
|
||||||
|
<path style="fill:#00C3FF;" d="M283.023,260.473c-23.844-61.418-74.093-137.198-169.947-168.553
|
||||||
|
c-5.247-1.716-10.733,1.759-11.692,7.195c-3.26,18.481-6.253,59.555,19.791,99.452l139.474,90.993L283.023,260.473z"/>
|
||||||
|
<path style="fill:#96E6FF;" d="M340.049,215.751c-19.154,0.569-36.128,9.747-47.223,23.792
|
||||||
|
c-9.504,12.03-20.355,22.916-33.075,31.046c-49.819,31.838-162.75,45.742-162.75,45.742c-26.048,3.453-54.285,5.819-84.328,6.553
|
||||||
|
c-9.327,0.228-15.347,9.996-11.488,18.49l30.47,67.032c3.192,7.024,11.725,9.791,18.414,5.95
|
||||||
|
c12.231-7.026,32.602-18.527,57.087-31.575c24.324,34.277,64.259,56.698,109.488,56.698c58.258,0,107.726-37.158,126.289-89.031
|
||||||
|
c2.057-5.748,7.147-9.721,13.097-11.087c28.525-6.548,49.625-32.518,48.525-63.28C403.345,242.237,373.896,214.744,340.049,215.751z
|
||||||
|
"/>
|
||||||
|
<path style="fill:#00D2FF;" d="M297.195,323.124c0,0-86.559-155.806-259.677-143.946c-4.858,0.332-8.194,5.039-7.081,9.779
|
||||||
|
c4.725,20.108,20.887,68.431,69.854,89.416l-18.651,12.434c-4.345,2.896-4.719,9.091-0.808,12.553
|
||||||
|
C104.834,324.596,185.537,382.675,297.195,323.124z"/>
|
||||||
|
<path style="fill:#0096DC;" d="M359.846,278.374c-4.947,0-8.95-4.007-8.95-8.95v-8.95c0-4.943,4.003-8.95,8.95-8.95
|
||||||
|
c4.947,0,8.95,4.007,8.95,8.95v8.95C368.796,274.366,364.793,278.374,359.846,278.374z"/>
|
||||||
|
<path style="fill:#7DC86E;" d="M440.397,314.174c-4.947,0-8.95-4.007-8.95-8.95c0-112.384,19.815-185.519,36.438-227.08
|
||||||
|
c1.836-4.589,7.071-6.809,11.633-4.986c4.589,1.84,6.827,7.045,4.991,11.637c-16.039,40.097-35.163,110.893-35.163,220.428
|
||||||
|
C449.347,310.167,445.344,314.174,440.397,314.174z"/>
|
||||||
|
<path style="fill:#00D2FF;" d="M13.047,367.47l18.607,40.935c3.192,7.024,11.724,9.791,18.414,5.948
|
||||||
|
c12.231-7.025,32.602-18.527,57.087-31.575l-10.313-15.157c-6.05-8.893-16.824-13.317-27.379-11.243L13.047,367.47z"/>
|
||||||
|
<path style="fill:#00C3FF;" d="M219.524,294.776c-32.788,1.918-90.925-2.55-119.233-16.402l-18.651,12.434
|
||||||
|
c-4.296,2.864-4.789,9.022-0.926,12.447c11.369,10.079,35.692,28.595,70.124,38.778c33.677-9.059,58.442-25.305,72.769-36.747
|
||||||
|
C228.116,301.685,225.284,294.439,219.524,294.776z"/>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,211 @@
|
|||||||
|
var CIM_URI="ws://127.0.0.1:23456";// 修改为服务器的真实IP
|
||||||
|
var CMD_HEARTBEAT_RESPONSE = new Uint8Array([67,82]);
|
||||||
|
var SDK_VERSION = "1.0.0";
|
||||||
|
var SDK_CHANNEL = "browser";
|
||||||
|
var APP_PACKAGE = "com.farsunset.cim";
|
||||||
|
const ACTION_999 = "999";//特殊的消息类型,代表被服务端强制下线
|
||||||
|
var DATA_HEADER_LENGTH = 3;
|
||||||
|
|
||||||
|
var C_H_RS = 0;
|
||||||
|
var S_H_RQ = 1;
|
||||||
|
var MESSAGE = 2;
|
||||||
|
var SENTBODY = 3;
|
||||||
|
var REPLYBODY = 4;
|
||||||
|
|
||||||
|
var socket;
|
||||||
|
var manualStop = false;
|
||||||
|
var CIMWebBridge = new Object();
|
||||||
|
CIMWebBridge.connection = function(){
|
||||||
|
manualStop = false;
|
||||||
|
window.localStorage.account = '';
|
||||||
|
socket = new WebSocket(CIM_URI);
|
||||||
|
socket.binaryType = 'arraybuffer';
|
||||||
|
socket.onopen = CIMWebBridge.innerOnConnectionSuccessed;
|
||||||
|
socket.onmessage = CIMWebBridge.innerOnMessageReceived;
|
||||||
|
socket.onclose = CIMWebBridge.innerOnConnectionClosed;
|
||||||
|
};
|
||||||
|
|
||||||
|
CIMWebBridge.bindAccount = function(account){
|
||||||
|
|
||||||
|
window.localStorage.account = account;
|
||||||
|
|
||||||
|
var deviceId = window.localStorage.deviceIddeviceId;
|
||||||
|
if(deviceId == '' || deviceId == undefined){
|
||||||
|
deviceId = generateUUID();
|
||||||
|
window.localStorage.deviceId = deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
var browser = getBrowser();
|
||||||
|
var body = new proto.com.farsunset.cim.sdk.web.model.SentBody();
|
||||||
|
body.setKey("client_bind");
|
||||||
|
body.getDataMap().set("account",account);
|
||||||
|
body.getDataMap().set("channel",SDK_CHANNEL);
|
||||||
|
body.getDataMap().set("version",SDK_VERSION);
|
||||||
|
body.getDataMap().set("osVersion", browser.version);
|
||||||
|
body.getDataMap().set("packageName", APP_PACKAGE);
|
||||||
|
body.getDataMap().set("deviceId", deviceId);
|
||||||
|
body.getDataMap().set("device", browser.name);
|
||||||
|
CIMWebBridge.sendRequest(body);
|
||||||
|
};
|
||||||
|
|
||||||
|
CIMWebBridge.stop = function(){
|
||||||
|
manualStop = true;
|
||||||
|
socket.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
CIMWebBridge.resume = function(){
|
||||||
|
manualStop = false;
|
||||||
|
CIMWebBridge.connection();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
CIMWebBridge.innerOnConnectionSuccessed = function(){
|
||||||
|
var account = window.localStorage.account;
|
||||||
|
if(account == '' || account == undefined){
|
||||||
|
onConnectionSuccessed();
|
||||||
|
}else{
|
||||||
|
CIMWebBridge.bindAccount(account);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CIMWebBridge.innerOnMessageReceived = function(e){
|
||||||
|
var data = new Uint8Array(e.data);
|
||||||
|
|
||||||
|
var type = data[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收到服务端发来的心跳请求,立即回复响应,否则服务端会在10秒后断开连接
|
||||||
|
*/
|
||||||
|
if(type == S_H_RQ){
|
||||||
|
CIMWebBridge.sendHeartbeatResponse();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == MESSAGE){
|
||||||
|
var length = getContentLength(data[1],data[2]);
|
||||||
|
var data = proto.com.farsunset.cim.sdk.web.model.Message.deserializeBinary(data.subarray(DATA_HEADER_LENGTH,DATA_HEADER_LENGTH+length));
|
||||||
|
onInterceptMessageReceived(data.toObject(false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == REPLYBODY){
|
||||||
|
var length = getContentLength(data[1],data[2]);
|
||||||
|
var data = proto.com.farsunset.cim.sdk.web.model.ReplyBody.deserializeBinary(data.subarray(DATA_HEADER_LENGTH,DATA_HEADER_LENGTH+length));
|
||||||
|
/**
|
||||||
|
* 将proto对象转换成json对象,去除无用信息
|
||||||
|
*/
|
||||||
|
var reply = {};
|
||||||
|
reply.code = data.getCode();
|
||||||
|
reply.key = data.getKey();
|
||||||
|
reply.message = data.getMessage();
|
||||||
|
reply.timestamp = data.getTimestamp();
|
||||||
|
reply.data = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注意,遍历map这里的参数 value在前key在后
|
||||||
|
*/
|
||||||
|
data.getDataMap().forEach(function (v,k){
|
||||||
|
reply.data[k] = v;
|
||||||
|
});
|
||||||
|
|
||||||
|
onReplyReceived(reply);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CIMWebBridge.innerOnConnectionClosed = function(e){
|
||||||
|
if(!manualStop){
|
||||||
|
var time = Math.floor(Math.random()*(30-15+1)+15);
|
||||||
|
setTimeout(function(){
|
||||||
|
CIMWebBridge.connection();
|
||||||
|
},time);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CIMWebBridge.sendRequest = function(body){
|
||||||
|
|
||||||
|
var data = body.serializeBinary();
|
||||||
|
var header = buildHeader(SENTBODY,data.length);
|
||||||
|
var protubuf = new Uint8Array(data.length + header.length);
|
||||||
|
protubuf.set(header,0);
|
||||||
|
protubuf.set(data,header.length);
|
||||||
|
socket.send(protubuf);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
CIMWebBridge.sendHeartbeatResponse = function(){
|
||||||
|
var data = CMD_HEARTBEAT_RESPONSE;
|
||||||
|
var header = buildHeader(C_H_RS,data.length);
|
||||||
|
var protubuf = new Uint8Array(data.length + header.length);
|
||||||
|
protubuf.set(header,0);
|
||||||
|
protubuf.set(data,header.length);
|
||||||
|
socket.send(protubuf);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getContentLength( lv, hv) {
|
||||||
|
var l = (lv & 0xff);
|
||||||
|
var h = (hv & 0xff);
|
||||||
|
return (l | (h <<= 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHeader(type,length){
|
||||||
|
var header = new Uint8Array(DATA_HEADER_LENGTH);
|
||||||
|
header[0] = type;
|
||||||
|
header[1] = (length & 0xff);
|
||||||
|
header[2] = ((length >> 8) & 0xff);
|
||||||
|
return header;
|
||||||
|
|
||||||
|
}
|
||||||
|
function onInterceptMessageReceived(message){
|
||||||
|
//被强制下线之后,不再继续连接服务端
|
||||||
|
if(message.action == ACTION_999){
|
||||||
|
manualStop = true;
|
||||||
|
}
|
||||||
|
//收到消息后,将消息发送给页面
|
||||||
|
if(onMessageReceived instanceof Function){
|
||||||
|
onMessageReceived(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBrowser() {
|
||||||
|
var explorer = window.navigator.userAgent.toLowerCase() ;
|
||||||
|
// ie
|
||||||
|
if (explorer.indexOf("msie") >= 0) {
|
||||||
|
var ver=explorer.match(/msie ([\d.]+)/)[1];
|
||||||
|
return {name:"IE",version:ver};
|
||||||
|
}
|
||||||
|
// firefox
|
||||||
|
else if (explorer.indexOf("firefox") >= 0) {
|
||||||
|
var ver=explorer.match(/firefox\/([\d.]+)/)[1];
|
||||||
|
return {name:"Firefox",version:ver};
|
||||||
|
}
|
||||||
|
// Chrome
|
||||||
|
else if(explorer.indexOf("chrome") >= 0){
|
||||||
|
var ver=explorer.match(/chrome\/([\d.]+)/)[1];
|
||||||
|
return {name:"Chrome",version:ver};
|
||||||
|
}
|
||||||
|
// Opera
|
||||||
|
else if(explorer.indexOf("opera") >= 0){
|
||||||
|
var ver=explorer.match(/opera.([\d.]+)/)[1];
|
||||||
|
return {name:"Opera",version:ver};
|
||||||
|
}
|
||||||
|
// Safari
|
||||||
|
else if(explorer.indexOf("Safari") >= 0){
|
||||||
|
var ver=explorer.match(/version\/([\d.]+)/)[1];
|
||||||
|
return {name:"Safari",version:ver};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {name:"Other",version:"1.0.0"};
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateUUID() {
|
||||||
|
var d = new Date().getTime();
|
||||||
|
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
var r = (d + Math.random()*16)%16 | 0;
|
||||||
|
d = Math.floor(d/16);
|
||||||
|
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
|
||||||
|
});
|
||||||
|
return uuid.replace(/-/g,'');
|
||||||
|
}
|
||||||
|
|
2
cim_for_netty/cim-boot-server/src/main/resources/static/js/jquery-3.3.1.min.js
vendored
Normal file
13
cim_for_netty/cim-boot-server/src/main/resources/static/js/jquery-ui.min.js
vendored
Normal file
BIN
cim_for_netty/cim-client-android/.idea/caches/build_file_checksums.ser
generated
Normal file
29
cim_for_netty/cim-client-android/.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<Objective-C-extensions>
|
||||||
|
<file>
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||||
|
</file>
|
||||||
|
<class>
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||||
|
</class>
|
||||||
|
<extensions>
|
||||||
|
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
||||||
|
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||||
|
</extensions>
|
||||||
|
</Objective-C-extensions>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
22
cim_for_netty/cim-client-android/.idea/compiler.xml
generated
@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<resourceExtensions />
|
|
||||||
<wildcardResourcePatterns>
|
|
||||||
<entry name="!?*.java" />
|
|
||||||
<entry name="!?*.form" />
|
|
||||||
<entry name="!?*.class" />
|
|
||||||
<entry name="!?*.groovy" />
|
|
||||||
<entry name="!?*.scala" />
|
|
||||||
<entry name="!?*.flex" />
|
|
||||||
<entry name="!?*.kt" />
|
|
||||||
<entry name="!?*.clj" />
|
|
||||||
<entry name="!?*.aj" />
|
|
||||||
</wildcardResourcePatterns>
|
|
||||||
<annotationProcessing>
|
|
||||||
<profile default="true" name="Default" enabled="false">
|
|
||||||
<processorPath useClasspath="true" />
|
|
||||||
</profile>
|
|
||||||
</annotationProcessing>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,3 +0,0 @@
|
|||||||
<component name="CopyrightManager">
|
|
||||||
<settings default="" />
|
|
||||||
</component>
|
|
@ -5,7 +5,7 @@
|
|||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="distributionType" value="LOCAL" />
|
<option name="distributionType" value="LOCAL" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleHome" value="D:\devtools\dev\gradle-3.4" />
|
<option name="gradleHome" value="C:/Program Files/gradle-4.5" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
22
cim_for_netty/cim-client-android/.idea/misc.xml
generated
@ -1,18 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="EntryPointsManager">
|
|
||||||
<entry_points version="2.0" />
|
|
||||||
</component>
|
|
||||||
<component name="NullableNotNullManager">
|
<component name="NullableNotNullManager">
|
||||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
||||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
||||||
<option name="myNullables">
|
<option name="myNullables">
|
||||||
<value>
|
<value>
|
||||||
<list size="4">
|
<list size="5">
|
||||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||||
</list>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
@ -27,17 +25,7 @@
|
|||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<OptionsSetting value="true" id="Add" />
|
|
||||||
<OptionsSetting value="true" id="Remove" />
|
|
||||||
<OptionsSetting value="true" id="Checkout" />
|
|
||||||
<OptionsSetting value="true" id="Update" />
|
|
||||||
<OptionsSetting value="true" id="Status" />
|
|
||||||
<OptionsSetting value="true" id="Edit" />
|
|
||||||
<ConfirmationsSetting value="0" id="Add" />
|
|
||||||
<ConfirmationsSetting value="0" id="Remove" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
@ -24,7 +24,7 @@ package com.farsunset.ichat.example.app;
|
|||||||
public interface Constant {
|
public interface Constant {
|
||||||
|
|
||||||
//服务端IP地址
|
//服务端IP地址
|
||||||
public static final String CIM_SERVER_HOST = "172.168.11.18";
|
public static final String CIM_SERVER_HOST = "192.168.1.103";
|
||||||
|
|
||||||
|
|
||||||
//注意,这里的端口不是tomcat的端口,CIM端口在服务端spring-cim.xml中配置的,没改动就使用默认的23456
|
//注意,这里的端口不是tomcat的端口,CIM端口在服务端spring-cim.xml中配置的,没改动就使用默认的23456
|
||||||
|
BIN
cim_for_netty/cim-java-sdk/libs/slf4j-api-1.7.25.jar
Normal file
@ -1,26 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry kind="src" path="src"/>
|
<classpathentry kind="src" path="src"/>
|
||||||
<classpathentry kind="lib" path="libs/log4j-1.2.17.jar"/>
|
|
||||||
<classpathentry kind="lib" path="libs/protobuf-java-3.2.0.jar"/>
|
<classpathentry kind="lib" path="libs/protobuf-java-3.2.0.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/netty-buffer-4.1.9.Final.jar"/>
|
<classpathentry kind="lib" path="libs/slf4j-api-1.7.25.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/netty-codec-4.1.9.Final.jar"/>
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
|
||||||
<classpathentry kind="lib" path="libs/netty-common-4.1.9.Final.jar"/>
|
|
||||||
<classpathentry kind="lib" path="libs/netty-handler-4.1.9.Final.jar"/>
|
|
||||||
<classpathentry kind="lib" path="libs/netty-transport-4.1.9.Final.jar"/>
|
|
||||||
<classpathentry kind="lib" path="libs/fastjson-1.2.39.jar"/>
|
|
||||||
<classpathentry kind="lib" path="libs/netty-codec-http-4.1.16.Final-sources.jar"/>
|
|
||||||
<classpathentry kind="lib" path="libs/netty-codec-http-4.1.16.Final.jar" sourcepath="libs/netty-codec-http-4.1.16.Final-sources.jar"/>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v7.0">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="owner.project.facets" value="jst.utility"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.8.0_131">
|
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="owner.project.facets" value="java"/>
|
<attribute name="owner.project.facets" value="java"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
|
<classpathentry kind="lib" path="libs/netty-buffer-4.1.28.Final.jar"/>
|
||||||
|
<classpathentry kind="lib" path="libs/netty-codec-4.1.28.Final.jar"/>
|
||||||
|
<classpathentry kind="lib" path="libs/netty-codec-http-4.1.28.Final.jar" sourcepath="/cim-boot-server"/>
|
||||||
|
<classpathentry kind="lib" path="libs/netty-common-4.1.28.Final.jar"/>
|
||||||
|
<classpathentry kind="lib" path="libs/netty-handler-4.1.28.Final.jar"/>
|
||||||
|
<classpathentry kind="lib" path="libs/netty-transport-4.1.28.Final.jar"/>
|
||||||
<classpathentry kind="output" path="bin"/>
|
<classpathentry kind="output" path="bin"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
@ -22,8 +22,6 @@
|
|||||||
</buildCommand>
|
</buildCommand>
|
||||||
</buildSpec>
|
</buildSpec>
|
||||||
<natures>
|
<natures>
|
||||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
|
||||||
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||||
</natures>
|
</natures>
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
|
|
||||||
<wb-module deploy-name="cim-server-sdk">
|
|
||||||
<wb-resource deploy-path="/" source-path="/src"/>
|
|
||||||
</wb-module>
|
|
||||||
</project-modules>
|
|
@ -1,8 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<faceted-project>
|
<faceted-project>
|
||||||
<runtime name="Apache Tomcat v7.0"/>
|
|
||||||
<fixed facet="java"/>
|
<fixed facet="java"/>
|
||||||
<fixed facet="jst.utility"/>
|
|
||||||
<installed facet="java" version="1.6"/>
|
<installed facet="java" version="1.6"/>
|
||||||
<installed facet="jst.utility" version="1.0"/>
|
|
||||||
</faceted-project>
|
</faceted-project>
|
||||||
|
BIN
cim_for_netty/cim-server-sdk/libs/netty-buffer-4.1.28.Final.jar
Normal file
BIN
cim_for_netty/cim-server-sdk/libs/netty-codec-4.1.28.Final.jar
Normal file
BIN
cim_for_netty/cim-server-sdk/libs/netty-common-4.1.28.Final.jar
Normal file
BIN
cim_for_netty/cim-server-sdk/libs/netty-handler-4.1.28.Final.jar
Normal file
BIN
cim_for_netty/cim-server-sdk/libs/slf4j-api-1.7.25.jar
Normal file
@ -22,13 +22,20 @@
|
|||||||
package com.farsunset.cim.sdk.server.filter;
|
package com.farsunset.cim.sdk.server.filter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
|
||||||
import com.farsunset.cim.sdk.server.filter.decoder.AppMessageDecoder;
|
import com.farsunset.cim.sdk.server.filter.decoder.AppMessageDecoder;
|
||||||
import com.farsunset.cim.sdk.server.filter.decoder.WebMessageDecoder;
|
import com.farsunset.cim.sdk.server.filter.decoder.WebMessageDecoder;
|
||||||
|
import com.farsunset.cim.sdk.server.handler.CIMNioSocketAcceptor;
|
||||||
|
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||||
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务端接收消息路由解码,通过消息类型分发到不同的真正解码器
|
* 服务端接收消息路由解码,通过消息类型分发到不同的真正解码器
|
||||||
@ -38,6 +45,11 @@ public class ServerMessageDecoder extends ByteToMessageDecoder {
|
|||||||
private WebMessageDecoder webMessageDecoder;
|
private WebMessageDecoder webMessageDecoder;
|
||||||
private AppMessageDecoder appMessageDecoder;
|
private AppMessageDecoder appMessageDecoder;
|
||||||
|
|
||||||
|
public static final Pattern SEC_KEY_PATTERN = Pattern.compile("^(Sec-WebSocket-Key:).+",
|
||||||
|
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
|
||||||
|
public static final Pattern UPGRADE_PATTERN = Pattern.compile("^(Upgrade:).+",
|
||||||
|
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
|
||||||
|
|
||||||
public ServerMessageDecoder() {
|
public ServerMessageDecoder() {
|
||||||
webMessageDecoder = new WebMessageDecoder();
|
webMessageDecoder = new WebMessageDecoder();
|
||||||
appMessageDecoder = new AppMessageDecoder();
|
appMessageDecoder = new AppMessageDecoder();
|
||||||
@ -46,19 +58,81 @@ public class ServerMessageDecoder extends ByteToMessageDecoder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext arg0, ByteBuf buffer, List<Object> queue) throws Exception {
|
protected void decode(ChannelHandlerContext arg0, ByteBuf buffer, List<Object> queue) throws Exception {
|
||||||
buffer.markReaderIndex();
|
|
||||||
byte conetnType = buffer.readByte();
|
Object protocol = arg0.channel().attr(AttributeKey.valueOf(CIMSession.PROTOCOL)).get();
|
||||||
buffer.resetReaderIndex();
|
|
||||||
|
if (Objects.equals(CIMSession.WEBSOCKET, protocol)) {
|
||||||
|
webMessageDecoder.decode(arg0, buffer, queue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Objects.equals(CIMSession.NATIVEAPP, protocol)) {
|
||||||
|
appMessageDecoder.decode(arg0, buffer, queue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean handshake = tryWebsocketHandleHandshake(arg0, buffer, queue);
|
||||||
|
if (!handshake) {
|
||||||
|
appMessageDecoder.decode(arg0, buffer, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryWebsocketHandleHandshake(ChannelHandlerContext arg0, ByteBuf iobuffer, List<Object> queue) {
|
||||||
|
|
||||||
|
iobuffer.markReaderIndex();
|
||||||
|
|
||||||
|
byte[] data = new byte[iobuffer.readableBytes()];
|
||||||
|
iobuffer.readBytes(data);
|
||||||
|
|
||||||
|
String request = new String(data);
|
||||||
|
String secKey = getSecWebSocketKey(request);
|
||||||
|
boolean handShake = secKey != null && Objects.equals(getUpgradeProtocol(request), CIMSession.WEBSOCKET);
|
||||||
|
if (handShake) {
|
||||||
|
/**
|
||||||
|
* 重要,握手响应之后,删除标志HANDSHAKE_FRAME,并标记当前session的协议为websocket
|
||||||
|
*/
|
||||||
|
arg0.channel().attr(AttributeKey.valueOf(CIMSession.PROTOCOL)).set(CIMSession.WEBSOCKET);
|
||||||
|
|
||||||
|
SentBody body = new SentBody();
|
||||||
|
body.setKey(CIMNioSocketAcceptor.WEBSOCKET_HANDLER_KEY);
|
||||||
|
body.setTimestamp(System.currentTimeMillis());
|
||||||
|
body.put("key", secKey);
|
||||||
|
queue.add(body);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
iobuffer.resetReaderIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
return handShake;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 原生SDK只会发送2种类型消息 1个心跳类型 另一个是sendbody,报文的第一个字节为消息类型,否则才是websocket的消息
|
* 通过正则获取websocket握手消息中的Sec-WebSocket-Key
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @return
|
||||||
*/
|
*/
|
||||||
if (conetnType == CIMConstant.ProtobufType.C_H_RS || conetnType == CIMConstant.ProtobufType.SENTBODY) {
|
private String getSecWebSocketKey(String message) {
|
||||||
appMessageDecoder.decode(arg0, buffer, queue);
|
Matcher m = SEC_KEY_PATTERN.matcher(message);
|
||||||
} else {
|
if (m.find()) {
|
||||||
webMessageDecoder.decode(arg0, buffer, queue);
|
return m.group().split(":")[1].trim();
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过正则获取websocket握手消息中的 Upgrade 值 预期为websocket
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String getUpgradeProtocol(String message) {
|
||||||
|
Matcher m = UPGRADE_PATTERN.matcher(message);
|
||||||
|
if (m.find()) {
|
||||||
|
return m.group().split(":")[1].trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.farsunset.cim.sdk.server.filter;
|
package com.farsunset.cim.sdk.server.filter;
|
||||||
|
|
||||||
import org.apache.log4j.Logger;
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||||
import com.farsunset.cim.sdk.server.model.WebsocketResponse;
|
import com.farsunset.cim.sdk.server.model.HandshakerResponse;
|
||||||
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
import com.farsunset.cim.sdk.server.model.feature.EncodeFormatable;
|
||||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
|
|
||||||
@ -38,62 +38,51 @@ import io.netty.util.AttributeKey;
|
|||||||
*/
|
*/
|
||||||
public class ServerMessageEncoder extends MessageToByteEncoder<Object> {
|
public class ServerMessageEncoder extends MessageToByteEncoder<Object> {
|
||||||
|
|
||||||
protected final Logger logger = Logger.getLogger(ServerMessageEncoder.class.getSimpleName());
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, Object object, ByteBuf out) throws Exception {
|
protected void encode(final ChannelHandlerContext ctx, final Object object, ByteBuf out) throws Exception {
|
||||||
|
Object protocol = ctx.channel().attr(AttributeKey.valueOf(CIMSession.PROTOCOL)).get();
|
||||||
Object channel = ctx.channel().attr(AttributeKey.valueOf("channel")).get();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* websocket的握手响应
|
* websocket的握手响应
|
||||||
*/
|
*/
|
||||||
if (CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof WebsocketResponse) {
|
if (Objects.equals(CIMSession.WEBSOCKET, protocol) && object instanceof HandshakerResponse) {
|
||||||
WebsocketResponse data = (WebsocketResponse) object;
|
out.writeBytes(object.toString().getBytes());
|
||||||
byte[] byteArray = data.getBytes();
|
return;
|
||||||
out.writeBytes(byteArray);
|
|
||||||
logger.info(data.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* websocket的数据传输使用JSON编码数据格式,因为Protobuf还没有支持js
|
* websocket的业务数据
|
||||||
*/
|
*/
|
||||||
if (CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof EncodeFormatable) {
|
if (Objects.equals(CIMSession.WEBSOCKET, protocol) && object instanceof EncodeFormatable) {
|
||||||
EncodeFormatable data = (EncodeFormatable) object;
|
EncodeFormatable data = (EncodeFormatable) object;
|
||||||
byte[] byteArray = encodeDataFrame(data.getJSONBody());
|
byte[] body = data.getProtobufBody();
|
||||||
/**
|
byte[] header = createHeader(data.getDataType(), body.length);
|
||||||
* 由于websocket没有黏包和断包的问题,所以不必知道消息体的大小
|
|
||||||
*/
|
byte[] protobuf = new byte[body.length + CIMConstant.DATA_HEADER_LENGTH];
|
||||||
out.writeBytes(byteArray);
|
System.arraycopy(header, 0, protobuf, 0, header.length);
|
||||||
logger.info(data.toString());
|
System.arraycopy(body,0, protobuf, header.length, body.length);
|
||||||
|
|
||||||
|
byte[] binaryFrame = encodeDataFrame(protobuf);
|
||||||
|
out.writeBytes(binaryFrame);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 非websocket的数据传输使用Protobuf编码数据格式
|
* 非websocket的数据传输使用Protobuf编码数据格式
|
||||||
*/
|
*/
|
||||||
if (!CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof EncodeFormatable) {
|
if (Objects.equals(CIMSession.NATIVEAPP, protocol) && object instanceof EncodeFormatable) {
|
||||||
|
|
||||||
EncodeFormatable data = (EncodeFormatable) object;
|
EncodeFormatable data = (EncodeFormatable) object;
|
||||||
byte[] byteArray = data.getProtobufBody();
|
byte[] body = data.getProtobufBody();
|
||||||
out.writeBytes(createHeader(data.getDataType(), byteArray.length));
|
byte[] header = createHeader(data.getDataType(), body.length);
|
||||||
out.writeBytes(byteArray);
|
|
||||||
logger.info(data.toString());
|
out.writeBytes(header);
|
||||||
}
|
out.writeBytes(body);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息体最大为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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,7 +109,7 @@ public class ServerMessageEncoder extends MessageToByteEncoder<Object> {
|
|||||||
|
|
||||||
// 开始计算ws-frame
|
// 开始计算ws-frame
|
||||||
// frame-fin + frame-rsv1 + frame-rsv2 + frame-rsv3 + frame-opcode
|
// frame-fin + frame-rsv1 + frame-rsv2 + frame-rsv3 + frame-opcode
|
||||||
result[0] = (byte) 0x81; // 129
|
result[0] = (byte) 0x82; // 0x82 二进制帧 0x80 文本帧
|
||||||
|
|
||||||
// frame-masked+frame-payload-length
|
// frame-masked+frame-payload-length
|
||||||
// 从第9个字节开始是 1111101=125,掩码是第3-第6个数据
|
// 从第9个字节开始是 1111101=125,掩码是第3-第6个数据
|
||||||
@ -143,4 +132,11 @@ public class ServerMessageEncoder extends MessageToByteEncoder<Object> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,26 +23,24 @@ package com.farsunset.cim.sdk.server.filter.decoder;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
|
|
||||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||||
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
|
||||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||||
import com.farsunset.cim.sdk.server.model.proto.SentBodyProto;
|
import com.farsunset.cim.sdk.server.model.proto.SentBodyProto;
|
||||||
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务端接收来自应用的消息解码
|
* 服务端接收来自应用的消息解码
|
||||||
*/
|
*/
|
||||||
public class AppMessageDecoder extends ByteToMessageDecoder {
|
public class AppMessageDecoder extends ByteToMessageDecoder {
|
||||||
|
|
||||||
protected final Logger logger = Logger.getLogger(AppMessageDecoder.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decode(ChannelHandlerContext arg0, ByteBuf buffer, List<Object> queue) throws Exception {
|
public void decode(ChannelHandlerContext arg0, ByteBuf buffer, List<Object> queue) throws Exception {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息头3位
|
* 消息头3位
|
||||||
*/
|
*/
|
||||||
@ -60,25 +58,24 @@ public class AppMessageDecoder extends ByteToMessageDecoder {
|
|||||||
int conetnLength = getContentLength(lv, hv);
|
int conetnLength = getContentLength(lv, hv);
|
||||||
|
|
||||||
// 如果消息体没有接收完整,则重置读取,等待下一次重新读取
|
// 如果消息体没有接收完整,则重置读取,等待下一次重新读取
|
||||||
if (conetnLength > buffer.readableBytes()) {
|
if (conetnLength <= buffer.readableBytes()) {
|
||||||
buffer.resetReaderIndex();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] dataBytes = new byte[conetnLength];
|
byte[] dataBytes = new byte[conetnLength];
|
||||||
buffer.readBytes(dataBytes);
|
buffer.readBytes(dataBytes);
|
||||||
|
|
||||||
Object message = mappingMessageObject(dataBytes, conetnType);
|
Object message = mappingMessageObject(dataBytes, conetnType);
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
|
arg0.channel().attr(AttributeKey.valueOf(CIMSession.PROTOCOL)).set(CIMSession.NATIVEAPP);
|
||||||
queue.add(message);
|
queue.add(message);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer.resetReaderIndex();
|
||||||
|
}
|
||||||
|
|
||||||
public Object mappingMessageObject(byte[] data, byte type) throws Exception {
|
public Object mappingMessageObject(byte[] data, byte type) throws Exception {
|
||||||
|
|
||||||
if (CIMConstant.ProtobufType.C_H_RS == type) {
|
if (CIMConstant.ProtobufType.C_H_RS == type) {
|
||||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
|
||||||
logger.info(response.toString());
|
|
||||||
SentBody body = new SentBody();
|
SentBody body = new SentBody();
|
||||||
body.setKey(CIMConstant.CLIENT_HEARTBEAT);
|
body.setKey(CIMConstant.CLIENT_HEARTBEAT);
|
||||||
body.setTimestamp(System.currentTimeMillis());
|
body.setTimestamp(System.currentTimeMillis());
|
||||||
@ -91,7 +88,6 @@ public class AppMessageDecoder extends ByteToMessageDecoder {
|
|||||||
body.setKey(bodyProto.getKey());
|
body.setKey(bodyProto.getKey());
|
||||||
body.setTimestamp(bodyProto.getTimestamp());
|
body.setTimestamp(bodyProto.getTimestamp());
|
||||||
body.putAll(bodyProto.getDataMap());
|
body.putAll(bodyProto.getDataMap());
|
||||||
logger.info(body.toString());
|
|
||||||
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
@ -21,161 +21,139 @@
|
|||||||
*/
|
*/
|
||||||
package com.farsunset.cim.sdk.server.filter.decoder;
|
package com.farsunset.cim.sdk.server.filter.decoder;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||||
import com.farsunset.cim.sdk.server.handler.CIMNioSocketAcceptor;
|
|
||||||
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
||||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||||
|
import com.farsunset.cim.sdk.server.model.proto.SentBodyProto;
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务端接收来自websocket消息解码
|
* 服务端接收来自websocket消息解码,netty自带的websocket协议解码器和普通二进制流解码器无法共存,所以只能重新写一遍websocket消息编解码咯
|
||||||
*/
|
*/
|
||||||
public class WebMessageDecoder extends ByteToMessageDecoder {
|
public class WebMessageDecoder extends ByteToMessageDecoder {
|
||||||
public static final byte MASK = 0x1;// 1000 0000
|
public static final byte MASK = 0x1;// 1000 0000
|
||||||
public static final byte HAS_EXTEND_DATA = 126;
|
public static final byte HAS_EXTEND_DATA = 126;
|
||||||
public static final byte HAS_EXTEND_DATA_CONTINUE = 127;
|
public static final byte HAS_EXTEND_DATA_CONTINUE = 127;
|
||||||
public static final byte PAYLOADLEN = 0x7F;// 0111 1111
|
public static final byte PAYLOADLEN = 0x7F;// 0111 1111
|
||||||
public static final Pattern SEC_KEY_PATTERN = Pattern.compile("^(Sec-WebSocket-Key:).+",
|
|
||||||
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
|
public static final byte TAG_MASK = 0x0F;// 0000 1111 > 15
|
||||||
protected final Logger logger = Logger.getLogger(WebMessageDecoder.class);
|
|
||||||
|
private static final byte OPCODE_BINARY = 0x2;
|
||||||
|
private static final byte OPCODE_CLOSE = 0x8;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decode(ChannelHandlerContext arg0, ByteBuf iobuffer, List<Object> queue) throws Exception {
|
public void decode(ChannelHandlerContext arg0, ByteBuf iobuffer, List<Object> queue) throws Exception {
|
||||||
/**
|
|
||||||
* 消息头2位
|
|
||||||
*/
|
|
||||||
if (iobuffer.readableBytes() < CIMConstant.WS_DATA_HEADER_LENGTH) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
iobuffer.markReaderIndex();
|
iobuffer.markReaderIndex();
|
||||||
|
|
||||||
byte head = iobuffer.readByte();// 第二个字节
|
/**
|
||||||
byte datalength = (byte) (head & PAYLOADLEN);// 得到第二个字节后七位的值
|
* 接下来判断fin标志位是否是1 如果是0 则等待消息接收完成
|
||||||
int length = 0;
|
*/
|
||||||
if (datalength < HAS_EXTEND_DATA) {// 第一种是消息内容少于126存储消息长度
|
byte tag = iobuffer.readByte();
|
||||||
length = datalength;
|
int frameFin = tag > 0 ? 0 : 1; // 有符号byte 第一位为1则为负数 第一位为0则为正数,以此 判断fin字段是 0 还是 1
|
||||||
} else if (datalength == HAS_EXTEND_DATA) {// 第二种是消息长度大于等于126且少于UINT16的情况此值为126
|
if (frameFin == 0) {
|
||||||
if (iobuffer.readableBytes() < 2) {
|
|
||||||
iobuffer.resetReaderIndex();
|
iobuffer.resetReaderIndex();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
byte[] extended = new byte[2];
|
|
||||||
iobuffer.readBytes(extended);
|
|
||||||
int shift = 0;
|
|
||||||
length = 0;
|
|
||||||
for (int i = extended.length - 1; i >= 0; i--) {
|
|
||||||
length = length + ((extended[i] & 0xFF) << shift);
|
|
||||||
shift += 8;
|
|
||||||
}
|
|
||||||
} else if (datalength == HAS_EXTEND_DATA_CONTINUE) {// 第三种是消息长度大于UINT16的情况下此值为127
|
|
||||||
if (iobuffer.readableBytes() < 4) {
|
|
||||||
iobuffer.resetReaderIndex();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
byte[] extended = new byte[4];
|
|
||||||
iobuffer.readBytes(extended);
|
|
||||||
int shift = 0;
|
|
||||||
length = 0;
|
|
||||||
for (int i = extended.length - 1; i >= 0; i--) {
|
|
||||||
length = length + ((extended[i] & 0xFF) << shift);
|
|
||||||
shift += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int ismask = head >> 7 & MASK;// 得到第二个字节第一位的值
|
/**
|
||||||
if ((ismask == 1 && iobuffer.readableBytes() < 4 + length)
|
* 获取帧类型,因为使用了protobuf,所以只支持二进制帧 OPCODE_BINARY,以及客户端关闭连接帧通知 OPCODE_CLOSE
|
||||||
|| (ismask == 0 && iobuffer.readableBytes() < length)) {// 有掩码
|
*/
|
||||||
iobuffer.resetReaderIndex();
|
int frameOqcode = tag & TAG_MASK;
|
||||||
return;
|
|
||||||
}
|
|
||||||
iobuffer.resetReaderIndex();
|
|
||||||
|
|
||||||
decodeDataBody(iobuffer, queue);
|
if (OPCODE_BINARY == frameOqcode) {
|
||||||
}
|
|
||||||
|
|
||||||
public void decodeDataBody(ByteBuf iobuffer, List<Object> queue) {
|
|
||||||
iobuffer.readByte();
|
|
||||||
byte head = iobuffer.readByte();
|
byte head = iobuffer.readByte();
|
||||||
byte datalength = (byte) (head & PAYLOADLEN);
|
byte datalength = (byte) (head & PAYLOADLEN);
|
||||||
if (datalength < HAS_EXTEND_DATA) {
|
int realLength = 0;
|
||||||
} else if (datalength == HAS_EXTEND_DATA) {
|
|
||||||
iobuffer.readBytes(new byte[2]);
|
/**
|
||||||
|
* Payload len,7位或者7+16位或者7+64位,表示数据帧中数据大小,这里有好几种情况。 如果值为0-125,那么该值就是payload
|
||||||
|
* data的真实长度。 如果值为126,那么该7位后面紧跟着的2个字节就是payload data的真实长度。
|
||||||
|
* 如果值为127,那么该7位后面紧跟着的8个字节就是payload data的真实长度。
|
||||||
|
*/
|
||||||
|
if (datalength == HAS_EXTEND_DATA) {
|
||||||
|
realLength = iobuffer.readShort();
|
||||||
} else if (datalength == HAS_EXTEND_DATA_CONTINUE) {
|
} else if (datalength == HAS_EXTEND_DATA_CONTINUE) {
|
||||||
iobuffer.readBytes(new byte[4]);
|
realLength = (int) iobuffer.readLong();
|
||||||
|
} else {
|
||||||
|
realLength = datalength;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ismask = head >> 7 & MASK;
|
boolean masked = (head >> 7 & MASK) == 1;
|
||||||
byte[] data = null;
|
if (masked) {// 有掩码
|
||||||
if (ismask == 1) {// 有掩码
|
|
||||||
// 获取掩码
|
// 获取掩码
|
||||||
byte[] mask = new byte[4];
|
byte[] mask = new byte[4];
|
||||||
iobuffer.readBytes(mask);
|
iobuffer.readBytes(mask);
|
||||||
|
|
||||||
data = new byte[iobuffer.readableBytes()];
|
byte[] data = new byte[realLength];
|
||||||
iobuffer.readBytes(data);
|
iobuffer.readBytes(data);
|
||||||
for (int i = 0; i < data.length; i++) {
|
for (int i = 0; i < realLength; i++) {
|
||||||
// 数据进行异或运算
|
// 数据进行异或运算
|
||||||
data[i] = (byte) (data[i] ^ mask[i % 4]);
|
data[i] = (byte) (data[i] ^ mask[i % 4]);
|
||||||
}
|
}
|
||||||
handleSentBodyAndHeartPing(data, queue);
|
|
||||||
|
handleMessage(data, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (OPCODE_CLOSE == frameOqcode) {
|
||||||
|
handleClose(arg0);
|
||||||
} else {
|
} else {
|
||||||
data = new byte[iobuffer.readableBytes()];
|
// 忽略其他类型的消息
|
||||||
iobuffer.readBytes(data);
|
iobuffer.readBytes(new byte[iobuffer.readableBytes()]);
|
||||||
handleWebsocketHandshake(data, queue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleWebsocketHandshake(byte[] data, List<Object> queue) {
|
|
||||||
String message = null;
|
|
||||||
try {
|
|
||||||
message = new String(data, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
SentBody body = new SentBody();
|
|
||||||
body.setKey(CIMNioSocketAcceptor.WEBSOCKET_HANDLER_KEY);
|
|
||||||
|
|
||||||
Matcher m = SEC_KEY_PATTERN.matcher(message);
|
|
||||||
if (m.find()) {
|
|
||||||
String foundstring = m.group();
|
|
||||||
body.put("key", foundstring.split(":")[1].trim());
|
|
||||||
}
|
|
||||||
queue.add(body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleSentBodyAndHeartPing(byte[] data, List<Object> queue) {
|
private void handleClose(ChannelHandlerContext arg0) {
|
||||||
String message = null;
|
arg0.channel().close();
|
||||||
try {
|
|
||||||
message = new String(data, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleMessage(byte[] data, List<Object> queue) throws InvalidProtocolBufferException {
|
||||||
|
byte type = data[0];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 只处理心跳响应以及,sentbody消息
|
* 只处理心跳响应以及,sentbody消息
|
||||||
*/
|
*/
|
||||||
if (HeartbeatResponse.CMD_HEARTBEAT_RESPONSE.equals(message)) {
|
if (type == CIMConstant.ProtobufType.C_H_RS) {
|
||||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
||||||
logger.info(response.toString());
|
|
||||||
queue.add(response);
|
queue.add(response);
|
||||||
} else if (data.length > 2) {
|
}
|
||||||
SentBody body = JSON.parseObject(message, SentBody.class);
|
|
||||||
logger.info(body.toString());
|
if (type == CIMConstant.ProtobufType.SENTBODY) {
|
||||||
|
|
||||||
|
int length = getContentLength(data[1], data[2]);
|
||||||
|
byte[] protobuf = new byte[length];
|
||||||
|
System.arraycopy(data, CIMConstant.DATA_HEADER_LENGTH, protobuf, 0, length);
|
||||||
|
|
||||||
|
SentBodyProto.Model bodyProto = SentBodyProto.Model.parseFrom(protobuf);
|
||||||
|
SentBody body = new SentBody();
|
||||||
|
body.setKey(bodyProto.getKey());
|
||||||
|
body.setTimestamp(bodyProto.getTimestamp());
|
||||||
|
body.putAll(bodyProto.getDataMap());
|
||||||
queue.add(body);
|
queue.add(body);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析消息体长度
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @param length
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private int getContentLength(byte lv, byte hv) {
|
||||||
|
int l = (lv & 0xff);
|
||||||
|
int h = (hv & 0xff);
|
||||||
|
return (l | (h <<= 8));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,25 +24,24 @@ package com.farsunset.cim.sdk.server.handler;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import org.apache.log4j.Logger;
|
|
||||||
|
|
||||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||||
import com.farsunset.cim.sdk.server.filter.ServerMessageDecoder;
|
import com.farsunset.cim.sdk.server.filter.ServerMessageDecoder;
|
||||||
import com.farsunset.cim.sdk.server.filter.ServerMessageEncoder;
|
import com.farsunset.cim.sdk.server.filter.ServerMessageEncoder;
|
||||||
import com.farsunset.cim.sdk.server.model.HeartbeatRequest;
|
import com.farsunset.cim.sdk.server.model.HeartbeatRequest;
|
||||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
|
||||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
|
|
||||||
import io.netty.bootstrap.ServerBootstrap;
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.ChannelHandler.Sharable;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
import io.netty.channel.ChannelOption;
|
import io.netty.channel.ChannelOption;
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import io.netty.channel.ChannelHandler.Sharable;
|
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.handler.logging.LogLevel;
|
||||||
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
import io.netty.handler.timeout.IdleState;
|
import io.netty.handler.timeout.IdleState;
|
||||||
import io.netty.handler.timeout.IdleStateEvent;
|
import io.netty.handler.timeout.IdleStateEvent;
|
||||||
import io.netty.handler.timeout.IdleStateHandler;
|
import io.netty.handler.timeout.IdleStateHandler;
|
||||||
@ -50,10 +49,19 @@ import io.netty.util.AttributeKey;
|
|||||||
|
|
||||||
@Sharable
|
@Sharable
|
||||||
public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody> {
|
public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* websocket特有的握手处理handler
|
||||||
|
*/
|
||||||
public final static String WEBSOCKET_HANDLER_KEY = "client_websocket_handshake";
|
public final static String WEBSOCKET_HANDLER_KEY = "client_websocket_handshake";
|
||||||
private final static String CIMSESSION_CLOSED_HANDLER_KEY = "client_cimsession_closed";
|
/**
|
||||||
private Logger logger = Logger.getLogger(CIMNioSocketAcceptor.class);
|
* 连接关闭处理handler
|
||||||
private HashMap<String, CIMRequestHandler> handlers = new HashMap<String, CIMRequestHandler>();
|
*/
|
||||||
|
public final static String CIMSESSION_CLOSED_HANDLER_KEY = "client_closed";
|
||||||
|
|
||||||
|
private HashMap<String, CIMRequestHandler> innerHandlerMap = new HashMap<String, CIMRequestHandler>();
|
||||||
|
private CIMRequestHandler outerRequestHandler;
|
||||||
|
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
// 连接空闲时间
|
// 连接空闲时间
|
||||||
@ -69,16 +77,20 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody>
|
|||||||
/**
|
/**
|
||||||
* 预制websocket握手请求的处理
|
* 预制websocket握手请求的处理
|
||||||
*/
|
*/
|
||||||
handlers.put(WEBSOCKET_HANDLER_KEY, new WebsocketHandler());
|
innerHandlerMap.put(WEBSOCKET_HANDLER_KEY, new WebsocketHandler());
|
||||||
|
|
||||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||||
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup());
|
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup());
|
||||||
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
|
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
|
||||||
|
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||||
bootstrap.channel(NioServerSocketChannel.class);
|
bootstrap.channel(NioServerSocketChannel.class);
|
||||||
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||||
@Override
|
@Override
|
||||||
public void initChannel(SocketChannel ch) throws Exception {
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
|
|
||||||
ch.pipeline().addLast(new ServerMessageDecoder());
|
ch.pipeline().addLast(new ServerMessageDecoder());
|
||||||
ch.pipeline().addLast(new ServerMessageEncoder());
|
ch.pipeline().addLast(new ServerMessageEncoder());
|
||||||
|
ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
|
||||||
ch.pipeline().addLast(new IdleStateHandler(READ_IDLE_TIME, WRITE_IDLE_TIME, 0));
|
ch.pipeline().addLast(new IdleStateHandler(READ_IDLE_TIME, WRITE_IDLE_TIME, 0));
|
||||||
ch.pipeline().addLast(CIMNioSocketAcceptor.this);
|
ch.pipeline().addLast(CIMNioSocketAcceptor.this);
|
||||||
}
|
}
|
||||||
@ -87,46 +99,43 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody>
|
|||||||
bootstrap.bind(port);
|
bootstrap.bind(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
/**
|
||||||
logger.info("sessionCreated()... from " + ctx.channel().remoteAddress() + " nid:"
|
* 设置应用层的sentbody处理handler
|
||||||
+ ctx.channel().id().asShortText());
|
*
|
||||||
|
* @param outerRequestHandler
|
||||||
|
*/
|
||||||
|
public void setAppSentBodyHandler(CIMRequestHandler outerRequestHandler) {
|
||||||
|
this.outerRequestHandler = outerRequestHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, SentBody body) throws Exception {
|
protected void channelRead0(ChannelHandlerContext ctx, SentBody body) throws Exception {
|
||||||
|
|
||||||
CIMSession cimSession = new CIMSession(ctx.channel());
|
CIMSession session = new CIMSession(ctx.channel());
|
||||||
|
|
||||||
CIMRequestHandler handler = handlers.get(body.getKey());
|
CIMRequestHandler handler = innerHandlerMap.get(body.getKey());
|
||||||
if (handler == null) {
|
/**
|
||||||
|
* 如果有内置的特殊handler需要处理,则使用内置的
|
||||||
ReplyBody reply = new ReplyBody();
|
*/
|
||||||
reply.setKey(body.getKey());
|
if (handler != null) {
|
||||||
reply.setCode(CIMConstant.ReturnCode.CODE_404);
|
handler.process(session, body);
|
||||||
reply.setMessage("KEY:" + body.getKey() + " not defined on server");
|
return;
|
||||||
cimSession.write(reply);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
ReplyBody reply = handler.process(cimSession, body);
|
|
||||||
if (reply != null) {
|
|
||||||
reply.setKey(body.getKey());
|
|
||||||
cimSession.write(reply);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有业务层去处理其他的sentbody
|
||||||
|
*/
|
||||||
|
outerRequestHandler.process(session, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
|
||||||
CIMSession cimSession = new CIMSession(ctx.channel());
|
CIMSession session = new CIMSession(ctx.channel());
|
||||||
|
SentBody body = new SentBody();
|
||||||
|
body.setKey(CIMSESSION_CLOSED_HANDLER_KEY);
|
||||||
|
outerRequestHandler.process(session, body);
|
||||||
|
|
||||||
logger.warn("sessionClosed()... from " + ctx.channel().remoteAddress() + " nid:" + cimSession.getNid()
|
|
||||||
+ ",isConnected:" + ctx.channel().isActive());
|
|
||||||
CIMRequestHandler handler = handlers.get(CIMSESSION_CLOSED_HANDLER_KEY);
|
|
||||||
if (handler != null) {
|
|
||||||
handler.process(cimSession, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,16 +144,11 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody>
|
|||||||
if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state().equals(IdleState.WRITER_IDLE)) {
|
if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state().equals(IdleState.WRITER_IDLE)) {
|
||||||
ctx.channel().attr(AttributeKey.valueOf(CIMConstant.HEARTBEAT_KEY)).set(System.currentTimeMillis());
|
ctx.channel().attr(AttributeKey.valueOf(CIMConstant.HEARTBEAT_KEY)).set(System.currentTimeMillis());
|
||||||
ctx.channel().writeAndFlush(HeartbeatRequest.getInstance());
|
ctx.channel().writeAndFlush(HeartbeatRequest.getInstance());
|
||||||
logger.debug(IdleState.WRITER_IDLE + "... from " + ctx.channel().remoteAddress() + " nid:"
|
|
||||||
+ ctx.channel().id().asShortText());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果心跳请求发出30秒内没收到响应,则关闭连接
|
// 如果心跳请求发出30秒内没收到响应,则关闭连接
|
||||||
if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state().equals(IdleState.READER_IDLE)) {
|
if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state().equals(IdleState.READER_IDLE)) {
|
||||||
|
|
||||||
logger.debug(IdleState.READER_IDLE + "... from " + ctx.channel().remoteAddress() + " nid:"
|
|
||||||
+ ctx.channel().id().asShortText());
|
|
||||||
Long lastTime = (Long) ctx.channel().attr(AttributeKey.valueOf(CIMConstant.HEARTBEAT_KEY)).get();
|
Long lastTime = (Long) ctx.channel().attr(AttributeKey.valueOf(CIMConstant.HEARTBEAT_KEY)).get();
|
||||||
if (lastTime != null && System.currentTimeMillis() - lastTime >= PING_TIME_OUT) {
|
if (lastTime != null && System.currentTimeMillis() - lastTime >= PING_TIME_OUT) {
|
||||||
ctx.channel().close();
|
ctx.channel().close();
|
||||||
@ -154,21 +158,8 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
||||||
|
|
||||||
logger.error("exceptionCaught()... from " + ctx.channel().remoteAddress() + " isConnected:"
|
|
||||||
+ ctx.channel().isActive() + " nid:" + ctx.channel().id().asShortText(), cause);
|
|
||||||
ctx.channel().close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPort(int port) {
|
public void setPort(int port) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHandlers(HashMap<String, CIMRequestHandler> handlers) {
|
|
||||||
this.handlers = handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,10 @@ package com.farsunset.cim.sdk.server.handler;
|
|||||||
* 请求处理接口,所有的请求实现必须实现此接口
|
* 请求处理接口,所有的请求实现必须实现此接口
|
||||||
* @author 3979434@qq.com
|
* @author 3979434@qq.com
|
||||||
*/
|
*/
|
||||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
|
||||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
|
|
||||||
public interface CIMRequestHandler {
|
public interface CIMRequestHandler {
|
||||||
|
|
||||||
ReplyBody process(CIMSession session, SentBody message);
|
void process(CIMSession session, SentBody message);
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,8 @@ package com.farsunset.cim.sdk.server.handler;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
import com.farsunset.cim.sdk.server.model.HandshakerResponse;
|
||||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||||
import com.farsunset.cim.sdk.server.model.WebsocketResponse;
|
|
||||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +38,7 @@ public class WebsocketHandler implements CIMRequestHandler {
|
|||||||
|
|
||||||
private final static String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
private final static String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
public ReplyBody process(CIMSession session, SentBody body) {
|
public void process(CIMSession session, SentBody body) {
|
||||||
session.setChannel(CIMSession.CHANNEL_BROWSER);
|
session.setChannel(CIMSession.CHANNEL_BROWSER);
|
||||||
String secKey = body.get("key") + GUID;
|
String secKey = body.get("key") + GUID;
|
||||||
try {
|
try {
|
||||||
@ -50,7 +49,6 @@ public class WebsocketHandler implements CIMRequestHandler {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
session.write(new WebsocketResponse(secKey));
|
session.write(new HandshakerResponse(secKey));
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|