更新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-common-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"/>
|
||||
</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;
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
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 {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public String list() {
|
||||
ServletActionContext.getRequest().setAttribute("sessionList",
|
||||
((DefaultSessionManager) ContextHolder.getBean("CIMSessionManager")).queryAll());
|
||||
|
||||
return "list";
|
||||
@Autowired
|
||||
private CIMSessionServiceImpl sessionManager;
|
||||
|
||||
@RequestMapping(value = "/list.action")
|
||||
public String list(Model model) {
|
||||
model.addAttribute("sessionList", sessionManager.queryAll());
|
||||
return "console/session/manage";
|
||||
}
|
||||
|
||||
public void offline() throws IOException {
|
||||
/*public void offline() throws IOException {
|
||||
|
||||
String account = ServletActionContext.getRequest().getParameter("account");
|
||||
Message msg = new Message();
|
||||
@ -56,5 +50,5 @@ public class SessionAction extends ActionSupport {
|
||||
// 向客户端 发送消息
|
||||
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");
|
||||
* 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 com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 记录心跳实现
|
||||
*
|
||||
*/
|
||||
public class HeartbeatHandler implements CIMRequestHandler {
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
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;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import org.apache.log4j.Logger;
|
||||
import java.util.Objects;
|
||||
|
||||
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.handler.CIMRequestHandler;
|
||||
import com.farsunset.cim.sdk.server.model.Message;
|
||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
import com.farsunset.cim.sdk.server.session.DefaultSessionManager;
|
||||
import com.farsunset.cim.util.ContextHolder;
|
||||
import com.farsunset.cim.service.impl.CIMSessionServiceImpl;
|
||||
import com.farsunset.cim.util.StringUtil;
|
||||
|
||||
|
||||
/**
|
||||
* 账号绑定实现
|
||||
*
|
||||
*/
|
||||
|
||||
@Component
|
||||
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) {
|
||||
DefaultSessionManager sessionManager = ((DefaultSessionManager) ContextHolder.getBean("CIMSessionManager"));
|
||||
@Autowired
|
||||
private CIMSessionServiceImpl sessionManager;
|
||||
|
||||
@Value("${server.host}")
|
||||
private String host;
|
||||
|
||||
|
||||
public void process(CIMSession newSession, SentBody message) {
|
||||
|
||||
ReplyBody reply = new ReplyBody();
|
||||
reply.setKey(message.getKey());
|
||||
reply.setCode(CIMConstant.ReturnCode.CODE_200);
|
||||
try {
|
||||
|
||||
@ -54,13 +67,14 @@ public class BindHandler implements CIMRequestHandler {
|
||||
newSession.setGid(StringUtil.getUUID());
|
||||
newSession.setAccount(account);
|
||||
newSession.setDeviceId(message.get("deviceId"));
|
||||
newSession.setHost(InetAddress.getLocalHost().getHostAddress());
|
||||
newSession.setHost(host);
|
||||
newSession.setChannel(message.get("channel"));
|
||||
newSession.setDeviceModel(message.get("device"));
|
||||
newSession.setClientVersion(message.get("version"));
|
||||
newSession.setSystemVersion(message.get("osVersion"));
|
||||
newSession.setBindTime(System.currentTimeMillis());
|
||||
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());
|
||||
}
|
||||
|
||||
// 如果是重复连接,则直接返回
|
||||
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());
|
||||
@ -91,39 +98,32 @@ public class BindHandler implements CIMRequestHandler {
|
||||
|
||||
} catch (Exception e) {
|
||||
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());
|
||||
return reply;
|
||||
|
||||
newSession.write(reply);
|
||||
}
|
||||
|
||||
private boolean fromOtherDevice(CIMSession oldSession ,CIMSession newSession) {
|
||||
|
||||
return !Objects.equals(oldSession.getDeviceId(), newSession.getDeviceId());
|
||||
}
|
||||
|
||||
private void sendForceOfflineMessage(CIMSession oldSession, String account, String deviceModel) {
|
||||
|
||||
Message msg = new Message();
|
||||
msg.setMid(String.valueOf(System.currentTimeMillis()));
|
||||
msg.setAction(CIMConstant.MessageAction.ACTION_999);// 强行下线消息类型
|
||||
msg.setReceiver(account);
|
||||
msg.setSender("system");
|
||||
msg.setContent(deviceModel);
|
||||
|
||||
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();
|
||||
}
|
||||
msg.setMid(StringUtil.getUUID());
|
||||
closeQuietly(oldSession,msg);
|
||||
|
||||
}
|
||||
|
||||
// 不同设备同一账号登录时关闭旧的连接
|
||||
private void closeQuietly(CIMSession oldSession, Message msg) {
|
||||
if (oldSession.isConnected()) {
|
||||
private void closeQuietly(CIMSession oldSession,Message msg) {
|
||||
if (oldSession.isConnected() && Objects.equals(host, oldSession.getHost())) {
|
||||
oldSession.write(msg);
|
||||
oldSession.removeAttribute(CIMConstant.SESSION_KEY);
|
||||
oldSession.closeNow();
|
@ -21,36 +21,41 @@
|
||||
*/
|
||||
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.handler.CIMRequestHandler;
|
||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
import com.farsunset.cim.sdk.server.session.DefaultSessionManager;
|
||||
import com.farsunset.cim.util.ContextHolder;
|
||||
import com.farsunset.cim.service.impl.CIMSessionServiceImpl;
|
||||
|
||||
|
||||
/**
|
||||
* 断开连接,清除session
|
||||
*
|
||||
*/
|
||||
|
||||
@Component
|
||||
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);
|
||||
if (account == null) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
DefaultSessionManager sessionManager = ((DefaultSessionManager) ContextHolder.getBean("CIMSessionManager"));
|
||||
|
||||
ios.removeAttribute(CIMConstant.SESSION_KEY);
|
||||
sessionManager.remove(account.toString());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -21,70 +21,70 @@
|
||||
*/
|
||||
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.session.CIMSession;
|
||||
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 {
|
||||
|
||||
@Value("${server.host}")
|
||||
private String host;
|
||||
|
||||
@Autowired
|
||||
private DefaultSessionManager sessionManager;
|
||||
|
||||
@Autowired
|
||||
private MessageDispatcherImpl messageDispatcher;
|
||||
|
||||
|
||||
public void setSessionManager(DefaultSessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ApnsService apnsService;
|
||||
|
||||
|
||||
/**
|
||||
* 向用户发送消息
|
||||
*
|
||||
* @param msg
|
||||
*/
|
||||
public void push(Message msg) {
|
||||
CIMSession session = sessionManager.get(msg.getReceiver());
|
||||
public void push(Message message) {
|
||||
CIMSession session = sessionManager.get(message.getReceiver());
|
||||
|
||||
/*
|
||||
/**
|
||||
* 服务器集群时,可以在此 判断当前session是否连接于本台服务器,如果是,继续往下走,如果不是,将此消息发往当前session连接的服务器并
|
||||
* return if(session!=null&&!session.isLocalhost()){//判断当前session是否连接于本台服务器,如不是
|
||||
* //发往目标服务器处理
|
||||
* MessageDispatcher.execute(MessageUtil.transform(msg),session.getHost());
|
||||
* return; }
|
||||
*/
|
||||
|
||||
if (session != null && session.isConnected()) {
|
||||
|
||||
session.write(msg);
|
||||
if (session.isConnected() && !Objects.equals(host, session.getHost())) {
|
||||
messageDispatcher.forward(message, session.getHost());
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* ios设备流程特别处理,如果长链接断开了,并且ApnsAble为打开状态的话优走apns
|
||||
*/
|
||||
if (Objects.equals(session.getChannel(), CIMSession.CHANNEL_IOS) && Objects.equals(session.getApnsAble(), CIMSession.APNS_ON)) {
|
||||
apnsService.push(message, session.getDeviceId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 引入javaapns相关jar
|
||||
*
|
||||
* @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(); }
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
@ -21,8 +21,11 @@
|
||||
*/
|
||||
package com.farsunset.cim.push;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.farsunset.cim.sdk.server.model.Message;
|
||||
|
||||
@Component
|
||||
public class SystemMessagePusher extends DefaultMessagePusher {
|
||||
|
||||
@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");
|
||||
* 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 org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import com.farsunset.cim.sdk.server.model.Message;
|
||||
|
||||
public class ContextHolder implements ApplicationContextAware {
|
||||
public interface ApnsService {
|
||||
|
||||
private static ApplicationContext context;
|
||||
|
||||
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;
|
||||
}
|
||||
void push( Message message, String deviceToken);
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,65 +1,65 @@
|
||||
/**
|
||||
* 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.session;
|
||||
|
||||
import java.util.List;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
import com.farsunset.cim.sdk.server.session.SessionManager;
|
||||
|
||||
/**
|
||||
* 集群 session管理实现示例, 各位可以自行实现 AbstractSessionManager接口来实现自己的 session管理 服务器集群时
|
||||
* 须要将CIMSession 信息存入数据库或者nosql 等 第三方存储空间中,便于所有服务器都可以访问
|
||||
*/
|
||||
public class ClusterSessionManager implements SessionManager {
|
||||
|
||||
public CIMSession get(String account) {
|
||||
|
||||
// 这里查询数据库
|
||||
/*
|
||||
* CIMSession session = database.getSession(account);
|
||||
* session.setIoSession(ContextHolder.getBean(CIMNioSocketAcceptor.class).
|
||||
* getManagedSessions().get(session.getNid())); return session;
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CIMSession> queryAll() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String account) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(CIMSession session) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(CIMSession arg0) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.session;
|
||||
|
||||
import java.util.List;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
import com.farsunset.cim.sdk.server.session.SessionManager;
|
||||
|
||||
/**
|
||||
* 集群 session管理实现示例, 各位可以自行实现 AbstractSessionManager接口来实现自己的 session管理 服务器集群时
|
||||
* 须要将CIMSession 信息存入数据库或者nosql 等 第三方存储空间中,便于所有服务器都可以访问
|
||||
*/
|
||||
public class ClusterSessionManager implements SessionManager {
|
||||
|
||||
public CIMSession get(String account) {
|
||||
|
||||
// 这里查询数据库
|
||||
/*
|
||||
* CIMSession session = database.getSession(account);
|
||||
* session.setIoSession(ContextHolder.getBean(CIMNioSocketAcceptor.class).
|
||||
* getManagedSessions().get(session.getNid())); return session;
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CIMSession> queryAll() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String account) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(CIMSession session) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(CIMSession arg0) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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() {
|
||||
// TODO Auto-generated method stub
|
||||
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">
|
||||
function doLogin()
|
||||
{
|
||||
ACCOUNT = $('#account').val();
|
||||
if($.trim(ACCOUNT)=='' )
|
||||
{
|
||||
if($.trim($('#account').val()) =='' ){
|
||||
return;
|
||||
}
|
||||
showProcess('正在接入请稍后......');
|
||||
@ -20,17 +11,17 @@
|
||||
</script>
|
||||
|
||||
<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-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;">
|
||||
<img src="<%=loginBasePath %>/resource/img/icon.png" style="height: 60px;width: 60px;margin-top:30px;"/>
|
||||
<div style="margin-top: 20px; color: #ffffff;font-size: 16px;">使用用户帐号登录</div>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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;" />
|
||||
</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-dialog" style="width: 500px;margin: 30px auto;">
|
||||
<div class="modal-dialog" style="width: 600px;margin: 30px auto;">
|
||||
<div class="modal-content" >
|
||||
<div class="modal-header" style="text-align: center;">
|
||||
<h4>消息列表</h4>
|
||||
<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 class="modal-body" id="messageList" style="min-height: 600px;" >
|
||||
|
||||
</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>
|
||||
<option name="distributionType" value="LOCAL" />
|
||||
<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">
|
||||
<set>
|
||||
<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"?>
|
||||
<project version="4">
|
||||
<component name="EntryPointsManager">
|
||||
<entry_points version="2.0" />
|
||||
</component>
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="4">
|
||||
<list size="5">
|
||||
<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="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
<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>
|
||||
</value>
|
||||
</option>
|
||||
@ -27,17 +25,7 @@
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
||||
<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">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
@ -24,7 +24,7 @@ package com.farsunset.ichat.example.app;
|
||||
public interface Constant {
|
||||
|
||||
//服务端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
|
||||
|
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"?>
|
||||
<classpath>
|
||||
<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/netty-buffer-4.1.9.Final.jar"/>
|
||||
<classpathentry kind="lib" path="libs/netty-codec-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-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">
|
||||
<classpathentry kind="lib" path="libs/slf4j-api-1.7.25.jar"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
|
||||
<attributes>
|
||||
<attribute name="owner.project.facets" value="java"/>
|
||||
</attributes>
|
||||
</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"/>
|
||||
</classpath>
|
||||
|
@ -22,8 +22,6 @@
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<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.wst.common.project.facet.core.nature</nature>
|
||||
</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"?>
|
||||
<faceted-project>
|
||||
<runtime name="Apache Tomcat v7.0"/>
|
||||
<fixed facet="java"/>
|
||||
<fixed facet="jst.utility"/>
|
||||
<installed facet="java" version="1.6"/>
|
||||
<installed facet="jst.utility" version="1.0"/>
|
||||
</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;
|
||||
|
||||
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.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.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
/**
|
||||
* 服务端接收消息路由解码,通过消息类型分发到不同的真正解码器
|
||||
@ -38,6 +45,11 @@ public class ServerMessageDecoder extends ByteToMessageDecoder {
|
||||
private WebMessageDecoder webMessageDecoder;
|
||||
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() {
|
||||
webMessageDecoder = new WebMessageDecoder();
|
||||
appMessageDecoder = new AppMessageDecoder();
|
||||
@ -46,19 +58,81 @@ public class ServerMessageDecoder extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext arg0, ByteBuf buffer, List<Object> queue) throws Exception {
|
||||
buffer.markReaderIndex();
|
||||
byte conetnType = buffer.readByte();
|
||||
buffer.resetReaderIndex();
|
||||
|
||||
/**
|
||||
* 原生SDK只会发送2种类型消息 1个心跳类型 另一个是sendbody,报文的第一个字节为消息类型,否则才是websocket的消息
|
||||
*/
|
||||
if (conetnType == CIMConstant.ProtobufType.C_H_RS || conetnType == CIMConstant.ProtobufType.SENTBODY) {
|
||||
appMessageDecoder.decode(arg0, buffer, queue);
|
||||
} else {
|
||||
|
||||
Object protocol = arg0.channel().attr(AttributeKey.valueOf(CIMSession.PROTOCOL)).get();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过正则获取websocket握手消息中的Sec-WebSocket-Key
|
||||
*
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
private String getSecWebSocketKey(String message) {
|
||||
Matcher m = SEC_KEY_PATTERN.matcher(message);
|
||||
if (m.find()) {
|
||||
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;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import java.util.Objects;
|
||||
|
||||
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.session.CIMSession;
|
||||
|
||||
@ -38,62 +38,51 @@ import io.netty.util.AttributeKey;
|
||||
*/
|
||||
public class ServerMessageEncoder extends MessageToByteEncoder<Object> {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(ServerMessageEncoder.class.getSimpleName());
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, Object object, ByteBuf out) throws Exception {
|
||||
|
||||
Object channel = ctx.channel().attr(AttributeKey.valueOf("channel")).get();
|
||||
|
||||
protected void encode(final ChannelHandlerContext ctx, final Object object, ByteBuf out) throws Exception {
|
||||
Object protocol = ctx.channel().attr(AttributeKey.valueOf(CIMSession.PROTOCOL)).get();
|
||||
|
||||
/**
|
||||
* websocket的握手响应
|
||||
*/
|
||||
if (CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof WebsocketResponse) {
|
||||
WebsocketResponse data = (WebsocketResponse) object;
|
||||
byte[] byteArray = data.getBytes();
|
||||
out.writeBytes(byteArray);
|
||||
logger.info(data.toString());
|
||||
if (Objects.equals(CIMSession.WEBSOCKET, protocol) && object instanceof HandshakerResponse) {
|
||||
out.writeBytes(object.toString().getBytes());
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
byte[] byteArray = encodeDataFrame(data.getJSONBody());
|
||||
/**
|
||||
* 由于websocket没有黏包和断包的问题,所以不必知道消息体的大小
|
||||
*/
|
||||
out.writeBytes(byteArray);
|
||||
logger.info(data.toString());
|
||||
byte[] body = data.getProtobufBody();
|
||||
byte[] header = createHeader(data.getDataType(), body.length);
|
||||
|
||||
byte[] protobuf = new byte[body.length + CIMConstant.DATA_HEADER_LENGTH];
|
||||
System.arraycopy(header, 0, protobuf, 0, header.length);
|
||||
System.arraycopy(body,0, protobuf, header.length, body.length);
|
||||
|
||||
byte[] binaryFrame = encodeDataFrame(protobuf);
|
||||
out.writeBytes(binaryFrame);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 非websocket的数据传输使用Protobuf编码数据格式
|
||||
*/
|
||||
if (!CIMSession.CHANNEL_BROWSER.equals(channel) && object instanceof EncodeFormatable) {
|
||||
if (Objects.equals(CIMSession.NATIVEAPP, protocol) && object instanceof EncodeFormatable) {
|
||||
|
||||
EncodeFormatable data = (EncodeFormatable) object;
|
||||
byte[] byteArray = data.getProtobufBody();
|
||||
out.writeBytes(createHeader(data.getDataType(), byteArray.length));
|
||||
out.writeBytes(byteArray);
|
||||
logger.info(data.toString());
|
||||
}
|
||||
}
|
||||
byte[] body = data.getProtobufBody();
|
||||
byte[] header = createHeader(data.getDataType(), body.length);
|
||||
|
||||
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
|
||||
// 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
|
||||
// 从第9个字节开始是 1111101=125,掩码是第3-第6个数据
|
||||
@ -143,4 +132,11 @@ public class ServerMessageEncoder extends MessageToByteEncoder<Object> {
|
||||
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 org.apache.log4j.Logger;
|
||||
|
||||
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.proto.SentBodyProto;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
/**
|
||||
* 服务端接收来自应用的消息解码
|
||||
*/
|
||||
public class AppMessageDecoder extends ByteToMessageDecoder {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(AppMessageDecoder.class);
|
||||
|
||||
@Override
|
||||
public void decode(ChannelHandlerContext arg0, ByteBuf buffer, List<Object> queue) throws Exception {
|
||||
|
||||
/**
|
||||
* 消息头3位
|
||||
*/
|
||||
@ -60,25 +58,24 @@ public class AppMessageDecoder extends ByteToMessageDecoder {
|
||||
int conetnLength = getContentLength(lv, hv);
|
||||
|
||||
// 如果消息体没有接收完整,则重置读取,等待下一次重新读取
|
||||
if (conetnLength > buffer.readableBytes()) {
|
||||
buffer.resetReaderIndex();
|
||||
return;
|
||||
if (conetnLength <= buffer.readableBytes()) {
|
||||
byte[] dataBytes = new byte[conetnLength];
|
||||
buffer.readBytes(dataBytes);
|
||||
|
||||
Object message = mappingMessageObject(dataBytes, conetnType);
|
||||
if (message != null) {
|
||||
arg0.channel().attr(AttributeKey.valueOf(CIMSession.PROTOCOL)).set(CIMSession.NATIVEAPP);
|
||||
queue.add(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] dataBytes = new byte[conetnLength];
|
||||
buffer.readBytes(dataBytes);
|
||||
|
||||
Object message = mappingMessageObject(dataBytes, conetnType);
|
||||
if (message != null) {
|
||||
queue.add(message);
|
||||
}
|
||||
buffer.resetReaderIndex();
|
||||
}
|
||||
|
||||
public Object mappingMessageObject(byte[] data, byte type) throws Exception {
|
||||
|
||||
if (CIMConstant.ProtobufType.C_H_RS == type) {
|
||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
||||
logger.info(response.toString());
|
||||
SentBody body = new SentBody();
|
||||
body.setKey(CIMConstant.CLIENT_HEARTBEAT);
|
||||
body.setTimestamp(System.currentTimeMillis());
|
||||
@ -91,7 +88,6 @@ public class AppMessageDecoder extends ByteToMessageDecoder {
|
||||
body.setKey(bodyProto.getKey());
|
||||
body.setTimestamp(bodyProto.getTimestamp());
|
||||
body.putAll(bodyProto.getDataMap());
|
||||
logger.info(body.toString());
|
||||
|
||||
return body;
|
||||
}
|
||||
|
@ -21,161 +21,139 @@
|
||||
*/
|
||||
package com.farsunset.cim.sdk.server.filter.decoder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
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.handler.CIMNioSocketAcceptor;
|
||||
import com.farsunset.cim.sdk.server.model.HeartbeatResponse;
|
||||
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.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
/**
|
||||
* 服务端接收来自websocket消息解码
|
||||
* 服务端接收来自websocket消息解码,netty自带的websocket协议解码器和普通二进制流解码器无法共存,所以只能重新写一遍websocket消息编解码咯
|
||||
*/
|
||||
public class WebMessageDecoder extends ByteToMessageDecoder {
|
||||
public static final byte MASK = 0x1;// 1000 0000
|
||||
public static final byte HAS_EXTEND_DATA = 126;
|
||||
public static final byte HAS_EXTEND_DATA_CONTINUE = 127;
|
||||
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);
|
||||
protected final Logger logger = Logger.getLogger(WebMessageDecoder.class);
|
||||
|
||||
public static final byte TAG_MASK = 0x0F;// 0000 1111 > 15
|
||||
|
||||
private static final byte OPCODE_BINARY = 0x2;
|
||||
private static final byte OPCODE_CLOSE = 0x8;
|
||||
|
||||
@Override
|
||||
public void decode(ChannelHandlerContext arg0, ByteBuf iobuffer, List<Object> queue) throws Exception {
|
||||
/**
|
||||
* 消息头2位
|
||||
*/
|
||||
if (iobuffer.readableBytes() < CIMConstant.WS_DATA_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
iobuffer.markReaderIndex();
|
||||
|
||||
byte head = iobuffer.readByte();// 第二个字节
|
||||
byte datalength = (byte) (head & PAYLOADLEN);// 得到第二个字节后七位的值
|
||||
int length = 0;
|
||||
if (datalength < HAS_EXTEND_DATA) {// 第一种是消息内容少于126存储消息长度
|
||||
length = datalength;
|
||||
} else if (datalength == HAS_EXTEND_DATA) {// 第二种是消息长度大于等于126且少于UINT16的情况此值为126
|
||||
if (iobuffer.readableBytes() < 2) {
|
||||
iobuffer.resetReaderIndex();
|
||||
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)
|
||||
|| (ismask == 0 && iobuffer.readableBytes() < length)) {// 有掩码
|
||||
/**
|
||||
* 接下来判断fin标志位是否是1 如果是0 则等待消息接收完成
|
||||
*/
|
||||
byte tag = iobuffer.readByte();
|
||||
int frameFin = tag > 0 ? 0 : 1; // 有符号byte 第一位为1则为负数 第一位为0则为正数,以此 判断fin字段是 0 还是 1
|
||||
if (frameFin == 0) {
|
||||
iobuffer.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
iobuffer.resetReaderIndex();
|
||||
|
||||
/**
|
||||
* 获取帧类型,因为使用了protobuf,所以只支持二进制帧 OPCODE_BINARY,以及客户端关闭连接帧通知 OPCODE_CLOSE
|
||||
*/
|
||||
int frameOqcode = tag & TAG_MASK;
|
||||
|
||||
decodeDataBody(iobuffer, queue);
|
||||
}
|
||||
if (OPCODE_BINARY == frameOqcode) {
|
||||
|
||||
public void decodeDataBody(ByteBuf iobuffer, List<Object> queue) {
|
||||
iobuffer.readByte();
|
||||
byte head = iobuffer.readByte();
|
||||
byte datalength = (byte) (head & PAYLOADLEN);
|
||||
if (datalength < HAS_EXTEND_DATA) {
|
||||
} else if (datalength == HAS_EXTEND_DATA) {
|
||||
iobuffer.readBytes(new byte[2]);
|
||||
} else if (datalength == HAS_EXTEND_DATA_CONTINUE) {
|
||||
iobuffer.readBytes(new byte[4]);
|
||||
}
|
||||
byte head = iobuffer.readByte();
|
||||
byte datalength = (byte) (head & PAYLOADLEN);
|
||||
int realLength = 0;
|
||||
|
||||
int ismask = head >> 7 & MASK;
|
||||
byte[] data = null;
|
||||
if (ismask == 1) {// 有掩码
|
||||
// 获取掩码
|
||||
byte[] mask = new byte[4];
|
||||
iobuffer.readBytes(mask);
|
||||
|
||||
data = new byte[iobuffer.readableBytes()];
|
||||
iobuffer.readBytes(data);
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
// 数据进行异或运算
|
||||
data[i] = (byte) (data[i] ^ mask[i % 4]);
|
||||
/**
|
||||
* 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) {
|
||||
realLength = (int) iobuffer.readLong();
|
||||
} else {
|
||||
realLength = datalength;
|
||||
}
|
||||
handleSentBodyAndHeartPing(data, queue);
|
||||
|
||||
boolean masked = (head >> 7 & MASK) == 1;
|
||||
if (masked) {// 有掩码
|
||||
// 获取掩码
|
||||
byte[] mask = new byte[4];
|
||||
iobuffer.readBytes(mask);
|
||||
|
||||
byte[] data = new byte[realLength];
|
||||
iobuffer.readBytes(data);
|
||||
for (int i = 0; i < realLength; i++) {
|
||||
// 数据进行异或运算
|
||||
data[i] = (byte) (data[i] ^ mask[i % 4]);
|
||||
}
|
||||
|
||||
handleMessage(data, queue);
|
||||
}
|
||||
|
||||
} else if (OPCODE_CLOSE == frameOqcode) {
|
||||
handleClose(arg0);
|
||||
} else {
|
||||
data = new byte[iobuffer.readableBytes()];
|
||||
iobuffer.readBytes(data);
|
||||
handleWebsocketHandshake(data, queue);
|
||||
// 忽略其他类型的消息
|
||||
iobuffer.readBytes(new byte[iobuffer.readableBytes()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
private void handleClose(ChannelHandlerContext arg0) {
|
||||
arg0.channel().close();
|
||||
}
|
||||
|
||||
public void handleSentBodyAndHeartPing(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();
|
||||
}
|
||||
public void handleMessage(byte[] data, List<Object> queue) throws InvalidProtocolBufferException {
|
||||
byte type = data[0];
|
||||
|
||||
/**
|
||||
* 只处理心跳响应以及,sentbody消息
|
||||
*/
|
||||
if (HeartbeatResponse.CMD_HEARTBEAT_RESPONSE.equals(message)) {
|
||||
if (type == CIMConstant.ProtobufType.C_H_RS) {
|
||||
HeartbeatResponse response = HeartbeatResponse.getInstance();
|
||||
logger.info(response.toString());
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析消息体长度
|
||||
*
|
||||
* @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.util.HashMap;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.farsunset.cim.sdk.server.constant.CIMConstant;
|
||||
import com.farsunset.cim.sdk.server.filter.ServerMessageDecoder;
|
||||
import com.farsunset.cim.sdk.server.filter.ServerMessageEncoder;
|
||||
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.session.CIMSession;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
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.IdleStateEvent;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
@ -50,10 +49,19 @@ import io.netty.util.AttributeKey;
|
||||
|
||||
@Sharable
|
||||
public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody> {
|
||||
|
||||
/**
|
||||
* websocket特有的握手处理handler
|
||||
*/
|
||||
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);
|
||||
private HashMap<String, CIMRequestHandler> handlers = new HashMap<String, CIMRequestHandler>();
|
||||
/**
|
||||
* 连接关闭处理handler
|
||||
*/
|
||||
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;
|
||||
|
||||
// 连接空闲时间
|
||||
@ -69,16 +77,20 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody>
|
||||
/**
|
||||
* 预制websocket握手请求的处理
|
||||
*/
|
||||
handlers.put(WEBSOCKET_HANDLER_KEY, new WebsocketHandler());
|
||||
innerHandlerMap.put(WEBSOCKET_HANDLER_KEY, new WebsocketHandler());
|
||||
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup());
|
||||
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
|
||||
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
bootstrap.channel(NioServerSocketChannel.class);
|
||||
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
|
||||
ch.pipeline().addLast(new ServerMessageDecoder());
|
||||
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(CIMNioSocketAcceptor.this);
|
||||
}
|
||||
@ -87,46 +99,43 @@ public class CIMNioSocketAcceptor extends SimpleChannelInboundHandler<SentBody>
|
||||
bootstrap.bind(port);
|
||||
}
|
||||
|
||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
||||
logger.info("sessionCreated()... from " + ctx.channel().remoteAddress() + " nid:"
|
||||
+ ctx.channel().id().asShortText());
|
||||
/**
|
||||
* 设置应用层的sentbody处理handler
|
||||
*
|
||||
* @param outerRequestHandler
|
||||
*/
|
||||
public void setAppSentBodyHandler(CIMRequestHandler outerRequestHandler) {
|
||||
this.outerRequestHandler = outerRequestHandler;
|
||||
}
|
||||
|
||||
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());
|
||||
if (handler == null) {
|
||||
|
||||
ReplyBody reply = new ReplyBody();
|
||||
reply.setKey(body.getKey());
|
||||
reply.setCode(CIMConstant.ReturnCode.CODE_404);
|
||||
reply.setMessage("KEY:" + body.getKey() + " not defined on server");
|
||||
cimSession.write(reply);
|
||||
|
||||
} else {
|
||||
ReplyBody reply = handler.process(cimSession, body);
|
||||
if (reply != null) {
|
||||
reply.setKey(body.getKey());
|
||||
cimSession.write(reply);
|
||||
}
|
||||
CIMRequestHandler handler = innerHandlerMap.get(body.getKey());
|
||||
/**
|
||||
* 如果有内置的特殊handler需要处理,则使用内置的
|
||||
*/
|
||||
if (handler != null) {
|
||||
handler.process(session, body);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 有业务层去处理其他的sentbody
|
||||
*/
|
||||
outerRequestHandler.process(session, body);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
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)) {
|
||||
ctx.channel().attr(AttributeKey.valueOf(CIMConstant.HEARTBEAT_KEY)).set(System.currentTimeMillis());
|
||||
ctx.channel().writeAndFlush(HeartbeatRequest.getInstance());
|
||||
logger.debug(IdleState.WRITER_IDLE + "... from " + ctx.channel().remoteAddress() + " nid:"
|
||||
+ ctx.channel().id().asShortText());
|
||||
|
||||
}
|
||||
|
||||
// 如果心跳请求发出30秒内没收到响应,则关闭连接
|
||||
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();
|
||||
if (lastTime != null && System.currentTimeMillis() - lastTime >= PING_TIME_OUT) {
|
||||
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) {
|
||||
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
|
||||
*/
|
||||
import com.farsunset.cim.sdk.server.model.ReplyBody;
|
||||
import com.farsunset.cim.sdk.server.model.SentBody;
|
||||
import com.farsunset.cim.sdk.server.session.CIMSession;
|
||||
|
||||
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.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.WebsocketResponse;
|
||||
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";
|
||||
|
||||
public ReplyBody process(CIMSession session, SentBody body) {
|
||||
public void process(CIMSession session, SentBody body) {
|
||||
session.setChannel(CIMSession.CHANNEL_BROWSER);
|
||||
String secKey = body.get("key") + GUID;
|
||||
try {
|
||||
@ -50,7 +49,6 @@ public class WebsocketHandler implements CIMRequestHandler {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
session.write(new WebsocketResponse(secKey));
|
||||
return null;
|
||||
session.write(new HandshakerResponse(secKey));
|
||||
}
|
||||
}
|
||||
|