From 7c217d06380b469d80474501ce3b6706215e0803 Mon Sep 17 00:00:00 2001 From: wangbin Date: Sun, 1 Nov 2020 10:22:50 +0800 Subject: [PATCH] first commit --- .gitignore | 12 ++++++++++++ README.md | 3 +++ build.gradle | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ gradle.properties | 21 +++++++++++++++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54413 bytes gradle/wrapper/gradle-wrapper.properties | 5 +++++ gradlew | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ gradlew.bat | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ settings.gradle | 10 ++++++++++ src/main/java/com/taover/heartbeat/ClientHolder.java | 14 ++++++++++++++ src/main/java/com/taover/heartbeat/ClientHolderImpl.java | 38 ++++++++++++++++++++++++++++++++++++++ src/main/java/com/taover/heartbeat/HeartbeatManager.java | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main/java/com/taover/heartbeat/ServerHolder.java | 13 +++++++++++++ src/main/java/com/taover/heartbeat/ServerHolderImpl.java | 35 +++++++++++++++++++++++++++++++++++ src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatService.java | 40 ++++++++++++++++++++++++++++++++++++++++ src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatServiceImpl.java | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main/java/com/taover/heartbeat/bean/ClientInstance.java | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main/java/com/taover/heartbeat/bean/ClientRequest.java | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main/java/com/taover/heartbeat/bean/Instance.java | 23 +++++++++++++++++++++++ src/main/java/com/taover/heartbeat/bean/ReformInstance.java | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main/java/com/taover/heartbeat/bean/ServerInstance.java | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main/java/com/taover/heartbeat/bean/ServerResponse.java | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 22 files changed, 1234 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/com/taover/heartbeat/ClientHolder.java create mode 100644 src/main/java/com/taover/heartbeat/ClientHolderImpl.java create mode 100644 src/main/java/com/taover/heartbeat/HeartbeatManager.java create mode 100644 src/main/java/com/taover/heartbeat/ServerHolder.java create mode 100644 src/main/java/com/taover/heartbeat/ServerHolderImpl.java create mode 100644 src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatService.java create mode 100644 src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatServiceImpl.java create mode 100644 src/main/java/com/taover/heartbeat/bean/ClientInstance.java create mode 100644 src/main/java/com/taover/heartbeat/bean/ClientRequest.java create mode 100644 src/main/java/com/taover/heartbeat/bean/Instance.java create mode 100644 src/main/java/com/taover/heartbeat/bean/ReformInstance.java create mode 100644 src/main/java/com/taover/heartbeat/bean/ServerInstance.java create mode 100644 src/main/java/com/taover/heartbeat/bean/ServerResponse.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c30588f --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.project +.classpath +.settings +/resources/ +/.gradle/ +/bin/ +/build/ +/WebContent/WEB-INF/lib/ +/WebContent/WEB-INF/classes/ +/src/main/resources/application.properties +/tmp/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddaeff8 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +### taover-心跳监控项目 + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1fc1a65 --- /dev/null +++ b/build.gradle @@ -0,0 +1,66 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java Library project to get you started. + * For more details take a look at the Java Libraries chapter in the Gradle + * user guide available at https://docs.gradle.org/4.5.1/userguide/java_library_plugin.html + */ + +plugins { + // Apply the java-library plugin to add support for Java Library + id 'java' + id 'eclipse' + id 'application' + id 'maven' +} + +jar.enabled = true +group = 'com.taover.heartbeat' +mainClassName = 'com.taover.heartbeat.HeartbeatManager' + +dependencies { + compile( + "com.taover:com-taover-util:1.1.116", + "javax.servlet:javax.servlet-api:4.0.1" + ) +} + +repositories { + jcenter() + maven{ url 'http://repository.sonatype.org/content/groups/public/' } + maven{ url 'https://repository.jboss.org/nexus/content/groups/public/' } + maven{ url 'http://nexus.taover.com:9001/repository/maven-releases/' } +} +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +artifacts { + archives sourcesJar +} + +uploadArchives { + configuration = configurations.archives + repositories { + mavenDeployer { + snapshotRepository(url: MAVEN_REPO_SNAPSHOT_URL) { + authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD) + } + repository(url: MAVEN_REPO_RELEASE_URL) { + authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD) + } + pom.project { + version '1.1.1' + artifactId ARTIFACT_Id + groupId GROUP_ID + packaging TYPE + description DESCRIPTION + } + } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..07b6069 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +#Maven Repo URL +MAVEN_REPO_RELEASE_URL=http://nexus.taover.com:9001/repository/maven-releases/ +MAVEN_REPO_SNAPSHOT_URL=http://nexus.taover.com:9001/repository/maven-snapshots/ + +#maven GroupId +GROUP=com.taover +#nexus ossde +NEXUS_USERNAME=dev +#nexus oss +NEXUS_PASSWORD=Nexus@dev + +# groupid +GROUP_ID=com.taover + +ARTIFACT_Id=com-taover-heartbeat + +# type +TYPE=jar + +# description +DESCRIPTION=heartbeat package \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..0d4a951 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a95009c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -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 diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..1f8a00c --- /dev/null +++ b/settings.gradle @@ -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.9/userguide/multi_project_builds.html + */ + +rootProject.name = 'com-taover-heartbeat' diff --git a/src/main/java/com/taover/heartbeat/ClientHolder.java b/src/main/java/com/taover/heartbeat/ClientHolder.java new file mode 100644 index 0000000..ec38965 --- /dev/null +++ b/src/main/java/com/taover/heartbeat/ClientHolder.java @@ -0,0 +1,14 @@ +package com.taover.heartbeat; + +import java.util.List; + +import com.taover.heartbeat.bean.ClientInstance; +import com.taover.heartbeat.bean.ClientRequest; + +interface ClientHolder { + void registryClientInstance(ClientRequest clientRequest); + + List getClientInstanceList(); + + void flushClientStatus(ClientInstance clientInstance); +} diff --git a/src/main/java/com/taover/heartbeat/ClientHolderImpl.java b/src/main/java/com/taover/heartbeat/ClientHolderImpl.java new file mode 100644 index 0000000..c454989 --- /dev/null +++ b/src/main/java/com/taover/heartbeat/ClientHolderImpl.java @@ -0,0 +1,38 @@ +package com.taover.heartbeat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.taover.heartbeat.bean.ClientInstance; +import com.taover.heartbeat.bean.ClientRequest; + +public class ClientHolderImpl implements ClientHolder{ + Map clientMap = new HashMap(); + + @Override + public List getClientInstanceList() { + return new ArrayList(clientMap.values()); + } + + @Override + public void flushClientStatus(ClientInstance clientInstance) { + clientInstance.flush(); + } + + @Override + public void registryClientInstance(ClientRequest clientRequest) { + synchronized (this.clientMap) { + ClientInstance client = null; + if(clientMap.containsKey(clientRequest.getIdentity())) { + client = clientMap.get(clientRequest.getIdentity()); + clientMap.put(client.getIdentity(), client); + }else { + client = new ClientInstance(clientRequest); + clientMap.put(client.getIdentity(), client); + } + client.dealClientRequest(clientRequest); + } + } +} diff --git a/src/main/java/com/taover/heartbeat/HeartbeatManager.java b/src/main/java/com/taover/heartbeat/HeartbeatManager.java new file mode 100644 index 0000000..635082c --- /dev/null +++ b/src/main/java/com/taover/heartbeat/HeartbeatManager.java @@ -0,0 +1,57 @@ +package com.taover.heartbeat; + +import java.util.List; + +import com.taover.heartbeat.bean.ClientInstance; +import com.taover.heartbeat.bean.ClientRequest; +import com.taover.heartbeat.bean.Instance; +import com.taover.heartbeat.bean.ReformInstance; +import com.taover.heartbeat.bean.ServerInstance; + +public class HeartbeatManager { + //客户端holder + private static ClientHolder clientHolder = new ClientHolderImpl(); + //服务端holder + private static ServerHolder serverHolder = new ServerHolderImpl(); + //通知对象 + private static ReformInstance reformData = new ReformInstance(); + + public static void registryClient(ClientRequest clientRequest) { + clientHolder.registryClientInstance(clientRequest); + } + + /** + * 刷新客户端信息 + */ + public static void flushClientStatus() { + List instances = clientHolder.getClientInstanceList(); + for(ClientInstance item: instances) { + item.flush(); + if(item.needReform()) { + HeartbeatManager.sendReform(item); + } + } + } + + public static void sendReform(Instance instance) { + reformData.doReform(instance); + } + + public static void sendServerHeartbeat() { + List instances = serverHolder.getServerInstanceList(); + for(ServerInstance item: instances) { + item.flush(); + if(item.needReform()) { + HeartbeatManager.sendReform(item); + } + } + } + + public static void registryServers(String code, String url, int fixRateSec, int maxWaitSec) throws Exception{ + serverHolder.registeServer(code, url, fixRateSec, maxWaitSec); + } + + public static void setReformData(String emailTo, String weixinWxid, String mobile) { + HeartbeatManager.reformData.loadConfig(emailTo, weixinWxid, mobile); + } +} diff --git a/src/main/java/com/taover/heartbeat/ServerHolder.java b/src/main/java/com/taover/heartbeat/ServerHolder.java new file mode 100644 index 0000000..8600e28 --- /dev/null +++ b/src/main/java/com/taover/heartbeat/ServerHolder.java @@ -0,0 +1,13 @@ +package com.taover.heartbeat; + +import java.util.List; + +import com.taover.heartbeat.bean.ServerInstance; + +interface ServerHolder { + List getServerInstanceList(); + + void registeServer(String code, String url, int fixRateSec, int maxWaitSec) throws Exception; + + void flushServerStatus(ServerInstance serverInstance); +} diff --git a/src/main/java/com/taover/heartbeat/ServerHolderImpl.java b/src/main/java/com/taover/heartbeat/ServerHolderImpl.java new file mode 100644 index 0000000..93ff8b5 --- /dev/null +++ b/src/main/java/com/taover/heartbeat/ServerHolderImpl.java @@ -0,0 +1,35 @@ +package com.taover.heartbeat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.taover.heartbeat.bean.ServerInstance; + +public class ServerHolderImpl implements ServerHolder { + Map serverMap = new HashMap(); + + @Override + public List getServerInstanceList() { + return new ArrayList(serverMap.values()); + } + + @Override + public void registeServer(String code, String url, int fixRateSec, int maxWaitSec) throws Exception { + ServerInstance server = new ServerInstance(code, url, fixRateSec, maxWaitSec); + synchronized (this.serverMap) { + if(serverMap.containsKey(server.getIdentity())) { + server = serverMap.get(server.getIdentity()); + }else { + serverMap.put(server.getIdentity(), server); + } + } + server.flush(); + } + + @Override + public void flushServerStatus(ServerInstance serverInstance) { + serverInstance.flush(); + } +} diff --git a/src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatService.java b/src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatService.java new file mode 100644 index 0000000..24e5a01 --- /dev/null +++ b/src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatService.java @@ -0,0 +1,40 @@ +package com.taover.heartbeat.adaptor; + +import javax.servlet.http.HttpServletRequest; + +import com.taover.util.bean.ResultInfo; + +public interface HttpHeartbeatService { + + /** + * 处理客户端心跳请求 + * @param clientRequest + * @return + */ + ResultInfo registryClient(HttpServletRequest request); + + /** + * 注册服务端 + * @param servers + * @param reformData + * @param fixRateSec + * @param maxWait + */ + void registryServers(String code, String servers, Integer fixRateSec, Integer maxWaitSec) throws Exception; + + /** + * 刷新客户端状态 + */ + void flushClientStatus(); + + /** + * 向服务器发送心跳 + */ + void sendServerHeartbeat(); + + /** + * 设置通知信息 + * @param reformData + */ + void setReformData(String emailTo, String weixinWxid, String mobile); +} diff --git a/src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatServiceImpl.java b/src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatServiceImpl.java new file mode 100644 index 0000000..51d3f01 --- /dev/null +++ b/src/main/java/com/taover/heartbeat/adaptor/HttpHeartbeatServiceImpl.java @@ -0,0 +1,89 @@ +package com.taover.heartbeat.adaptor; + +import javax.servlet.http.HttpServletRequest; + +import com.taover.heartbeat.HeartbeatManager; +import com.taover.heartbeat.bean.ClientRequest; +import com.taover.heartbeat.bean.Instance; +import com.taover.util.UtilLog; +import com.taover.util.bean.ResultInfo; +import com.taover.util.bean.UtilResultInfo; + +public class HttpHeartbeatServiceImpl implements HttpHeartbeatService { + public HttpHeartbeatServiceImpl() {} + + /** + * 处理客户端心跳请求 + * @param clientRequest + * @return + */ + @Override + public ResultInfo registryClient(HttpServletRequest request) { + try { + HeartbeatManager.registryClient(ClientRequest.createClientRequest(request)); + return UtilResultInfo.getSuccess(""); + }catch (Exception e) { + return UtilResultInfo.getFailure(e.getMessage()); + } + } + + /** + * 刷新客户端状态 + */ + @Override + public void flushClientStatus() { + try { + HeartbeatManager.flushClientStatus(); + }catch (Exception e) { + UtilLog.errorForException(e, this.getClass()); + } + } + + /** + * 向服务器发送心跳 + */ + @Override + public void sendServerHeartbeat() { + try { + HeartbeatManager.sendServerHeartbeat(); + }catch (Exception e) { + UtilLog.errorForException(e, this.getClass()); + } + } + + @Override + public void registryServers(String code, String servers, Integer fixRateSec, Integer maxWaitSec) throws Exception{ + if(servers == null || servers.trim().equals("")) { + return; + } + String[] serverSplit = servers.split(","); + if(code == null || code.trim().equals("")) { + throw new Exception("property[heartbeat.clientCode] is blank"); + } + if(fixRateSec == null) { + fixRateSec = Instance.DEFAULT_FIX_RATE_SEC; + } + if(maxWaitSec == null) { + maxWaitSec = Instance.DEFAULT_MAX_WAIT_SEC; + } + for(String serverItem: serverSplit) { + if(code == null || code.trim().equals("") || !serverItem.toLowerCase().startsWith("http")) { + continue; + } + try { + HeartbeatManager.registryServers(code, serverItem, fixRateSec, maxWaitSec); + }catch (Exception e) { + UtilLog.errorForException(e, this.getClass()); + } + } + } + + @Override + public void setReformData(String emailTo, String weixinWxid, String mobile) { + try { + HeartbeatManager.setReformData(emailTo, weixinWxid, mobile); + }catch (Exception e) { + UtilLog.errorForException(e, this.getClass()); + } + } +} diff --git a/src/main/java/com/taover/heartbeat/bean/ClientInstance.java b/src/main/java/com/taover/heartbeat/bean/ClientInstance.java new file mode 100644 index 0000000..4f09a3d --- /dev/null +++ b/src/main/java/com/taover/heartbeat/bean/ClientInstance.java @@ -0,0 +1,73 @@ +package com.taover.heartbeat.bean; + +public class ClientInstance extends ClientRequest implements Instance{ + public int DEFAULT_REFORM_MIN_ERROR_COUNT = 1; + public int DEFAULT_REFORM_MAX_ERROR_COUNT = 3; + + private long lastestServerUnixtime = System.currentTimeMillis()/1000; + private int lostClientRequestCount = 0; + private ClientRequest lastestClientRequest; + + public ClientInstance(ClientRequest clientRequest) { + super(clientRequest); + this.lastestClientRequest = clientRequest; + } + + @Override + public void flush() { + //还未收到请求,则无需刷新失败次数 + if(lastestClientRequest == null) { + return; + } + + //通过服务器时间,判断是否超时未收到客户端请求 + if(isLatestRequestGreateEqualFixRateSec()) { + ++this.lostClientRequestCount; + } + } + + private boolean isLatestRequestGreateEqualFixRateSec() { + return (System.currentTimeMillis()/1000 - this.lastestServerUnixtime) > this.getFixRateSec(); + } + + @Override + public boolean needReform() { + return isLatestRequestGreateEqualFixRateSec() && this.lostClientRequestCount>=DEFAULT_REFORM_MIN_ERROR_COUNT && this.lostClientRequestCount<=DEFAULT_REFORM_MAX_ERROR_COUNT; + } + + public long getLastestServerUnixtime() { + return lastestServerUnixtime; + } + + public void setLastestServerUnixtime(long lastestServerUnixtime) { + this.lastestServerUnixtime = lastestServerUnixtime; + } + + public int getLostClientRequestCount() { + return lostClientRequestCount; + } + + public void setLostClientRequestCount(int lostClientRequestCount) { + this.lostClientRequestCount = lostClientRequestCount; + } + + public ClientRequest getLastestClientRequest() { + return lastestClientRequest; + } + + public void setLastestClientRequest(ClientRequest lastestClientRequest) { + this.lastestClientRequest = lastestClientRequest; + } + + public void dealClientRequest(ClientRequest clientRequest) { + if(this.getFixRateSec() != clientRequest.getFixRateSec()) { + this.setFixRateSec(clientRequest.getFixRateSec()); + } + if(this.getMaxWaitSec() != clientRequest.getMaxWaitSec()) { + this.setMaxWaitSec(clientRequest.getMaxWaitSec()); + } + this.setLastestClientRequest(clientRequest); + this.lastestServerUnixtime = System.currentTimeMillis()/1000; + this.lostClientRequestCount = 0; + } +} diff --git a/src/main/java/com/taover/heartbeat/bean/ClientRequest.java b/src/main/java/com/taover/heartbeat/bean/ClientRequest.java new file mode 100644 index 0000000..beecbb7 --- /dev/null +++ b/src/main/java/com/taover/heartbeat/bean/ClientRequest.java @@ -0,0 +1,74 @@ +package com.taover.heartbeat.bean; + +import javax.servlet.http.HttpServletRequest; + +public class ClientRequest { + private String code = ""; + private String ip = ""; + private int maxWaitSec = Instance.DEFAULT_MAX_WAIT_SEC; + private int fixRateSec = Instance.DEFAULT_FIX_RATE_SEC; + private long unixtime; + + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getIp() { + return ip; + } + public void setIp(String ip) { + this.ip = ip; + } + public int getMaxWaitSec() { + return maxWaitSec; + } + public void setMaxWaitSec(int maxWaitSec) { + this.maxWaitSec = maxWaitSec; + } + public int getFixRateSec() { + return fixRateSec; + } + public void setFixRateSec(int fixRateSec) { + this.fixRateSec = fixRateSec; + } + public long getUnixtime() { + return unixtime; + } + public void setUnixtime(long unixtime) { + this.unixtime = unixtime; + } + + public ClientRequest(String code, String ip, int maxWaitSec, int fixRateSec, long unixtime) { + this.code = code; + this.ip = ip; + this.maxWaitSec = maxWaitSec; + this.fixRateSec = fixRateSec; + this.unixtime = unixtime; + } + + public ClientRequest(ClientRequest clientRequest) { + this.code = clientRequest.getCode(); + this.ip = clientRequest.getIp(); + this.maxWaitSec = clientRequest.getMaxWaitSec(); + this.fixRateSec = clientRequest.getFixRateSec(); + this.unixtime = clientRequest.getUnixtime(); + } + + public static ClientRequest createClientRequest(HttpServletRequest request) { + String code = request.getParameter("code"); + String maxWaitSec = request.getParameter("maxWaitSec"); + String fixRateSec = request.getParameter("fixRateSec"); + String unixtime = request.getParameter("unixtime"); + String ip = request.getRemoteHost(); + return new ClientRequest(code, ip, + maxWaitSec==null?Instance.DEFAULT_MAX_WAIT_SEC:Integer.valueOf(maxWaitSec), + fixRateSec==null?Instance.DEFAULT_FIX_RATE_SEC:Integer.valueOf(fixRateSec), + unixtime==null?System.currentTimeMillis()/1000:Long.valueOf(unixtime)); + } + + public String getIdentity() { + return this.code + "@" + this.ip; + } +} diff --git a/src/main/java/com/taover/heartbeat/bean/Instance.java b/src/main/java/com/taover/heartbeat/bean/Instance.java new file mode 100644 index 0000000..c949662 --- /dev/null +++ b/src/main/java/com/taover/heartbeat/bean/Instance.java @@ -0,0 +1,23 @@ +package com.taover.heartbeat.bean; + +public interface Instance { + public static final int DEFAULT_MAX_WAIT_SEC = -1; + public static final int DEFAULT_FIX_RATE_SEC = -1; + + /** + * 获取id字符串 + * @return + */ + String getIdentity(); + + /** + * 是否可用 + * @return + */ + boolean needReform(); + + /** + * 刷新状态 + */ + void flush(); +} diff --git a/src/main/java/com/taover/heartbeat/bean/ReformInstance.java b/src/main/java/com/taover/heartbeat/bean/ReformInstance.java new file mode 100644 index 0000000..4f2ec3a --- /dev/null +++ b/src/main/java/com/taover/heartbeat/bean/ReformInstance.java @@ -0,0 +1,204 @@ +package com.taover.heartbeat.bean; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.taover.util.UtilEmail; +import com.taover.util.UtilLog; +import com.taover.util.UtilWeixinMsg; + +public class ReformInstance { + private List emailToList; + private List weixinWxidList; + private List mobileList; + + public ReformInstance() {} + + public void doReform(Instance instance) { + if(instance == null) { + return; + } + if(this.emailToList != null && !this.emailToList.isEmpty()) { + this.sendEmail(instance); + } + if(this.weixinWxidList != null && !this.weixinWxidList.isEmpty()) { + this.sendWeixin(instance); + } + } + + private void sendWeixin(Instance instance) { + String weixinContent = "报警--项目可用性监控\n"; + if(instance instanceof ClientInstance) { + weixinContent += this.formatWeixin((ClientInstance)instance); + }else if(instance instanceof ServerInstance) { + weixinContent += this.formatWeixin((ServerInstance)instance); + }else { + try { + weixinContent += " JSON序列化:"+JSONObject.toJSONString(instance); + }catch (Exception e) { + weixinContent += " 对象无法序列化打印,instance.toString() ==>> "+instance.toString(); + } + } + try { + for(String weixinWxid: this.weixinWxidList) { + if(weixinWxid == null || weixinWxid.trim().equals("")) { + UtilWeixinMsg.sendTextMessage(weixinWxid, weixinContent); + } + } + } catch (Exception e) { + UtilLog.infoForMessage("发送微信消息失败,消息内容:"+weixinContent, this.getClass()); + UtilLog.errorForException(e, this.getClass()); + } + } + + private String formatWeixin(ServerInstance instance) { + String htmlContent = ""; + if(instance.getLatestServerResponse() == null) { + htmlContent += " 未收到服务器响应\n"; + }else { + if(instance.getLatestServerResponse().isOverdue()) { + htmlContent += " 向服务器接口["+instance.getUrl()+"]发送请求,响应超时["+instance.getMaxWaitSec()+"s],请及时确认服务器是否正常运行\n"; + }else if(!instance.getLatestServerResponse().isCodeOk()) { + htmlContent += " 向服务器接口["+instance.getUrl()+"]发送请求,响应数据不正常(详细请查看邮件),请及时确认服务器是否正常运行\n"; + }else { + htmlContent += " 向服务器接口["+instance.getUrl()+"]发送请求,未知异常,请及时确认服务器是否正常运行\n"; + } + } + htmlContent += " 最后一次请求时间: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(instance.getLatestRequestUnixtime()*1000)); + return htmlContent; + } + + private String formatWeixin(ClientInstance instance) { + String htmlContent = " 超过"+instance.getFixRateSec()+"秒未收到请求,请确认CODE["+instance.getCode()+"]对应的客户端是否运行正常\n"; + htmlContent += " 客户端最后一次请求时间: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(instance.getUnixtime()*1000)); + htmlContent += " 客户端IP:"+instance.getIp(); + return htmlContent; + } + + private void sendEmail(Instance instance) { + String subject = "报警-项目可用性监控--监控ID["+instance.getIdentity()+"]"; + String htmlContent = "

监控ID:"+instance.getIdentity()+"

"; + if(instance instanceof ClientInstance) { + htmlContent += this.formatHtml((ClientInstance)instance); + }else if(instance instanceof ServerInstance) { + htmlContent += this.formatHtml((ServerInstance)instance); + }else { + try { + htmlContent += "

"+JSONObject.toJSONString(instance)+"

"; + }catch (Exception e) { + htmlContent += "对象无法序列化打印,instance.toString() ==>> "+instance.toString()+""; + } + } + try { + UtilEmail.sendHtmlMail(emailToList, subject, htmlContent); + } catch (Exception e) { + UtilLog.infoForMessage("发送邮件失败,消息内容:[标题:"+subject+"],[正文:"+htmlContent+"]", this.getClass()); + UtilLog.errorForException(e, this.getClass()); + } + } + + private String formatHtml(ServerInstance instance) { + String htmlContent = ""; + htmlContent += "

"; + if(instance.getLatestServerResponse() != null) { + if(instance.getLatestServerResponse().isOverdue()) { + htmlContent += " 向服务器接口["+instance.getUrl()+"]发送请求,响应超时["+instance.getMaxWaitSec()+"s],请及时确认服务器是否正常运行"; + }else if(!instance.getLatestServerResponse().isCodeOk()) { + htmlContent += " 向服务器接口["+instance.getUrl()+"]发送请求,响应数据不正常(详细请查看邮件),请及时确认服务器是否正常运行"; + }else { + htmlContent += "未知异常"; + } + }else { + htmlContent += "未记录最后一次响应记录"; + } + htmlContent += "

"; + htmlContent += "
    "; + htmlContent += "
  • CODE: "+instance.getCode()+"
  • "; + htmlContent += "
  • URL: "+instance.getUrl()+"
  • "; + htmlContent += "
  • 请求频率: "+instance.getFixRateSec()+"秒/次
  • "; + htmlContent += "
  • 请求最大等待时间: "+instance.getMaxWaitSec()+"秒
  • "; + htmlContent += "
  • 客户端请求时间戳: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(instance.getLatestRequestUnixtime()*1000))+"
  • "; + htmlContent += "
  • 最后一次收到的响应包: "+JSON.toJSONString(instance.getLatestServerResponse())+"
  • "; + htmlContent += "
"; + return htmlContent; + } + + private String formatHtml(ClientInstance instance) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String htmlContent = ""; + htmlContent += "

"; + htmlContent += "超过"+instance.getFixRateSec()+"秒未收到请求,请确认CODE["+instance.getCode()+"]对应的客户端是否运行正常
"; + htmlContent += "客户端最后一次请求时间: "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(instance.getUnixtime()*1000)); + htmlContent += "

"; + htmlContent += "
    "; + htmlContent += "
  • CODE: "+instance.getCode()+"
  • "; + htmlContent += "
  • IP: "+instance.getIp()+"
  • "; + htmlContent += "
  • 请求频率: "+instance.getFixRateSec()+"秒/次
  • "; + htmlContent += "
  • 请求最大等待时间: "+instance.getMaxWaitSec()+"秒
  • "; + htmlContent += "
  • 客户端请求时间戳: "+sdf.format(new Date(instance.getUnixtime()*1000))+"
  • "; + htmlContent += "
  • 服务器接收时间: "+sdf.format(new Date(instance.getLastestServerUnixtime()*1000))+"
  • "; + htmlContent += "
  • 最后一次收到的请求包: "+JSON.toJSONString(instance.getLastestClientRequest())+"
  • "; + htmlContent += "
"; + return htmlContent; + } + + public void loadConfig(String emailTo, String weixinWxid, String mobile) { + if(strNotBlank(emailTo)) { + this.emailToList = Arrays.asList(emailTo.split(",")); + } + if(strNotBlank(weixinWxid)) { + this.weixinWxidList = Arrays.asList(weixinWxid.split(",")); + } + if(strNotBlank(mobile)) { + this.mobileList = Arrays.asList(mobile.split(",")); + } + } + + public void loadConfig(Map data) { + Object emailTo = data.get("emailTo"); + if(emailTo != null && strNotBlank(emailTo.toString())) { + this.emailToList = Arrays.asList(emailTo.toString().split(",")); + } + Object weixinWxid = data.get("weixinWxid"); + if(weixinWxid != null && strNotBlank(weixinWxid.toString())) { + this.weixinWxidList = Arrays.asList(weixinWxid.toString().split(",")); + } + Object mobile = data.get("mobile"); + if(mobile != null && strNotBlank(mobile.toString())) { + this.mobileList = Arrays.asList(mobile.toString().split(",")); + } + } + + private boolean strNotBlank(String data) { + return !(data == null || data.trim().equals("")); + } + + public List getEmailToList() { + return emailToList; + } + + public void setEmailToList(List emailToList) { + this.emailToList = emailToList; + } + + public List getWeixinWxidList() { + return weixinWxidList; + } + + public void setWeixinWxidList(List weixinWxidList) { + this.weixinWxidList = weixinWxidList; + } + + public List getMobileList() { + return mobileList; + } + + public void setMobileList(List mobileList) { + this.mobileList = mobileList; + } +} diff --git a/src/main/java/com/taover/heartbeat/bean/ServerInstance.java b/src/main/java/com/taover/heartbeat/bean/ServerInstance.java new file mode 100644 index 0000000..503060b --- /dev/null +++ b/src/main/java/com/taover/heartbeat/bean/ServerInstance.java @@ -0,0 +1,128 @@ +package com.taover.heartbeat.bean; + +import java.net.SocketTimeoutException; + +import com.taover.util.UtilHttpByOkHttp; + +public class ServerInstance implements Instance { + public int DEFAULT_REFORM_MIN_ERROR_COUNT = 1; + public int DEFAULT_REFORM_MAX_ERROR_COUNT = 3; + + private String code; + private String url; + private int fixRateSec; + private int maxWaitSec; + private long latestRequestUnixtime; + private ServerResponse latestServerResponse; + private int errorServerResponseCount; + + public ServerInstance(String code, String url, int fixRateSec, int maxWaitSec) throws Exception { + super(); + if(fixRateSec < 60) { + throw new Exception("固定时间间隔不允许小于60s"); + } + + this.code = code; + this.url = url; + this.fixRateSec = fixRateSec; + this.maxWaitSec = maxWaitSec; + } + + @Override + public void flush() { + //如果小于设置的发送时间间隔,则不发送请求 + if(!this.isLatestRequestGreateEqualFixRateSec()) { + return; + } + this.latestRequestUnixtime = System.currentTimeMillis()/1000; + try { + this.latestServerResponse = ServerResponse.createByJSONString(UtilHttpByOkHttp.sendGet(this.url + this.getUrlParams(), null, maxWaitSec)); + if(this.latestServerResponse.isCodeOk()) { + this.errorServerResponseCount = 0; + } + } catch(SocketTimeoutException timeError) { + this.latestServerResponse = ServerResponse.createOverdue(); + } catch (Exception e) { + this.latestServerResponse = ServerResponse.createException(e); + } + if(this.latestServerResponse.isOverdue() || !this.latestServerResponse.isCodeOk()) { + ++this.errorServerResponseCount; + } + } + + private String getUrlParams() { + return "?code="+this.code+"&fixRateSec="+this.fixRateSec+"&maxWaitSec="+this.maxWaitSec+"&unixtime="+(System.currentTimeMillis()/1000); + } + + private boolean isLatestRequestGreateEqualFixRateSec() { + return (System.currentTimeMillis()/1000 - this.latestRequestUnixtime) >= this.fixRateSec; + } + + @Override + public String getIdentity() { + return this.code + "@" + this.url; + } + + @Override + public boolean needReform() { + return this.isLatestRequestGreateEqualFixRateSec() + && this.errorServerResponseCount>=DEFAULT_REFORM_MIN_ERROR_COUNT + && this.errorServerResponseCount<=DEFAULT_REFORM_MAX_ERROR_COUNT; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getFixRateSec() { + return fixRateSec; + } + + public void setFixRateSec(int fixRateSec) { + this.fixRateSec = fixRateSec; + } + + public int getMaxWaitSec() { + return maxWaitSec; + } + + public void setMaxWaitSec(int maxWaitSec) { + this.maxWaitSec = maxWaitSec; + } + + public long getLatestRequestUnixtime() { + return latestRequestUnixtime; + } + + public void setLatestRequestUnixtime(long latestRequestUnixtime) { + this.latestRequestUnixtime = latestRequestUnixtime; + } + + public ServerResponse getLatestServerResponse() { + return latestServerResponse; + } + + public void setLatestServerResponse(ServerResponse latestServerResponse) { + this.latestServerResponse = latestServerResponse; + } + + public int getLostServerResponseCount() { + return errorServerResponseCount; + } + + public void setLostServerResponseCount(int lostServerResponseCount) { + this.errorServerResponseCount = lostServerResponseCount; + } +} diff --git a/src/main/java/com/taover/heartbeat/bean/ServerResponse.java b/src/main/java/com/taover/heartbeat/bean/ServerResponse.java new file mode 100644 index 0000000..e3e4815 --- /dev/null +++ b/src/main/java/com/taover/heartbeat/bean/ServerResponse.java @@ -0,0 +1,73 @@ +package com.taover.heartbeat.bean; + +import org.apache.commons.lang.StringUtils; + +import com.alibaba.fastjson.JSONObject; + +public class ServerResponse { + public static String RESPONSE_CODE_OK = "ok"; + + private boolean isOverdue; + private boolean isCodeOk; + private String responseContent; + private Exception exception; + + private ServerResponse(boolean isOverdue, boolean isCodeOk, String responseContent, Exception e) { + super(); + this.isOverdue = isOverdue; + this.isCodeOk = isCodeOk; + this.responseContent = responseContent; + this.exception = e; + } + + public boolean isOverdue() { + return isOverdue; + } + public void setOverdue(boolean isOverdue) { + this.isOverdue = isOverdue; + } + public boolean isCodeOk() { + return isCodeOk; + } + public void setCodeOk(boolean isCodeOk) { + this.isCodeOk = isCodeOk; + } + public String getResponseContent() { + return responseContent; + } + public void setResponseContent(String responseContent) { + this.responseContent = responseContent; + } + public Exception getException() { + return exception; + } + public void setException(Exception exception) { + this.exception = exception; + } + + public static ServerResponse createByJSONString(String response) { + if(StringUtils.isBlank(response)) { + return new ServerResponse(false, false, response, null); + }else { + try { + JSONObject data = JSONObject.parseObject(response); + String code = data.getString("code"); + if(RESPONSE_CODE_OK.equals(code.toLowerCase())) { + return new ServerResponse(false, true, response, null); + }else { + return new ServerResponse(false, false, response, null); + } + }catch (Exception e) { + return new ServerResponse(false, false, response, e); + } + } + } + + public static ServerResponse createOverdue() { + return new ServerResponse(false, false, "", null); + } + + public static ServerResponse createException(Exception e) { + return new ServerResponse(false, false, "", e); + } +} -- libgit2 0.21.2