Browse Source

创建eoss项目

csk 4 years ago
commit
d53958d000
86 changed files with 9771 additions and 0 deletions
  1. 37 0
      .gitignore
  2. 40 0
      README.MD
  3. 80 0
      build.gradle
  4. 185 0
      gradlew
  5. 89 0
      gradlew.bat
  6. 1 0
      settings.gradle
  7. 13 0
      src/main/java/com/emato/eoss/EossApplication.java
  8. 13 0
      src/main/java/com/emato/eoss/ServletInitializer.java
  9. 266 0
      src/main/java/com/emato/eoss/api/OssApi.java
  10. 30 0
      src/main/java/com/emato/eoss/api/Test.java
  11. 33 0
      src/main/java/com/emato/eoss/config/WebConfigration.java
  12. 86 0
      src/main/java/com/emato/eoss/manager/authz/common/authc/BasicCredentials.java
  13. 46 0
      src/main/java/com/emato/eoss/manager/authz/common/authc/Credentials.java
  14. 29 0
      src/main/java/com/emato/eoss/manager/authz/common/authc/RequestSigner.java
  15. 97 0
      src/main/java/com/emato/eoss/manager/authz/common/authc/ServiceSignature.java
  16. 42 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/HttpHeaderSupport.java
  17. 45 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/HttpHeaders.java
  18. 70 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/HttpMesssage.java
  19. 91 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/HttpRequestHeader.java
  20. 103 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/HttpResponseHeader.java
  21. 42 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/Protocol.java
  22. 173 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/RequestMessage.java
  23. 30 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/ResponseHandler.java
  24. 85 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/ResponseMessage.java
  25. 6 0
      src/main/java/com/emato/eoss/manager/authz/common/comm/SignVersion.java
  26. 187 0
      src/main/java/com/emato/eoss/manager/authz/common/model/Callback.java
  27. 84 0
      src/main/java/com/emato/eoss/manager/authz/common/model/WebServiceRequest.java
  28. 61 0
      src/main/java/com/emato/eoss/manager/authz/common/utils/BinaryUtil.java
  29. 98 0
      src/main/java/com/emato/eoss/manager/authz/common/utils/DateUtil.java
  30. 123 0
      src/main/java/com/emato/eoss/manager/authz/common/utils/HttpUtil.java
  31. 74 0
      src/main/java/com/emato/eoss/manager/authz/common/utils/LogUtils.java
  32. 51 0
      src/main/java/com/emato/eoss/manager/authz/common/utils/ResourceManager.java
  33. 826 0
      src/main/java/com/emato/eoss/manager/oss/minio/ClientConfiguration.java
  34. 64 0
      src/main/java/com/emato/eoss/manager/oss/minio/ClientErrorCode.java
  35. 160 0
      src/main/java/com/emato/eoss/manager/oss/minio/ClientException.java
  36. 55 0
      src/main/java/com/emato/eoss/manager/oss/minio/HttpMethod.java
  37. 77 0
      src/main/java/com/emato/eoss/manager/oss/minio/MinioTest.java
  38. 299 0
      src/main/java/com/emato/eoss/manager/oss/minio/OSSErrorCode.java
  39. 85 0
      src/main/java/com/emato/eoss/manager/oss/minio/OSSException.java
  40. 231 0
      src/main/java/com/emato/eoss/manager/oss/minio/ServiceException.java
  41. 45 0
      src/main/java/com/emato/eoss/manager/oss/minio/config/MinioConfig.java
  42. 72 0
      src/main/java/com/emato/eoss/manager/oss/minio/config/MinioProp.java
  43. 56 0
      src/main/java/com/emato/eoss/manager/oss/minio/internal/OSSConstants.java
  44. 105 0
      src/main/java/com/emato/eoss/manager/oss/minio/internal/OSSHeaders.java
  45. 64 0
      src/main/java/com/emato/eoss/manager/oss/minio/internal/OSSRequestSigner.java
  46. 411 0
      src/main/java/com/emato/eoss/manager/oss/minio/internal/OSSUtils.java
  47. 689 0
      src/main/java/com/emato/eoss/manager/oss/minio/utils/MinioUtils.java
  48. 32 0
      src/main/java/com/emato/eoss/msg/MessageToResultAdapt.java
  49. 68 0
      src/main/java/com/emato/eoss/msg/req/OutRequestMessage.java
  50. 46 0
      src/main/java/com/emato/eoss/msg/req/RequestOutMessage.java
  51. 155 0
      src/main/java/com/emato/eoss/msg/resp/BizData.java
  52. 40 0
      src/main/java/com/emato/eoss/msg/resp/ResponseStatus.java
  53. 247 0
      src/main/java/com/emato/eoss/msg/resp/Result.java
  54. 57 0
      src/main/java/com/emato/eoss/msg/resp/ResultException.java
  55. 168 0
      src/main/java/com/emato/eoss/msg/service/Message.java
  56. 35 0
      src/main/java/com/emato/eoss/msg/service/MessageStatus.java
  57. 27 0
      src/main/java/com/emato/eoss/util/date/DateConstant.java
  58. 505 0
      src/main/java/com/emato/eoss/util/date/DateUtils.java
  59. 236 0
      src/main/java/com/emato/eoss/util/date/LocalDateTimeUtils.java
  60. 32 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/LocalDateTimeToStringConverter.java
  61. 32 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/LocalDateToStringConverter.java
  62. 32 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/LocalTimeToStringConverter.java
  63. 59 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/StringToLocalDateConverter.java
  64. 61 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/StringToLocalDateTimeConverter.java
  65. 62 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/StringToLocalTimeConverter.java
  66. 68 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/deser/LocalDateDeserializer.java
  67. 71 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/deser/LocalDateTimeDeserializer.java
  68. 71 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/deser/LocalTimeDeserializer.java
  69. 33 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/ser/LocalDateSerializer.java
  70. 33 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/ser/LocalDateTimeSerializer.java
  71. 33 0
      src/main/java/com/emato/eoss/util/date/cvt/ldt/ser/LocalTimeSerializer.java
  72. 82 0
      src/main/java/com/emato/eoss/util/date/cvt/ser/DateTimeDeserializer.java
  73. 34 0
      src/main/java/com/emato/eoss/util/date/cvt/ser/DateTimeSerializer.java
  74. 32 0
      src/main/java/com/emato/eoss/util/date/cvt/tmst/DateTimestampEditor.java
  75. 24 0
      src/main/java/com/emato/eoss/util/date/cvt/tmst/DateTimestampWebBindingInitializer.java
  76. 69 0
      src/main/java/com/emato/eoss/util/jackson/JacksonStringUnicodeSerializer.java
  77. 385 0
      src/main/java/com/emato/eoss/util/jackson/JacksonUtils.java
  78. 254 0
      src/main/java/com/emato/eoss/util/sign/Dsa.java
  79. 225 0
      src/main/java/com/emato/eoss/util/sign/Md5.java
  80. 381 0
      src/main/java/com/emato/eoss/util/sign/Rsa.java
  81. 85 0
      src/main/java/com/emato/eoss/util/sign/Test.java
  82. 30 0
      src/main/java/com/emato/eoss/web/OssFilter.java
  83. 19 0
      src/main/resources/application-dev.yml
  84. 18 0
      src/main/resources/application-test.yml
  85. 49 0
      src/main/resources/application.yml
  86. 232 0
      src/main/resources/logback.xml

+ 37 - 0
.gitignore

@@ -0,0 +1,37 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/

+ 40 - 0
README.MD

@@ -0,0 +1,40 @@
+# oss
+
+## minio
+1. 安装,参见`https://docs.min.io/docs/minio-quickstart-guide.html`;
+    + docker安装
+        ```
+        docker run -p 9000:9000 minio/minio server <your_storage_of_file_directory>
+        ```
+2. 启动
+   ```
+   minio.exe server <your_storage_of_file_directory>
+   ```
+3. 默认访问,`http://127.0.0.1:9000/`;
+4. 存储桶`bucket`命名有一定规则
+    + 小写字母或数字开头;
+    + 长度介于 3 和 63 个字符之间,并且只能包含小写字母、数字、句点和短划线;
+    + 不能是IP地址;
+    + 其它参见相关文档;
+    
+## mc客户端
+1. 安装,参见`https://docs.min.io/docs/minio-client-quickstart-guide.html`;
+2. 访问`minio server`,必须先添加一个服务别名
+   + 命令
+      ```
+      mc alias set <ALIAS> <YOUR-S3-ENDPOINT> <YOUR-ACCESS-KEY> <YOUR-SECRET-KEY> --api <API-SIGNATURE> --path <BUCKET-LOOKUP-TYPE>
+      ```
+   + 示例,添加foo作为`http://127.0.0.1:9000`的别名。
+      ```
+      mc alias set foo http://127.0.0.1:9000 BKIKJAA5BMMU2RHO6IBB V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12
+      ```
+   + Minio允许常规字符串作为Access和Secret密钥。  
+      ```
+      mc alias set foo http://127.0.0.1:9000 minioadmin minioadmin
+      ```
+3. 使用mc
+    + 列出存储桶`bucket`或其中`bar`文件夹的文件
+        ```
+        mc ls foo
+        mc ls foo/bar
+        ```

+ 80 - 0
build.gradle

@@ -0,0 +1,80 @@
+plugins {
+    id 'org.springframework.boot' version '2.4.5'
+    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
+    id 'java'
+    id 'war'
+}
+
+group = 'com.emato'
+version = '1.0.0'
+sourceCompatibility = '1.8'
+
+repositories {
+    // 本地仓库
+    mavenLocal()
+    // ali 代理的central仓和jcenter仓的聚合仓
+    maven {url 'https://maven.aliyun.com/repository/public/'}
+    maven {url 'https://maven.aliyun.com/repository/gradle-plugin/'}
+    maven {url 'https://maven.aliyun.com/repository/spring/'}
+    maven {url 'https://maven.aliyun.com/repository/spring-plugin/'}
+    maven {url 'https://maven.aliyun.com/repository/google/'}
+    maven {url 'https://maven.aliyun.com/repository/grails-core/'}
+
+    maven {url 'https://repository.jboss.org/nexus/content/repositories/releases/'}
+    maven {url 'https://plugins.gradle.org/m2/'}
+    maven {url 'https://repo.spring.io/libs-milestone/'}
+    maven {url 'https://repo.spring.io/plugins-release/'}
+    maven {url 'https://repo.grails.org/grails/core/'}
+    maven {url 'https://repository.apache.org/content/groups/public/'}
+    // 远程中央仓库
+    mavenCentral()
+    maven {url 'https://repo1.maven.org/maven2/'}
+}
+
+ext {
+    junit = '4.13'
+
+    minio = '8.1.0'
+
+    // logback对spring的支持
+    logback_ext_spring = '0.1.4'
+
+    // Java工具包类库,各种Util工具类
+    hutool = '5.6.2'
+
+    commons_beanutils = '1.9.3'
+    commons_codec = '1.13'
+    commons_io = '2.6'
+    commons_lang3 = '3.6'
+    commons_pool2 = '2.6.2'
+
+    apache_httpclient = '4.5.4'
+
+}
+
+dependencies {
+    testImplementation("junit:junit:${junit}")
+
+    implementation 'org.springframework.boot:spring-boot-starter-web'
+    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
+    testImplementation 'org.springframework.boot:spring-boot-starter-test'
+
+    implementation "io.minio:minio:${minio}"
+
+    // Java工具包类库,各种Util工具类
+    implementation("cn.hutool:hutool-all:${hutool}")
+
+    implementation "commons-beanutils:commons-beanutils:${commons_beanutils}"
+    implementation "commons-codec:commons-codec:${commons_codec}"
+    implementation "commons-io:commons-io:${commons_io}"
+    implementation "org.apache.commons:commons-lang3:${commons_lang3}"
+    // for springLettuceStandalonePoolingClientProperties()
+    implementation "org.apache.commons:commons-pool2:${commons_pool2}"
+
+    implementation "org.apache.httpcomponents:httpclient:${apache_httpclient}"
+
+}
+
+test {
+    useJUnitPlatform()
+}

+ 185 - 0
gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+#      https://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.
+#
+
+##############################################################################
+##
+##  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='"-Xmx64m" "-Xms64m"'
+
+# 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 or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@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 Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@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="-Xmx64m" "-Xms64m"
+
+@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 execute
+
+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 execute
+
+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
+
+: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 %*
+
+: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

+ 1 - 0
settings.gradle

@@ -0,0 +1 @@
+rootProject.name = 'eoss'

+ 13 - 0
src/main/java/com/emato/eoss/EossApplication.java

@@ -0,0 +1,13 @@
+package com.emato.eoss;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class EossApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(EossApplication.class, args);
+    }
+
+}

+ 13 - 0
src/main/java/com/emato/eoss/ServletInitializer.java

@@ -0,0 +1,13 @@
+package com.emato.eoss;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+public class ServletInitializer extends SpringBootServletInitializer {
+
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+        return application.sources(EossApplication.class);
+    }
+
+}

+ 266 - 0
src/main/java/com/emato/eoss/api/OssApi.java

@@ -0,0 +1,266 @@
+package com.emato.eoss.api;
+
+import com.emato.eoss.manager.oss.minio.utils.MinioUtils;
+import com.emato.eoss.msg.resp.Result;
+import io.minio.ObjectWriteResponse;
+import okhttp3.Headers;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 文件存储API
+ *
+ * @author Scott Chen
+ * @date 2021-03-09
+ */
+@RestController
+public class OssApi {
+    private static final Logger logger = LoggerFactory.getLogger(OssApi.class);
+
+    @Autowired
+    private MinioUtils minioUtils;
+
+    @Value("${minio.buckets.product}")
+    private String bucketName;
+
+    /**
+     * 上传文件
+     * MultipartFile 方式上传
+     *
+     * @param file 上传文件
+     * @param bucket OSS存储桶
+     * @param request
+     * @param response
+     * @return
+     */
+    @RequestMapping("/upload")
+    public Result upload(@RequestParam(value="file") MultipartFile file,
+                         @RequestParam(value="bucket") String bucket,
+                         HttpServletRequest request, HttpServletResponse response) {
+
+        bucket = StringUtils.isBlank(bucket) ? bucketName : bucket;
+
+        String objectName = file.getOriginalFilename();
+        if (StringUtils.isBlank(objectName)) {
+            return Result.error("上传文件名为空");
+        }
+
+        String contentType = file.getContentType();
+
+        try{
+            InputStream is = file.getInputStream();
+            ObjectWriteResponse owr = minioUtils.putFileWithInputStream(bucket, objectName, is,
+                    null, null, contentType);
+
+            String region = owr.region();
+            String object = owr.object();
+            Headers headers = owr.headers();
+
+            logger.info("{}上传到{}成功。", object, bucket);
+
+            Map<String, String> map = new HashMap<>();
+            map.put("bucket", bucket);
+            map.put("fileName", object);
+            return Result.success(map);
+        } catch (Exception e) {
+            String info = "上传文件" + objectName + "出错";
+            logger.error(info);
+            return Result.error(info);
+        }
+    }
+
+    /**
+     * 上传文件
+     * 流方式上传
+     *
+     * @param bucket OSS存储桶
+     * @param objectName OSS存储对象
+     * @param byteFile 上传文件字节码
+     * @param request
+     * @param response
+     * @return
+     */
+    @RequestMapping("/uploadWithByte")
+    public Result upload(@RequestParam(value="bucket") String bucket,
+                         @RequestParam(value="objectName") String objectName,
+                         @RequestParam(value="inputStream") byte[] byteFile,
+                         HttpServletRequest request, HttpServletResponse response) {
+        try{
+            bucket = StringUtils.isBlank(bucket) ? bucketName : bucket;
+
+            if (byteFile == null) {
+                return Result.error("上传文件byteFile为空");
+            }
+            ByteArrayInputStream inputStream = new ByteArrayInputStream(byteFile);
+
+            ObjectWriteResponse owr = minioUtils.putFileWithInputStream(bucket, objectName, inputStream,
+                    null, null, null);
+
+            String region = owr.region();
+            String object = owr.object();
+            Headers headers = owr.headers();
+
+            logger.info("{}上传到{}成功。", object, bucket);
+
+            Map<String, String> map = new HashMap<>();
+            map.put("bucket", bucket);
+            map.put("fileName", object);
+            return Result.success(map);
+        } catch (Exception e) {
+            String info = "上传文件" + objectName + "出错";
+            logger.error(info);
+            return Result.error(info);
+        }
+    }
+
+    /**
+     * 上传文件
+     * 流方式上传
+     *
+     * @param bucket OSS存储桶
+     * @param objectName OSS存储对象
+     * @param inputStream 上传文件流
+     * @param request
+     * @param response
+     * @return
+     */
+    @RequestMapping("/uploadWithInputStream")
+    public Result upload(@RequestParam(value="bucket") String bucket,
+                        @RequestParam(value="objectName") String objectName,
+                        @RequestParam(value="inputStream") InputStream inputStream,
+                         HttpServletRequest request, HttpServletResponse response) {
+        try{
+            bucket = StringUtils.isBlank(bucket) ? bucketName : bucket;
+
+            ObjectWriteResponse owr = minioUtils.putFileWithInputStream(bucket, objectName, inputStream,
+                    null, null, null);
+
+            String region = owr.region();
+            String object = owr.object();
+            Headers headers = owr.headers();
+
+            logger.info("{}上传到{}成功。", object, bucket);
+
+            Map<String, String> map = new HashMap<>();
+            map.put("bucket", bucket);
+            map.put("fileName", object);
+            return Result.success(map);
+        } catch (Exception e) {
+            String info = "上传文件" + objectName + "出错";
+            logger.error(info);
+            return Result.error(info);
+        }
+    }
+
+    /**
+     * 下载文件
+     *
+     * @param bucket OSS存储桶
+     * @param object OSS存储对象
+     * @param outputFileName 另存为文件名
+     * @param request
+     * @param response
+     * @return
+     */
+    @RequestMapping("/download")
+    public Result download(@RequestParam(value="bucket") String bucket,
+                           @RequestParam(value="object") String object,
+                           @RequestParam(value="outputFileName", required=false) String outputFileName,
+                           HttpServletRequest request, HttpServletResponse response) {
+        if (StringUtils.isBlank(bucket)) {
+            return Result.error("bucket为空");
+        }
+        if (StringUtils.isBlank(object)) {
+            return Result.error("object为空");
+        }
+
+        if (StringUtils.isBlank(outputFileName)) {
+            outputFileName = object;
+        }
+
+        OutputStream os = null;
+        try{
+            byte[] buffer = minioUtils.getObjectWithByte(bucket, object);
+            logger.info("从{}下载{}文件成功,另存为{}", bucket, object, outputFileName);
+
+            response.setHeader("Content-Disposition", "attachment;filename=" + outputFileName);
+            response.setCharacterEncoding("UTF-8");
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+
+            os = response.getOutputStream();
+            os.write(buffer);
+            os.close();
+            return null;
+        } catch (Exception e) {
+            String info = "下载文件" + object + "出错";
+            logger.error(info, e);
+            return Result.error(info);
+        } finally {
+            if (os != null) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    logger.error("下载文件" + object + ",关闭OutputStream异常", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 下载到服务端指定路径
+     * 服务端指定路径为OSS系统部署的服务端
+     *
+     * @param bucket OSS存储桶
+     * @param object OSS存储对象
+     * @param targetPath 另存为路径
+     * @param targetFileName 另存为文件名
+     * @param request
+     * @param response
+     * @return
+     */
+    @RequestMapping("/downloadObject")
+    public Result downloadObject(@RequestParam(value="bucket") String bucket,
+                       @RequestParam(value="object") String object,
+                       @RequestParam(value="targetPath", required=false) String targetPath,
+                       @RequestParam(value="targetFileName", required=false) String targetFileName,
+                           HttpServletRequest request, HttpServletResponse response) {
+        if (StringUtils.isBlank(bucket)) {
+            return Result.error("bucket为空");
+        }
+        if (StringUtils.isBlank(object)) {
+            return Result.error("object为空");
+        }
+
+        File file = new File(targetPath);
+        if (!file.exists()) {
+            file.mkdirs();
+        }
+        // 输出文件全路径
+        String outputFullPath = targetPath + File.pathSeparator + targetFileName;
+
+        try{
+            minioUtils.downloadObject(
+                    bucket, object, outputFullPath);
+
+            logger.info("从{}下载{}文件成功,已另存为{}", bucket, object, outputFullPath);
+            return Result.success("从" + bucket + "下载" + object + "文件成功,已另存为" + outputFullPath);
+        } catch (Exception e) {
+            String info = "下载文件" + object + "并另存为" + outputFullPath + "出错";
+            logger.error(info);
+            return Result.error(info);
+        }
+    }
+
+}

+ 30 - 0
src/main/java/com/emato/eoss/api/Test.java

@@ -0,0 +1,30 @@
+package com.emato.eoss.api;
+
+import com.emato.eoss.msg.resp.Result;
+import com.emato.eoss.util.jackson.JacksonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+/**
+ * @author Scott Chen
+ * @date 2021-03-02
+ */
+@RestController
+public class Test {
+    private static final Logger logger = LoggerFactory.getLogger(Test.class);
+
+    @RequestMapping("/queryOmsInfo/queryInveMng")
+    public Result test(@RequestBody Map<String, String> params) throws InterruptedException{
+        logger.debug("---请求处理---");
+        String a = JacksonUtils.toJsonStr(params);
+        logger.debug("入参:" + a);
+        Thread.sleep(2000);
+        return Result.success("okttp test");
+    }
+}

+ 33 - 0
src/main/java/com/emato/eoss/config/WebConfigration.java

@@ -0,0 +1,33 @@
+package com.emato.eoss.config;
+
+import com.emato.eoss.web.OssFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Scott Chen
+ * @date 2021-03-29
+ */
+@Configuration
+public class WebConfigration {
+    private static final Logger logger = LoggerFactory.getLogger(WebConfigration.class);
+
+    @Bean
+    public OssFilter ossFilter(){
+        return new OssFilter();
+    }
+
+    @Bean
+    public FilterRegistrationBean urlFilterRegister() {
+        FilterRegistrationBean filterBean = new FilterRegistrationBean();
+        filterBean.setFilter(ossFilter());
+        filterBean.addUrlPatterns("/*");
+
+        logger.debug("--- 创建OssFilter Bean");
+        return filterBean;
+    }
+
+}

+ 86 - 0
src/main/java/com/emato/eoss/manager/authz/common/authc/BasicCredentials.java

@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.authc;
+
+
+public class BasicCredentials implements Credentials {
+
+    public BasicCredentials(String accessKeyId, String accessKeySecret, String securityToken) {
+        this(accessKeyId, accessKeySecret, securityToken, 0);
+    }
+
+    public BasicCredentials(String accessKeyId, String accessKeySecret, String securityToken,
+                            long expiredDurationSeconds) {
+        this.accessKeyId = accessKeyId;
+        this.accessKeySecret = accessKeySecret;
+        this.securityToken = securityToken;
+        this.expiredDurationSeconds = expiredDurationSeconds;
+        this.startedTimeInMilliSeconds = System.currentTimeMillis();
+    }
+
+    public BasicCredentials withExpiredFactor(double expiredFactor) {
+        this.expiredFactor = expiredFactor;
+        return this;
+    }
+
+    public BasicCredentials withExpiredDuration(long expiredDurationSeconds) {
+        this.expiredDurationSeconds = expiredDurationSeconds;
+        return this;
+    }
+
+    @Override
+    public String getAccessKeyId() {
+        return accessKeyId;
+    }
+
+    @Override
+    public String getSecretAccessKey() {
+        return accessKeySecret;
+    }
+
+    @Override
+    public String getSecurityToken() {
+        return securityToken;
+    }
+
+    @Override
+    public boolean useSecurityToken() {
+        return this.securityToken != null;
+    }
+
+    public boolean willSoonExpire() {
+        if (expiredDurationSeconds == 0) {
+            return false;
+        }
+        long now = System.currentTimeMillis();
+        return expiredDurationSeconds * expiredFactor < (now - startedTimeInMilliSeconds) / 1000.0;
+    }
+
+    public static final double DEFAULT_EXPIRED_FACTOR = 0.8;
+
+    protected String accessKeyId;
+    protected String accessKeySecret;
+    protected String securityToken;
+
+    protected long expiredDurationSeconds;
+    protected long startedTimeInMilliSeconds = 0;
+    protected double expiredFactor = DEFAULT_EXPIRED_FACTOR;
+
+}

+ 46 - 0
src/main/java/com/emato/eoss/manager/authz/common/authc/Credentials.java

@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.authc;
+
+/**
+ * Provides access to credentials used for accessing OSS, these credentials are
+ * used to securely sign requests to OSS.
+ */
+public interface Credentials {
+    /**
+     * Returns the access key ID for this credentials.
+     */
+    public String getAccessKeyId();
+
+    /**
+     * Returns the secret access key for this credentials.
+     */
+    public String getSecretAccessKey();
+
+    /**
+     * Returns the security token for this credentials.
+     */
+    public String getSecurityToken();
+
+    /**
+     * Determines whether to use security token for http requests.
+     */
+    public boolean useSecurityToken();
+}

+ 29 - 0
src/main/java/com/emato/eoss/manager/authz/common/authc/RequestSigner.java

@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.authc;
+
+import com.emato.eoss.manager.authz.common.comm.RequestMessage;
+import com.emato.eoss.manager.oss.minio.ClientException;
+
+public interface RequestSigner {
+
+    public void sign(RequestMessage request) throws ClientException;
+
+}

+ 97 - 0
src/main/java/com/emato/eoss/manager/authz/common/authc/ServiceSignature.java

@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.authc;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * The interface to compute the signature of the data.
+ */
+public abstract class ServiceSignature {
+
+    /**
+     * Gets the algorithm of signature.
+     * 
+     * @return The algorithm of the signature.
+     */
+    public abstract String getAlgorithm();
+
+    /**
+     * Gets the algorithm version.
+     * 
+     * @return The algorithm version.
+     */
+    public abstract String getVersion();
+
+    /**
+     * Computes the signature of the data by the given key.
+     * 
+     * @param key
+     *            The key for the signature.
+     * @param data
+     *            The data to compute the signature on.
+     * @return The signature in string.
+     */
+    public abstract String computeSignature(String key, String data);
+
+    /**
+     *
+     * Creates the default <code>ServiceSignature</code> instance which is
+     * {@link HmacSHA1Signature}.
+     * 
+     * @return The default <code>ServiceSignature</code> instance
+     */
+    public static ServiceSignature create() {
+        return new HmacSHA1Signature();
+    }
+
+    protected byte[] sign(byte[] key, byte[] data, Mac macInstance, Object lock, String algorithm) {
+        try {
+            // Because Mac.getInstance(String) calls a synchronized method, it
+            // could block on
+            // invoked concurrently, so use prototype pattern to improve perf.
+            if (macInstance == null) {
+                synchronized (lock) {
+                    if (macInstance == null) {
+                        macInstance = Mac.getInstance(algorithm);
+                    }
+                }
+            }
+
+            Mac mac;
+            try {
+                mac = (Mac) macInstance.clone();
+            } catch (CloneNotSupportedException e) {
+                // If it is not clonable, create a new one.
+                mac = Mac.getInstance(algorithm);
+            }
+            mac.init(new SecretKeySpec(key, algorithm));
+            return mac.doFinal(data);
+        } catch (NoSuchAlgorithmException ex) {
+            throw new RuntimeException("Unsupported algorithm: " + algorithm, ex);
+        } catch (InvalidKeyException ex) {
+            throw new RuntimeException("Invalid key: " + key, ex);
+        }
+    }
+
+}

+ 42 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/HttpHeaderSupport.java

@@ -0,0 +1,42 @@
+package com.emato.eoss.manager.authz.common.comm;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * @author Scott Chen
+ * @date 2021-03-16
+ */
+public class HttpHeaderSupport {
+
+    public static HttpRequestHeader buildOssRequestHeader(HttpServletRequest req, HttpServletResponse resp) {
+        HttpRequestHeader mate = new HttpRequestHeader();
+        mate.setAuthorization(req.getHeader(HttpHeaders.AUTHORIZATION));
+        mate.setContentLength(req.getHeader(HttpHeaders.CONTENT_LENGTH));
+        mate.setContentType(req.getHeader(HttpHeaders.CONTENT_TYPE));
+        // The RFC-1123 date-time formatter, such as 'Tue, 3 Jun 2008 11:05:30 GMT'
+        mate.setDate(req.getHeader(HttpHeaders.DATE));
+        mate.setHost(req.getHeader(HttpHeaders.HOST));
+        return mate;
+    }
+
+    public static HttpServletResponse buildOssResponseHeader(HttpResponseHeader httpResponseHeader, HttpServletRequest req, HttpServletResponse resp) {
+        resp.setHeader(HttpHeaders.CONTENT_LENGTH, httpResponseHeader.getContentLength());
+        resp.setHeader(HttpHeaders.CONNECTION, httpResponseHeader.getConnection());
+        // The RFC-1123 date-time formatter, such as 'Tue, 3 Jun 2008 11:05:30 GMT'
+        // resp.setHeader("Date", DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now()));
+        resp.setHeader(HttpHeaders.DATE, new SimpleDateFormat().format(new Date()));
+
+        resp.setHeader(HttpHeaders.ETAG, httpResponseHeader.getETag());
+        resp.setHeader("Server", httpResponseHeader.getServer());
+        resp.setHeader("x-oss-request-id", UUID.randomUUID().toString());
+        return resp;
+    }
+
+
+
+
+}

+ 45 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/HttpHeaders.java

@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.comm;
+
+/*
+ * A collection of common HTTP headers.
+ */
+public interface HttpHeaders {
+
+    String AUTHORIZATION = "Authorization";
+    String CACHE_CONTROL = "Cache-Control";
+    String CONTENT_DISPOSITION = "Content-Disposition";
+    String CONTENT_ENCODING = "Content-Encoding";
+    String CONTENT_LENGTH = "Content-Length";
+    String CONTENT_MD5 = "Content-MD5";
+    String CONTENT_TYPE = "Content-Type";
+    String TRANSFER_ENCODING = "Transfer-Encoding";
+    String DATE = "Date";
+    String ETAG = "ETag";
+    String EXPIRES = "Expires";
+    String HOST = "Host";
+    String LAST_MODIFIED = "Last-Modified";
+    String RANGE = "Range";
+    String LOCATION = "Location";
+    String CONNECTION = "Connection";
+
+
+}

+ 70 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/HttpMesssage.java

@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.comm;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Common class for both HTTP request and HTTP response.
+ */
+public abstract class HttpMesssage {
+
+    private Map<String, String> headers = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);;
+    private InputStream content;
+    private long contentLength;
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    public void setHeaders(Map<String, String> headers) {
+        this.headers = headers;
+    }
+
+    public void addHeader(String key, String value) {
+        this.headers.put(key, value);
+    }
+
+    public InputStream getContent() {
+        return content;
+    }
+
+    public void setContent(InputStream content) {
+        this.content = content;
+    }
+
+    public long getContentLength() {
+        return contentLength;
+    }
+
+    public void setContentLength(long contentLength) {
+        this.contentLength = contentLength;
+    }
+
+    public void close() throws IOException {
+        if (content != null) {
+            content.close();
+            content = null;
+        }
+    }
+}

+ 91 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/HttpRequestHeader.java

@@ -0,0 +1,91 @@
+package com.emato.eoss.manager.authz.common.comm;
+
+import java.io.Serializable;
+
+/**
+ * RESTful接口中使用了一些公共请求头
+ *
+ * @author Scott Chen
+ * @date 2021-03-16
+ */
+public class HttpRequestHeader implements Serializable {
+    private static final long serialVersionUID = -4388587992068206792L;
+
+    /**
+     * 用于验证请求合法性的认证信息。
+     * 默认值:无
+     *
+     * 使用场景:非匿名请求
+     */
+    private String authorization;
+
+    /**
+     * RFC2616中定义的HTTP请求内容长度。
+     * 默认值:无
+     *
+     * 使用场景:需要向OSS提交数据的请求
+     */
+    private String contentLength;
+
+    /**
+     * RFC2616中定义的HTTP请求内容类型。
+     * 默认值:无
+     *
+     * 使用场景:需要向OSS提交数据的请求
+     */
+    private String contentType;
+
+    /**
+     * The RFC-1123 date-time formatter, such as 'Tue, 3 Jun 2008 11:05:30 GMT'
+     *
+     * HTTP 1.1协议中规定的GMT时间,例如:Wed, 05 Sep. 2012 23:00:00 GMT
+     * 默认值:无
+     */
+    private String date;
+
+    /**
+     * 访问Host值,格式为:<bucketname>.oss-cn-hangzhou.aliyuncs.com。
+     * 默认值:无
+     */
+    private String host;
+
+    public String getAuthorization() {
+        return authorization;
+    }
+
+    public void setAuthorization(String authorization) {
+        this.authorization = authorization;
+    }
+
+    public String getContentLength() {
+        return contentLength;
+    }
+
+    public void setContentLength(String contentLength) {
+        this.contentLength = contentLength;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    public String getDate() {
+        return date;
+    }
+
+    public void setDate(String date) {
+        this.date = date;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+}

+ 103 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/HttpResponseHeader.java

@@ -0,0 +1,103 @@
+package com.emato.eoss.manager.authz.common.comm;
+
+import java.io.Serializable;
+
+/**
+ * RESTful接口中使用了一些公共响应头
+ *
+ * @author Scott Chen
+ * @date 2021-03-16
+ */
+public class HttpResponseHeader implements Serializable {
+    private static final long serialVersionUID = 2478360627834922277L;
+
+    /**
+     * RFC2616中定义的HTTP请求内容长度。
+     * 默认值:无
+     *
+     * 使用场景:需要向OSS提交数据的请求
+     */
+    private String contentLength;
+
+    /**
+     * 标明客户端和OSS服务器之间的链接状态。
+     * 有效值:open、close
+     * 默认值:无
+     */
+    private String Connection;
+
+    /**
+     * HTTP 1.1协议中规定的GMT时间,例如:Wed, 05 Sep. 2012 23:00:00 GMT
+     * 默认值:无
+     */
+    private String Date;
+
+    /**
+     * ETag (entity tag) 在每个Object生成的时候被创建,用于标示一个Object的内容。
+     *      对于Put Object请求创建的Object,ETag值是其内容的MD5值;
+     *      对于其他方式创建的Object,ETag值是其内容的UUID。ETag值可以用于检查Object内容是否发生变化。
+     * 默认值:无
+     */
+    private String ETag;
+
+    /**
+     * 生成Response的服务器。
+     *  默认值:OSS
+     */
+    private String Server;
+
+    /**
+     * x-oss-request-id是由OSS创建,并唯一标识这个response的UUID。
+     * 如果在使用OSS服务时遇到问题,可以凭借该字段联系OSS工作人员,快速定位问题。
+     * 默认值:无
+     */
+    private String xOssRequestId;
+
+    public String getContentLength() {
+        return contentLength;
+    }
+
+    public void setContentLength(String contentLength) {
+        this.contentLength = contentLength;
+    }
+
+    public String getConnection() {
+        return Connection;
+    }
+
+    public void setConnection(String connection) {
+        Connection = connection;
+    }
+
+    public String getDate() {
+        return Date;
+    }
+
+    public void setDate(String date) {
+        Date = date;
+    }
+
+    public String getETag() {
+        return ETag;
+    }
+
+    public void setETag(String ETag) {
+        this.ETag = ETag;
+    }
+
+    public String getServer() {
+        return Server;
+    }
+
+    public void setServer(String server) {
+        Server = server;
+    }
+
+    public String getxOssRequestId() {
+        return xOssRequestId;
+    }
+
+    public void setxOssRequestId(String xOssRequestId) {
+        this.xOssRequestId = xOssRequestId;
+    }
+}

+ 42 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/Protocol.java

@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.comm;
+
+/**
+ * Represents the communication protocol to use when sending requests to OSS, we
+ * use HTTPS by default.
+ */
+public enum Protocol {
+
+    HTTP("http"),
+
+    HTTPS("https");
+
+    private final String protocol;
+
+    private Protocol(String protocol) {
+        this.protocol = protocol;
+    }
+
+    @Override
+    public String toString() {
+        return protocol;
+    }
+}

+ 173 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/RequestMessage.java

@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.comm;
+
+
+import com.emato.eoss.manager.authz.common.model.WebServiceRequest;
+import com.emato.eoss.manager.oss.minio.HttpMethod;
+
+import java.net.URI;
+import java.net.URL;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Represent HTTP requests sent to OSS.
+ */
+public class RequestMessage extends HttpMesssage {
+
+    /* bucket name */
+    private String bucket;
+
+    /* object name */
+    private String key;
+
+    /* The resource path being requested */
+    private String resourcePath;
+
+    /* The service endpoint to which this request should be sent */
+    private URI endpoint;
+
+    /* The HTTP method to use when sending this request */
+    private HttpMethod method = HttpMethod.GET;
+
+    /* Use a LinkedHashMap to preserve the insertion order. */
+    private Map<String, String> parameters = new LinkedHashMap<String, String>();
+
+    /* The absolute url to which the request should be sent */
+    private URL absoluteUrl;
+
+    /* Indicate whether using url signature */
+    private boolean useUrlSignature = false;
+
+    /* Indicate whether using chunked encoding */
+    private boolean useChunkEncoding = false;
+
+    /* The original request provided by user */
+    private final WebServiceRequest originalRequest;
+
+    public RequestMessage(String bucketName, String key) {
+        this(null, bucketName, key);
+    }
+
+    public RequestMessage(WebServiceRequest originalRequest, String bucketName, String key) {
+        this.originalRequest = (originalRequest == null) ? WebServiceRequest.NOOP : originalRequest;
+    }
+
+    public HttpMethod getMethod() {
+        return method;
+    }
+
+    public void setMethod(HttpMethod method) {
+        this.method = method;
+    }
+
+    public URI getEndpoint() {
+        return endpoint;
+    }
+
+    public void setEndpoint(URI endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public String getResourcePath() {
+        return resourcePath;
+    }
+
+    public void setResourcePath(String resourcePath) {
+        this.resourcePath = resourcePath;
+    }
+
+    public Map<String, String> getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Map<String, String> parameters) {
+        this.parameters.clear();
+        if (parameters != null && !parameters.isEmpty()) {
+            this.parameters.putAll(parameters);
+        }
+    }
+
+    public void addParameter(String key, String value) {
+        this.parameters.put(key, value);
+    }
+
+    public void removeParameter(String key) {
+        this.parameters.remove(key);
+    }
+
+    /**
+     * Indicate whether the request should be repeatedly sent.
+     */
+    public boolean isRepeatable() {
+        return this.getContent() == null || this.getContent().markSupported();
+    }
+
+    public String toString() {
+        return "Endpoint: " + this.getEndpoint().getHost() + ", ResourcePath: " + this.getResourcePath() + ", Headers:"
+                + this.getHeaders();
+    }
+
+    public URL getAbsoluteUrl() {
+        return absoluteUrl;
+    }
+
+    public void setAbsoluteUrl(URL absoluteUrl) {
+        this.absoluteUrl = absoluteUrl;
+    }
+
+    public boolean isUseUrlSignature() {
+        return useUrlSignature;
+    }
+
+    public void setUseUrlSignature(boolean useUrlSignature) {
+        this.useUrlSignature = useUrlSignature;
+    }
+
+    public boolean isUseChunkEncoding() {
+        return useChunkEncoding;
+    }
+
+    public void setUseChunkEncoding(boolean useChunkEncoding) {
+        this.useChunkEncoding = useChunkEncoding;
+    }
+
+    public WebServiceRequest getOriginalRequest() {
+        return originalRequest;
+    }
+
+    public String getBucket() {
+        return bucket;
+    }
+
+    public void setBucket(String bucket) {
+        this.bucket = bucket;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+}

+ 30 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/ResponseHandler.java

@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.comm;
+
+
+import com.emato.eoss.manager.oss.minio.ClientException;
+import com.emato.eoss.manager.oss.minio.OSSException;
+
+public interface ResponseHandler {
+
+    public void handle(ResponseMessage response) throws OSSException, ClientException;
+
+}

+ 85 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/ResponseMessage.java

@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.comm;
+
+
+import com.emato.eoss.manager.oss.minio.internal.OSSHeaders;
+import org.apache.http.client.methods.CloseableHttpResponse;
+
+import java.io.IOException;
+
+public class ResponseMessage extends HttpMesssage {
+
+    private static final int HTTP_SUCCESS_STATUS_CODE = 200;
+
+    private String uri;
+    private int statusCode;
+
+    private CloseableHttpResponse httpResponse;
+
+    // For convenience of logging invalid response
+    private String errorResponseAsString;
+
+    public String getUri() {
+        return uri;
+    }
+
+    public void setUrl(String uri) {
+        this.uri = uri;
+    }
+
+    public int getStatusCode() {
+        return statusCode;
+    }
+
+    public void setStatusCode(int statusCode) {
+        this.statusCode = statusCode;
+    }
+
+    public String getRequestId() {
+        return getHeaders().get(OSSHeaders.OSS_HEADER_REQUEST_ID);
+    }
+
+    public boolean isSuccessful() {
+        return statusCode / 100 == HTTP_SUCCESS_STATUS_CODE / 100;
+    }
+
+    public String getErrorResponseAsString() {
+        return errorResponseAsString;
+    }
+
+    public void setErrorResponseAsString(String errorResponseAsString) {
+        this.errorResponseAsString = errorResponseAsString;
+    }
+
+    public void abort() throws IOException {
+        if (httpResponse != null) {
+            httpResponse.close();
+        }
+    }
+
+    public CloseableHttpResponse getHttpResponse() {
+        return httpResponse;
+    }
+
+    public void setHttpResponse(CloseableHttpResponse httpResponse) {
+        this.httpResponse = httpResponse;
+    }
+}

+ 6 - 0
src/main/java/com/emato/eoss/manager/authz/common/comm/SignVersion.java

@@ -0,0 +1,6 @@
+package com.emato.eoss.manager.authz.common.comm;
+
+public enum SignVersion {
+    V1,
+    V2
+}

+ 187 - 0
src/main/java/com/emato/eoss/manager/authz/common/model/Callback.java

@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * When an upload succeeds, OSS provides the mechanism to send a post message to
+ * a callbackurl to trigger some action defined by that callbackurl. The
+ * message's method must be Post and the body is the callbackBody's content and
+ * it must match the callbackurl's expectation.
+ *
+ * APIs that support callback include PutObject, PstObject,
+ * CompleteMultipartUpload.
+ */
+public class Callback {
+
+    public static enum CalbackBodyType {
+        URL(1), JSON(2);
+
+        private int nCode;
+
+        private CalbackBodyType(int nCode) {
+            this.nCode = nCode;
+        }
+
+        @Override
+        public String toString() {
+            return String.valueOf(this.nCode);
+        }
+    }
+
+    public String getCallbackUrl() {
+        return callbackUrl;
+    }
+
+    /**
+     * Sets the callback url---the callbackUrl parameter must be Url encoded. It
+     * supports multiple callback urls (separated by ';'). When multiple
+     * callback urls are specified, OSS will send callback request one by one
+     * until the first successful response. After the callback request is sent,
+     * OSS expects to get "200 OK" response with a JSON body. The body size
+     * should be no more than 3MB.
+     * 
+     * @param callbackUrl
+     *            The callback url(s) in url encoding.
+     */
+    public void setCallbackUrl(String callbackUrl) {
+        this.callbackUrl = callbackUrl;
+    }
+
+    public String getCallbackHost() {
+        return callbackHost;
+    }
+
+    /**
+     * Sets the callback host, only valid when callbackUrl is set. If this is
+     * not set, the host will be extracted from the callbackUrl.
+     * 
+     * @param callbackHost
+     *            The host of OSS callback.
+     */
+    public void setCallbackHost(String callbackHost) {
+        this.callbackHost = callbackHost;
+    }
+
+    public String getCallbackBody() {
+        return callbackBody;
+    }
+
+    /**
+     * Sets the callback body.For example:
+     * key=$(key) &amp; etag=$(etag) &amp; my_var=$(x:my_var). It supports the OSS system
+     * variable, custom defined variable or constant and custom defined
+     * variable's callbackVar.
+     * 
+     * @param callbackBody
+     *            OSS callback body.
+     */
+    public void setCallbackBody(String callbackBody) {
+        this.callbackBody = callbackBody;
+    }
+
+    public CalbackBodyType getCalbackBodyType() {
+        return calbackBodyType;
+    }
+
+    /**
+     * The content-type header in OSS's callback request. It supports
+     * application/x-www-form-urlencoded(url) and application/json(json). The
+     * default is the former, which means the variable in callback body will be
+     * url encoded. If it's latter, the variable in callback body will be
+     * formatted (by the SDK) as json's variable.
+     *
+     * @param calbackBodyType
+     *            The content-type header in OSS callback request.
+     */
+    public void setCalbackBodyType(CalbackBodyType calbackBodyType) {
+        this.calbackBodyType = calbackBodyType;
+    }
+
+    public Map<String, String> getCallbackVar() {
+        return callbackVar;
+    }
+
+    /**
+     * Sets user customized parameter(s).
+     *
+     * Customized parameter is a Map&lt;key,value&gt; instance. In the callback
+     * request, OSS would put these parameters into the post body. The keys must
+     * start with "x:", such as x:my_var.
+     * 
+     * @param callbackVar
+     *            A {@link Map} instance that stores the &lt;key, value&gt; pairs.
+     */
+    public void setCallbackVar(Map<String, String> callbackVar) {
+        this.callbackVar.clear();
+        if (callbackVar != null && !callbackVar.isEmpty()) {
+            this.callbackVar.putAll(callbackVar);
+        }
+    }
+
+    /**
+     * Adds a new custom parameter.
+     * 
+     * @param key
+     *            Custom key starting with "x:".
+     * @param value
+     *            The value for the custom key.
+     */
+    public void addCallbackVar(String key, String value) {
+        this.callbackVar.put(key, value);
+    }
+
+    public boolean hasCallbackVar() {
+        if (this.callbackVar != null && this.callbackVar.size() > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * The callbackUrl after a successful upload
+     */
+    private String callbackUrl;
+
+    /**
+     * The callback host, only vaid after the callbackUrl is set. If
+     * callbackHost is null, the SDK will extract the host from the callbackUrl.
+     */
+    private String callbackHost;
+
+    /**
+     * The callback body in the request.
+     */
+    private String callbackBody;
+
+    /**
+     * The content-type header in the request. It supports url or json type and
+     * url is the default.
+     */
+    private CalbackBodyType calbackBodyType;
+
+    /**
+     * The custom parameters
+     */
+    private Map<String, String> callbackVar = new HashMap<String, String>();
+
+}

+ 84 - 0
src/main/java/com/emato/eoss/manager/authz/common/model/WebServiceRequest.java

@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.model;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public abstract class WebServiceRequest {
+
+    public static final WebServiceRequest NOOP = new WebServiceRequest() {
+    };
+
+    //This flag is used to enable/disable INFO and WARNING logs for requests
+    //We enable INFO and WARNING logs by default.
+    private boolean logEnabled = true;
+
+    private Map<String, String> parameters = new LinkedHashMap<String, String>();
+    private Map<String, String> headers = new LinkedHashMap<String, String>();
+
+    private Set<String> additionalHeaderNames = new HashSet<String>();
+
+    public Map<String, String> getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Map<String, String> parameters) {
+        this.parameters = parameters;
+    }
+
+    public void addParameter(String key, String value) {
+        this.parameters.put(key, value);
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    public void setHeaders(Map<String, String> headers) {
+        this.headers = headers;
+    }
+
+    public void addHeader(String key, String value) {
+        this.headers.put(key, value);
+    }
+
+    public Set<String> getAdditionalHeaderNames() {
+        return additionalHeaderNames;
+    }
+
+    public void setAdditionalHeaderNames(Set<String> additionalHeaderNames) {
+        this.additionalHeaderNames = additionalHeaderNames;
+    }
+
+    public void addAdditionalHeaderName(String name) {
+        this.additionalHeaderNames.add(name);
+    }
+
+    public boolean isLogEnabled() {
+        return logEnabled;
+    }
+
+    public void setLogEnabled(boolean logEnabled) {
+        this.logEnabled = logEnabled;
+    }
+}

+ 61 - 0
src/main/java/com/emato/eoss/manager/authz/common/utils/BinaryUtil.java

@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.utils;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class BinaryUtil {
+
+    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
+            'E', 'F' };
+
+    public static String toBase64String(byte[] binaryData) {
+        return new String(Base64.encodeBase64(binaryData));
+    }
+
+    public static byte[] fromBase64String(String base64String) {
+        return Base64.decodeBase64(base64String);
+    }
+
+    public static byte[] calculateMd5(byte[] binaryData) {
+        MessageDigest messageDigest = null;
+        try {
+            messageDigest = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("MD5 algorithm not found.");
+        }
+        messageDigest.update(binaryData);
+        return messageDigest.digest();
+    }
+
+    public static String encodeMD5(byte[] binaryData) {
+        byte[] md5Bytes = calculateMd5(binaryData);
+        int len = md5Bytes.length;
+        char buf[] = new char[len * 2];
+        for (int i = 0; i < len; i++) {
+            buf[i * 2] = HEX_DIGITS[(md5Bytes[i] >>> 4) & 0x0f];
+            buf[i * 2 + 1] = HEX_DIGITS[md5Bytes[i] & 0x0f];
+        }
+        return new String(buf);
+    }
+}

+ 98 - 0
src/main/java/com/emato/eoss/manager/authz/common/utils/DateUtil.java

@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.utils;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+
+/**
+ * A simple utility class for date formating.
+ */
+public class DateUtil {
+
+    // RFC 822 Date Format
+    private static final String RFC822_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
+
+    // ISO 8601 format
+    private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+
+    // Alternate ISO 8601 format without fractional seconds
+    private static final String ALTERNATIVE_ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+
+    /**
+     * Formats Date to GMT string.
+     */
+    public static String formatRfc822Date(Date date) {
+        return getRfc822DateFormat().format(date);
+    }
+
+    /**
+     * Parses a GMT-format string.
+     */
+    public static Date parseRfc822Date(String dateString) throws ParseException {
+        return getRfc822DateFormat().parse(dateString);
+    }
+
+    private static DateFormat getRfc822DateFormat() {
+        SimpleDateFormat rfc822DateFormat = new SimpleDateFormat(RFC822_DATE_FORMAT, Locale.US);
+        rfc822DateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
+
+        return rfc822DateFormat;
+    }
+
+    public static String formatIso8601Date(Date date) {
+        return getIso8601DateFormat().format(date);
+    }
+
+    public static String formatAlternativeIso8601Date(Date date) {
+        return getAlternativeIso8601DateFormat().format(date);
+    }
+
+    /**
+     * Parse a date string in the format of ISO 8601.
+     * 
+     * @param dateString
+     * @return a {@link Date} instance.
+     * @throws ParseException
+     */
+    public static Date parseIso8601Date(String dateString) throws ParseException {
+        try {
+            return getIso8601DateFormat().parse(dateString);
+        } catch (ParseException e) {
+            return getAlternativeIso8601DateFormat().parse(dateString);
+        }
+    }
+
+    private static DateFormat getIso8601DateFormat() {
+        SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT, Locale.US);
+        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
+        return df;
+    }
+
+    private static DateFormat getAlternativeIso8601DateFormat() {
+        SimpleDateFormat df = new SimpleDateFormat(ALTERNATIVE_ISO8601_DATE_FORMAT, Locale.US);
+        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
+        return df;
+    }
+}

+ 123 - 0
src/main/java/com/emato/eoss/manager/authz/common/utils/HttpUtil.java

@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static com.emato.eoss.manager.oss.minio.internal.OSSUtils.OSS_RESOURCE_MANAGER;
+
+
+public class HttpUtil {
+
+    /**
+     * Encode a URL segment with special chars replaced.
+     */
+    public static String urlEncode(String value, String encoding) {
+        if (value == null) {
+            return "";
+        }
+
+        try {
+            String encoded = URLEncoder.encode(value, encoding);
+            return encoded.replace("+", "%20").replace("*", "%2A").replace("~", "%7E").replace("/", "%2F");
+        } catch (UnsupportedEncodingException e) {
+            throw new IllegalArgumentException(OSS_RESOURCE_MANAGER.getString("FailedToEncodeUri"), e);
+        }
+    }
+
+    public static String urlDecode(String value, String encoding) {
+        if (StringUtils.isNotBlank(value)) {
+            return value;
+        }
+
+        try {
+            return URLDecoder.decode(value, encoding);
+        } catch (UnsupportedEncodingException e) {
+            throw new IllegalArgumentException(OSS_RESOURCE_MANAGER.getString("FailedToDecodeUrl"), e);
+        }
+    }
+
+    /**
+     * Encode request parameters to URL segment.
+     */
+    public static String paramToQueryString(Map<String, String> params, String charset) {
+
+        if (params == null || params.isEmpty()) {
+            return null;
+        }
+
+        StringBuilder paramString = new StringBuilder();
+        boolean first = true;
+        for (Entry<String, String> p : params.entrySet()) {
+            String key = p.getKey();
+            String value = p.getValue();
+
+            if (!first) {
+                paramString.append("&");
+            }
+
+            // Urlencode each request parameter
+            paramString.append(urlEncode(key, charset));
+            if (value != null) {
+                paramString.append("=").append(urlEncode(value, charset));
+            }
+
+            first = false;
+        }
+
+        return paramString.toString();
+    }
+
+    private static final String ISO_8859_1_CHARSET = "iso-8859-1";
+    private static final String UTF8_CHARSET = "utf-8";
+
+    // To fix the bug that the header value could not be unicode chars.
+    // Because HTTP headers are encoded in iso-8859-1,
+    // we need to convert the utf-8(java encoding) strings to iso-8859-1 ones.
+    public static void convertHeaderCharsetFromIso88591(Map<String, String> headers) {
+        convertHeaderCharset(headers, ISO_8859_1_CHARSET, UTF8_CHARSET);
+    }
+
+    // For response, convert from iso-8859-1 to utf-8.
+    public static void convertHeaderCharsetToIso88591(Map<String, String> headers) {
+        convertHeaderCharset(headers, UTF8_CHARSET, ISO_8859_1_CHARSET);
+    }
+
+    private static void convertHeaderCharset(Map<String, String> headers, String fromCharset, String toCharset) {
+
+        for (Entry<String, String> header : headers.entrySet()) {
+            if (header.getValue() == null) {
+                continue;
+            }
+
+            try {
+                header.setValue(new String(header.getValue().getBytes(fromCharset), toCharset));
+            } catch (UnsupportedEncodingException e) {
+                throw new IllegalArgumentException("Invalid charset name: " + e.getMessage(), e);
+            }
+        }
+    }
+}

+ 74 - 0
src/main/java/com/emato/eoss/manager/authz/common/utils/LogUtils.java

@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.utils;
+
+
+import com.emato.eoss.manager.oss.minio.OSSErrorCode;
+import com.emato.eoss.manager.oss.minio.ServiceException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.emato.eoss.manager.oss.minio.internal.OSSConstants.LOGGER_PACKAGE_NAME;
+
+public class LogUtils {
+
+    private static final Log log = LogFactory.getLog(LOGGER_PACKAGE_NAME);
+
+    // Set logger level to INFO specially if reponse error code is 404 in order
+    // to
+    // prevent from dumping a flood of logs when trying access to none-existent
+    // resources.
+    private static List<String> errorCodeFilterList = new ArrayList<String>();
+    static {
+        errorCodeFilterList.add(OSSErrorCode.NO_SUCH_BUCKET);
+        errorCodeFilterList.add(OSSErrorCode.NO_SUCH_KEY);
+        errorCodeFilterList.add(OSSErrorCode.NO_SUCH_UPLOAD);
+        errorCodeFilterList.add(OSSErrorCode.NO_SUCH_CORS_CONFIGURATION);
+        errorCodeFilterList.add(OSSErrorCode.NO_SUCH_WEBSITE_CONFIGURATION);
+        errorCodeFilterList.add(OSSErrorCode.NO_SUCH_LIFECYCLE);
+    }
+
+    public static Log getLog() {
+        return log;
+    }
+
+    public static <ExType> void logException(String messagePrefix, ExType ex) {
+        logException(messagePrefix, ex, true);
+    }
+
+    public static <ExType> void logException(String messagePrefix, ExType ex, boolean logEnabled) {
+
+        assert (ex instanceof Exception);
+
+        String detailMessage = messagePrefix + ((Exception) ex).getMessage();
+        if (ex instanceof ServiceException && errorCodeFilterList.contains(((ServiceException) ex).getErrorCode())) {
+            if (logEnabled) {
+                log.debug(detailMessage);
+            }
+        } else {
+            if (logEnabled) {
+                log.warn(detailMessage);
+            }
+        }
+    }
+}

+ 51 - 0
src/main/java/com/emato/eoss/manager/authz/common/utils/ResourceManager.java

@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.authz.common.utils;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+/**
+ * Manager class to get localized resources.
+ */
+public class ResourceManager {
+    private ResourceBundle bundle;
+
+    ResourceManager(String baseName, Locale locale) {
+        this.bundle = ResourceBundle.getBundle(baseName, locale);
+    }
+
+    public static ResourceManager getInstance(String baseName) {
+        return new ResourceManager(baseName, Locale.getDefault());
+    }
+
+    public static ResourceManager getInstance(String baseName, Locale locale) {
+        return new ResourceManager(baseName, locale);
+    }
+
+    public String getString(String key) {
+        return bundle.getString(key);
+    }
+
+    public String getFormattedString(String key, Object... args) {
+        return MessageFormat.format(getString(key), args);
+    }
+}

+ 826 - 0
src/main/java/com/emato/eoss/manager/oss/minio/ClientConfiguration.java

@@ -0,0 +1,826 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio;
+
+
+import com.emato.eoss.manager.authz.common.authc.RequestSigner;
+import com.emato.eoss.manager.authz.common.comm.Protocol;
+import com.emato.eoss.manager.authz.common.comm.SignVersion;
+import com.emato.eoss.manager.oss.minio.internal.OSSConstants;
+import com.emato.eoss.manager.authz.common.utils.ResourceManager;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.SecureRandom;
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Client configurations for accessing to OSS services.
+ */
+public class ClientConfiguration {
+
+    public static final int DEFAULT_MAX_RETRIES = 3;
+
+    public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT = -1;
+    public static final int DEFAULT_CONNECTION_TIMEOUT = 50 * 1000;
+    public static final int DEFAULT_SOCKET_TIMEOUT = 50 * 1000;
+    public static final int DEFAULT_MAX_CONNECTIONS = 1024;
+    public static final long DEFAULT_CONNECTION_TTL = -1;
+    public static final long DEFAULT_IDLE_CONNECTION_TIME = 60 * 1000;
+    public static final int DEFAULT_VALIDATE_AFTER_INACTIVITY = 2 * 1000;
+    public static final int DEFAULT_THREAD_POOL_WAIT_TIME = 60 * 1000;
+    public static final int DEFAULT_REQUEST_TIMEOUT = 5 * 60 * 1000;
+    public static final long DEFAULT_SLOW_REQUESTS_THRESHOLD = 5 * 60 * 1000;
+
+    public static final boolean DEFAULT_USE_REAPER = true;
+
+    public static final String DEFAULT_CNAME_EXCLUDE_LIST = "aliyuncs.com,aliyun-inc.com,aliyun.com";
+
+    public static final SignVersion DEFAULT_SIGNATURE_VERSION = SignVersion.V1;
+
+    protected int maxErrorRetry = DEFAULT_MAX_RETRIES;
+    protected int connectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT;
+    protected int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
+    protected int socketTimeout = DEFAULT_SOCKET_TIMEOUT;
+    protected int maxConnections = DEFAULT_MAX_CONNECTIONS;
+    protected long connectionTTL = DEFAULT_CONNECTION_TTL;
+    protected boolean useReaper = DEFAULT_USE_REAPER;
+    protected long idleConnectionTime = DEFAULT_IDLE_CONNECTION_TIME;
+
+    protected Protocol protocol = Protocol.HTTP;
+
+    protected String proxyHost = null;
+    protected int proxyPort = -1;
+    protected String proxyUsername = null;
+    protected String proxyPassword = null;
+    protected String proxyDomain = null;
+    protected String proxyWorkstation = null;
+
+    protected boolean supportCname = true;
+    protected List<String> cnameExcludeList = new ArrayList<String>();
+    protected Lock rlock = new ReentrantLock();
+
+    protected boolean sldEnabled = false;
+
+    protected int requestTimeout = DEFAULT_REQUEST_TIMEOUT;
+    protected boolean requestTimeoutEnabled = false;
+    protected long slowRequestsThreshold = DEFAULT_SLOW_REQUESTS_THRESHOLD;
+
+    protected Map<String, String> defaultHeaders = new LinkedHashMap<String, String>();
+
+    protected boolean crcCheckEnabled = true;
+
+    protected List<RequestSigner> signerHandlers = new LinkedList<RequestSigner>();
+
+    protected SignVersion signatureVersion = DEFAULT_SIGNATURE_VERSION;
+
+    protected long tickOffset = 0;
+
+    private boolean redirectEnable = true;
+
+    private boolean verifySSLEnable = true;
+    private KeyManager[] keyManagers = null;
+    private X509TrustManager[] x509TrustManagers = null;
+    private SecureRandom secureRandom = null;
+    private HostnameVerifier hostnameVerifier = null;
+
+    protected boolean logConnectionPoolStats = false;
+
+    public ClientConfiguration() {
+        super();
+        AppendDefaultExcludeList(this.cnameExcludeList);
+    }
+
+
+    /**
+     * Gets proxy host.
+     *
+     * @return The proxy host in string.
+     */
+    public String getProxyHost() {
+        return proxyHost;
+    }
+
+    /**
+     * Sets the proxy host.
+     *
+     * @param proxyHost
+     *            The proxy host in string.
+     */
+    public void setProxyHost(String proxyHost) {
+        this.proxyHost = proxyHost;
+    }
+
+    /**
+     * Gets the proxy host's port.
+     *
+     * @return The proxy host.
+     */
+    public int getProxyPort() {
+        return proxyPort;
+    }
+
+    /**
+     * Sets proxy port.
+     *
+     * @param proxyPort
+     *            The proxy port.
+     * @throws ClientException
+     */
+    public void setProxyPort(int proxyPort) throws ClientException {
+        if (proxyPort <= 0) {
+            throw new ClientException(
+                    ResourceManager.getInstance(OSSConstants.RESOURCE_NAME_COMMON).getString("ParameterIsInvalid"),
+                    null);
+        }
+        this.proxyPort = proxyPort;
+    }
+
+    /**
+     * Gets the proxy user name.
+     *
+     * @return The user name.
+     */
+    public String getProxyUsername() {
+        return proxyUsername;
+    }
+
+    /**
+     * Sets the proxy user name.
+     *
+     * @param proxyUsername
+     *            The user name.
+     */
+    public void setProxyUsername(String proxyUsername) {
+        this.proxyUsername = proxyUsername;
+    }
+
+    /**
+     * Gets the proxy user password.
+     *
+     * @return The proxy user password.
+     */
+    public String getProxyPassword() {
+        return proxyPassword;
+    }
+
+    /**
+     * Sets the proxy user password.
+     *
+     * @param proxyPassword
+     *            The proxy user password.
+     */
+    public void setProxyPassword(String proxyPassword) {
+        this.proxyPassword = proxyPassword;
+    }
+
+    /**
+     * Gets the proxy server's domain, which could do the NTLM authentiation
+     * (optional).
+     *
+     * @return The proxy domain name.
+     */
+    public String getProxyDomain() {
+        return proxyDomain;
+    }
+
+    /**
+     * Sets the proxy server's domain, which could do the NTLM authentication
+     * (optional).
+     *
+     * @param proxyDomain
+     *            The proxy domain name.
+     */
+    public void setProxyDomain(String proxyDomain) {
+        this.proxyDomain = proxyDomain;
+    }
+
+    /**
+     * Gets the proxy host's NTLM authentication server.
+     *
+     * @return The NTLM authentication server name.
+     */
+    public String getProxyWorkstation() {
+        return proxyWorkstation;
+    }
+
+    /**
+     * Sets the proxy host's NTLM authentication server(optional, if the proxy
+     * server does not require NTLM authentication, then it's not needed).
+     *
+     * @param proxyWorkstation
+     *            The proxy host's NTLM authentication server name.
+     */
+    public void setProxyWorkstation(String proxyWorkstation) {
+        this.proxyWorkstation = proxyWorkstation;
+    }
+
+    /**
+     * Gets the max connection count.
+     *
+     * @return The max connection count. By default it's 1024.
+     */
+    public int getMaxConnections() {
+        return maxConnections;
+    }
+
+    /**
+     * Sets the max connection count.
+     *
+     * @param maxConnections
+     *            The max connection count.
+     */
+    public void setMaxConnections(int maxConnections) {
+        this.maxConnections = maxConnections;
+    }
+
+    /**
+     * Gets the socket timeout in millisecond. 0 means infinite timeout, not
+     * recommended.
+     *
+     * @return The socket timeout in millisecond.
+     */
+    public int getSocketTimeout() {
+        return socketTimeout;
+    }
+
+    /**
+     * Sets the socket timeout in millisecond. 0 means infinite timeout, not
+     * recommended.
+     *
+     * @param socketTimeout
+     *            The socket timeout in millisecond.
+     */
+    public void setSocketTimeout(int socketTimeout) {
+        this.socketTimeout = socketTimeout;
+    }
+
+    /**
+     * Gets the socket connection timeout in millisecond.
+     *
+     * @return The socket connection timeout in millisecond.
+     */
+    public int getConnectionTimeout() {
+        return connectionTimeout;
+    }
+
+    /**
+     * Sets the socket connection timeout in millisecond.
+     *
+     * @param connectionTimeout
+     *            The socket connection timeout in millisecond.
+     */
+    public void setConnectionTimeout(int connectionTimeout) {
+        this.connectionTimeout = connectionTimeout;
+    }
+
+    /**
+     * Gets the timeout in millisecond for retrieving an available connection
+     * from the connection manager. 0 means infinite and -1 means not defined.
+     * By default it's -1.
+     *
+     * @return The timeout in millisecond.
+     */
+    public int getConnectionRequestTimeout() {
+        return connectionRequestTimeout;
+    }
+
+    /**
+     * Sets the timeout in millisecond for retrieving an available connection
+     * from the connection manager.
+     *
+     * @param connectionRequestTimeout
+     *            The timeout in millisecond.
+     */
+    public void setConnectionRequestTimeout(int connectionRequestTimeout) {
+        this.connectionRequestTimeout = connectionRequestTimeout;
+    }
+
+    /**
+     * Gets the max retry count upon a retryable error. By default it's 3.
+     *
+     * @return The max retry count.
+     */
+    public int getMaxErrorRetry() {
+        return maxErrorRetry;
+    }
+
+    /**
+     * Sets the max retry count upon a retryable error. By default it's 3.
+     *
+     * @param maxErrorRetry
+     *            The max retry count.
+     */
+    public void setMaxErrorRetry(int maxErrorRetry) {
+        this.maxErrorRetry = maxErrorRetry;
+    }
+
+    /**
+     * Gets the connection TTL (time to live). Http connection is cached by the
+     * connection manager with a TTL.
+     *
+     * @return The connection TTL in millisecond.
+     */
+    public long getConnectionTTL() {
+        return connectionTTL;
+    }
+
+    /**
+     * Sets the connection TTL (time to live). Http connection is cached by the
+     * connection manager with a TTL.
+     *
+     * @param connectionTTL
+     *            The connection TTL in millisecond.
+     */
+    public void setConnectionTTL(long connectionTTL) {
+        this.connectionTTL = connectionTTL;
+    }
+
+    /**
+     * Gets the flag of using IdleConnectionReaper to manage expired
+     * connection.
+     */
+    public boolean isUseReaper() {
+        return useReaper;
+    }
+
+    /**
+     * Sets the flag of using IdleConnectionReaper to manage expired
+     * connection.
+     */
+    public void setUseReaper(boolean useReaper) {
+        this.useReaper = useReaper;
+    }
+
+    /**
+     * Gets the connection's max idle time. If a connection has been idle for
+     * more than this number, it would be closed.
+     *
+     * @return The connection's max idle time in millisecond.
+     */
+    public long getIdleConnectionTime() {
+        return idleConnectionTime;
+    }
+
+    /**
+     * Sets the connection's max idle time. If a connection has been idle for
+     * more than this number, it would be closed.
+     *
+     * @param idleConnectionTime
+     *            The connection's max idle time in millisecond.
+     */
+    public void setIdleConnectionTime(long idleConnectionTime) {
+        this.idleConnectionTime = idleConnectionTime;
+    }
+
+    /**
+     * Gets the OSS's protocol (HTTP or HTTPS).
+     */
+    public Protocol getProtocol() {
+        return protocol;
+    }
+
+    /**
+     * Sets the OSS's protocol (HTTP or HTTPS).
+     */
+    public void setProtocol(Protocol protocol) {
+        this.protocol = protocol;
+    }
+
+    /**
+     * Gets the immutable excluded CName list----any domain ends with an item in
+     * this list will not do Cname resolution.
+     *
+     * @return The excluded CName list, immutable.
+     */
+    public List<String> getCnameExcludeList() {
+        return Collections.unmodifiableList(this.cnameExcludeList);
+    }
+
+    /**
+     * Sets the immutable excluded CName list----any domain ends with an item in
+     * this list will not do Cname resolution.
+     *
+     * @param cnameExcludeList
+     *            The excluded CName list, immutable.
+     */
+    public void setCnameExcludeList(List<String> cnameExcludeList) {
+        if (cnameExcludeList == null) {
+            throw new IllegalArgumentException("cname exclude list should not be null.");
+        }
+
+        this.cnameExcludeList.clear();
+        for (String excl : cnameExcludeList) {
+            if (!excl.trim().isEmpty()) {
+                this.cnameExcludeList.add(excl);
+            }
+        }
+
+        AppendDefaultExcludeList(this.cnameExcludeList);
+    }
+
+    /**
+     * Append default excluded CName list.
+     *
+     * @param excludeList
+     *            The excluded CName list.
+     */
+    private static void AppendDefaultExcludeList(List<String> excludeList) {
+        String[] excludes = DEFAULT_CNAME_EXCLUDE_LIST.split(",");
+        for (String excl : excludes) {
+            if (!excl.trim().isEmpty() && !excludeList.contains(excl)) {
+                excludeList.add(excl.trim().toLowerCase());
+            }
+        }
+    }
+
+    /**
+     * Gets the flag if supporting Cname in the endpoint. By default it's true.
+     *
+     * @return True if supporting Cname; False if not.
+     */
+    public boolean isSupportCname() {
+        return supportCname;
+    }
+
+    /**
+     * Sets the flag if supporting Cname in the endpoint. By default it's true.
+     *
+     * <p>
+     * If this value is set true, when building a canonical url, the host would
+     * be checked against the Cname excluded list. If that host is found in the
+     * list, then it's treated as non-CName and accessed as TLD (third level
+     * domain). If the host is found, then it's thought as CName. If this value
+     * is set false, then always uses TLD to access the endpoint.
+     * </p>
+     *
+     * @param supportCname
+     *            The flag if supporting CName.
+     */
+    public ClientConfiguration setSupportCname(boolean supportCname) {
+        this.supportCname = supportCname;
+        return this;
+    }
+
+    /**
+     * Gets the flag of using SLD (Second Level Domain) style to access the
+     * endpoint. By default it's false. When using SLD, then the bucket endpoint
+     * would be: http://host/bucket. Otherwise, it will be http://bucket.host
+     *
+     * @return True if it's enabled; False if it's disabled.
+     */
+    public boolean isSLDEnabled() {
+        return sldEnabled;
+    }
+
+    /**
+     * Sets the flag of using SLD (Second Level Domain) style to access the
+     * endpoint. By default it's false.
+     *
+     * @param enabled
+     *            True if it's enabled; False if it's disabled.
+     */
+    public ClientConfiguration setSLDEnabled(boolean enabled) {
+        this.sldEnabled = enabled;
+        return this;
+    }
+
+    /**
+     * The connection idle time threshold in millisecond that triggers the
+     * validation. By default it's 2000.
+     *
+     * @return The connection idle time threshold.
+     */
+    public int getValidateAfterInactivity() {
+        return DEFAULT_VALIDATE_AFTER_INACTIVITY;
+    }
+
+    /**
+     * Gets the flag of enabling request timeout. By default it's disabled.
+     *
+     * @return true enabled; false disabled.
+     */
+    public boolean isRequestTimeoutEnabled() {
+        return requestTimeoutEnabled;
+    }
+
+    /**
+     * Gets the flag of enabling request timeout. By default it's disabled.
+     *
+     * @param requestTimeoutEnabled
+     *            true to enable; false to disable.
+     */
+    public void setRequestTimeoutEnabled(boolean requestTimeoutEnabled) {
+        this.requestTimeoutEnabled = requestTimeoutEnabled;
+    }
+
+    /**
+     * Sets the timeout value in millisecond. By default it's 5 min.
+     */
+    public void setRequestTimeout(int requestTimeout) {
+        this.requestTimeout = requestTimeout;
+    }
+
+    /**
+     * Gets the timeout value in millisecond.
+     */
+    public int getRequestTimeout() {
+        return requestTimeout;
+    }
+
+    /**
+     * Sets the slow request's latency threshold. If a request's latency is more
+     * than it, the request will be logged. By default the threshold is 5 min.
+     */
+    public long getSlowRequestsThreshold() {
+        return slowRequestsThreshold;
+    }
+
+    /**
+     * Gets the slow request's latency threshold. If a request's latency is more
+     * than it, the request will be logged.
+     */
+    public void setSlowRequestsThreshold(long slowRequestsThreshold) {
+        this.slowRequestsThreshold = slowRequestsThreshold;
+    }
+
+    /**
+     * Gets the default http headers. All these headers would be automatically
+     * added in every request. And if a header is also specified in the request,
+     * the default one will be overwritten.
+     */
+    public Map<String, String> getDefaultHeaders() {
+        return defaultHeaders;
+    }
+
+    /**
+     * Sets the default http headers. All these headers would be automatically
+     * added in every request. And if a header is also specified in the request,
+     * the default one will be overwritten.
+     *
+     * @param defaultHeaders
+     *            Default http headers.
+     */
+    public void setDefaultHeaders(Map<String, String> defaultHeaders) {
+        this.defaultHeaders = defaultHeaders;
+    }
+
+    /**
+     * Add a default header into the default header list.
+     *
+     * @param key
+     *            The default header name.
+     * @param value
+     *            The default header value.
+     */
+    public void addDefaultHeader(String key, String value) {
+        this.defaultHeaders.put(key, value);
+    }
+
+    /**
+     * Gets the flag of enabling CRC checksum on upload and download. By default
+     * it's true.
+     *
+     * @return true enable CRC;false disable CRC.
+     */
+    public boolean isCrcCheckEnabled() {
+        return crcCheckEnabled;
+    }
+
+    /**
+     * Sets the flag of enabling CRC checksum on upload and download. By default
+     * it's true.
+     *
+     * @param crcCheckEnabled
+     *            True to enable CRC; False to disable CRC.
+     */
+    public void setCrcCheckEnabled(boolean crcCheckEnabled) {
+        this.crcCheckEnabled = crcCheckEnabled;
+    }
+
+    /**
+     * Gets signer handlers
+     *
+     * @return signer handlers
+     */
+    public List<RequestSigner> getSignerHandlers() {
+        return signerHandlers;
+    }
+
+    /**
+     * Sets signer handlers using for authentication of the proxy server.
+     *
+     * @param signerHandlers
+     */
+    public void setSignerHandlers(List<RequestSigner> signerHandlers) {
+        if (signerHandlers == null) {
+            return;
+        }
+        this.signerHandlers.clear();
+        for (RequestSigner signer : signerHandlers) {
+            if (signer != null) {
+                this.signerHandlers.add(signer);
+            }
+        }
+    }
+
+    /**
+     * Gets signature version
+     *
+     * @return signature version
+     */
+    public SignVersion getSignatureVersion() {
+        return signatureVersion;
+    }
+
+    /**
+     * Sets signature version for all request.
+     *
+     * @param signatureVersion
+     */
+    public void setSignatureVersion(SignVersion signatureVersion) {
+        this.signatureVersion = signatureVersion;
+    }
+
+    /**
+     * Gets the difference between customized epoch time and local time, in millisecond.
+     *
+     * @return tick Offset
+     */
+    public long getTickOffset() {
+        return tickOffset;
+    }
+
+    /**
+     * Sets the custom base time.
+     * OSS's token validation logic depends on the time.
+     * It requires that there's no more than 15 min time difference between client and OSS server.
+     * This API calculates the difference between local time to epoch time.
+     * Later one other APIs use this difference to offset the local time before sending request to OSS.
+     *
+     * @param epochTicks
+     *             Custom Epoch ticks (in millisecond).
+     */
+    public void setTickOffset(long epochTicks) {
+        long localTime = new Date().getTime();
+        this.tickOffset = epochTicks - localTime;
+    }
+
+    /**
+     * Gets the flag of http redirection.
+     *
+     * @return the flag of http redirection.
+     */
+    public boolean isRedirectEnable() {
+        return redirectEnable;
+    }
+
+    /**
+     * Sets the flag of http redirection.
+     *
+     * @param redirectEnable
+     *          Determines whether redirects should be handled automatically.
+     */
+    public void setRedirectEnable(boolean redirectEnable) {
+        this.redirectEnable = redirectEnable;
+    }
+
+    /**
+     * Gets the flag of verifing SSL certificate. By default it's true.
+     *
+     * @return true verify SSL certificate;false ignore SSL certificate.
+     */
+    public boolean isVerifySSLEnable() {
+        return verifySSLEnable;
+    }
+
+    /**
+     * Sets the flag of verifing SSL certificate.
+     *
+     * @param verifySSLEnable
+     *            True to verify SSL certificate; False to ignore SSL certificate.
+     */
+    public void setVerifySSLEnable(boolean verifySSLEnable) {
+        this.verifySSLEnable = verifySSLEnable;
+    }
+
+    /**
+     * Gets the KeyManagers are responsible for managing the key material
+     * which is used to authenticate the local SSLSocket to its peer.
+     *
+     * @return the key managers.
+     */
+    public KeyManager[] getKeyManagers() {
+        return keyManagers;
+    }
+
+    /**
+     * Sets the key managers are responsible for managing the key material
+     * which is used to authenticate the local SSLSocket to its peer.
+     *
+     * @param keyManagers
+     *            the key managers
+     */
+    public void setKeyManagers(KeyManager[] keyManagers) {
+        this.keyManagers = keyManagers;
+    }
+
+    /**
+     * Gets the instance of this interface manage which X509 certificates
+     * may be used to authenticate the remote side of a secure socket.
+     *
+     * @return the x509 trust managers .
+     */
+    public X509TrustManager[] getX509TrustManagers() {
+        return x509TrustManagers;
+    }
+
+    /**
+     * Sets the instance of this interface manage which X509 certificates
+     * may be used to authenticate the remote side of a secure socket.
+     *
+     * @param x509TrustManagers
+     *            x509 trust managers
+     */
+    public void setX509TrustManagers(X509TrustManager[] x509TrustManagers) {
+        this.x509TrustManagers = x509TrustManagers;
+    }
+
+    /**
+     * Gets the cryptographically strong random number.
+     *
+     * @return random number.
+     */
+    public SecureRandom getSecureRandom() {
+        return secureRandom;
+    }
+
+    /**
+     * Sets the cryptographically strong random number.
+     *
+     * @param secureRandom
+     *            the cryptographically strong random number
+     */
+    public void setSecureRandom(SecureRandom secureRandom) {
+        this.secureRandom = secureRandom;
+    }
+
+    /**
+     * Gets the instance of this interface for hostname verification.
+     *
+     * @return the hostname verification instance.
+     */
+    public HostnameVerifier getHostnameVerifier() {
+        return hostnameVerifier;
+    }
+
+	/**
+     * Sets instance of this interface for hostname verification.
+     *
+     * @param hostnameVerifier
+     *            the hostname verification instance
+     */
+    public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+        this.hostnameVerifier = hostnameVerifier;
+    }
+
+    /**
+     * Sets the flag of logging connection pool statistics.
+     *
+     * @param enabled
+     *            True if it's enabled; False if it's disabled.
+     */
+    public void setLogConnectionPoolStats(boolean enabled) {
+        this.logConnectionPoolStats = enabled;
+    }
+
+    /**
+     * Gets the flag of logging connection pool statistics. By default it's disabled.
+     *
+     * @return true enabled; false disabled.
+     */
+    public boolean isLogConnectionPoolStatsEnable() {
+        return logConnectionPoolStats;
+    }
+
+}

+ 64 - 0
src/main/java/com/emato/eoss/manager/oss/minio/ClientErrorCode.java

@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio;
+
+public interface ClientErrorCode {
+
+    /**
+     * Unknown error. This means the error is not expected.
+     */
+    static final String UNKNOWN = "Unknown";
+
+    /**
+     * Unknown host. This error is returned when a
+     * {@link java.net.UnknownHostException} is thrown.
+     */
+    static final String UNKNOWN_HOST = "UnknownHost";
+
+    /**
+     * connection times out.
+     */
+    static final String CONNECTION_TIMEOUT = "ConnectionTimeout";
+
+    /**
+     * Socket times out
+     */
+    static final String SOCKET_TIMEOUT = "SocketTimeout";
+
+    /**
+     * Socket exception
+     */
+    static final String SOCKET_EXCEPTION = "SocketException";
+
+    /**
+     * Connection is refused by server side.
+     */
+    static final String CONNECTION_REFUSED = "ConnectionRefused";
+
+    /**
+     * The input stream is not repeatable for reading.
+     */
+    static final String NONREPEATABLE_REQUEST = "NonRepeatableRequest";
+    
+    /**
+     * Thread interrupted while reading the input stream.
+     */
+    static final String INPUTSTREAM_READING_ABORTED = "InputStreamReadingAborted";
+}

+ 160 - 0
src/main/java/com/emato/eoss/manager/oss/minio/ClientException.java

@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio;
+
+/**
+ * <p>
+ * This exception is the one thrown by the client side when accessing OSS.
+ * </p>
+ * 
+ * <p>
+ * {@link ClientException} is the class to represent any exception in OSS client
+ * side. Generally ClientException occurs either before sending the request or
+ * after receving the response from OSS server side. For example, if the network
+ * is broken when it tries to send a request, then the SDK will throw a
+ * {@link ClientException} instance.
+ * </p>
+ * 
+ * <p>
+ * {@link ServiceException} is converted from error code from OSS response. For
+ * example, when OSS tries to authenticate a request, if Access ID does not
+ * exist, the SDK will throw a {@link ServiceException} or its subclass instance
+ * with the specific error code, which the caller could handle that with
+ * specific logic.
+ * </p>
+ * 
+ */
+public class ClientException extends RuntimeException {
+
+    private static final long serialVersionUID = 1870835486798448798L;
+
+    private String errorMessage;
+    private String requestId;
+    private String errorCode;
+
+    /**
+     * Creates a default instance.
+     */
+    public ClientException() {
+        super();
+    }
+
+    /**
+     * Creates an instance with error message.
+     * 
+     * @param errorMessage
+     *            Error message.
+     */
+    public ClientException(String errorMessage) {
+        this(errorMessage, null);
+    }
+
+    /**
+     * Creates an instance with an exception
+     * 
+     * @param cause
+     *            An exception.
+     */
+    public ClientException(Throwable cause) {
+        this(null, cause);
+    }
+
+    /**
+     * Creates an instance with error message and an exception.
+     * 
+     * @param errorMessage
+     *            Error message.
+     * @param cause
+     *            An exception.
+     */
+    public ClientException(String errorMessage, Throwable cause) {
+        super(null, cause);
+        this.errorMessage = errorMessage;
+        this.errorCode = ClientErrorCode.UNKNOWN;
+        this.requestId = "Unknown";
+    }
+
+    /**
+     * Creates an instance with error message, error code, request Id
+     * 
+     * @param errorMessage
+     *            Error message.
+     * @param errorCode
+     *            Error code, which typically is from a set of predefined
+     *            errors. The handler code could do action based on this.
+     * @param requestId
+     *            Request Id.
+     */
+    public ClientException(String errorMessage, String errorCode, String requestId) {
+        this(errorMessage, errorCode, requestId, null);
+    }
+
+    /**
+     * Creates an instance with error message, error code, request Id and an
+     * exception.
+     * 
+     * @param errorMessage
+     *            Error message.
+     * @param errorCode
+     *            Error code.
+     * @param requestId
+     *            Request Id.
+     * @param cause
+     *            An exception.
+     */
+    public ClientException(String errorMessage, String errorCode, String requestId, Throwable cause) {
+        this(errorMessage, cause);
+        this.errorCode = errorCode;
+        this.requestId = requestId;
+    }
+
+    /**
+     * Get error message.
+     * 
+     * @return Error message in string.
+     */
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    /**
+     * Get error code.
+     * 
+     * @return Error code.
+     */
+    public String getErrorCode() {
+        return errorCode;
+    }
+
+    /**
+     * Gets request id.
+     * 
+     * @return The request Id.
+     */
+    public String getRequestId() {
+        return requestId;
+    }
+
+    @Override
+    public String getMessage() {
+        return getErrorMessage() + "\n[ErrorCode]: " + (errorCode != null ? errorCode
+                : "") + "\n[RequestId]: " + (requestId != null ? requestId : "");
+    }
+}

+ 55 - 0
src/main/java/com/emato/eoss/manager/oss/minio/HttpMethod.java

@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio;
+
+/**
+ * Http Methods
+ */
+public enum HttpMethod {
+    /**
+     * HTTP DELETE.
+     */
+    DELETE,
+
+    /**
+     * HTTP GET
+     */
+    GET,
+
+    /**
+     * HTTP HEAD
+     */
+    HEAD,
+
+    /**
+     * HTTP POST
+     */
+    POST,
+
+    /**
+     * HTTP PUT
+     */
+    PUT,
+
+    /**
+     * HTTP OPTION
+     */
+    OPTIONS
+}

+ 77 - 0
src/main/java/com/emato/eoss/manager/oss/minio/MinioTest.java

@@ -0,0 +1,77 @@
+package com.emato.eoss.manager.oss.minio;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import cn.hutool.crypto.digest.HmacAlgorithm;
+import com.emato.eoss.util.sign.Md5;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * @author Scott Chen
+ * @date 2021-04-06
+ */
+public class MinioTest {
+    private static final Logger logger = LoggerFactory.getLogger(MinioTest.class);
+
+    private static String accessKeySecret = "my secret";
+
+
+
+    public void setOssAuthorization() {
+
+    }
+
+    public void send() {
+        String method = HttpMethod.PUT.toString();
+        String contentMd5 = "";
+        String contentType = "";
+        String date = "";
+        String canonicalizedOSSHeaders = "";
+        String canonicalizedResource = "";
+
+    }
+
+    public String build() {
+        return null;
+    }
+
+    public static void main(String[] args) {
+        String str = "0123456789";
+        String key = "abc-123";
+        String str1 = "PUT\nODBGOERFMDMzQTczRUY3NUE3NzA5QzdFNUYzMDQxNEM=\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-oss-magic:abracadabra\nx-oss-meta-author:foo@bar.com\n/oss-example/nelson";
+        String key1 = "OtxrzxIsfpFjA7Sw******8Bw21TLhquhboDYROV";
+
+        byte[] hCode = DigestUtil.md5(str);
+        String hHexCode = DigestUtil.md5Hex(str);
+        String base64HCode = Base64.encode(hCode);
+        String base64HHexCode = Base64.encode(hHexCode);
+        System.out.println("hCode:" + hCode);
+        System.out.println("hHexCode:" + hHexCode);
+        System.out.println("base64HCode:" + base64HCode);
+        System.out.println("base64HHexCode:" + base64HHexCode);
+
+        System.out.println("-------------------------------------");
+
+        byte[] abc = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, key1.getBytes(StandardCharsets.UTF_8))
+                .digest(str1);
+        String abcc = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, key.getBytes(StandardCharsets.UTF_8))
+                .digestHex(str);
+        String base64Abc =Base64.encode(abc);
+        String base64Abcc = Base64.encode(abcc);
+
+        System.out.println("abc:" + abc);
+        System.out.println("abcc:" + abcc);
+        System.out.println("base64Abc:" + base64Abc);
+        System.out.println("base64Abcc:" + base64Abcc);
+
+
+    }
+
+}

+ 299 - 0
src/main/java/com/emato/eoss/manager/oss/minio/OSSErrorCode.java

@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio;
+
+/**
+ * OSS Server side error code.
+ */
+public interface OSSErrorCode {
+
+    /**
+     * Access Denied (401)
+     */
+    static final String ACCESS_DENIED = "AccessDenied";
+
+    /**
+     * Access Forbidden (403)
+     */
+    static final String ACCESS_FORBIDDEN = "AccessForbidden";
+
+    /**
+     * Bucket pre-exists
+     */
+    static final String BUCKET_ALREADY_EXISTS = "BucketAlreadyExists";
+
+    /**
+     * Bucket not empty.
+     */
+    static final String BUCKET_NOT_EMPTY = "BucketNotEmpty";
+
+    /**
+     * File groups is too large.
+     */
+    static final String FILE_GROUP_TOO_LARGE = "FileGroupTooLarge";
+
+    /**
+     * File part is stale.
+     */
+    static final String FILE_PART_STALE = "FilePartStale";
+
+    /**
+     * Invalid argument.
+     */
+    static final String INVALID_ARGUMENT = "InvalidArgument";
+
+    /**
+     * Non-existing Access ID
+     */
+    static final String INVALID_ACCESS_KEY_ID = "InvalidAccessKeyId";
+
+    /**
+     * Invalid bucket name
+     */
+    static final String INVALID_BUCKET_NAME = "InvalidBucketName";
+
+    /**
+     * Invalid object name
+     */
+    static final String INVALID_OBJECT_NAME = "InvalidObjectName";
+
+    /**
+     * Invalid part
+     */
+    static final String INVALID_PART = "InvalidPart";
+
+    /**
+     * Invalid part order
+     */
+    static final String INVALID_PART_ORDER = "InvalidPartOrder";
+
+    /**
+     * The target bucket does not exist when setting logging.
+     */
+    static final String INVALID_TARGET_BUCKET_FOR_LOGGING = "InvalidTargetBucketForLogging";
+
+    /**
+     * OSS Internal error.
+     */
+    static final String INTERNAL_ERROR = "InternalError";
+
+    /**
+     * Missing content length.
+     */
+    static final String MISSING_CONTENT_LENGTH = "MissingContentLength";
+
+    /**
+     * Missing required argument.
+     */
+    static final String MISSING_ARGUMENT = "MissingArgument";
+
+    /**
+     * No bucket meets the requirement specified.
+     */
+    static final String NO_SUCH_BUCKET = "NoSuchBucket";
+
+    /**
+     * File does not exist.
+     */
+    static final String NO_SUCH_KEY = "NoSuchKey";
+
+    /**
+     * Version does not exist.
+     */
+    static final String NO_SUCH_VERSION = "NoSuchVersion";
+    
+    /**
+     * Not implemented method.
+     */
+    static final String NOT_IMPLEMENTED = "NotImplemented";
+
+    /**
+     * Error occurred in precondition.
+     */
+    static final String PRECONDITION_FAILED = "PreconditionFailed";
+
+    /**
+     * 304 Not Modified。
+     */
+    static final String NOT_MODIFIED = "NotModified";
+
+    /**
+     * Invalid location.
+     */
+    static final String INVALID_LOCATION_CONSTRAINT = "InvalidLocationConstraint";
+
+    /**
+     * The specified location does not match with the request.
+     */
+    static final String ILLEGAL_LOCATION_CONSTRAINT_EXCEPTION = "IllegalLocationConstraintException";
+
+    /**
+     * The time skew between the time in request headers and server is more than
+     * 15 min.
+     */
+    static final String REQUEST_TIME_TOO_SKEWED = "RequestTimeTooSkewed";
+
+    /**
+     * Request times out.
+     */
+    static final String REQUEST_TIMEOUT = "RequestTimeout";
+
+    /**
+     * Invalid signature.
+     */
+    static final String SIGNATURE_DOES_NOT_MATCH = "SignatureDoesNotMatch";
+
+    /**
+     * Too many buckets under a user.
+     */
+    static final String TOO_MANY_BUCKETS = "TooManyBuckets";
+
+    /**
+     * Source buckets is not configured with CORS.
+     */
+    static final String NO_SUCH_CORS_CONFIGURATION = "NoSuchCORSConfiguration";
+
+    /**
+     * The source bucket is not configured with static website (the index page
+     * is null).
+     */
+    static final String NO_SUCH_WEBSITE_CONFIGURATION = "NoSuchWebsiteConfiguration";
+
+    /**
+     * The source bucket is not configured with lifecycle rule.
+     */
+    static final String NO_SUCH_LIFECYCLE = "NoSuchLifecycle";
+
+    /**
+     * Malformed xml.
+     */
+    static final String MALFORMED_XML = "MalformedXML";
+
+    /**
+     * Invalid encryption algorithm error.
+     */
+    static final String INVALID_ENCRYPTION_ALGORITHM_ERROR = "InvalidEncryptionAlgorithmError";
+
+    /**
+     * The upload Id does not exist.
+     */
+    static final String NO_SUCH_UPLOAD = "NoSuchUpload";
+
+    /**
+     * The entity is too small. (Part must be more than 100K)
+     */
+    static final String ENTITY_TOO_SMALL = "EntityTooSmall";
+
+    /**
+     * The entity is too big.
+     */
+    static final String ENTITY_TOO_LARGE = "EntityTooLarge";
+
+    /**
+     * Invalid MD5 digest.
+     */
+    static final String INVALID_DIGEST = "InvalidDigest";
+
+    /**
+     * Invalid range of the character.
+     */
+    static final String INVALID_RANGE = "InvalidRange";
+
+    /**
+     * Security token is not supported.
+     */
+    static final String SECURITY_TOKEN_NOT_SUPPORTED = "SecurityTokenNotSupported";
+
+    /**
+     * The specified object does not support append operation.
+     */
+    static final String OBJECT_NOT_APPENDALBE = "ObjectNotAppendable";
+
+    /**
+     * The position of append on the object is not same as the current length.
+     */
+    static final String POSITION_NOT_EQUAL_TO_LENGTH = "PositionNotEqualToLength";
+
+    /**
+     * Invalid response.
+     */
+    static final String INVALID_RESPONSE = "InvalidResponse";
+
+    /**
+     * Callback failed. The operation (such as download or upload) succeeded
+     * though.
+     */
+    static final String CALLBACK_FAILED = "CallbackFailed";
+
+    /**
+     * The Live Channel does not exist.
+     */
+    static final String NO_SUCH_LIVE_CHANNEL = "NoSuchLiveChannel";
+
+    /**
+     * symlink target file does not exist.
+     */
+    static final String NO_SUCH_SYM_LINK_TARGET = "SymlinkTargetNotExist";
+
+    /**
+     * The archive file is not restored before usage.
+     */
+    static final String INVALID_OBJECT_STATE = "InvalidObjectState";
+    
+    /**
+     * The policy text is illegal.
+     */
+    static final String INVALID_POLICY_DOCUMENT = "InvalidPolicyDocument";
+    
+    /**
+     * The exsiting bucket without policy.
+     */
+    static final String NO_SUCH_BUCKET_POLICY = "NoSuchBucketPolicy";
+
+    /**
+     * The object has already exists.
+     */
+    static final String OBJECT_ALREADY_EXISTS = "ObjectAlreadyExists";
+
+    /**
+     * The exsiting bucket without inventory.
+     */
+    static final String NO_SUCH_INVENTORY = "NoSuchInventory";
+
+    /**
+     * The part is not upload sequentially
+     */
+    static final String PART_NOT_SEQUENTIAL = "PartNotSequential";
+
+    /**
+     * The file is immutable.
+     */
+    static final String FILE_IMMUTABLE = "FileImmutable";
+
+    /**
+     * The worm configuration is locked.
+     */
+    static final String WORM_CONFIGURATION_LOCKED = "WORMConfigurationLocked";
+
+    /**
+     * The worm configuration is invalid.
+     */
+    static final String INVALID_WORM_CONFIGURATION = "InvalidWORMConfiguration";
+}

+ 85 - 0
src/main/java/com/emato/eoss/manager/oss/minio/OSSException.java

@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio;
+
+/**
+ * The OSSException is thrown upon error when accessing OSS.
+ */
+public class OSSException extends ServiceException {
+
+    private static final long serialVersionUID = -1979779664334663173L;
+
+    private String resourceType;
+    private String header;
+    private String method;
+
+    public OSSException() {
+        super();
+    }
+
+    public OSSException(String errorMessage) {
+        super(errorMessage);
+    }
+
+    public OSSException(String errorMessage, Throwable cause) {
+        super(errorMessage, cause);
+    }
+
+    public OSSException(String errorMessage, String errorCode, String requestId, String hostId, String header,
+                        String resourceType, String method) {
+        this(errorMessage, errorCode, requestId, hostId, header, resourceType, method, null, null);
+    }
+
+    public OSSException(String errorMessage, String errorCode, String requestId, String hostId, String header,
+                        String resourceType, String method, Throwable cause) {
+        this(errorMessage, errorCode, requestId, hostId, header, resourceType, method, null, cause);
+    }
+
+    public OSSException(String errorMessage, String errorCode, String requestId, String hostId, String header,
+                        String resourceType, String method, String rawResponseError) {
+        this(errorMessage, errorCode, requestId, hostId, header, resourceType, method, rawResponseError, null);
+    }
+
+    public OSSException(String errorMessage, String errorCode, String requestId, String hostId, String header,
+                        String resourceType, String method, String rawResponseError, Throwable cause) {
+        super(errorMessage, errorCode, requestId, hostId, rawResponseError, cause);
+        this.resourceType = resourceType;
+        this.header = header;
+        this.method = method;
+    }
+
+    public String getResourceType() {
+        return resourceType;
+    }
+
+    public String getHeader() {
+        return header;
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    @Override
+    public String getMessage() {
+        return super.getMessage() + (resourceType == null ? "" : "\n[ResourceType]: " + resourceType)
+                + (header == null ? "" : "\n[Header]: " + header) + (method == null ? "" : "\n[Method]: " + method);
+    }
+}

+ 231 - 0
src/main/java/com/emato/eoss/manager/oss/minio/ServiceException.java

@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio;
+
+/**
+ * <p>
+ * This is the base exception class to represent any expected or unexpected OSS
+ * server side errors.
+ * </p>
+ * 
+ * <p>
+ * {@link ServiceException} is converted from error code from OSS response. For
+ * example, when OSS tries to authenticate a request, if Access ID does not
+ * exist, the SDK will throw a {@link ServiceException} or its subclass instance
+ * with the specific error code, which the caller could handle that with
+ * specific logic.
+ * </p>
+ * 
+ * <p>
+ * On the other side, {@link ClientException} is the class to represent any
+ * exception in OSS client side. Generally ClientException occurs either before
+ * sending the request or after receving the response from OSS server side. For
+ * example, if the network is broken when it tries to send a request, then the
+ * SDK will throw a {@link ClientException} instance.
+ * </p>
+ * 
+ * <p>
+ * So generally speaking, the caller only needs to handle
+ * {@link ServiceException} properly as it means the request is processed, but
+ * not completely finished due to different errors. The error code in the
+ * exception is a good diagnostics information. Sometimes these exceptions are
+ * completely expected.
+ * </p>
+ */
+public class ServiceException extends RuntimeException {
+
+    private static final long serialVersionUID = 430933593095358673L;
+
+    private String errorMessage;
+    private String errorCode;
+    private String requestId;
+    private String hostId;
+
+    private String rawResponseError;
+
+    /**
+     * Creates a default instance.
+     */
+    public ServiceException() {
+        super();
+    }
+
+    /**
+     * Creates an instance with the error message.
+     * 
+     * @param errorMessage
+     *            Error message.
+     */
+    public ServiceException(String errorMessage) {
+        super((String) null);
+        this.errorMessage = errorMessage;
+    }
+
+    /**
+     * Creates an instance with a {@link Throwable} instance.
+     * 
+     * @param cause
+     *            A {@link Throwable} instance.
+     */
+    public ServiceException(Throwable cause) {
+        super(null, cause);
+    }
+
+    /**
+     * Creates an instance with a {@link Throwable} instance and error message.
+     * 
+     * @param errorMessage
+     *            Error message.
+     * @param cause
+     *            A {@link Throwable} instance.
+     */
+    public ServiceException(String errorMessage, Throwable cause) {
+        super(null, cause);
+        this.errorMessage = errorMessage;
+    }
+
+    /**
+     * Creates an instance with error message, error code, request id, host id.
+     * 
+     * @param errorMessage
+     *            Error message.
+     * @param errorCode
+     *            Error code.
+     * @param requestId
+     *            Request Id.
+     * @param hostId
+     *            Host Id.
+     */
+    public ServiceException(String errorMessage, String errorCode, String requestId, String hostId) {
+        this(errorMessage, errorCode, requestId, hostId, null);
+    }
+
+    /**
+     * Creates an instance with error message, error code, request id, host id.
+     * 
+     * @param errorMessage
+     *            Error message.
+     * @param errorCode
+     *            Error code.
+     * @param requestId
+     *            Request Id.
+     * @param hostId
+     *            Host Id.
+     * @param cause
+     *            A {@link Throwable} instance indicates a specific exception.
+     */
+    public ServiceException(String errorMessage, String errorCode, String requestId, String hostId, Throwable cause) {
+        this(errorMessage, errorCode, requestId, hostId, null, cause);
+    }
+
+    /**
+     * Creates an instance with error message, error code, request id, host id,
+     * OSS response error, and a Throwable instance.
+     * 
+     * @param errorMessage
+     *            Error message.
+     * @param errorCode
+     *            Error code.
+     * @param requestId
+     *            Request Id.
+     * @param hostId
+     *            Host Id.
+     * @param rawResponseError
+     *            OSS error message in response.
+     * @param cause
+     *            A {@link Throwable} instance indicates a specific exception.
+     */
+    public ServiceException(String errorMessage, String errorCode, String requestId, String hostId,
+                            String rawResponseError, Throwable cause) {
+        this(errorMessage, cause);
+        this.errorCode = errorCode;
+        this.requestId = requestId;
+        this.hostId = hostId;
+        this.rawResponseError = rawResponseError;
+    }
+
+    /**
+     * Gets error message.
+     * 
+     * @return Error message.
+     */
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    /**
+     * Gets the error code.
+     * 
+     * @return The error code in string.
+     */
+    public String getErrorCode() {
+        return errorCode;
+    }
+
+    /**
+     * Gets the request id.
+     * 
+     * @return The request Id in string.
+     */
+    public String getRequestId() {
+        return requestId;
+    }
+
+    /**
+     * Gets the host id.
+     * 
+     * @return The host Id in string.
+     */
+    public String getHostId() {
+        return hostId;
+    }
+
+    /**
+     * Gets the error message in OSS response.
+     * 
+     * @return Error response in string.
+     */
+    public String getRawResponseError() {
+        return rawResponseError;
+    }
+
+    /**
+     * Sets the error response from OSS.
+     * 
+     * @param rawResponseError
+     *            The error response from OSS.
+     */
+    public void setRawResponseError(String rawResponseError) {
+        this.rawResponseError = rawResponseError;
+    }
+
+    private String formatRawResponseError() {
+        if (rawResponseError == null || rawResponseError.equals("")) {
+            return "";
+        }
+        return String.format("\n[ResponseError]:\n%s", this.rawResponseError);
+    }
+
+    @Override
+    public String getMessage() {
+        return getErrorMessage() + "\n[ErrorCode]: " + getErrorCode() + "\n[RequestId]: " + getRequestId()
+                + "\n[HostId]: " + getHostId() + formatRawResponseError();
+    }
+}

+ 45 - 0
src/main/java/com/emato/eoss/manager/oss/minio/config/MinioConfig.java

@@ -0,0 +1,45 @@
+package com.emato.eoss.manager.oss.minio.config;
+
+import com.emato.eoss.manager.oss.minio.utils.MinioUtils;
+import io.minio.MinioClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Scott Chen
+ * @date 2021-03-04
+ */
+@Configuration
+public class MinioConfig {
+
+    @Autowired
+    private MinioProp minioProp;
+
+    @Bean
+    @ConfigurationProperties("minio")
+    public MinioProp minioProperties(){
+        return new MinioProp();
+    };
+
+    /**
+     * MinioClient
+     *
+     * @return
+     */
+    @Bean
+    public MinioClient minioClient() {
+        return new MinioClient.Builder().endpoint(minioProp.getEndpoint()).credentials(minioProp.getAccesskeyId(), minioProp.getSecretAccessKey()).build();
+    }
+
+    /**
+     * MiniUtils工具类
+     * @return
+     */
+    @Bean
+    public MinioUtils minioUtils() {
+        return MinioUtils.create(minioProp);
+    }
+
+}

+ 72 - 0
src/main/java/com/emato/eoss/manager/oss/minio/config/MinioProp.java

@@ -0,0 +1,72 @@
+package com.emato.eoss.manager.oss.minio.config;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author Scott Chen
+ * @date 2021-03-05
+ */
+public class MinioProp implements Serializable {
+    private static final long serialVersionUID = 6142961236114842876L;
+
+    // true:scheme为https,false:scheme为http
+    private boolean secure;
+    private String endpoint;
+    private int port;
+    private String accesskeyId;
+    private String secretAccessKey;
+    // 当stoage下的存储桶 buckets
+    //  跟具体业务相关名称,对应如下的 buckets值
+    private Map<String, String> buckets;
+
+
+    public boolean getSecure() {
+        return secure;
+    }
+
+    public void setSecure(boolean secure) {
+        this.secure = secure;
+    }
+
+    public String getEndpoint() {
+        return endpoint;
+    }
+
+    public void setEndpoint(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public String getAccesskeyId() {
+        return accesskeyId;
+    }
+
+    public void setAccesskeyId(String accesskeyId) {
+        this.accesskeyId = accesskeyId;
+    }
+
+    public String getSecretAccessKey() {
+        return secretAccessKey;
+    }
+
+    public void setSecretAccessKey(String secretAccessKey) {
+        this.secretAccessKey = secretAccessKey;
+    }
+
+    public Map<String, String> getBuckets() {
+        return buckets;
+    }
+
+    public void setBuckets(Map<String, String> buckets) {
+        this.buckets = buckets;
+    }
+
+}

+ 56 - 0
src/main/java/com/emato/eoss/manager/oss/minio/internal/OSSConstants.java

@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio.internal;
+
+/**
+ * Miscellaneous constants used for oss client service.
+ */
+public final class OSSConstants {
+
+    public static final String DEFAULT_OSS_ENDPOINT = "http://oss.songmao.com";
+
+    public static final String DEFAULT_CHARSET_NAME = "utf-8";
+    public static final String DEFAULT_XML_ENCODING = "utf-8";
+
+    public static final String DEFAULT_OBJECT_CONTENT_TYPE = "application/octet-stream";
+
+    public static final int KB = 1024;
+    public static final int DEFAULT_BUFFER_SIZE = 8 * KB;
+    public static final int DEFAULT_STREAM_BUFFER_SIZE = 512 * KB;
+
+    public static final long DEFAULT_FILE_SIZE_LIMIT = 5 * 1024 * 1024 * 1024L;
+
+    public static final String RESOURCE_NAME_COMMON = "common";
+    public static final String RESOURCE_NAME_OSS = "oss";
+
+    public static final int OBJECT_NAME_MAX_LENGTH = 1024;
+
+    public static final String LOGGER_PACKAGE_NAME = "com.songmao.oss";
+
+    public static final String PROTOCOL_HTTP = "http://";
+    public static final String PROTOCOL_HTTPS = "https://";
+    public static final String PROTOCOL_RTMP = "rtmp://";
+    
+    /** Represents a null OSS version ID */
+    public static final String NULL_VERSION_ID = "null";
+    
+    /** URL encoding for OSS object keys */
+    public static final String URL_ENCODING = "url";
+}

+ 105 - 0
src/main/java/com/emato/eoss/manager/oss/minio/internal/OSSHeaders.java

@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio.internal;
+
+
+import com.emato.eoss.manager.authz.common.comm.HttpHeaders;
+
+public interface OSSHeaders extends HttpHeaders {
+
+    static final String OSS_PREFIX = "x-oss-";
+    static final String OSS_USER_METADATA_PREFIX = "x-oss-meta-";
+
+    static final String OSS_CANNED_ACL = "x-oss-acl";
+    static final String STORAGE_CLASS = "x-oss-storage-class";
+    static final String OSS_VERSION_ID = "x-oss-version-id";
+
+    static final String OSS_SERVER_SIDE_ENCRYPTION = "x-oss-server-side-encryption";
+    static final String OSS_SERVER_SIDE_ENCRYPTION_KEY_ID = "x-oss-server-side-encryption-key-id";
+    static final String OSS_SERVER_SIDE_DATA_ENCRYPTION = "x-oss-server-side-data-encryption";
+
+    static final String GET_OBJECT_IF_MODIFIED_SINCE = "If-Modified-Since";
+    static final String GET_OBJECT_IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
+    static final String GET_OBJECT_IF_MATCH = "If-Match";
+    static final String GET_OBJECT_IF_NONE_MATCH = "If-None-Match";
+
+    static final String HEAD_OBJECT_IF_MODIFIED_SINCE = "If-Modified-Since";
+    static final String HEAD_OBJECT_IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
+    static final String HEAD_OBJECT_IF_MATCH = "If-Match";
+    static final String HEAD_OBJECT_IF_NONE_MATCH = "If-None-Match";
+
+    static final String COPY_OBJECT_SOURCE = "x-oss-copy-source";
+    static final String COPY_SOURCE_RANGE = "x-oss-copy-source-range";
+    static final String COPY_OBJECT_SOURCE_IF_MATCH = "x-oss-copy-source-if-match";
+    static final String COPY_OBJECT_SOURCE_IF_NONE_MATCH = "x-oss-copy-source-if-none-match";
+    static final String COPY_OBJECT_SOURCE_IF_UNMODIFIED_SINCE = "x-oss-copy-source-if-unmodified-since";
+    static final String COPY_OBJECT_SOURCE_IF_MODIFIED_SINCE = "x-oss-copy-source-if-modified-since";
+    static final String COPY_OBJECT_METADATA_DIRECTIVE = "x-oss-metadata-directive";
+    static final String COPY_OBJECT_TAGGING_DIRECTIVE = "x-oss-tagging-directive";
+
+    static final String OSS_HEADER_REQUEST_ID = "x-oss-request-id";
+    static final String OSS_HEADER_VERSION_ID = "x-oss-version-id";
+
+    static final String ORIGIN = "origin";
+    static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
+    static final String ACCESS_CONTROL_REQUEST_HEADER = "Access-Control-Request-Headers";
+
+    static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
+    static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
+    static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
+    static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
+    static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
+
+    static final String OSS_SECURITY_TOKEN = "x-oss-security-token";
+
+    static final String OSS_NEXT_APPEND_POSITION = "x-oss-next-append-position";
+    static final String OSS_HASH_CRC64_ECMA = "x-oss-hash-crc64ecma";
+    static final String OSS_OBJECT_TYPE = "x-oss-object-type";
+
+    static final String OSS_OBJECT_ACL = "x-oss-object-acl";
+
+    static final String OSS_HEADER_CALLBACK = "x-oss-callback";
+    static final String OSS_HEADER_CALLBACK_VAR = "x-oss-callback-var";
+    static final String OSS_HEADER_SYMLINK_TARGET = "x-oss-symlink-target";
+
+    static final String OSS_STORAGE_CLASS = "x-oss-storage-class";
+    static final String OSS_RESTORE = "x-oss-restore";
+    static final String OSS_ONGOING_RESTORE = "ongoing-request=\"true\"";
+
+    static final String OSS_BUCKET_REGION = "x-oss-bucket-region";
+
+    static final String OSS_SELECT_PREFIX = "x-oss-select";
+    static final String OSS_SELECT_CSV_ROWS = OSS_SELECT_PREFIX + "-csv-rows";
+    static final String OSS_SELECT_OUTPUT_RAW = OSS_SELECT_PREFIX + "-output-raw";
+    static final String OSS_SELECT_CSV_SPLITS = OSS_SELECT_PREFIX + "-csv-splits";
+    static final String OSS_SELECT_INPUT_LINE_RANGE = OSS_SELECT_PREFIX + "-line-range";
+    static final String OSS_SELECT_INPUT_SPLIT_RANGE = OSS_SELECT_PREFIX + "-split-range";
+    
+    static final String OSS_TAGGING = "x-oss-tagging";
+
+    static final String OSS_REQUEST_PAYER = "x-oss-request-payer";
+
+    static final String OSS_HEADER_TRAFFIC_LIMIT = "x-oss-traffic-limit";
+
+    static final String OSS_HEADER_TASK_ID = "x-oss-task-id";
+
+    static final String OSS_HEADER_WORM_ID = "x-oss-worm-id";
+
+}

+ 64 - 0
src/main/java/com/emato/eoss/manager/oss/minio/internal/OSSRequestSigner.java

@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio.internal;
+
+
+import com.emato.eoss.manager.authz.common.authc.Credentials;
+import com.emato.eoss.manager.authz.common.authc.RequestSigner;
+import com.emato.eoss.manager.authz.common.comm.RequestMessage;
+import com.emato.eoss.manager.authz.common.comm.SignVersion;
+import com.emato.eoss.manager.oss.minio.ClientException;
+
+public class OSSRequestSigner implements RequestSigner {
+
+    private String httpMethod;
+
+    /* Note that resource path should not have been url-encoded. */
+    private String resourcePath;
+
+    private Credentials creds;
+
+    private SignVersion signatureVersion;
+
+    public OSSRequestSigner(String httpMethod, String resourcePath, Credentials creds, SignVersion signatureVersion) {
+        this.httpMethod = httpMethod;
+        this.resourcePath = resourcePath;
+        this.creds = creds;
+        this.signatureVersion = signatureVersion;
+    }
+
+    @Override
+    public void sign(RequestMessage request) throws ClientException {
+        String accessKeyId = creds.getAccessKeyId();
+        String secretAccessKey = creds.getSecretAccessKey();
+
+        if (accessKeyId.length() > 0 && secretAccessKey.length() > 0) {
+            String signature;
+
+            if (signatureVersion == SignVersion.V2) {
+                signature = SignV2Utils.buildSignature(secretAccessKey, httpMethod, resourcePath, request);
+                request.addHeader(OSSHeaders.AUTHORIZATION, SignV2Utils.composeRequestAuthorization(accessKeyId,signature, request));
+            } else {
+                signature = SignUtils.buildSignature(secretAccessKey, httpMethod, resourcePath, request);
+                request.addHeader(OSSHeaders.AUTHORIZATION, SignUtils.composeRequestAuthorization(accessKeyId, signature));
+            }
+        }
+    }
+}

+ 411 - 0
src/main/java/com/emato/eoss/manager/oss/minio/internal/OSSUtils.java

@@ -0,0 +1,411 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package com.emato.eoss.manager.oss.minio.internal;
+
+
+import com.emato.eoss.manager.authz.common.comm.ResponseMessage;
+import com.emato.eoss.manager.authz.common.model.Callback;
+import com.emato.eoss.manager.authz.common.utils.DateUtil;
+import com.emato.eoss.manager.authz.common.utils.ResourceManager;
+import com.emato.eoss.manager.oss.minio.ClientConfiguration;
+import com.emato.eoss.manager.authz.common.utils.HttpUtil;
+import com.emato.eoss.manager.authz.common.model.Callback.CalbackBodyType;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static com.emato.eoss.manager.oss.minio.internal.OSSConstants.*;
+
+
+public class OSSUtils {
+
+    public static final ResourceManager OSS_RESOURCE_MANAGER = ResourceManager.getInstance(RESOURCE_NAME_OSS);
+    public static final ResourceManager COMMON_RESOURCE_MANAGER = ResourceManager.getInstance(RESOURCE_NAME_COMMON);
+
+    private static final String BUCKET_NAMING_CREATION_REGEX = "^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$";
+    private static final String BUCKET_NAMING_REGEX = "^[a-z0-9][a-z0-9-_]{1,61}[a-z0-9]$";
+    private static final String ENDPOINT_REGEX = "^[a-zA-Z0-9._-]+$";
+
+    /**
+     * Validate endpoint.
+     */
+    public static boolean validateEndpoint(String endpoint) {
+        if (endpoint == null) {
+            return false;
+        }
+        return endpoint.matches(ENDPOINT_REGEX);
+    }
+
+    public static void ensureEndpointValid(String endpoint) {
+        if (!validateEndpoint(endpoint)) {
+            throw new IllegalArgumentException(
+                    OSS_RESOURCE_MANAGER.getFormattedString("EndpointInvalid", endpoint));
+        }
+    }
+
+    /**
+     * Validate bucket name.
+     */
+    public static boolean validateBucketName(String bucketName) {
+
+        if (bucketName == null) {
+            return false;
+        }
+
+        return bucketName.matches(BUCKET_NAMING_REGEX);
+    }
+
+    public static void ensureBucketNameValid(String bucketName) {
+        if (!validateBucketName(bucketName)) {
+            throw new IllegalArgumentException(
+                    OSS_RESOURCE_MANAGER.getFormattedString("BucketNameInvalid", bucketName));
+        }
+    }
+
+    /**
+     * Validate bucket creation name.
+     */
+    public static boolean validateBucketNameCreation(String bucketName) {
+
+        if (bucketName == null) {
+            return false;
+        }
+
+        return bucketName.matches(BUCKET_NAMING_CREATION_REGEX);
+    }
+
+    public static void ensureBucketNameCreationValid(String bucketName) {
+        if (!validateBucketNameCreation(bucketName)) {
+            throw new IllegalArgumentException(
+                    OSS_RESOURCE_MANAGER.getFormattedString("BucketNameInvalid", bucketName));
+        }
+    }
+
+    /**
+     * Validate object name.
+     */
+    public static boolean validateObjectKey(String key) {
+
+        if (key == null || key.length() == 0) {
+            return false;
+        }
+
+        byte[] bytes = null;
+        try {
+            bytes = key.getBytes(DEFAULT_CHARSET_NAME);
+        } catch (UnsupportedEncodingException e) {
+            return false;
+        }
+
+        // Validate exculde xml unsupported chars
+        char keyChars[] = key.toCharArray();
+        char firstChar = keyChars[0];
+        if (firstChar == '\\') {
+            return false;
+        }
+
+        return (bytes.length > 0 && bytes.length < OBJECT_NAME_MAX_LENGTH);
+    }
+
+    public static void ensureObjectKeyValid(String key) {
+        if (!validateObjectKey(key)) {
+            throw new IllegalArgumentException(OSS_RESOURCE_MANAGER.getFormattedString("ObjectKeyInvalid", key));
+        }
+    }
+
+    public static void ensureLiveChannelNameValid(String liveChannelName) {
+        if (!validateObjectKey(liveChannelName)) {
+            throw new IllegalArgumentException(
+                    OSS_RESOURCE_MANAGER.getFormattedString("LiveChannelNameInvalid", liveChannelName));
+        }
+    }
+
+    /**
+     * Make a third-level domain by appending bucket name to front of original
+     * endpoint if no binding to CNAME, otherwise use original endpoint as
+     * second-level domain directly.
+     */
+    public static URI determineFinalEndpoint(URI endpoint, String bucket, ClientConfiguration clientConfig) {
+        try {
+            StringBuilder conbinedEndpoint = new StringBuilder();
+            conbinedEndpoint.append(String.format("%s://", endpoint.getScheme()));
+            conbinedEndpoint.append(buildCanonicalHost(endpoint, bucket, clientConfig));
+            conbinedEndpoint.append(endpoint.getPort() != -1 ? String.format(":%s", endpoint.getPort()) : "");
+            conbinedEndpoint.append(endpoint.getPath());
+            return new URI(conbinedEndpoint.toString());
+        } catch (URISyntaxException ex) {
+            throw new IllegalArgumentException(ex.getMessage(), ex);
+        }
+    }
+
+    private static String buildCanonicalHost(URI endpoint, String bucket, ClientConfiguration clientConfig) {
+        String host = endpoint.getHost();
+
+        boolean isCname = false;
+        if (clientConfig.isSupportCname()) {
+            isCname = cnameExcludeFilter(host, clientConfig.getCnameExcludeList());
+        }
+
+        StringBuffer cannonicalHost = new StringBuffer();
+        if (bucket != null && !isCname && !clientConfig.isSLDEnabled()) {
+            cannonicalHost.append(bucket).append(".").append(host);
+        } else {
+            cannonicalHost.append(host);
+        }
+
+        return cannonicalHost.toString();
+    }
+
+    private static boolean cnameExcludeFilter(String hostToFilter, List<String> excludeList) {
+        if (hostToFilter != null && !hostToFilter.trim().isEmpty()) {
+            String canonicalHost = hostToFilter.toLowerCase();
+            for (String excl : excludeList) {
+                if (canonicalHost.endsWith(excl)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        throw new IllegalArgumentException("Host name can not be null.");
+    }
+
+    public static String determineResourcePath(String bucket, String key, boolean sldEnabled) {
+        return sldEnabled ? makeResourcePath(bucket, key) : makeResourcePath(key);
+    }
+
+    /**
+     * Make a resource path from the object key, used when the bucket name
+     * pearing in the endpoint.
+     */
+    public static String makeResourcePath(String key) {
+        return key != null ? OSSUtils.urlEncodeKey(key) : null;
+    }
+
+    /**
+     * Make a resource path from the bucket name and the object key.
+     */
+    public static String makeResourcePath(String bucket, String key) {
+        if (bucket != null) {
+            return bucket + "/" + (key != null ? OSSUtils.urlEncodeKey(key) : "");
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Encode object URI.
+     */
+    private static String urlEncodeKey(String key) {
+        if (key.startsWith("/")) {
+            return HttpUtil.urlEncode(key, DEFAULT_CHARSET_NAME);
+        }
+
+        StringBuffer resultUri = new StringBuffer();
+
+        String[] keys = key.split("/");
+        resultUri.append(HttpUtil.urlEncode(keys[0], DEFAULT_CHARSET_NAME));
+        for (int i = 1; i < keys.length; i++) {
+            resultUri.append("/").append(HttpUtil.urlEncode(keys[i], DEFAULT_CHARSET_NAME));
+        }
+
+        if (key.endsWith("/")) {
+            // String#split ignores trailing empty strings,
+            // e.g., "a/b/" will be split as a 2-entries array,
+            // so we have to append all the trailing slash to the uri.
+            for (int i = key.length() - 1; i >= 0; i--) {
+                if (key.charAt(i) == '/') {
+                    resultUri.append("/");
+                } else {
+                    break;
+                }
+            }
+        }
+
+        return resultUri.toString();
+    }
+
+    public static void addHeader(Map<String, String> headers, String header, String value) {
+        if (value != null) {
+            headers.put(header, value);
+        }
+    }
+
+    public static void addDateHeader(Map<String, String> headers, String header, Date value) {
+        if (value != null) {
+            headers.put(header, DateUtil.formatRfc822Date(value));
+        }
+    }
+
+    public static void addStringListHeader(Map<String, String> headers, String header, List<String> values) {
+        if (values != null && !values.isEmpty()) {
+            headers.put(header, join(values));
+        }
+    }
+
+    public static void removeHeader(Map<String, String> headers, String header) {
+        if (header != null && headers.containsKey(header)) {
+            headers.remove(header);
+        }
+    }
+
+    public static String join(List<String> strings) {
+
+        StringBuilder sb = new StringBuilder();
+        boolean first = true;
+
+        for (String s : strings) {
+            if (!first) {
+                sb.append(", ");
+            }
+            sb.append(s);
+
+            first = false;
+        }
+
+        return sb.toString();
+    }
+
+    public static String trimQuotes(String s) {
+
+        if (s == null) {
+            return null;
+        }
+
+        s = s.trim();
+        if (s.startsWith("\"")) {
+            s = s.substring(1);
+        }
+        if (s.endsWith("\"")) {
+            s = s.substring(0, s.length() - 1);
+        }
+
+        return s;
+    }
+
+    public static void safeCloseResponse(ResponseMessage response) {
+        try {
+            response.close();
+        } catch (IOException e) {
+        }
+    }
+
+    public static void mandatoryCloseResponse(ResponseMessage response) {
+        try {
+            response.abort();
+        } catch (IOException e) {
+        }
+    }
+
+    public static long determineInputStreamLength(InputStream instream, long hintLength) {
+
+        if (hintLength <= 0 || !instream.markSupported()) {
+            return -1;
+        }
+
+        return hintLength;
+    }
+
+    public static long determineInputStreamLength(InputStream instream, long hintLength, boolean useChunkEncoding) {
+
+        if (useChunkEncoding) {
+            return -1;
+        }
+
+        if (hintLength <= 0 || !instream.markSupported()) {
+            return -1;
+        }
+
+        return hintLength;
+    }
+
+    public static String joinETags(List<String> eTags) {
+
+        StringBuilder sb = new StringBuilder();
+        boolean first = true;
+
+        for (String eTag : eTags) {
+            if (!first) {
+                sb.append(", ");
+            }
+            sb.append(eTag);
+
+            first = false;
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Encode the callback with JSON.
+     */
+    public static String jsonizeCallback(Callback callback) {
+        StringBuffer jsonBody = new StringBuffer();
+
+        jsonBody.append("{");
+        // url, required
+        jsonBody.append("\"callbackUrl\":" + "\"" + callback.getCallbackUrl() + "\"");
+
+        // host, optional
+        if (callback.getCallbackHost() != null && !callback.getCallbackHost().isEmpty()) {
+            jsonBody.append(",\"callbackHost\":" + "\"" + callback.getCallbackHost() + "\"");
+        }
+
+        // body, require
+        jsonBody.append(",\"callbackBody\":" + "\"" + callback.getCallbackBody() + "\"");
+
+        // bodyType, optional
+        if (callback.getCalbackBodyType() == CalbackBodyType.JSON) {
+            jsonBody.append(",\"callbackBodyType\":\"application/json\"");
+        } else if (callback.getCalbackBodyType() == CalbackBodyType.URL) {
+            jsonBody.append(",\"callbackBodyType\":\"application/x-www-form-urlencoded\"");
+        }
+        jsonBody.append("}");
+
+        return jsonBody.toString();
+    }
+
+    /**
+     * Encode CallbackVar with Json.
+     */
+    public static String jsonizeCallbackVar(Callback callback) {
+        StringBuffer jsonBody = new StringBuffer();
+
+        jsonBody.append("{");
+        for (Entry<String, String> entry : callback.getCallbackVar().entrySet()) {
+            if (entry.getKey() != null && entry.getValue() != null) {
+                if (!jsonBody.toString().equals("{")) {
+                    jsonBody.append(",");
+                }
+                jsonBody.append("\"" + entry.getKey() + "\":\"" + entry.getValue() + "\" ");
+            }
+        }
+        jsonBody.append("}");
+
+        return jsonBody.toString();
+    }
+
+}

+ 689 - 0
src/main/java/com/emato/eoss/manager/oss/minio/utils/MinioUtils.java

@@ -0,0 +1,689 @@
+package com.emato.eoss.manager.oss.minio.utils;
+
+import com.emato.eoss.manager.oss.minio.config.MinioProp;
+import io.minio.*;
+import io.minio.errors.MinioException;
+import io.minio.http.Method;
+import io.minio.messages.Bucket;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+
+import javax.annotation.Nonnull;
+import javax.crypto.SecretKey;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * minio 工具类
+ *
+ * @author Scott Chen
+ * @date 2021-03-04
+ */
+public class MinioUtils {
+    private static final Logger logger = LoggerFactory.getLogger(MinioUtils.class);
+
+    private static final ThreadLocal<MinioClient> minioClientThreadLocal = new ThreadLocal<>();
+    private static final ThreadLocal<MinioUtils> minioUtilsThreadLocal = new ThreadLocal<>();
+
+    private MinioProp minioProp = null;
+
+
+    private MinioUtils() {}
+
+    public static MinioUtils create(@Nonnull MinioProp minioProp){
+        MinioUtils minioUtils = minioUtilsThreadLocal.get();
+        if (minioUtils == null) {
+            synchronized (MinioUtils.class) {
+                minioUtils = MinioUtils.builder().minioProp(minioProp).build();
+                minioUtilsThreadLocal.set(minioUtils);
+            }
+        }
+        return minioUtils;
+    }
+
+    public MinioClient minioClient() {
+        MinioClient client = minioClientThreadLocal.get();
+        if (client == null) {
+            synchronized (MinioUtils.class) {
+                client = MinioClient.builder()
+                        .endpoint(minioProp.getEndpoint(),
+                                minioProp.getPort(),
+                                minioProp.getSecure())
+                        .credentials(minioProp.getAccesskeyId(),
+                                minioProp.getSecretAccessKey())
+                        .build();
+                minioClientThreadLocal.set(client);
+            }
+        }
+        return client;
+    }
+
+    
+    // ------------------------------ Bucket ------------------------------
+
+    /**
+     * 存储桶是否存在
+     *
+     * @param bucket
+     * @return
+     * @throws MinioException
+     */
+    public boolean existBucket(String bucket) throws MinioException {
+        boolean found = true;
+        try {
+            found = minioClient().bucketExists(
+                    BucketExistsArgs.builder()
+                            .bucket(bucket)
+                            .build());
+        } catch (Exception e) {
+            logger.error("judge bucket error.", e);
+            throw new MinioException(e.getMessage());
+        }
+        return found;
+    }
+
+    /**
+     * 创建存储桶
+     *
+     * @param bucket 存储桶名 @NotNull
+     * @return
+     * @date 2021-03-05
+     **/
+    public void createBucketByNotExists(String bucket) throws MinioException {
+        if (!existBucket(bucket)) {
+            try {
+                minioClient().makeBucket(
+                        MakeBucketArgs.builder()
+                                .bucket(bucket)
+                                .build());
+            } catch (Exception e) {
+                logger.error("create bucket error.", e);
+                throw new MinioException(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 获取bucket
+     *
+     * @param bucket
+     * @return
+     * @throws MinioException
+     */
+    public Optional<Bucket> getBucket(String bucket) throws MinioException {
+        try {
+            List<Bucket> bucketList = getAllBuckets(Collections.emptyMap());
+            return bucketList.stream().filter(i -> i.name().equals(bucket)).findFirst();
+        } catch (Exception e) {
+            logger.error("get bucket error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+    /**
+     * 获取全部bucket
+     *
+     * @param headers
+     * @return
+     * @throws MinioException
+     */
+    public List<Bucket> getAllBuckets(Map<String, String> headers) throws MinioException {
+        try {
+            return minioClient().listBuckets(
+                    ListBucketsArgs.builder()
+                            .extraHeaders(headers)
+                            .build());
+        } catch (Exception e) {
+            logger.error("get all bucket error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+    /**
+     * 删除bucketName
+     *
+     * @param bucket bucket名称
+     */
+    public void removeBucket(String bucket) throws MinioException {
+        try {
+            minioClient().removeBucket(
+                    RemoveBucketArgs.builder()
+                            .bucket(bucket)
+                            .build());
+        } catch (Exception e) {
+            logger.error("remove bucket error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+
+
+    // ------------------------------ Object ------------------------------
+
+    /**
+     * 获取对象数据
+     * 在释放网络资源时,必须释放InputStream流
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull
+     * @return java.io.InputStream
+     * @date 2021-03-05
+     **/
+    public InputStream getObject(String bucket, String objectName) throws MinioException{
+        try {
+            return getObject(bucket, objectName, null, null, null);
+        } catch (Exception e) {
+            logger.error("get object error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+    /**
+     * 获取对象数据
+     * 在释放网络资源时,必须释放InputStream流
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull
+     * @param offset 获取开始位置
+     * @param length 获取长度
+     * @param secretKey secretKey
+     * @return
+     * @throws MinioException
+     */
+    public InputStream getObject(String bucket, String objectName,
+                                 Long offset, Long length,
+                                 SecretKey secretKey) throws MinioException{
+        try{
+            GetObjectArgs.Builder builder = GetObjectArgs.builder();
+            builder.bucket(bucket);
+            builder.object(objectName);
+            if (offset != null && offset >= 0) {
+                builder.offset(offset);
+            }
+            if (length != null && length > 0) {
+                builder.length(length);
+            }
+            if (secretKey != null) {
+                builder.ssec(new ServerSideEncryptionCustomerKey(secretKey));
+            }
+            return minioClient().getObject(builder.build());
+        } catch (Exception e) {
+            logger.error("get object with length and ssec error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+
+
+    /**
+     * 字节方式获取对象数据
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull
+     * @return byte[]
+     * @date 2021-03-05
+     **/
+    public byte[] getObjectWithByte(String bucket, String objectName) throws MinioException{
+        ArrayList<byte[]> partList = new ArrayList<>();
+        try {
+            return getObjectWithByte(bucket, objectName, null, null, null);
+        } catch (Exception e) {
+            logger.error("get file object return byte error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+    /**
+     * 字节方式获取对象数据
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull
+     * @param offset 获取开始位置
+     * @param length 获取长度
+     * @param secretKey secretKey
+     * @return byte[]
+     * @date 2021-03-05
+     **/
+    public byte[] getObjectWithByte(String bucket, String objectName,
+                                         Long offset, Long length,
+                                         SecretKey secretKey) throws MinioException{
+        ArrayList<byte[]> partList = new ArrayList<>();
+        InputStream is = null;
+        try {
+            is = getObject(bucket, objectName, offset, length, secretKey);
+
+            byte[] buffered = new byte[1024];
+            int len;
+            while ((len = is.read(buffered)) > 0) {
+                byte[] temp = new byte[len];
+                System.arraycopy(buffered, 0, temp, 0, len);
+                partList.add(temp);
+            }
+            is.close();
+        } catch (Exception e) {
+            logger.error("get file object with length ssec return error.", e);
+            throw new MinioException(e.getMessage());
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    logger.error("下载文件" + objectName + ",关闭InputStream异常", e);
+                }
+            }
+        }
+
+        if (partList.isEmpty()) {
+            return new byte[]{0};
+        }
+
+        Integer size = partList.stream().map(x -> x.length).reduce(0, Integer::sum);
+        byte[] result = new byte[size];
+        int currentIndex = 0;
+        for (byte[] bytes : partList) {
+            for (byte aByte : bytes) {
+                result[currentIndex++] = aByte;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 将对象的数据下载到文件
+     * 下载到服务端指定路径
+     * objectName下载为outputFileName
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull
+     * @param outputFileName 输出文件,可以包含路径 @NotNull
+     * @return
+     * @date 2021-03-05
+     **/
+    public void downloadObject(String bucket, String objectName, String outputFileName) throws MinioException{
+        try {
+            DownloadObjectArgs.Builder builder = DownloadObjectArgs.builder();
+            builder.bucket(bucket);
+            builder.object(objectName);
+
+            outputFileName = StringUtils.isBlank(outputFileName) ? objectName : outputFileName;
+            builder.filename(outputFileName);
+
+            minioClient().downloadObject(builder.build());
+        } catch (Exception e) {
+            logger.error("download object error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+    /**
+     * 将对象的数据下载到文件
+     * 下载到服务端指定路径
+     * objectName下载为outputFileName
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull
+     * @param outputFileName 输出文件,可以包含路径 @NotNull
+     * @param secretKey secretKey @NotNull
+     * @return
+     * @date 2021-03-05
+     **/
+    public void downloadObjectBySsec(String bucket, String objectName,
+                                     String outputFileName, SecretKey secretKey) throws MinioException{
+        try {
+            if (secretKey == null) {
+                String info = "download object error. secretKey is not null.";
+                logger.error(info);
+                throw new MinioException(info);
+            }
+            DownloadObjectArgs.Builder builder = DownloadObjectArgs.builder();
+            builder.bucket(bucket);
+            builder.object(objectName);
+            builder.ssec(new ServerSideEncryptionCustomerKey(secretKey));
+            builder.filename(outputFileName);
+
+            minioClient().downloadObject(builder.build());
+        } catch (Exception e) {
+            logger.error("download object with ssec error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+    /**
+     * 流式上传文件
+     * 流putFileWithInputStream存储为objectName
+     * 存储桶不存在会抛异常
+     *
+     * @param bucket 桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull 多路径存储实例: package/subpackage/.../objectName 开头不要有/
+     * @param putFileWithInputStream 上传文件输入流 @NotNull
+     * @param objectSize 文件大小
+     * @param partSize 分片大小
+     * @param contentType 文本类型 默认类型:application/octet-stream
+     * @return
+     * @date 2021-03-05
+     **/
+    public ObjectWriteResponse putFileWithInputStreamExistBucket(String bucket, String objectName, InputStream putFileWithInputStream,
+                                                                 Long objectSize, Long partSize, String contentType) throws MinioException {
+        if (!existBucket(bucket)) {
+            throw new MinioException("The bucket not exists.");
+        }
+        return putFileWithInputStream(bucket, objectName, putFileWithInputStream, objectSize, partSize, contentType);
+    }
+
+    /**
+     * 流式上传文件
+     * 流putFileWithInputStream存储为objectName
+     * 存储桶不存在会创建
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull 多路径存储实例: package/subpackage/.../objectName 开头不要有/
+     * @param putFileWithInputStream 上传文件输入流 @NotNull
+     * @param objectSize 文件大小
+     * @param partSize 分片大小
+     * @param contentType 文本类型 默认类型:application/octet-stream
+     * @return
+     * @date 2021-03-05
+     **/
+    public ObjectWriteResponse putFileWithInputStream(String bucket, String objectName, InputStream putFileWithInputStream,
+                           Long objectSize, Long partSize, String contentType) throws MinioException {
+        createBucketByNotExists(bucket);
+        try {
+            objectSize = objectSize == null ? 0 : objectSize;
+            partSize = partSize == null ? 0 : partSize;
+            if (objectSize <= 0 && partSize <= 0) {
+                objectSize = Long.valueOf(putFileWithInputStream.available());
+                partSize = -1L;
+            }
+
+            if (StringUtils.isBlank(contentType)) {
+                contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
+            }
+
+            PutObjectArgs.Builder builder = PutObjectArgs.builder();
+            builder.bucket(bucket);
+            builder.object(objectName);
+            builder.stream(putFileWithInputStream, objectSize, partSize);
+            builder.contentType(contentType);
+            return minioClient().putObject(builder.build());
+        } catch (Exception e) {
+            logger.error("put file error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+
+    /**
+     * 文件地址上传文件
+     * 存储桶不存在抛出异常
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param uploadFileName 上传文件 @NotNull
+     * @param contentType 文本类型 默认类型:application/octet-stream
+     * @return
+     * @date 2021-03-05
+     **/
+    public ObjectWriteResponse uploadFile(String bucket, String uploadFileName,
+                                                  String contentType) throws MinioException {
+        if (!existBucket(bucket)) {
+            throw new MinioException("The bucket not exists.");
+        }
+        if (!existBucket(uploadFileName)) {
+            throw new MinioException("The upload file name is null.");
+        }
+        // uploadFileName存储为objectName
+        String objectName = uploadFileName;
+        return uploadFileSaveAsObject(bucket, objectName, uploadFileName, contentType);
+    }
+
+    /**
+     * 文件上传文件
+     * uploadFileName存储为objectName
+     * 存储桶不存在抛出异常
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull 多路径存储实例: package/subpackage/.../objectName 开头不要有/
+     * @param uploadFileName 上传文件 @NotNull
+     * @param contentType 文本类型 默认类型:application/octet-stream
+     * @return
+     * @date 2021-03-05
+     **/
+    public ObjectWriteResponse uploadFileSaveAsObject(String bucket, String objectName,
+                                                      String uploadFileName, String contentType) throws MinioException {
+        if (!existBucket(bucket)) {
+            throw new MinioException("The bucket not exists.");
+        }
+        if (!existBucket(uploadFileName)) {
+            throw new MinioException("The upload file name is null.");
+        }
+
+        try {
+            UploadObjectArgs.Builder builder = UploadObjectArgs.builder();
+            builder.bucket(bucket);
+            builder.filename(uploadFileName);
+
+            objectName = StringUtils.isBlank(objectName) ? uploadFileName : objectName;
+            builder.object(objectName);
+
+            contentType = StringUtils.isNotBlank(contentType) ? contentType : MediaType.APPLICATION_OCTET_STREAM_VALUE;
+            builder.contentType(contentType);
+
+            return minioClient().uploadObject(builder.build());
+        } catch (Exception e) {
+            logger.error("upload file error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param bucket 存储桶名称 @NotNull
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称 @NotNull
+     * @return
+     * @operation add
+     * @date 2020/10/30
+     **/
+    public void removeObject(String bucket, String objectName) throws MinioException {
+        try {
+            minioClient().removeObject(
+                    RemoveObjectArgs.builder()
+                            .bucket(bucket)
+                            .object(objectName)
+                            .build());
+        } catch (Exception e) {
+            logger.error("remove file error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+
+    /**
+     * 复制文件
+     *
+     * @param sourceBucket 源文件桶 @NotNull
+     * @param sourceObject 源文件名 @NotNull
+     * @param bucket 目标桶 @NotNull
+     * @param object 目标文件 @NotNull
+     * @return
+     * @date 2020/10/30
+     **/
+    public void copyObject(String sourceBucket, String sourceObject,
+                           String bucket, String object) throws MinioException{
+        try {
+            CopySource source = CopySource.builder().bucket(sourceBucket).object(sourceObject).build();
+            minioClient().copyObject(
+                    CopyObjectArgs.builder()
+                            .bucket(bucket)
+                            .object(object)
+                            .source(source)
+                            .build());
+        } catch (Exception e) {
+            logger.error("copy file error.", e);
+            throw new MinioException(e.getMessage());
+        }
+    }
+
+
+    /**
+     * 获取存储对象下载用的url
+     * url可以指定适不同HTTP Method用于不同发送请求,url也可以指定失效时间
+     * 即使bucketName是私有的,该url也可以访问
+     * {@link  Method}
+     *
+     * @param bucket bucket名称
+     * @param objectName 文件名称
+     * @param method HTTP method {@link Method}
+     * @param expires 过期时间,以秒为单位
+     * @param timeUnit 过期时间的单位
+     * @param responseContentTypeKey 返回数据类型key,如 response-content-type
+     * @param mimeTypeValue 返回数据类型value,如 application/json
+     * @throws Exception
+     */
+    public String getObjectUrl(String bucket, String objectName,
+                               Method method, Integer expires, TimeUnit timeUnit,
+                               String responseContentTypeKey, String mimeTypeValue) throws Exception {
+
+
+        GetPresignedObjectUrlArgs.Builder builder = GetPresignedObjectUrlArgs.builder();
+
+        method = method == null ? Method.GET : method;
+        builder.method(method);
+        builder.bucket(bucket);
+        builder.object(objectName);
+
+        expires = expires == null ? 7 : expires;
+        timeUnit = timeUnit == null ? TimeUnit.DAYS : timeUnit;
+        builder.expiry(expires, timeUnit);
+
+        if (StringUtils.isNotBlank(responseContentTypeKey) && StringUtils.isNotBlank(mimeTypeValue)) {
+            Map<String, String> reqParams = new HashMap<>();
+            reqParams.put(responseContentTypeKey, mimeTypeValue);
+            builder.extraQueryParams(reqParams);
+        }
+        return minioClient().getPresignedObjectUrl(builder.build());
+    }
+
+    /**
+     * 获取存储对象信息
+     *
+     * @param bucket bucket名称
+     * @param objectName 文件名称
+     * @throws Exception
+     */
+    public StatObjectResponse getObjectInfo(String bucket, String objectName) throws Exception {
+        return minioClient().statObject(
+                StatObjectArgs.builder()
+                        .bucket(bucket)
+                        .object(objectName)
+                        .build());
+    }
+
+    /**
+     * 获取存储对象信息
+     * Get information of SSE-C encrypted object.
+     *
+     * @param bucket bucket名称
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称
+     * @param secretKey secretKey
+     * @throws Exception
+     */
+    public StatObjectResponse getObjectInfoWithSsec(String bucket, String objectName,
+                                                SecretKey secretKey) throws Exception {
+        if (secretKey == null) {
+            String info = "get object error. secretKey is not null.";
+            logger.error(info);
+            throw new MinioException(info);
+        }
+        return minioClient().statObject(
+                StatObjectArgs.builder()
+                        .bucket(bucket)
+                        .object(objectName)
+                        .ssec(new ServerSideEncryptionCustomerKey(secretKey))
+                        .build());
+    }
+
+    /**
+     * 获取存储对象信息
+     * Get information of a versioned object.
+     *
+     * @param bucket bucket名称
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称
+     * @param versionId version id
+     * @throws Exception
+     */
+    public StatObjectResponse getObjectInfoWithVersion(String bucket, String objectName,
+                                                   String versionId) throws Exception {
+        if (versionId == null) {
+            String info = "get object info. versionId is not null.";
+            logger.error(info);
+            throw new MinioException(info);
+        }
+        return minioClient().statObject(
+                StatObjectArgs.builder()
+                        .bucket(bucket)
+                        .object(objectName)
+                        .versionId(versionId)
+                        .build());
+    }
+
+    /**
+     * 获取存储对象信息
+     * Get information of a versioned object.
+     *
+     * @param bucket bucket名称
+     * @param objectName 存储对象名称,在存储桶内存储的对象名称
+     * @param versionId version id
+     * @param secretKey secretKey
+     * @throws Exception
+     */
+    public StatObjectResponse getObjectInfoWithVersionSsec(String bucket, String objectName,
+                                                   String versionId, SecretKey secretKey) throws Exception {
+        if (versionId == null) {
+            String info = "get object info. versionId is not null.";
+            logger.error(info);
+            throw new MinioException(info);
+        }
+        if (secretKey == null) {
+            String info = "get object info. secretKey is not null.";
+            logger.error(info);
+            throw new MinioException(info);
+        }
+        return minioClient().statObject(
+                StatObjectArgs.builder()
+                        .bucket(bucket)
+                        .object(objectName)
+                        .versionId(versionId)
+                        .ssec(new ServerSideEncryptionCustomerKey(secretKey))
+                        .build());
+    }
+
+    private static Builder builder() {
+        return new Builder();
+    }
+
+
+    // ------------------------------ Builder ------------------------------
+
+    private static class Builder {
+        private MinioProp minioProp;
+
+        private Builder(){}
+
+        public Builder minioProp(MinioProp minioProp) {
+            this.minioProp = minioProp;
+            return this;
+        }
+
+        public MinioUtils build() {
+            MinioUtils minioUtils = new MinioUtils();
+            minioUtils.minioProp = this.minioProp;
+            return minioUtils;
+        }
+    }
+
+
+}

+ 32 - 0
src/main/java/com/emato/eoss/msg/MessageToResultAdapt.java

@@ -0,0 +1,32 @@
+package com.emato.eoss.msg;
+
+import com.emato.eoss.msg.resp.ResponseStatus;
+import com.emato.eoss.msg.resp.Result;
+import com.emato.eoss.msg.service.Message;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 把message装换成result 返回给前端
+ * 
+ * @author lqg
+ *
+ */
+public class MessageToResultAdapt {
+
+	public static Result toResult(Message message) {
+		return toResult(message, null);
+	}
+
+	public static Result toResult(Message message, String msg) {
+		if (message == null) {
+			Result.error("系统异常");
+		}
+
+		msg = StringUtils.isBlank(msg) ? (message.isSuccess() ? ResponseStatus.SUCCESS.msg
+				: ResponseStatus.FAILURE.msg) : msg;
+
+		return message.isSuccess() ? Result.success(msg, message.getExtend(), Object.class)
+				: Result.failed(message.getCode(), msg);
+	}
+
+}

+ 68 - 0
src/main/java/com/emato/eoss/msg/req/OutRequestMessage.java

@@ -0,0 +1,68 @@
+package com.emato.eoss.msg.req;
+
+import java.io.Serializable;
+
+/**
+ * 外部请求消息结构
+ * 由外部系统发起
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-03-31
+ */
+public class OutRequestMessage implements Serializable {
+
+    private static final long serialVersionUID = 1519467353112381590L;
+
+    /**
+     * 请求主体编号
+     */
+    private String subjectSn;
+
+    /**
+     * 请求业务数据
+     */
+    private Object data;
+
+    /**
+     * 时间戳
+     */
+    private String timestamp;
+
+    /**
+     * 请求签名
+     */
+    private String signature;
+
+    public String getSubjectSn() {
+        return subjectSn;
+    }
+
+    public void setSubjectSn(String subjectSn) {
+        this.subjectSn = subjectSn;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(Object data) {
+        this.data = data;
+    }
+
+    public String getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(String timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public String getSignature() {
+        return signature;
+    }
+
+    public void setSignature(String signature) {
+        this.signature = signature;
+    }
+}

+ 46 - 0
src/main/java/com/emato/eoss/msg/req/RequestOutMessage.java

@@ -0,0 +1,46 @@
+package com.emato.eoss.msg.req;
+
+import java.io.Serializable;
+
+/**
+ * 请求外部消息结构
+ * 由当前系统发起
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-03-28
+ */
+public class RequestOutMessage implements Serializable {
+    private static final long serialVersionUID = 519875740038267887L;
+
+    //业务数据
+    private Object data;
+    //时间戳
+    private String timestamp;
+    //签名
+    private String sign;
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(Object data) {
+        this.data = data;
+    }
+
+    public String getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(String timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public String getSign() {
+        return sign;
+    }
+
+    public void setSign(String sign) {
+        this.sign = sign;
+    }
+}

+ 155 - 0
src/main/java/com/emato/eoss/msg/resp/BizData.java

@@ -0,0 +1,155 @@
+package com.emato.eoss.msg.resp;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Scott Chen
+ * @date 2017/4/20
+ */
+public class BizData implements Serializable {
+
+    private static final long serialVersionUID = -2649758273960153111L;
+
+    // 当前第几页
+    private int page;
+    // 总页数
+    private int totalPage;
+    // 单页记录数
+    private int count;
+    // 总记录数
+    private int totalCount;
+    // 查询数据
+    private Object rows;
+
+    private BizData() {
+    }
+
+    public int getPage() {
+        return page;
+    }
+
+    public int getTotalPage() {
+        return totalPage;
+    }
+
+    public int getCount(){
+        return count;
+    }
+
+    public int getTotalCount() {
+        return totalCount;
+    }
+
+    public Object getRows() {
+        return rows;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+    public static Builder builder(List rows) {
+        return new Builder(rows);
+    }
+    public static Builder builder(int page, int totalPage, int count, int totalCount, List rows) {
+        return new Builder(page, totalPage, count, totalCount, rows);
+    }
+    public static <T> Builder builder(T t, Class<T> type) {
+        return new Builder(t, type);
+    }
+    public static <T> Builder builder(int page, int totalPage, int count, int totalCount, T t, Class<T> type) {
+        return new Builder(page, totalPage, count, totalCount, t, type);
+    }
+
+    /**
+     * 消息构建器类
+     */
+    public static class Builder {
+        private int page;
+        private int totalPage;
+        private int count;
+        private int totalCount;
+        private List rows;
+
+        public Builder() {
+            this(0, 0, 0, 0, Collections.emptyList());
+        }
+
+        public Builder(List rows) {
+            this(0, 0, 0, 0, rows);
+        }
+
+        public Builder(int page, int totalPage, int count, int totalCount, List rows) {
+            this.page = page <= 0 ? 0 : page;
+            this.totalPage = totalPage <= 0 ? 0 : totalPage;
+            this.count = count <= 0 && rows == null || rows.size() == 0
+                    ? 0 : count <= 0 && (rows != null && rows.size() > 0) ? rows.size() : count;
+            this.totalCount = totalCount <= 0 ? 0 : totalCount;
+            this.rows = rows == null ? Collections.emptyList() : rows;
+        }
+
+        public <T> Builder(T t, Class<T> type) {
+            this(0, 0, 0, 0, t, type);
+        }
+
+        public <T> Builder(int page, int totalPage, int count, int totalCount, T t, Class<T> type) {
+            this.page = page <= 0 ? 0 : page;
+            this.totalPage = totalPage <= 0 ? 0 : totalPage;
+            this.count = count <= 0 ? 0 : count;
+            this.totalCount = totalCount <= 0 ? 0 : totalCount;
+
+            if (t == null) {
+                this.count = 0;
+            } else{
+                if (t instanceof List) {
+                    this.rows = (List) t;
+                } else {
+                    List<T> rows = new ArrayList<>();
+                    rows.add(t);
+                    this.rows = rows;
+                }
+                this.count = count == 0 ? this.rows.size() : count;
+            }
+        }
+
+        public Builder setPage(int page) {
+            this.page = page;
+            return this;
+        }
+
+        public Builder setTotalPage(int totalPage) {
+            this.totalPage = totalPage;
+            return this;
+        }
+
+        public Builder setTotalCount(int totalCount) {
+            this.totalCount = totalCount;
+            return this;
+        }
+
+        public Builder setCount(int count) {
+            this.count = count;
+            return this;
+        }
+
+        public Builder setRows(List rows) {
+            this.rows = rows;
+            this.count = this.count == 0 ? this.rows.size() : this.count;
+            return this;
+        }
+
+        public BizData build() {
+            BizData data = new BizData();
+            data.page = this.page;
+            data.totalPage = this.totalPage;
+            data.count = this.count;
+            data.totalCount = this.totalCount;
+            data.rows = this.rows;
+            return data;
+        }
+
+    }
+
+}

+ 40 - 0
src/main/java/com/emato/eoss/msg/resp/ResponseStatus.java

@@ -0,0 +1,40 @@
+package com.emato.eoss.msg.resp;
+
+/**
+ * 回执状态码
+ *
+ * @author Scott Chen
+ * @date 2017/3/9
+ */
+public enum ResponseStatus {
+
+    // 全局消息码
+    SUCCESS("0", "成功"),
+    ERROR("-1", "错误"),
+    FAILURE("-2", "失败"),
+    SYS_INNER_EXCEPTION("-3", "系统内部异常"),
+
+    NULL("-4", "为空"),
+    TIMESTAMP_EXPIRES("-5", "时间戳已过期"),
+    OVER_LENGTH("-6", "超出长度范围"),
+    HAVE_EXISTED("-20", "已经存在"),
+
+    // HTTP状态码 按HttpStatus 定义执行
+
+    // 会话消息码
+    TOKEN_ILLEGAL("100010", "非法请求"),
+    SESSION_EXPIRE("100020", "会话过期"),
+    UNKNOWN_SESSION("100021", "未知会话"),
+    INVALID_SESSION("100022", "无效会话"),
+    AUTHORIZATION_EXCEPTION("100023", "授权异常"),
+    AUTHORIZATION_NOT_DATA("100024", "授权信息缺失");
+
+    public final String code;
+    public final String msg;
+
+    ResponseStatus(String code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+}

+ 247 - 0
src/main/java/com/emato/eoss/msg/resp/Result.java

@@ -0,0 +1,247 @@
+package com.emato.eoss.msg.resp;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 回执消息结构
+ *
+ * @author Scott Chen
+ * @date 2017/4/20
+ */
+public class Result implements Serializable {
+
+    private static final long serialVersionUID = 6695762416670978135L;
+
+    /**
+     * 消息码
+     */
+    private String code;
+
+    /**
+     * 消息
+     */
+    private String message;
+
+    /**
+     * 回执业务数据
+     */
+    private BizData data;
+
+    private Result() {
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public BizData getData() {
+        return data;
+    }
+
+    public static boolean isSuccess(Result message) {
+        return message.getCode() == null ? false : message.getCode().equals(ResponseStatus.SUCCESS.code) ? true : false;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+    public static Builder builder(String code, String message) {
+        return new Builder(code, message);
+    }
+    public static Builder builder(String code, String message, BizData bizData) {
+        return new Builder(code, message, bizData);
+    }
+    public static Builder builder(String code, String message, List list) {
+        return new Builder(code, message, list);
+    }
+    public static <T> Builder builder(T t,  Class<T> type) {
+        return new Builder(ResponseStatus.SUCCESS.code, ResponseStatus.SUCCESS.msg, t, type);
+    }
+    public static <T> Builder builder(String message, T t,  Class<T> type) {
+        return new Builder(ResponseStatus.SUCCESS.code, message, t, type);
+    }
+    public static <T> Builder builder(String code, String message, T t,  Class<T> type) {
+        return new Builder(code, message, t, type);
+    }
+
+
+    //---------- 直接返回结果 ----------
+    //成功
+    public static Result success() {
+        return new Builder().build();
+    }
+    public static Result success(String code, String message) {
+        return new Builder(code, message).build();
+    }
+    public static Result success(String message) {
+        return new Builder(ResponseStatus.SUCCESS.code, message).build();
+    }
+
+    public static Result success(Map map) {
+        return new Builder(ResponseStatus.SUCCESS.code, ResponseStatus.SUCCESS.msg, map).build();
+    }
+    public static Result success(String message, Map map) {
+        return new Builder(ResponseStatus.SUCCESS.code, message, map).build();
+    }
+    public static Result success(List rows) {
+        return new Builder(ResponseStatus.SUCCESS.code, ResponseStatus.SUCCESS.msg, rows).build();
+    }
+    public static Result success(String message, List rows) {
+        return new Builder(ResponseStatus.SUCCESS.code, message, rows).build();
+    }
+    public static Result success(String code, String message, List rows) {
+        return new Builder(code, message, rows).build();
+    }
+    public static <T> Result success(T t, Class<T> type) {
+        return new Builder(ResponseStatus.SUCCESS.code, ResponseStatus.SUCCESS.msg, t, type).build();
+    }
+    public static <T> Result success(String message, T t, Class<T> type) {
+        return new Builder(ResponseStatus.SUCCESS.code, message, t, type).build();
+    }
+    public static <T> Result success(String code, String message, T t, Class<T> type) {
+        return new Builder(code, message, t, type).build();
+    }
+
+    //错误
+    public static Result error() {
+        return new Builder(ResponseStatus.ERROR.code, ResponseStatus.ERROR.msg).build();
+    }
+    public static Result error(String code, String message) {
+        return new Builder(code, message).build();
+    }
+    public static Result error(String message) {
+        return new Builder(ResponseStatus.ERROR.code, message).build();
+    }
+    public static Result error(Map map) {
+        return new Builder(ResponseStatus.ERROR.code, ResponseStatus.ERROR.msg, map).build();
+    }
+    public static Result error(String message, Map map) {
+        return new Builder(ResponseStatus.ERROR.code, message, map).build();
+    }
+    public static Result error(List rows) {
+        return new Builder(ResponseStatus.ERROR.code, ResponseStatus.ERROR.msg, rows).build();
+    }
+    public static Result error(String message, List rows) {
+        return new Builder(ResponseStatus.ERROR.code, message, rows).build();
+    }
+    public static Result error(String code, String message, List rows) {
+        return new Builder(code, message, rows).build();
+    }
+
+    //失败
+    public static Result failed() {
+        return new Builder(ResponseStatus.FAILURE.code, ResponseStatus.FAILURE.msg).build();
+    }
+    public static Result failed(String code, String message) {
+        return new Builder(code, message).build();
+    }
+    public static Result failed(String message) {
+        return new Builder(ResponseStatus.FAILURE.code, message).build();
+    }
+
+    public static Result failed(Map map) {
+        return new Builder(ResponseStatus.ERROR.code, ResponseStatus.ERROR.msg, map).build();
+    }
+    public static Result failed(String message, Map map) {
+        return new Builder(ResponseStatus.ERROR.code, message, map).build();
+    }
+    public static Result failed(List rows) {
+        return new Builder(ResponseStatus.FAILURE.code, ResponseStatus.FAILURE.msg, rows).build();
+    }
+    public static Result failed(String message, List rows) {
+        return new Builder(ResponseStatus.ERROR.code, message, rows).build();
+    }
+    public static Result failed(String code, String message, List rows) {
+        return new Builder(code, message, rows).build();
+    }
+
+    /**
+     * 消息构建器类
+     */
+    public static class Builder {
+        private String code;
+        private String message;
+        private BizData data;
+
+        public Builder() {
+            this.code = ResponseStatus.SUCCESS.code;
+            this.message = ResponseStatus.SUCCESS.msg;
+            this.data = BizData.builder().build();
+        }
+
+        public Builder(String code, String message) {
+            this.code = StringUtils.isBlank(code) ? ResponseStatus.SUCCESS.code : code;
+            this.message = StringUtils.isBlank(message) ? ResponseStatus.SUCCESS.msg : message;
+            this.data = BizData.builder().build();
+        }
+
+        public Builder(String code, String message, BizData bizData) {
+            this.code = StringUtils.isBlank(code) ? ResponseStatus.SUCCESS.code : code;
+            this.message = StringUtils.isBlank(message) ? ResponseStatus.SUCCESS.msg : message;
+            this.data = bizData;
+        }
+
+        public Builder(String code, String message, Map map) {
+            this.code = StringUtils.isBlank(code) ? ResponseStatus.SUCCESS.code : code;
+            this.message = StringUtils.isBlank(message) ? ResponseStatus.SUCCESS.msg : message;
+            this.data = BizData.builder(map, Map.class).build();
+        }
+
+        public Builder(String code, String message, List rows) {
+            this.code = StringUtils.isBlank(code) ? ResponseStatus.SUCCESS.code : code;
+            this.message = StringUtils.isBlank(message) ? ResponseStatus.SUCCESS.msg : message;
+            this.data = BizData.builder(rows).build();
+        }
+
+        public <T> Builder(T t, Class<T> type) {
+            this.code = ResponseStatus.SUCCESS.code;
+            this.message = ResponseStatus.SUCCESS.msg;
+            this.data = BizData.builder(t, type).build();
+        }
+
+        public <T> Builder(String message, T t, Class<T> type) {
+            this.code = ResponseStatus.SUCCESS.code;
+            this.message = StringUtils.isBlank(message) ? ResponseStatus.SUCCESS.msg : message;
+            this.data = BizData.builder(t, type).build();
+        }
+
+        public <T> Builder(String code, String message, T t, Class<T> type) {
+            this.code = StringUtils.isBlank(code) ? ResponseStatus.SUCCESS.code : code;
+            this.message = StringUtils.isBlank(message) ? ResponseStatus.SUCCESS.msg : message;
+            this.data = BizData.builder(t, type).build();
+        }
+
+        public Builder setCode(String code) {
+            this.code = code;
+            return this;
+        }
+
+        public Builder setMessage(String message) {
+            this.message = message;
+            return this;
+        }
+
+        public Builder setData(BizData data) {
+            this.data = data;
+            return this;
+        }
+
+        public Result build() {
+            Result message = new Result();
+            message.code = this.code;
+            message.message = this.message;
+            message.data = this.data;
+            return message;
+        }
+
+    }
+}
+

+ 57 - 0
src/main/java/com/emato/eoss/msg/resp/ResultException.java

@@ -0,0 +1,57 @@
+package com.emato.eoss.msg.resp;
+
+/**
+ * 自定义异常
+ *
+ * @author Scott Chen
+ * @date 2017/3/22
+ */
+public class ResultException extends RuntimeException {
+
+    private static final long serialVersionUID = -1422580005904561463L;
+
+    private String code;
+    private String message;
+
+    public ResultException() {
+        super();
+    }
+
+    public ResultException(String message) {
+        super(message);
+        this.message = message;
+    }
+
+    public ResultException(String code, String message) {
+        super(message);
+        this.code = code;
+        this.message = message;
+    }
+
+    public ResultException(String code, String message, Throwable cause) {
+        super(message, cause);
+        this.code = code;
+        this.message = message;
+    }
+
+    public ResultException(String message, Throwable cause) {
+        super(message, cause);
+        this.message = message;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+}

+ 168 - 0
src/main/java/com/emato/eoss/msg/service/Message.java

@@ -0,0 +1,168 @@
+package com.emato.eoss.msg.service;
+
+import com.google.common.base.Strings;
+
+import java.io.Serializable;
+
+/**
+ * 通用消息封装
+ * @author Scott Chen
+ * @date 2017-09-19 16:60
+ * @Description:
+ */
+public class Message implements Serializable {
+
+	private static final long serialVersionUID = 4081619286503989887L;
+
+	private boolean success;
+
+	/**
+	 * 消息代码
+	 */
+	private String code;
+	/**
+	 * 消息内容体
+	 */
+	private String msg;
+	/**
+	 * 扩展
+	 */
+	private Object extend;
+
+	private Message() {
+	}
+
+	public String getCode() {
+		return code;
+	}
+	public String getMsg() {
+		return msg;
+	}
+	public Object getExtend() {
+		return extend;
+	}
+	public boolean isSuccess() {
+		return success;
+	}
+
+	public static Builder builder() {
+		return new Builder();
+	}
+	public static Builder builder(String code, String msg) {
+		return new Builder(code, msg);
+	}
+	public static Builder builder(String code, String msg, Object extend) {
+		return new Builder(code, msg, extend);
+	}
+
+
+	//---------- 直接返回结果 ----------
+	public static Message success() {
+		return new Builder().build();
+	}
+	public static Message success(Object object) {
+		Builder builder = new Builder();
+		return builder.setExtend(object).build();
+	}
+
+	public static Message success(String msg) {
+		return new Builder(MessageStatus.SUCCESS.innerCode, msg).build();
+	}
+
+	public static Message success(String msg, Object object) {
+		Builder builder = new Builder(MessageStatus.SUCCESS.innerCode, msg, object);
+		return builder.build();
+	}
+
+	public static Message success(String code, String msg, Object object) {
+		Builder builder = new Builder(code, msg, object);
+		return builder.build();
+	}
+
+
+	//--- 错误----------
+	public static Message error() {
+		return new Builder(MessageStatus.ERROR.innerCode, MessageStatus.ERROR.innerMsg).build();
+	}
+	public static Message error(String msg) {
+		return new Builder(MessageStatus.ERROR.innerCode, msg).build();
+	}
+	public static Message error(String code, String msg) {
+		return new Builder(code, msg).build();
+	}
+
+	//--- 失败----------
+	public static Message failed() {
+		return new Builder(MessageStatus.FAILURE.innerCode, MessageStatus.FAILURE.innerMsg).build();
+	}
+	public static Message failed(String msg) {
+		return new Builder(MessageStatus.FAILURE.innerCode, msg).build();
+	}
+	public static Message failed(String code, String msg) {
+		return new Builder(code, msg).build();
+	}
+
+
+	/**
+	 * 消息构建器类
+	 */
+	public static class Builder {
+
+		private boolean success;
+		private String code;
+		private String msg;
+		private Object extend;
+
+		public Builder() {
+			this.success = true;
+			this.code = MessageStatus.SUCCESS.innerCode;
+			this.msg = MessageStatus.SUCCESS.innerMsg;
+			this.extend = null;
+		}
+
+		public Builder(String code, String msg) {
+			this.code = Strings.isNullOrEmpty(code) ? MessageStatus.SUCCESS.innerCode : code;
+			this.msg = Strings.isNullOrEmpty(msg) ? MessageStatus.SUCCESS.innerMsg : msg;
+			this.success = this.code.equals(MessageStatus.SUCCESS.innerCode) ? true : false;
+		}
+
+		public Builder(String code, String msg, Object extend) {
+			this.code = Strings.isNullOrEmpty(code) ? MessageStatus.SUCCESS.innerCode : code;
+			this.msg = Strings.isNullOrEmpty(msg) ? MessageStatus.SUCCESS.innerMsg : msg;
+			this.extend = extend;
+			this.success = this.code.equals(MessageStatus.SUCCESS.innerCode) ? true : false;
+		}
+
+		public Message build() {
+			Message message = new Message();
+			message.success = this.success;
+			message.code = this.code;
+			message.msg = this.msg;
+			message.extend = this.extend;
+			return message;
+		}
+
+		public Builder setSuccess(boolean success) {
+			this.success = success;
+			return this;
+		}
+
+		public Builder setCode(String code) {
+			this.code = code;
+			return this;
+		}
+
+		public Builder setMsg(String msg) {
+			this.msg = msg;
+			return this;
+		}
+
+		public Builder setExtend(Object extend) {
+			this.extend = extend;
+			return this;
+		}
+
+
+	}
+
+}

+ 35 - 0
src/main/java/com/emato/eoss/msg/service/MessageStatus.java

@@ -0,0 +1,35 @@
+package com.emato.eoss.msg.service;
+
+/**
+ * @author Scott Chen
+ * @version 1.0
+ * 2017-09-20 12:38
+ */
+public enum MessageStatus {
+
+    // 全局消息码
+    SUCCESS("0", "成功"),
+    ERROR("-1", "错误"),
+    FAILURE("-2", "失败"),
+    SYS_INNER_EXCEPTION("-3", "系统内部异常"),
+
+    NULL("-4", "为空"),
+    TIMESTAMP_EXPIRES("-5", "时间戳已过期"),
+    OVER_LENGTH("-6", "超出长度范围"),
+    HAVE_EXISTED("-20", "已经存在"),
+
+    // 登录
+    UPDATE_LOGIN_IP_FAILED("200010","更新登录IP失败"),
+    // 认证信息
+    UPDATE_AUTC_IDEN_FAILED("200011","更新认证信息失败");
+
+
+    public final String innerCode;
+    public final String innerMsg;
+
+    MessageStatus(String innerCode, String innerMsg) {
+        this.innerCode = innerCode;
+        this.innerMsg = innerMsg;
+    }
+
+}

+ 27 - 0
src/main/java/com/emato/eoss/util/date/DateConstant.java

@@ -0,0 +1,27 @@
+package com.emato.eoss.util.date;
+
+/**
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-03
+ */
+public class DateConstant {
+
+    public static final String DATE_TIME_SECOND_ZONE = "yyyy-MM-dd HH:mm:ssZ";
+    public static final String DATE_TIME_SECOND_MILL = "yyyy-MM-dd HH:mm:ss.SSS";
+    public static final String DATE_TIME_SECOND = "yyyy-MM-dd HH:mm:ss";
+    public static final String DATE_TIME_MINUTE = "yyyy-MM-dd HH:mm";
+    // 格林威治GMT时间
+    public static final String DATE_TIME_GMT_EN = "EEE d MMM yyyy HH:mm:ss 'GMT'";
+    // CST时间
+    public static final String DATE_TIME_CST_US = "EEE MMM d HH:mm:ss 'CST' yyyy";
+
+    public static final String TIME_SECOND_MILL = "HH:mm:ss.SSS";
+    public static final String TIME_SECOND = "HH:mm:ss";
+    public static final String TIME_MINUTE = "HH:mm";
+
+    public static final String DATE_MONTH_DAY = "yyyy-MM-dd";
+    public static final String DATE_MONTH = "yyyy-MM";
+    public static final String MONTH_DAY = "MM-dd";
+
+}

+ 505 - 0
src/main/java/com/emato/eoss/util/date/DateUtils.java

@@ -0,0 +1,505 @@
+package com.emato.eoss.util.date;
+
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Date 工具类
+ *
+ * 时间区分
+ *      CST: 北京时间
+ *      GMT: 格林威治时间
+ * CST表示四个不同时区(不同语言默认的CST时区不同,可能会造成不一样的时间)
+ *      Central Standard Time (USA) UT-6:00
+ *      Central Standard Time (Australia) UT+9:30
+ *      China Standard Time UT+8:00
+ *      Cuba Standard Time UT-4:00
+ *      对CST时间而言,java默认的是北京时间,javascript中默认CST是美国中部时间,同一个时间,在前后端转换的时间结果可能不一样;
+ * 日期格式
+ *      CN: yyyy MM dd
+ *      US: MM dd yyyy
+ *      EN: dd MM yyyy
+ *
+ * @author Scott Chen
+ * @date 2019-12-12日
+ */
+public class DateUtils {
+
+    /**
+     * 获取当前时间,并指定格式化样式
+     * @param formatStyle
+     * @return
+     */
+    public static String nowToString(String formatStyle) {
+        return dateToString(Calendar.getInstance().getTime(), formatStyle);
+    }
+
+    /**
+     * 获得当前系统时间
+     *
+     * @return
+     */
+    public static String cstToString() {
+        SimpleDateFormat sdf = new SimpleDateFormat(DateConstant.DATE_TIME_SECOND);
+        return sdf.format(Calendar.getInstance().getTime());
+    }
+
+    /**
+     * 获得当前系统时间
+     *
+     * @return
+     */
+    public static String gmtToStringWithCn() {
+        SimpleDateFormat sdf = new SimpleDateFormat(DateConstant.DATE_TIME_SECOND);
+        // 格林威治所在时区
+        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+        return sdf.format(Calendar.getInstance().getTime());
+    }
+
+    /**
+     * 获得当前系统时间
+     *      EEE MMM d HH:mm:ss 'CST' yyyy
+     * @return
+     */
+    public static String cstToStringWithUs() {
+        // 英文显示年月日
+        SimpleDateFormat sdf = new SimpleDateFormat(DateConstant.DATE_TIME_CST_US, Locale.US);
+        return sdf.format(Calendar.getInstance().getTime());
+    }
+
+    /**
+     * 以GMT方式获取当前系统时间
+     *      EEE d MMM yyyy HH:mm:ss 'GMT'
+     *
+     * @return
+     */
+    public static String gmtToStringWithEn() {
+        // 英文显示年月日
+        SimpleDateFormat sdf = new SimpleDateFormat(DateConstant.DATE_TIME_GMT_EN, Locale.US);
+        // 所在时区,如下时区可以不写,java默认为GMT+8
+        sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
+        return sdf.format(Calendar.getInstance().getTime());
+    }
+
+    /**
+     * 获取GMT格林威治时间
+     *      EEE d MMM yyyy HH:mm:ss 'GMT'
+     *
+     * @return
+     */
+    public static String gmtOfUsToStringWithEn() {
+        // 英文显示年月日
+        SimpleDateFormat sdf = new SimpleDateFormat(DateConstant.DATE_TIME_GMT_EN, Locale.US);
+        // 格林威治所在时区
+        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+        return sdf.format(Calendar.getInstance().getTime());
+    }
+
+    /**
+     * 字符串的日期类型 转换为 Date 类型
+     *
+     * @param timeContent 字符串的日期类型
+     * @param formatStyle 日期格式
+     * @return
+     */
+    public static Date stringToDate(String timeContent, String formatStyle) {
+        SimpleDateFormat format = new SimpleDateFormat(formatStyle);
+        try {
+            return format.parse(timeContent);
+        } catch (Exception e) {
+            // e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Date 转化成 String 类型的字符串日期格式
+     *
+     * @param date
+     * @param formatStyle 转化成 什么格式
+     * @return
+     */
+    public static String dateToString(Date date, String formatStyle) {
+        SimpleDateFormat format = new SimpleDateFormat(formatStyle);
+        return format.format(date);
+    }
+
+    /**
+     * Long 转Date
+     * @param timeContent
+     * @param formatStyle
+     * @return
+     */
+    public static Date longToDate(Long timeContent, String formatStyle) {
+        try {
+            return stringToDate(longToDateWithString(timeContent, formatStyle), formatStyle);
+        } catch (Exception e) {
+            // e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Long 转String
+     * @param timeContent
+     * @param formatStyle
+     * @return
+     */
+    public static String longToDateWithString(Long timeContent, String formatStyle) {
+        SimpleDateFormat format = new SimpleDateFormat(formatStyle);
+        try {
+            return format.format(timeContent);
+        } catch (Exception e) {
+            // e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 检查日期格式
+     *
+     * @param time
+     * @param format
+     * @return
+     */
+    public static boolean checkDate(String time, String format) {
+        if (stringToDate(time, format) == null) {
+            return false;
+        }
+        return true;
+    }
+
+
+    /**
+     * 字符串日期格式 转换成 带 地区标识的 Date 类型
+     *
+     * @param strDate
+     * @param locale
+     * @param formatStyle
+     * @return
+     */
+    public static Date stringToLocal(String strDate, Locale locale, String formatStyle) {
+        SimpleDateFormat srcsdf = new SimpleDateFormat(formatStyle, locale);
+        try {
+            return srcsdf.parse(strDate);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    /**
+     * 获取本地时间相对的UTC | GMT时间
+     *
+     * @return
+     */
+    public static Date getUtcTime() {
+        // 1、取得本地时间:
+        Calendar calendar = Calendar.getInstance();
+        // 2、取得时间偏移量:
+        final int zoneOffset = calendar.get(Calendar.ZONE_OFFSET);
+        // 3、取得夏令时差:
+        final int dstOffset = calendar.get(Calendar.DST_OFFSET);
+        // 4、从本地时间里扣除这些差量,即可以取得UTC时间:
+        calendar.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset));
+        return calendar.getTime();
+    }
+
+    /**
+     * 获取2个时间相差多少秒
+     *
+     * @param date1
+     * @param date2
+     * @return
+     */
+    public static Long getDiffSeconds(Date date1, Date date2) {
+        long milliseconds1 = date1.getTime();
+        long milliseconds2 = date2.getTime();
+        long diff = milliseconds1 - milliseconds2;
+        if (diff < 0) {
+            diff = -diff;
+        }
+        long diffSeconds = diff / (1000);
+        return diffSeconds;
+    }
+
+    /**
+     * 获取2个时间相差多少分钟
+     *
+     * @param date1
+     * @param date2
+     * @return
+     */
+    public static Long getDiffMinutes(Date date1, Date date2) {
+        Long diffMinutes = getDiffSeconds(date1, date2) / 60;
+        return diffMinutes;
+    }
+
+    /**
+     * 获取2个时间直接 相差多少小时
+     *
+     * @param date1
+     * @param date2
+     * @return
+     */
+    public static Long getDiffHours(Date date1, Date date2) {
+        Long diffHours = getDiffMinutes(date1, date2) / 60;
+        return diffHours;
+    }
+
+    /**
+     * 获取2个时间直接 相差多少天
+     *
+     * @param date1
+     * @param date2
+     * @return
+     */
+    public static Long getDiffDays(Date date1, Date date2) {
+        Long diffDays = getDiffHours(date1, date2) / 24;
+        return diffDays;
+    }
+
+    /**
+     * 多少天以前
+     *
+     * @param ago
+     * @return
+     */
+    public static String getDaysAgoStart(int ago) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.DAY_OF_YEAR, -ago);
+        String time = DateUtils.dateToString(calendar.getTime(), DateConstant.DATE_MONTH_DAY);
+        String timeFrom = time + " 00:00:00";
+        return timeFrom;
+    }
+
+    /**
+     * 获得指定日期的下一天
+     *
+     * @param day
+     * @return
+     */
+    public static String getNextDayTime(String day) {
+        Date date = DateUtils.stringToDate(day, DateConstant.DATE_TIME_SECOND);
+
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        cal.add(Calendar.DATE, 1);
+
+        return new SimpleDateFormat(DateConstant.DATE_TIME_SECOND).format(cal.getTime());
+    }
+
+    /**
+     * 获得指定日期的前一天
+     *
+     * @param day
+     * @return
+     */
+    public static String getPreDayTime(String day) {
+        Date date = DateUtils.stringToDate(day, DateConstant.DATE_TIME_SECOND);
+
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        cal.add(Calendar.DATE, -1);
+
+        return new SimpleDateFormat(DateConstant.DATE_TIME_SECOND).format(cal.getTime());
+    }
+
+    /**
+     * 多少天以后
+     *
+     * @param date
+     * @param dayNum
+     * @return
+     */
+    public static Date getNextPrevDay(Date date, int dayNum) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.add(Calendar.DAY_OF_MONTH, dayNum);
+        return calendar.getTime();
+    }
+
+    /**
+     * 判断time1 是否早于time2
+     *
+     * @param time1
+     * @param time2
+     * @return
+     */
+    public static boolean before(String time1, String time2) {
+        Date date1 = DateUtils.stringToDate(time1, DateConstant.DATE_TIME_SECOND);
+        Date date2 = DateUtils.stringToDate(time2, DateConstant.DATE_TIME_SECOND);
+
+        return date1.before(date2);
+    }
+
+    /**
+     * 得到某个时间段加上多少小时的时间
+     *
+     * @param dateTime
+     * @param hours
+     * @return
+     */
+    public static Date getDateTimeAddHours(Date dateTime, Integer hours) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(dateTime);
+        calendar.add(Calendar.HOUR, hours);
+        return calendar.getTime();
+    }
+
+    /**
+     * 获取当前时间的时间戳
+     *
+     * @return
+     */
+    public static long getTimestampByDate() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * 获取指定时间的时间戳
+     *
+     * @param dt
+     * @return
+     */
+    public static long getTimestampByDate(Date dt) {
+        return dt.getTime();
+    }
+
+    /**
+     * 取得当前unix时间戳(精确到秒)
+     *
+     * @return unixTimeStamp
+     */
+    public static String getUnixTimeStamp() {
+        long time = System.currentTimeMillis();
+        String unixTimeStamp = String.valueOf(time / 1000);
+        return unixTimeStamp;
+    }
+
+    /**
+     * Java将Unix时间戳转换成指定格式日期字符串
+     *
+     * @param timestampString 时间戳 如:"1473048265";
+     * @param formats         要格式化的格式 默认:"yyyy-MM-dd HH:mm:ss";
+     * @return 返回结果 如:"2016-09-05 16:06:42";
+     */
+    public static String unixTimeStamp2Date(String timestampString, String formats) {
+        if (StringUtils.isBlank(formats)) {
+            formats = DateConstant.DATE_TIME_SECOND;
+        }
+        Long timestamp = Long.parseLong(timestampString) * 1000;
+        String date = new SimpleDateFormat(formats, Locale.CHINA).format(new Date(timestamp));
+        return date;
+    }
+
+
+    /**
+     * Java将Unix时间戳转换成指定格式日期
+     *
+     * @param timestampString 时间戳 如:"1507701120000";
+     * @param formats         要格式化的格式 默认:"yyyy-MM-dd HH:mm:ss";
+     * @return 返回结果 如:"2016-09-05 16:06:42";
+     */
+    public static Date unixTimeStampDate(String timestampString, String formats) {
+        if (StringUtils.isBlank(formats)) {
+            formats = DateConstant.DATE_TIME_SECOND;
+        }
+        SimpleDateFormat format = new SimpleDateFormat(formats);
+        Long time = new Long(timestampString);
+        String d = format.format(time);
+        Date date = null;
+        try {
+            date = format.parse(d);
+        } catch (ParseException p) {
+			p.printStackTrace();
+        }
+        return date;
+    }
+
+    /**
+     * 日期格式字符串转换成时间戳
+     *
+     * @param dateStr 字符串日期
+     * @param format  如:yyyy-MM-dd HH:mm:ss
+     * @return
+     */
+    public static String date2UnixTimeStamp(String dateStr, String format) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat(format);
+            return String.valueOf(sdf.parse(dateStr).getTime() / 1000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    /**
+     * 在时间上增加
+     * 返回Date
+     * @param ori
+     * @param addIncr
+     * @param unit
+     * @return
+     */
+    public static Date add(Date ori, int addIncr, int unit) {
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.setTime(ori);
+        cal.add(unit, addIncr);
+        return cal.getTime();
+    }
+
+    /**
+     * 在时间上增加
+     * 返回String
+     * @param ori
+     * @param addIncr
+     * @param unit
+     * @param formatStyle
+     * @return
+     */
+    public static String addWithString(Date ori, int addIncr, int unit, String formatStyle) {
+        Date dt = add(ori, addIncr, unit);
+        return new SimpleDateFormat(formatStyle).format(dt);
+    }
+
+    /**
+     * 在时间上减少
+     * 返回Date
+     * @param ori
+     * @param minusIncr
+     * @param unit
+     * @return
+     */
+    public static Date minus(Date ori, int minusIncr, int unit) {
+        Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.setTime(ori);
+        cal.add(unit, -minusIncr);
+        return cal.getTime();
+    }
+
+    /**
+     * 在时间上减少
+     * 返回String
+     * @param ori
+     * @param minusIncr
+     * @param unit
+     * @param formatStyle
+     * @return
+     */
+    public static String minusWithString(Date ori, int minusIncr, int unit, String formatStyle) {
+        Date dt = minus(ori, minusIncr, unit);
+        return new SimpleDateFormat(formatStyle).format(dt);
+    }
+
+}

+ 236 - 0
src/main/java/com/emato/eoss/util/date/LocalDateTimeUtils.java

@@ -0,0 +1,236 @@
+package com.emato.eoss.util.date;
+
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.Date;
+
+/**
+ * @author Scott Chen
+ * @version 1.0
+ * 2017-10-28 11:19
+ */
+public class LocalDateTimeUtils {
+
+    private LocalDateTimeUtils() {}
+
+    //获取当前时间的LocalDateTime对象
+    //LocalDateTime.now();
+
+    //根据年月日构建LocalDateTime
+    //LocalDateTime.of();
+
+    //比较日期先后
+    //LocalDateTime.now().isBefore(),
+    //LocalDateTime.now().isAfter(),
+
+    /**
+     * String转LocalDateTime
+     * @param src
+     * @param formatter
+     * @return
+     */
+    public static LocalDateTime stringToLocalDateTime(String src, DateTimeFormatter formatter) {
+       return LocalDateTime.parse(src, formatter);
+    }
+
+    /**
+     * LocalDateTime转String
+     * @param dateTime
+     * @param pattern
+     * @return
+     */
+    public static String localDateTimeToString(LocalDateTime dateTime, String pattern) {
+        return dateTime.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * 将当前LocalDateTime时间按为指定格式转为String
+     * @param pattern
+     * @return
+     */
+    public static String formatNow(String pattern) {
+        return  localDateTimeToString(LocalDateTime.now(), pattern);
+    }
+
+    /**
+     * String转LocalDate
+     * @param src
+     * @param formatter
+     * @return
+     */
+    public static LocalDate stringToLocalDate(String src, DateTimeFormatter formatter) {
+        return LocalDate.parse(src, formatter);
+    }
+
+    /**
+     * LocalDate转String
+     * @param time
+     * @param pattern
+     * @return
+     */
+    public static String localDateToString(LocalDate time, String pattern) {
+        return time.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * String转LocalTime
+     * @param src
+     * @param formatter
+     * @return
+     */
+    public static LocalTime stringToLocalTime(String src, DateTimeFormatter formatter) {
+        return LocalTime.parse(src, formatter);
+    }
+
+    /**
+     * LocalTime转String
+     * @param time
+     * @param pattern
+     * @return
+     */
+    public static String localTimeToString(LocalTime time, String pattern) {
+        return time.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Date转换为LocalDateTime
+     * @param date
+     * @return
+     */
+    public static LocalDateTime localDateTimeToDate(Date date) {
+        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+    }
+
+    /**
+     * LocalDateTime转换为Date
+     * @param time
+     * @return
+     */
+    public static Date dateToLocalDateTime(LocalDateTime time) {
+        return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+
+    /**
+     * 获取指定日期的毫秒
+     * @param time
+     * @return
+     */
+    public static Long getMilliByTime(LocalDateTime time) {
+        return time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+    }
+
+
+    /**
+     * 获取指定日期的秒
+     * @param time
+     * @return
+     */
+    public static Long getSecondsByTime(LocalDateTime time) {
+        return time.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
+    }
+
+
+    //日期加上一个数,根据field不同加不同值,field为ChronoUnit.*
+    public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) {
+        return time.plus(number, field);
+    }
+
+    //日期减去一个数,根据field不同减不同值,field参数为ChronoUnit.*
+    public static LocalDateTime minu(LocalDateTime time, long number, TemporalUnit field) {
+        return time.minus(number, field);
+    }
+
+    /**
+     * 获取两个日期的差  field参数为ChronoUnit.*
+     * @param startTime
+     * @param endTime
+     * @param field  单位(年月日时分秒)
+     * @return
+     */
+    public static long betweenTwoTime(LocalDateTime startTime, LocalDateTime endTime, ChronoUnit field) {
+        Period period = Period.between(LocalDate.from(startTime), LocalDate.from(endTime));
+        if (field == ChronoUnit.YEARS) {
+            return period.getYears();
+        }
+        if (field == ChronoUnit.MONTHS) {
+            return period.getYears() * 12 + period.getMonths();
+        }
+        return field.between(startTime, endTime);
+    }
+
+    //获取一天的开始时间,2017,7,22 00:00
+    public static LocalDateTime getDayStart(LocalDateTime time) {
+        return time.withHour(0)
+                .withMinute(0)
+                .withSecond(0)
+                .withNano(0);
+    }
+
+    //获取一天的结束时间,2017,7,22 23:59:59.999999999
+    public static LocalDateTime getDayEnd(LocalDateTime time) {
+        return time.withHour(23)
+                .withMinute(59)
+                .withSecond(59)
+                .withNano(999999999);
+    }
+
+
+    /**
+     * 纳秒转换
+     * 可转为:天/小时/分钟/秒/毫秒
+     *
+     * @param nanoTime
+     * @return
+     */
+    public static String fromNanoTime(long nanoTime){
+        if (nanoTime <= 0) {
+            return "0秒";
+        }
+        double a,b,c,d = 0.000d;
+        long day = 0L;  //天
+        long HH = 0L;   //小时
+        long mm = 0L;   //分
+        long ss = 0L;   //秒
+        long ms = 0L;   //毫秒
+
+        a = ((double) nanoTime) / 1000000000d / 60d / 60d / 24d;
+        if (a > 24) {
+            return (long) a + "天";
+        }else{
+            day = (long) a; //整数部分为天
+            b = (a - day) * 24; //小数部分转为小时
+
+            HH = (long) b; //小时
+            c = (b - HH) * 60;  //小数部分转为分钟
+
+            mm = (long) c; //分钟
+            d = (c - mm) * 60;  //小数部分转为秒
+
+            ss = (long)d; //秒
+            ms = (long)((d - ss) * 1000); //毫秒
+        }
+
+        String result = "";
+        if (day > 0) {
+            result = day + "天";
+        }
+        if (HH > 0) {
+            result += HH + "小时";
+        }
+        if (mm > 0) {
+            result += mm + "分钟";
+        }
+        if (ss > 0) {
+            result += ss + "秒";
+        }
+        if (ms > 0) {
+            result += ms + "毫秒";
+        }
+        return result;
+    }
+
+
+}

+ 32 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/LocalDateTimeToStringConverter.java

@@ -0,0 +1,32 @@
+package com.emato.eoss.util.date.cvt.ldt.cvt;
+
+import com.emato.eoss.util.date.DateConstant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.convert.converter.Converter;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+
+/**
+ * HTTP请求参数<code>LocalDateTime</code>日期格式转换
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-01-29
+ */
+public class LocalDateTimeToStringConverter implements Converter<LocalDateTime, String> {
+    private static final Logger logger = LoggerFactory.getLogger(LocalDateTimeToStringConverter.class);
+
+    @Override
+    public String convert(LocalDateTime source) {
+        if(source == null){
+            return null;
+        }
+        String format = DateConstant.DATE_TIME_SECOND;
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+        return source.format(formatter);
+    }
+
+}

+ 32 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/LocalDateToStringConverter.java

@@ -0,0 +1,32 @@
+package com.emato.eoss.util.date.cvt.ldt.cvt;
+
+import com.emato.eoss.util.date.DateConstant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.convert.converter.Converter;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+
+/**
+ * HTTP请求参数<code>LocalDate</code>日期格式转换
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-01-29
+ */
+public class LocalDateToStringConverter implements Converter<LocalDate, String> {
+    private static final Logger logger = LoggerFactory.getLogger(LocalDateToStringConverter.class);
+    
+    @Override
+    public String convert(LocalDate source) {
+        if(source == null){
+            return null;
+        }
+        String format = DateConstant.DATE_MONTH_DAY;
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+        return source.format(formatter);
+    }
+
+}

+ 32 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/LocalTimeToStringConverter.java

@@ -0,0 +1,32 @@
+package com.emato.eoss.util.date.cvt.ldt.cvt;
+
+import com.emato.eoss.util.date.DateConstant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.convert.converter.Converter;
+
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+
+/**
+ * HTTP请求参数<code>LocalTime</code>日期格式转换
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-01-29
+ */
+public class LocalTimeToStringConverter implements Converter<LocalTime, String> {
+    private static final Logger logger = LoggerFactory.getLogger(LocalTimeToStringConverter.class);
+
+    @Override
+    public String convert(LocalTime source) {
+        if(source == null){
+            return null;
+        }
+        String format = DateConstant.TIME_SECOND;
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+        return source.format(formatter);
+    }
+
+}

+ 59 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/StringToLocalDateConverter.java

@@ -0,0 +1,59 @@
+package com.emato.eoss.util.date.cvt.ldt.cvt;
+
+import com.emato.eoss.util.date.DateConstant;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.convert.converter.Converter;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedList;
+import java.util.List;
+
+
+/**
+ * HTTP请求参数<code>LocalDate</code>日期格式转换
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-01-29
+ */
+public class StringToLocalDateConverter implements Converter<String, LocalDate> {
+    private static final Logger logger = LoggerFactory.getLogger(StringToLocalDateConverter.class);
+
+    private static final List<String> formarts = new LinkedList();
+    static {
+        formarts.add(DateConstant.DATE_MONTH);
+        formarts.add(DateConstant.DATE_MONTH_DAY);
+    }
+
+    @Override
+    public LocalDate convert(String source) {
+        if(StringUtils.isBlank(source)){
+            return null;
+        }
+
+        String format = "";
+
+        try {
+            if (source.matches("^\\d{4}-\\d{1,2}$")) {
+                format = formarts.get(0);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
+                format = formarts.get(1);
+            } else if (source.matches("^\\d{10}$|^\\d{13}$")) {
+                // Windows时间戳(13位),Unix时间戳(10位)
+                format = formarts.get(1);
+            } else {
+                throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
+            }
+
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+            return LocalDate.parse(source, formatter);
+        } catch (Exception e){
+            logger.error("参数" + source + ", 通过【"+ this.getClass().getName() +"】转换为日期格式【" + format + "】时, 发生错误");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+}

+ 61 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/StringToLocalDateTimeConverter.java

@@ -0,0 +1,61 @@
+package com.emato.eoss.util.date.cvt.ldt.cvt;
+
+import com.emato.eoss.util.date.DateConstant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.convert.converter.Converter;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedList;
+import java.util.List;
+
+
+/**
+ * HTTP请求参数<code>LocalDateTime</code>日期格式转换
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-01-29
+ */
+public class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
+    private static final Logger logger = LoggerFactory.getLogger(StringToLocalDateTimeConverter.class);
+
+    private static final List<String> formarts = new LinkedList();
+    static {
+        formarts.add(DateConstant.DATE_TIME_MINUTE);
+        formarts.add(DateConstant.DATE_TIME_SECOND);
+        formarts.add(DateConstant.DATE_TIME_SECOND_MILL);
+    }
+
+    @Override
+    public LocalDateTime convert(String source) {
+        if(source == null){
+            return null;
+        }
+
+        String format = "";
+
+        try {
+            if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(0);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(1);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}\\.\\d{3}$")) {
+                format = formarts.get(2);
+            } else if (source.matches("^\\d{10}$|^\\d{13}$")) {
+                // Windows时间戳(13位),Unix时间戳(10位)
+                format = formarts.get(1);
+            } else {
+                throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
+            }
+
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+            return LocalDateTime.parse(source, formatter);
+        } catch (Exception e){
+            logger.error("参数" + source + ", 通过【"+ this.getClass().getName() +"】转换为日期格式【" + format + "】时, 发生错误");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+}

+ 62 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/cvt/StringToLocalTimeConverter.java

@@ -0,0 +1,62 @@
+package com.emato.eoss.util.date.cvt.ldt.cvt;
+
+import com.emato.eoss.util.date.DateConstant;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.convert.converter.Converter;
+
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedList;
+import java.util.List;
+
+
+/**
+ * HTTP请求参数<code>LocalTime</code>日期格式转换
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-01-29
+ */
+public class StringToLocalTimeConverter implements Converter<String, LocalTime> {
+    private static final Logger logger = LoggerFactory.getLogger(StringToLocalTimeConverter.class);
+
+    private static final List<String> formarts = new LinkedList();
+    static {
+        formarts.add(DateConstant.TIME_MINUTE);
+        formarts.add(DateConstant.TIME_SECOND);
+        formarts.add(DateConstant.TIME_SECOND_MILL);
+    }
+
+    @Override
+    public LocalTime convert(String source) {
+        if(StringUtils.isBlank(source)){
+            return null;
+        }
+
+        String format = "";
+
+        try {
+            if (source.matches("^\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(0);
+            } else if (source.matches("^\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(1);
+            } else if (source.matches("^\\d{1,2}:\\d{1,2}:\\d{1,2}\\.\\d{3}$")) {
+                format = formarts.get(2);
+            } else if (source.matches("^\\d{10}$|^\\d{13}$")) {
+                // Windows时间戳(13位),Unix时间戳(10位)
+                format = formarts.get(2);
+            } else {
+                throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
+            }
+
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+            return LocalTime.parse(source, formatter);
+        } catch (Exception e){
+            logger.error("参数" + source + ", 通过【"+ this.getClass().getName() +"】转换为日期格式【" + format + "】时, 发生错误");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+}

+ 68 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/deser/LocalDateDeserializer.java

@@ -0,0 +1,68 @@
+package com.emato.eoss.util.date.cvt.ldt.deser;
+
+import com.emato.eoss.util.date.DateConstant;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * LocalDate 反序列化
+ * jackson已经通过jsr310实现了 LocalDateDeserializer
+ * @see {@link com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer}
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-08
+ */
+public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
+    private static final Logger logger = LoggerFactory.getLogger(LocalDateDeserializer.class);
+
+    public static final LocalDateDeserializer instance = new LocalDateDeserializer();
+
+    private static final List<String> formarts = new LinkedList();
+    static {
+        formarts.add(DateConstant.DATE_MONTH);
+        formarts.add(DateConstant.DATE_MONTH_DAY);
+    }
+
+    @Override
+    public LocalDate deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+        String source = parser.getText().trim();
+        if (source.length() == 0) {
+            return null;
+        }
+
+        String format = "";
+
+        try {
+            if (source.matches("^\\d{4}-\\d{1,2}$")) {
+                format = formarts.get(0);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
+                format = formarts.get(1);
+            } else if (source.matches("^\\d{10}$|^\\d{13}$")) {
+                // Windows时间戳(13位),Unix时间戳(10位)
+                format = formarts.get(1);
+            } else {
+                throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
+            }
+
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+            return LocalDate.parse(source, formatter);
+        }catch (Exception e) {
+            logger.error("参数" + source + ", 通过【"+ this.getClass().getName() +"】转换为日期格式【" + format + "】时, 发生错误");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+}

+ 71 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/deser/LocalDateTimeDeserializer.java

@@ -0,0 +1,71 @@
+package com.emato.eoss.util.date.cvt.ldt.deser;
+
+import com.emato.eoss.util.date.DateConstant;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * LocalDateTime 反序列化
+ * jackson已经通过jsr310实现了 LocalDateTimeDeserializer
+ * @see {@link com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer}
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-08
+ */
+public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
+    private static final Logger logger = LoggerFactory.getLogger(LocalDateTimeDeserializer.class);
+
+    public static final LocalDateTimeDeserializer instance = new LocalDateTimeDeserializer();
+
+    private static final List<String> formarts = new LinkedList();
+    static {
+        formarts.add(DateConstant.DATE_TIME_MINUTE);
+        formarts.add(DateConstant.DATE_TIME_SECOND);
+        formarts.add(DateConstant.DATE_TIME_SECOND_MILL);
+    }
+
+    @Override
+    public LocalDateTime deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+        String source = parser.getText().trim();
+        if (source.length() == 0) {
+            return null;
+        }
+
+        String format = "";
+
+        try {
+            if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(0);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(1);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}\\.\\d{3}$")) {
+                format = formarts.get(2);
+            } else if (source.matches("^\\d{10}$|^\\d{13}$")) {
+                // Windows时间戳(13位),Unix时间戳(10位)
+                format = formarts.get(1);
+            } else {
+                throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
+            }
+
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+            return LocalDateTime.parse(source, formatter);
+        }catch (Exception e) {
+            logger.error("参数" + source + ", 通过【"+ this.getClass().getName() +"】转换为日期格式【" + format + "】时, 发生错误");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+}

+ 71 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/deser/LocalTimeDeserializer.java

@@ -0,0 +1,71 @@
+package com.emato.eoss.util.date.cvt.ldt.deser;
+
+import com.emato.eoss.util.date.DateConstant;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * LocalTime 反序列化
+ * jackson已经通过jsr310实现了 LocalTimeDeserializer
+ * @see {@link com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer}
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-08
+ */
+public class LocalTimeDeserializer extends JsonDeserializer<LocalTime> {
+    private static final Logger logger = LoggerFactory.getLogger(LocalTimeDeserializer.class);
+
+    public static final LocalTimeDeserializer instance = new LocalTimeDeserializer();
+
+    private static final List<String> formarts = new LinkedList();
+    static {
+        formarts.add(DateConstant.TIME_MINUTE);
+        formarts.add(DateConstant.TIME_SECOND);
+        formarts.add(DateConstant.TIME_SECOND_MILL);
+    }
+
+    @Override
+    public LocalTime deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+        String source = parser.getText().trim();
+        if (source.length() == 0) {
+            return null;
+        }
+
+        String format = "";
+
+        try {
+            if (source.matches("^\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(0);
+            } else if (source.matches("^\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(1);
+            } else if (source.matches("^\\d{1,2}:\\d{1,2}:\\d{1,2}\\.\\d{3}$")) {
+                format = formarts.get(2);
+            } else if (source.matches("^\\d{10}$|^\\d{13}$")) {
+                // Windows时间戳(13位),Unix时间戳(10位)
+                format = formarts.get(2);
+            } else {
+                throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
+            }
+
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+            return LocalTime.parse(source, formatter);
+        }catch (Exception e) {
+            logger.error("参数" + source + ", 通过【"+ this.getClass().getName() +"】转换为日期格式【" + format + "】时, 发生错误");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+}

+ 33 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/ser/LocalDateSerializer.java

@@ -0,0 +1,33 @@
+package com.emato.eoss.util.date.cvt.ldt.ser;
+
+import com.emato.eoss.util.date.DateConstant;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * LocalDate 序列化
+ * jackson已经通过jsr310实现了 LocalDateSerializer
+ * @see {@link com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer}
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-08
+ */
+public class LocalDateSerializer extends JsonSerializer<LocalDate> {
+
+    public static final LocalDateSerializer instance = new LocalDateSerializer();
+
+    @Override
+    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        String format = DateConstant.DATE_MONTH_DAY;
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+        gen.writeString(value.format(formatter));
+    }
+
+}

+ 33 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/ser/LocalDateTimeSerializer.java

@@ -0,0 +1,33 @@
+package com.emato.eoss.util.date.cvt.ldt.ser;
+
+import com.emato.eoss.util.date.DateConstant;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * LocalDateTime 序列化
+ * jackson已经通过jsr310实现了 LocalDateTimeSerializer
+ * @see {@link com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer}
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-08
+ */
+public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
+
+    public static final LocalDateTimeSerializer instance = new LocalDateTimeSerializer();
+
+    @Override
+    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        String format = DateConstant.DATE_TIME_SECOND;
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+        gen.writeString(value.format(formatter));
+    }
+
+}

+ 33 - 0
src/main/java/com/emato/eoss/util/date/cvt/ldt/ser/LocalTimeSerializer.java

@@ -0,0 +1,33 @@
+package com.emato.eoss.util.date.cvt.ldt.ser;
+
+import com.emato.eoss.util.date.DateConstant;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * LocalTime 序列化
+ * jackson已经通过jsr310实现了 LocalTimeSerializer
+ * @see {@link com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer}
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-08
+ */
+public class LocalTimeSerializer extends JsonSerializer<LocalTime> {
+
+    public static final LocalTimeSerializer instance = new LocalTimeSerializer();
+
+    @Override
+    public void serialize(LocalTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        String format = DateConstant.TIME_SECOND;
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+        gen.writeString(value.format(formatter));
+    }
+
+}

+ 82 - 0
src/main/java/com/emato/eoss/util/date/cvt/ser/DateTimeDeserializer.java

@@ -0,0 +1,82 @@
+package com.emato.eoss.util.date.cvt.ser;
+
+import com.emato.eoss.util.date.DateConstant;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * 反序列化Date
+ * String类型反序列化为Date类型
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-07
+ */
+public class DateTimeDeserializer extends JsonDeserializer<Date> {
+    private static final Logger logger = LoggerFactory.getLogger(DateTimeDeserializer.class);
+
+    public static final DateTimeDeserializer instance = new DateTimeDeserializer();
+
+    private static final List<String> formarts = new LinkedList();
+
+    static {
+        formarts.add(DateConstant.DATE_MONTH);
+        formarts.add(DateConstant.DATE_MONTH_DAY);
+        formarts.add(DateConstant.DATE_TIME_MINUTE);
+        formarts.add(DateConstant.DATE_TIME_SECOND);
+        formarts.add(DateConstant.DATE_TIME_SECOND_MILL);
+    }
+
+    @Override
+    public Date deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+        String source = parser.getText().trim();
+        if (source.length() == 0) {
+            return null;
+        }
+
+        String format = "";
+
+        try {
+            if (source.matches("^\\d{4}-\\d{1,2}$")) {
+                format = formarts.get(0);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
+                format = formarts.get(1);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(2);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
+                format = formarts.get(3);
+            } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}\\.\\d{3}$")) {
+                format = formarts.get(4);
+            } else if (source.matches("^\\d{10}$|^\\d{13}$")) {
+                // Windows时间戳(13位),Unix时间戳(10位)
+                format = formarts.get(3);
+            } else {
+                throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
+            }
+
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+
+            //使用LocalDateTime作转换
+            LocalDateTime dateTime = LocalDateTime.parse(source, formatter);
+            return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
+        } catch (Exception e) {
+            logger.error("参数" + source + ", 通过【"+ this.getClass().getName() +"】转换为日期格式【" + format + "】时, 发生错误");
+            e.printStackTrace();
+            return null;
+        }
+
+    }
+
+}

+ 34 - 0
src/main/java/com/emato/eoss/util/date/cvt/ser/DateTimeSerializer.java

@@ -0,0 +1,34 @@
+package com.emato.eoss.util.date.cvt.ser;
+
+import com.emato.eoss.util.date.DateConstant;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * 序列化Date
+ * Date类型序列化为String类型
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-07
+ */
+public class DateTimeSerializer extends JsonSerializer<Date> {
+
+    @Override
+    public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        String format = DateConstant.DATE_TIME_SECOND;
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.SIMPLIFIED_CHINESE);
+
+        //使用LocalDateTime作转换
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault());
+        gen.writeString(localDateTime.format(formatter));
+    }
+
+}

+ 32 - 0
src/main/java/com/emato/eoss/util/date/cvt/tmst/DateTimestampEditor.java

@@ -0,0 +1,32 @@
+package com.emato.eoss.util.date.cvt.tmst;
+
+import java.beans.PropertyEditorSupport;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Date与Timestamp相互转换
+ * 前端是时间戳格式,后端是Date类型
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-07
+ */
+public class DateTimestampEditor extends PropertyEditorSupport {
+    /**
+     * @see PropertyEditorSupport#setAsText(String)
+     */
+    @Override
+    public void setAsText(String text) throws IllegalArgumentException {
+        setValue(new Date(Long.decode(text)));
+    }
+
+    /**
+     * @see PropertyEditorSupport#getAsText()
+     */
+    @Override
+    public String getAsText() {
+        Date value = (Date) getValue();
+        return (value != null ? String.valueOf(TimeUnit.MILLISECONDS.toSeconds(value.getTime())) : "");
+    }
+
+}

+ 24 - 0
src/main/java/com/emato/eoss/util/date/cvt/tmst/DateTimestampWebBindingInitializer.java

@@ -0,0 +1,24 @@
+package com.emato.eoss.util.date.cvt.tmst;
+
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.support.WebBindingInitializer;
+
+import java.util.Date;
+
+/**
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-02-07
+ */
+public class DateTimestampWebBindingInitializer implements WebBindingInitializer {
+
+    /**
+     * @see WebBindingInitializer#initBinder(WebDataBinder)
+     */
+    @Override
+    public void initBinder(WebDataBinder binder) {
+        binder.registerCustomEditor(Date.class, new DateTimestampEditor());
+    }
+
+
+}

+ 69 - 0
src/main/java/com/emato/eoss/util/jackson/JacksonStringUnicodeSerializer.java

@@ -0,0 +1,69 @@
+package com.emato.eoss.util.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.io.CharTypes;
+import com.fasterxml.jackson.core.json.JsonWriteContext;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+
+/**
+ * jackson处理以unicode方式编码中文
+ */
+public class JacksonStringUnicodeSerializer extends JsonSerializer<String> {
+
+	private final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+	private final int[] ESCAPE_CODES = CharTypes.get7BitOutputEscapes();
+
+	private void writeUnicodeEscape(JsonGenerator gen, char c) throws IOException {
+		gen.writeRaw('\\');
+		gen.writeRaw('u');
+		gen.writeRaw(HEX_CHARS[(c >> 12) & 0xF]);
+		gen.writeRaw(HEX_CHARS[(c >> 8) & 0xF]);
+		gen.writeRaw(HEX_CHARS[(c >> 4) & 0xF]);
+		gen.writeRaw(HEX_CHARS[c & 0xF]);
+	}
+
+	private void writeShortEscape(JsonGenerator gen, char c) throws IOException {
+		gen.writeRaw('\\');
+		gen.writeRaw(c);
+	}
+
+	@Override
+	public void serialize(String str, JsonGenerator gen,
+                          SerializerProvider provider) throws IOException,
+			JsonProcessingException {
+		int status = ((JsonWriteContext) gen.getOutputContext()).writeValue();
+	    switch (status) {
+	      case JsonWriteContext.STATUS_OK_AFTER_COLON:
+	        gen.writeRaw(':');
+	        break;
+	      case JsonWriteContext.STATUS_OK_AFTER_COMMA:
+	        gen.writeRaw(',');
+	        break;
+	      case JsonWriteContext.STATUS_EXPECT_NAME:
+	        throw new JsonParseException(null, "Can not write string value here");
+	    }
+	    gen.writeRaw('"');//写入JSON中字符串的开头引号
+	    for (char c : str.toCharArray()) {
+	      if (c >= 0x80){
+	    	  writeUnicodeEscape(gen, c); // 为所有非ASCII字符生成转义的unicode字符
+	      }else {
+	        // 为ASCII字符中前128个字符使用转义的unicode字符
+	        int code = (c < ESCAPE_CODES.length ? ESCAPE_CODES[c] : 0);
+	        if (code == 0){
+	        	gen.writeRaw(c); // 此处不用转义
+	        }else if (code < 0){
+	        	writeUnicodeEscape(gen, (char) (-code - 1)); // 通用转义字符
+	        }else {
+	        	writeShortEscape(gen, (char) code); // 短转义字符 (\n \t ...)
+	        }
+	      }
+	    }
+	    gen.writeRaw('"');//写入JSON中字符串的结束引号
+	}
+
+}

+ 385 - 0
src/main/java/com/emato/eoss/util/jackson/JacksonUtils.java

@@ -0,0 +1,385 @@
+package com.emato.eoss.util.jackson;
+
+
+import com.emato.eoss.util.date.DateConstant;
+import com.emato.eoss.util.date.cvt.ser.DateTimeDeserializer;
+import com.emato.eoss.util.date.cvt.ser.DateTimeSerializer;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * JacksonUtils 工具类
+ * @author Scott Chen
+ * @version 1.0
+ * 2017-09-18 18:42
+ */
+public class JacksonUtils {
+
+    private static final Logger logger = LoggerFactory.getLogger(JacksonUtils.class);
+
+    private static ObjectMapper objectMapper;
+
+    private static final ThreadLocal<JacksonUtils> jacksonUtilThreadLocal = new ThreadLocal<>();
+
+    private static final ThreadLocal<ObjectMapper> objectMapperThreadLocal = new ThreadLocal<>();
+    
+    /**
+     * 禁止调用无参构造
+     */
+    private JacksonUtils() {}
+
+    /**
+     * JacksonUtils 工具类实例
+     * @return
+     */
+    public static JacksonUtils instance() {
+        JacksonUtils instance = jacksonUtilThreadLocal.get();
+        if (instance == null) {
+            instance = new JacksonUtils();
+            jacksonUtilThreadLocal.set(instance);
+        }
+        return instance;
+    }
+
+    /**
+     * ObjectMapper实例
+     * @return
+     */
+    public static ObjectMapper objectMapper() {
+        objectMapper = objectMapperThreadLocal.get();
+        if (objectMapper== null){
+            objectMapper= new ObjectMapper();
+
+            // 格式化国家环境指定
+            objectMapper.setLocale(Locale.SIMPLIFIED_CHINESE);
+
+            // 设置可见性
+            // PropertyAccessor: 属性访问器, IS_GETTER, getter-like methods that are named "isXxx" (instead of "getXxx" for getters) and return boolean value
+            // Visibility: 可见性, PUBLIC_ONLY, 仅 public 修饰符 允许
+            objectMapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY);
+
+            // 序列化时, 当序列化数据中如果没有对应类型的数据时发生什么, 设置为true(默认),则会抛出异常,设置为false, 则会序列化为空对象
+            objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+
+            // 反序列化时, 数据有值,但类型没有对应属性, 是否抛JsonMappingException异常, 默认为true, 抛出JsonMappingException
+            objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+            // 反序列化时, 允许空值("")是否转换为空对象`null` , 默认false, 不允许
+            objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
+            // 反序列化时, 多态的类型时会发生什么的, true(默认),抛出异常; 如果为false, 则使用null值
+            //objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
+            // 反序列化时, 确定遇到Object Id引用时会发生什么的, 默认true,抛出异常,false, 则不会进行进一步处理
+            //objectMapper.disable(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS);
+            // 反序列化时, 属性注释不存在, 但关联的类型id可用的处理, 默认true, 抛出JsonMappingException
+            objectMapper.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY);
+
+            // 序列化和反序列化时, 避免转义字符抛出异常情况
+            objectMapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS) ;
+            // 序列化和反序列化时, 允许没有引号的字段名(非标准)
+            objectMapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
+
+
+            //设置null值不参与序列化(字段不被显示)
+            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+
+            //注册序列化LocalDateTime模块
+            objectMapper.registerModule(localDateTimeSerializer());
+            //注册反序列化LocalDateTime模块
+            objectMapper.registerModule(localDateTimeDeserializer());
+
+            //注册序列化Date模块
+            objectMapper.registerModule(dateTimeSerializer());
+            //注册反序列化Date模块
+            objectMapper.registerModule(dateTimeDeserializer());
+
+            //序列化Unicode编码非ASCII字符
+            //objectMapper.registerModule(unicodeSerModule());
+
+            objectMapperThreadLocal.set(objectMapper);
+        }
+        return objectMapper;
+    }
+
+
+    public ObjectMapper getObjectMapper() {
+        return objectMapper();
+    }
+
+
+    /**
+     * 序列化Date
+     * 自定义
+     * @return
+     */
+    private static SimpleModule dateTimeSerializer(){
+        SimpleModule module = new SimpleModule();
+        module.addSerializer(Date.class, new DateTimeSerializer());
+        return module;
+    }
+
+    /**
+     * 反列化Date
+     * 自定义
+     * @return
+     */
+    private static SimpleModule dateTimeDeserializer(){
+        SimpleModule module = new SimpleModule();
+        module.addDeserializer(Date.class, new DateTimeDeserializer());
+        return module;
+    }
+
+    /**
+     * 序列化LocalDateTime时间
+     * 使用 <code>jackson-datatype-jsr310</code>
+     * @return
+     */
+    private static SimpleModule localDateTimeSerializer() {
+        SimpleModule module = new SimpleModule();
+        module.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DateConstant.DATE_MONTH_DAY, Locale.SIMPLIFIED_CHINESE)));
+        module.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DateConstant.TIME_SECOND, Locale.SIMPLIFIED_CHINESE)));
+        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DateConstant.DATE_TIME_SECOND, Locale.SIMPLIFIED_CHINESE)));
+        return module;
+    }
+
+    /**
+     * 反序列化LocalDateTime时间
+     * 使用 <code>jackson-datatype-jsr310</code>
+     * @return
+     */
+    private static SimpleModule localDateTimeDeserializer() {
+        SimpleModule module = new SimpleModule();
+        module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DateConstant.DATE_MONTH_DAY, Locale.SIMPLIFIED_CHINESE)));
+        module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DateConstant.TIME_SECOND, Locale.SIMPLIFIED_CHINESE)));
+        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DateConstant.DATE_TIME_SECOND, Locale.SIMPLIFIED_CHINESE)));
+        return module;
+    }
+
+
+    /**
+     * 序列化Unicode编码非ASCII字符
+     */
+    private static SimpleModule unicodeSerModule() {
+        SimpleModule unicodeSerModule = new SimpleModule();
+        unicodeSerModule.addSerializer(String.class, new JacksonStringUnicodeSerializer());
+        return unicodeSerModule;
+    }
+
+    private static JsonFactory getJsonFactory() {
+        return objectMapper().getFactory();
+    }
+
+
+    // -------------------- to json --------------------
+
+    /**
+     * object对象序列化为Json字符串
+     * @param obj
+     * @param <T>
+     * @return
+     */
+    public static <T> String toJsonStr(T obj) {
+        return toJsonStr(obj, false);
+    }
+
+    /**
+     * Object对象序列化为Json字符串
+     *
+     * @param obj
+     * @param isPretty 是否美化
+     * @param <T>
+     * @return
+     */
+    public static <T> String toJsonStr(T obj, boolean isPretty) {
+        if (obj == null) {
+            return null;
+        }
+
+        if (obj instanceof String) {
+            return (String) obj;
+        }
+
+        StringWriter sw = new StringWriter();
+        JsonGenerator generator;
+        try {
+            generator = objectMapper().getFactory().createGenerator(sw);
+            if (generator == null) {
+                sw.close();
+                return null;
+            }
+
+            if (isPretty) {
+                generator.useDefaultPrettyPrinter().writeObject(obj);
+            } else {
+                generator.writeObject(obj);
+            }
+
+            generator.close();
+            return sw.toString();
+        } catch (IOException ioe) {
+            logger.error("toJSON序列化失败, 异常类型【IOException】, 错误原因:{}", ioe.getMessage());
+            ioe.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Object对象序列化为Json字符串
+     *
+     * @param obj
+     * @param isPretty
+     * @param <T>
+     * @return
+     */
+    public static <T> String toJsonStrWithObjectMapper(T obj, boolean isPretty) {
+        if (obj == null){
+            return null;
+        }
+
+        if (obj instanceof String) {
+            return (String) obj;
+        }
+
+        String result;
+        try {
+            if (isPretty) {
+                result = objectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(obj);
+            } else {
+                result = objectMapper().writeValueAsString(obj);
+            }
+
+            return result;
+        } catch (JsonProcessingException jpe) {
+            logger.error("toJSON序列化失败, 异常类型【JsonProcessingException】, 错误原因:{}", jpe.getMessage());
+            jpe.printStackTrace();
+            return null;
+        }
+
+    }
+
+
+    // -------------------- from json --------------------
+
+
+    /**
+     * 将Json Byte反序列化成对象
+     *
+     * @param data
+     * @param clazz
+     * @return
+     */
+    public static <T> T parseJson(byte[] data, Class<T> clazz) {
+        try {
+            JsonParser parser = objectMapper().getFactory().createParser(data);
+            return parser.readValueAs(clazz);
+        } catch (IOException e){
+            logger.error(String.format("fromJson反序列化失败, 异常类型【IOException】, 错误原因:{}", e.getMessage()));
+        }
+        return null;
+    }
+
+    /**
+     * 将Json String反序列化成对象
+     *
+     * @param json
+     * @param clazz
+     * @return
+     */
+    public static <T> T parseJson(String json, Class<T> clazz) {
+        try {
+            JsonParser parser = objectMapper().getFactory().createParser(json);
+            return clazz.equals(String.class) ? (T) json : parser.readValueAs(clazz);
+        } catch (IOException e) {
+            logger.error(String.format("fromJson反序列化失败, 异常类型【IOException】, 错误原因:{}", e.getMessage()));
+            logger.error("decode(String, Class<T>)", e);
+        }
+        return null;
+    }
+
+    /**
+     * 将Json Array或List反序列化为对象
+     *
+     * @param json
+     * @param typeReference 被转对象引用类型
+     * @param <T>
+     * @return
+     */
+    public static <T> T parseJson(String json, TypeReference<T> typeReference) {
+        try {
+            JsonParser parser = objectMapper().getFactory().createParser(json);
+
+            //写成List.class是不行的
+            return (T) (typeReference.getType().equals(String.class) ? json : parser.readValueAs(typeReference));
+
+        }catch (IOException e) {
+            logger.error(String.format("fromJson反序列化失败, 异常类型【IOException】, 错误原因:{}", e.getMessage()));
+            logger.error("decode(String, Class<T>)", e);
+        }
+        return null;
+    }
+
+    /**
+     * string转object 用于转为集合对象
+     * @param json json字符串
+     * @param collectionClass 被转集合class
+     * @param elementClasses 被转集合中对象类型class
+     * @param <T>
+     * @return
+     */
+    public static <T> T parseJson(String json, Class<?> collectionClass, Class<?>... elementClasses){
+        JavaType javaType = objectMapper()
+                .getTypeFactory()
+                .constructParametricType(collectionClass, elementClasses);
+        try {
+            return objectMapper().readValue(json, javaType);
+        } catch (IOException e) {
+            logger.error("Parse String to Object error.");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+
+    // -------------------- map to bean --------------------
+
+    /**
+     * map 转 bean
+     * @param map
+     * @param clazz
+     * @param <T>
+     * @return
+     */
+    public static <T> T mapToBean(Map map, Class<T> clazz) {
+        return objectMapper().convertValue(map, clazz);
+    }
+
+
+}

+ 254 - 0
src/main/java/com/emato/eoss/util/sign/Dsa.java

@@ -0,0 +1,254 @@
+package com.emato.eoss.util.sign;
+
+import java.nio.charset.StandardCharsets;
+import java.security.*;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * DSA 签名
+ * DSA 主要用作签名算法
+ *
+ * @author Scott Chen
+ * @date 2017/2/27
+ */
+public class Dsa {
+
+    public static final String DSA_KEY_ALGORITHM = "DSA";
+    public static final String SHA1_DSA_SIGN_ALGORITHMS = "SHA1withDSA";
+
+
+    private static final String DSA_PUBLIC_KEY = "DSAPublicKey";
+    private static final String DSA_PRIVATE_KEY = "DSAPrivateKey";
+
+    /**
+     * DSA密钥长度默认为1024位,密钥长度必须是64的整数倍,范围在512~1024之间
+     */
+    private static final int KEY_SIZE = 1024;
+
+
+    // ------------------------------ 生成公钥私钥字符串 ------------------------------
+
+    /**
+     * 初始化公钥私钥 KeyPair
+     *
+     * @return
+     * @throws Exception
+     */
+    public static Map<String, Object> initKey() {
+        KeyPairGenerator keyPairGen = null;
+        try {
+            keyPairGen = KeyPairGenerator.getInstance(DSA_KEY_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+
+        keyPairGen.initialize(KEY_SIZE);
+        KeyPair keyPair = keyPairGen.genKeyPair();
+
+        //获取公私钥对象
+        DSAPublicKey publicKey = (DSAPublicKey) keyPair.getPublic();
+        DSAPrivateKey privateKey = (DSAPrivateKey) keyPair.getPrivate();
+
+        Map<String, Object> keyMap = new HashMap<>();
+        keyMap.put(DSA_PUBLIC_KEY, publicKey);
+        keyMap.put(DSA_PRIVATE_KEY, privateKey);
+
+        return keyMap;
+    }
+
+
+    /**
+     * 从 KeyPair 中获取公钥字符串
+     *
+     * @param keyMap
+     * @return
+     */
+    public static String getPublicKeyStr(Map<String, Object> keyMap) {
+        Key key = (Key) keyMap.get(DSA_PUBLIC_KEY);
+        return Base64.getEncoder().encodeToString(key.getEncoded());
+    }
+
+    /**
+     * 从 KeyPair 中获取私钥字符串
+     *
+     * @param keyMap
+     * @return
+     */
+    public static String getPrivateKeyStr(Map<String, Object> keyMap) {
+        Key key = (Key) keyMap.get(DSA_PRIVATE_KEY);
+        return Base64.getEncoder().encodeToString(key.getEncoded());
+    }
+
+
+    // ------------------------------ 生成公钥私钥 ------------------------------
+
+    /**
+     * 生成公钥
+     *
+     * @param base64PublicKeyStr 密钥字符串(经过base64编码)
+     * @return
+     * @throws Exception
+     */
+    public static PublicKey generatePublicKey(String base64PublicKeyStr) throws Exception {
+        return generatePublicKey(Base64.getDecoder().decode(base64PublicKeyStr));
+    }
+
+
+    /**
+     * 生成公钥
+     *
+     * @param base64PublicKeyByte 密钥字节码
+     * @return
+     * @throws Exception
+     */
+    public static PublicKey generatePublicKey(byte[] base64PublicKeyByte) throws Exception {
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64PublicKeyByte);
+        KeyFactory keyFactory = KeyFactory.getInstance(DSA_KEY_ALGORITHM);
+        PublicKey publicKey = keyFactory.generatePublic(keySpec);
+
+        return publicKey;
+    }
+
+
+    /**
+     * 生成私钥
+     *
+     * @param base64PrivateKeyStr 密钥字符串(经过base64编码)
+     * @return
+     * @throws Exception
+     */
+    public static PrivateKey generatePrivateKey(String base64PrivateKeyStr) throws Exception {
+        return generatePrivateKey(Base64.getDecoder().decode(base64PrivateKeyStr));
+    }
+
+    /**
+     * 生成私钥
+     *
+     * @param base64PrivateKeyByte 密钥字节码
+     * @return
+     * @throws Exception
+     */
+    public static PrivateKey generatePrivateKey(byte[] base64PrivateKeyByte) throws Exception {
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(base64PrivateKeyByte);
+        KeyFactory keyFactory = KeyFactory.getInstance(DSA_KEY_ALGORITHM);
+        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
+
+        return privateKey;
+    }
+
+
+
+    // ------------------------------ 加签验签 ------------------------------
+
+    /**
+     * RSA 签名
+     * 私钥签名
+     *
+     * @param plainStr 待签名字符串
+     * @param base64PrivateKeyStr
+     * @param inputCharset
+     * @return 签名值
+     */
+    public static String sign(String plainStr, String base64PrivateKeyStr, String inputCharset){
+        try
+        {
+            inputCharset = (inputCharset == null || "".equals(inputCharset)) ? StandardCharsets.UTF_8.toString() : inputCharset;
+            return sign(plainStr.getBytes(inputCharset), Base64.getDecoder().decode(base64PrivateKeyStr));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    /**
+     * RSA 签名
+     * 私钥签名
+     *
+     * @param plainByte 待签名字节码
+     * @param base64PrivateKeyByte 私钥
+     * @return 签名值
+     */
+    public static String sign(byte[] plainByte, byte[] base64PrivateKeyByte) {
+        try
+        {
+            PrivateKey priKey = generatePrivateKey(base64PrivateKeyByte);
+
+            Signature signature = Signature.getInstance(SHA1_DSA_SIGN_ALGORITHMS);
+
+            signature.initSign(priKey);
+            signature.update(plainByte);
+
+            byte[] signed = signature.sign();
+
+            return Base64.getEncoder().encodeToString(signed);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+
+    /**
+     * RSA 签名
+     * 私钥签名
+     *
+     * @param plainStr 待签名字符串
+     * @param base64SignStr
+     * @param base64PublicKeyStr
+     * @param inputCharset
+     * @return 签名值
+     */
+    public static boolean verify(String plainStr, String base64SignStr, String base64PublicKeyStr, String inputCharset){
+        try
+        {
+            inputCharset = (inputCharset == null || "".equals(inputCharset)) ? StandardCharsets.UTF_8.toString() : inputCharset;
+            return verify(
+                    plainStr.getBytes(inputCharset),
+                    Base64.getDecoder().decode(base64SignStr),
+                    Base64.getDecoder().decode(base64PublicKeyStr)
+            );
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+    /**
+     * RSA 验签
+     * 公钥验签
+     * @param plainByte 待签名字节码
+     * @param base64SignByte 签名值
+     * @param base64PublicKeyByte 公钥
+     * @return 布尔值
+     */
+    public static boolean verify(byte[] plainByte, byte[] base64SignByte, byte[] base64PublicKeyByte) {
+        try
+        {
+            PublicKey pubKey = generatePublicKey(base64PublicKeyByte);
+
+            Signature signature = Signature.getInstance(SHA1_DSA_SIGN_ALGORITHMS);
+
+            signature.initVerify(pubKey);
+            signature.update(plainByte);
+
+            return signature.verify(base64SignByte);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+
+}

+ 225 - 0
src/main/java/com/emato/eoss/util/sign/Md5.java

@@ -0,0 +1,225 @@
+package com.emato.eoss.util.sign;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+/** 
+ * MD5 digest
+ *
+ * @author Scott Chen
+ * @date 2017/2/27
+ */
+public class Md5 {
+
+    /**
+     * 签名字符串
+     * @param text
+     * @return
+     */
+    public static byte[] signMd5(String text) {
+        return signMd5(text, null, null);
+    }
+
+    /**
+     * 签名字符串
+     * @param text
+     * @param key
+     * @param charset
+     * @return return byte
+     */
+    public static byte[] signMd5(String text, String key, Charset charset) {
+        charset = charset == null ? StandardCharsets.UTF_8 : charset;
+        text = text + (key == null ? "" : key);
+        return DigestUtils.md5(text.getBytes(charset));
+    }
+
+    /**
+     * 签名字符串
+     * @param text
+     * @return
+     */
+    public static String signMd5Base64(String text) {
+        return signMd5Base64(text, null, null);
+    }
+
+    /**
+     * 签名字符串
+     * @param text
+     * @param key
+     * @param charset
+     * @return return string of Base64
+     */
+    public static String signMd5Base64(String text, String key, Charset charset) {
+        byte[] code = signMd5(text, key, charset);
+        return Base64.getEncoder().encodeToString(code);
+    }
+
+    /**
+     * 签名字符串
+     * @param text
+     * @return
+     */
+    public static String signMd5Hex(String text) {
+        return signMd5Hex(text, null, null);
+    }
+
+    /**
+     * 签名字符串
+     * @param text 需要签名的字符串
+     * @param key 密钥
+     * @param charset 编码格式
+     * @return 签名结果 MD5 digest as a 32 character hex string
+     */
+    public static String signMd5Hex(String text, String key, Charset charset) {
+        charset = charset == null ? StandardCharsets.UTF_8 : charset;
+        text = text + (key == null ? "" : key);
+        return DigestUtils.md5Hex(text.getBytes(charset));
+    }
+
+    /**
+     * 签名字符串
+     * @param text
+     * @return return string of Base64
+     */
+    public static String singMd5HexBase64(String text) {
+        return singMd5HexBase64(text, null, null);
+    }
+
+    /**
+     * 签名字符串
+     * @param text
+     * @param key
+     * @param charset
+     * @return return string of Base64
+     */
+    public static String singMd5HexBase64(String text, String key, Charset charset) {
+        String code = signMd5Hex(text, key, charset);
+        return Base64.getEncoder().encodeToString(code.getBytes());
+    }
+
+    /**
+     * 验证签名
+     * @param text
+     * @param sign
+     * @return
+     */
+    public static boolean verifyMd5(String text, String sign) {
+        return verifyMd5(text, sign, null, null);
+    }
+
+    /**
+     * 验证签名
+     * @param text
+     * @param sign
+     * @param key
+     * @param charset
+     * @return
+     */
+    public static boolean verifyMd5(String text, String sign, String key, Charset charset) {
+        charset = charset == null ? StandardCharsets.UTF_8 : charset;
+        text = text + (key == null ? "" : key);
+        byte[] mySign = DigestUtils.md5(text.getBytes(charset));
+        if(sign.equals(new String(mySign, StandardCharsets.UTF_8))) {
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    /**
+     * 验证签名
+     * @param text
+     * @param base64Sign
+     * @return
+     */
+    public static boolean verifyMd5Base64(String text, String base64Sign) {
+        return verifyMd5Base64(text, base64Sign, null, null);
+    }
+
+    /**
+     * 验证签名
+     * @param text
+     * @param base64Sign
+     * @param key
+     * @param charset
+     * @return
+     */
+    public static boolean verifyMd5Base64(String text, String base64Sign, String key, Charset charset) {
+        charset = charset == null ? StandardCharsets.UTF_8 : charset;
+        text = text + (key == null ? "" : key);
+        byte[] mySign = DigestUtils.md5(text.getBytes(charset));
+        String base64MySign = Base64.getEncoder().encodeToString(mySign);
+        if(base64Sign.equals(base64MySign)) {
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    /**
+     * 验证签名
+     * @param text
+     * @param sign
+     * @return
+     */
+    public static boolean verifyMd5Hex(String text, String sign) {
+        return verifyMd5Hex(text, sign, null, null);
+    }
+
+    /**
+     * 验证签名
+     * @param text 需要签名的字符串
+     * @param sign 签名结果 MD5 digest as a 32 character hex string
+     * @param key 密钥
+     * @param charset 编码格式
+     * @return
+     */
+    public static boolean verifyMd5Hex(String text, String sign, String key, Charset charset) {
+        charset = charset == null ? StandardCharsets.UTF_8 : charset;
+        text = text + (key == null ? "" : key);
+    	String mySign = DigestUtils.md5Hex(text.getBytes(charset));
+    	if(mySign.equals(sign)) {
+    		return true;
+    	}
+    	else {
+    		return false;
+    	}
+    }
+
+    /**
+     * 验证签名
+     * @param text
+     * @param base64Sign
+     * @return
+     */
+    public static boolean verifyMd5HexBase64(String text, String base64Sign) {
+        return verifyMd5HexBase64(text, base64Sign, null, null);
+    }
+
+    /**
+     * 验证签名
+     * @param text
+     * @param base64Sign
+     * @param key
+     * @param charset
+     * @return
+     */
+    public static boolean verifyMd5HexBase64(String text, String base64Sign, String key, Charset charset) {
+        charset = charset == null ? StandardCharsets.UTF_8 : charset;
+        text = text + (key == null ? "" : key);
+        String mySign = DigestUtils.md5Hex(text.getBytes(charset));
+        String base64MySign = Base64.getEncoder().encodeToString(mySign.getBytes(charset));
+        if(base64MySign.equals(base64Sign)) {
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+}

+ 381 - 0
src/main/java/com/emato/eoss/util/sign/Rsa.java

@@ -0,0 +1,381 @@
+
+package com.emato.eoss.util.sign;
+
+import javax.crypto.Cipher;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.*;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * RSA 签名加密
+ * RSA 可以签名, 也可以加密
+ *
+ * @author Scott Chen
+ * @date 2017/2/27
+ */
+public class Rsa {
+
+    public static final String RSA_KEY_ALGORITHM = "RSA";
+    public static final String SHA1_RSA_SIGN_ALGORITHMS = "SHA1withRSA";
+
+
+    private static final String RSA_PUBLIC_KEY = "RSAPublicKey";
+    private static final String RSA_PRIVATE_KEY = "RSAPrivateKey";
+
+
+    /**
+     * RSA 密钥长度默认为1024位,密钥长度必须是64的整数倍,范围在512~1024之间
+     */
+    private static final int KEY_SIZE = 1024;
+
+    /**
+     * RSA最大加密明文大小
+     */
+    private static final int MAX_ENCRYPT_BLOCK = 117;
+
+    /**
+     * RSA最大解密密文大小
+     */
+    private static final int MAX_DECRYPT_BLOCK = 128;
+
+
+    // ------------------------------ 生成公钥私钥字符串 ------------------------------
+
+    /**
+     * 初始化公钥私钥 KeyPair
+     *
+     * @return
+     * @throws Exception
+     */
+    public static Map<String, Object> initKey() {
+        KeyPairGenerator keyPairGen = null;
+        try {
+            keyPairGen = KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+
+        keyPairGen.initialize(KEY_SIZE);
+        KeyPair keyPair = keyPairGen.genKeyPair();
+
+        //获取公私钥对象
+        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+
+        Map<String, Object> keyMap = new HashMap<>();
+        keyMap.put(RSA_PUBLIC_KEY, publicKey);
+        keyMap.put(RSA_PRIVATE_KEY, privateKey);
+
+        return keyMap;
+    }
+
+    /**
+     * 从 KeyPair 中获取公钥字符串
+     *
+     * @param keyMap
+     * @return
+     */
+    public static String getPublicKeyStr(Map<String, Object> keyMap) {
+        Key key = (Key) keyMap.get(RSA_PUBLIC_KEY);
+        return Base64.getEncoder().encodeToString(key.getEncoded());
+    }
+
+    /**
+     * 从 KeyPair 中获取私钥字符串
+     *
+     * @param keyMap
+     * @return
+     */
+    public static String getPrivateKeyStr(Map<String, Object> keyMap) {
+        Key key = (Key) keyMap.get(RSA_PRIVATE_KEY);
+        return Base64.getEncoder().encodeToString(key.getEncoded());
+    }
+
+
+    // ------------------------------ 生成公钥私钥 ------------------------------
+
+    /**
+     * 生成公钥
+     *
+     * @param base64PublicKeyStr 密钥字符串(经过base64编码)
+     * @return
+     * @throws Exception
+     */
+    public static PublicKey generatePublicKey(String base64PublicKeyStr) throws Exception {
+        return generatePublicKey(Base64.getDecoder().decode(base64PublicKeyStr));
+    }
+
+
+    /**
+     * 生成公钥
+     *
+     * @param base64PublicKeyByte 密钥字节码
+     * @return
+     * @throws Exception
+     */
+    public static PublicKey generatePublicKey(byte[] base64PublicKeyByte) throws Exception {
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64PublicKeyByte);
+        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
+        PublicKey publicKey = keyFactory.generatePublic(keySpec);
+
+        return publicKey;
+    }
+
+
+    /**
+     * 生成私钥
+     *
+     * @param base64PrivateKeyStr 密钥字符串(经过base64编码)
+     * @return
+     * @throws Exception
+     */
+    public static PrivateKey generatePrivateKey(String base64PrivateKeyStr) throws Exception {
+        return generatePrivateKey(Base64.getDecoder().decode(base64PrivateKeyStr));
+    }
+
+    /**
+     * 生成私钥
+     *
+     * @param base64PrivateKeyByte 密钥字节码
+     * @return
+     * @throws Exception
+     */
+    public static PrivateKey generatePrivateKey(byte[] base64PrivateKeyByte) throws Exception {
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(base64PrivateKeyByte);
+        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
+        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
+
+        return privateKey;
+    }
+
+
+
+    // ------------------------------ 加签验签 ------------------------------
+
+    /**
+     * RSA 签名
+     * 私钥签名
+     *
+     * @param plainStr 待签名字符串
+     * @param base64PrivateKeyStr
+     * @param inputCharset
+     * @return 签名值
+     */
+    public static String sign(String plainStr, String base64PrivateKeyStr, String inputCharset){
+        try
+        {
+            inputCharset = (inputCharset == null || "".equals(inputCharset)) ? StandardCharsets.UTF_8.toString() : inputCharset;
+            return sign(plainStr.getBytes(inputCharset), Base64.getDecoder().decode(base64PrivateKeyStr));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    /**
+	 * RSA 签名
+	 * 私钥签名
+     *
+	 * @param plainByte 待签名字节码
+	 * @param base64PrivateKeyByte 私钥
+	 * @return 签名值
+	 */
+	public static String sign(byte[] plainByte, byte[] base64PrivateKeyByte) {
+        try 
+        {
+			PrivateKey priKey = generatePrivateKey(base64PrivateKeyByte);
+
+            Signature signature = Signature.getInstance(SHA1_RSA_SIGN_ALGORITHMS);
+
+            signature.initSign(priKey);
+            signature.update(plainByte);
+
+            byte[] signed = signature.sign();
+            
+            return Base64.getEncoder().encodeToString(signed);
+        } catch (Exception e) {
+        	e.printStackTrace();
+        }
+        
+        return null;
+    }
+
+
+    /**
+     * RSA 签名
+     * 私钥签名
+     *
+     * @param plainStr 待签名字符串
+     * @param base64SignStr
+     * @param base64PublicKeyStr
+     * @param inputCharset
+     * @return 签名值
+     */
+    public static boolean verify(String plainStr, String base64SignStr, String base64PublicKeyStr, String inputCharset){
+        try
+        {
+            inputCharset = (inputCharset == null || "".equals(inputCharset)) ? StandardCharsets.UTF_8.toString() : inputCharset;
+            return verify(
+                    plainStr.getBytes(inputCharset),
+                    Base64.getDecoder().decode(base64SignStr),
+                    Base64.getDecoder().decode(base64PublicKeyStr)
+            );
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+	/**
+	 * RSA 验签
+	 * 公钥验签
+	 * @param plainByte 待签名字节码
+	 * @param base64SignByte 签名值
+	 * @param base64PublicKeyByte 公钥
+	 * @return 布尔值
+	 */
+	public static boolean verify(byte[] plainByte, byte[] base64SignByte, byte[] base64PublicKeyByte) {
+		try 
+		{
+	        PublicKey pubKey = generatePublicKey(base64PublicKeyByte);
+
+			Signature signature = Signature.getInstance(SHA1_RSA_SIGN_ALGORITHMS);
+		
+			signature.initVerify(pubKey);
+			signature.update(plainByte);
+
+			return signature.verify(base64SignByte);
+			
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		
+		return false;
+	}
+
+
+	// ------------------------------ 加密解密 ------------------------------
+
+	/**
+	 * 加密
+	 * 公钥加密
+	 * @param plainStr 待加密字符串
+	 * @param base64PublicKeyStr 公钥
+	 * @param inputCharset
+	 * @return 加密字符串
+	 * @throws Exception
+	 */
+	public static String encrypt(String plainStr, String base64PublicKeyStr, String inputCharset) throws Exception {
+        inputCharset = (inputCharset == null || "".equals(inputCharset)) ? StandardCharsets.UTF_8.toString() : inputCharset;
+	    return encrypt(plainStr.getBytes(inputCharset), Base64.getDecoder().decode(base64PublicKeyStr));
+	}
+
+    /**
+     * 加密
+     * 公钥加密
+     * @param plainByte 待加密字节码
+     * @param base64PublicKeyByte 公钥
+     * @return
+     * @throws Exception
+     */
+    public static String encrypt(byte[] plainByte, byte[] base64PublicKeyByte) throws Exception {
+        PublicKey pubKey = generatePublicKey(base64PublicKeyByte);
+
+        Cipher cipher = Cipher.getInstance(RSA_KEY_ALGORITHM);
+        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+        int inputLen = plainByte.length;
+
+        int offSet = 0;
+        int i = 0;
+        byte[] cache;
+
+        while (inputLen - offSet > 0) {
+            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
+                cache = cipher.doFinal(plainByte, offSet, MAX_ENCRYPT_BLOCK);
+            } else {
+                cache = cipher.doFinal(plainByte, offSet, inputLen - offSet);
+            }
+            out.write(cache, 0, cache.length);
+            i++;
+            offSet = i * MAX_ENCRYPT_BLOCK;
+        }
+
+        byte[] encryptByte = out.toByteArray();
+        out.close();
+
+        return new String(encryptByte);
+    }
+
+
+
+    /**
+     * 解密
+     * 私钥解密
+     * @param encryptStr 加密字符串
+     * @param base64PrivateKeyStr 私钥
+     * @param inputCharset
+     * @return 解密后的字符串
+     * @throws Exception
+     */
+    public static String decrypt(String encryptStr , String base64PrivateKeyStr, String inputCharset) throws Exception {
+        inputCharset = (inputCharset == null || "".equals(inputCharset)) ? StandardCharsets.UTF_8.toString() : inputCharset;
+        return decrypt(encryptStr .getBytes(inputCharset), Base64.getDecoder().decode(base64PrivateKeyStr));
+    }
+
+    /**
+	 * 解密
+	 * 私钥解密
+	 * @param encryptByte 加密字节码
+	 * @param base64PrivateKeyByte 私钥
+	 * @return 解密后的字符串
+	 */
+	public static String decrypt(byte[] encryptByte, byte[] base64PrivateKeyByte) throws Exception {
+        PrivateKey priKey = generatePrivateKey(base64PrivateKeyByte);
+
+        Cipher cipher = Cipher.getInstance(RSA_KEY_ALGORITHM);
+        cipher.init(Cipher.DECRYPT_MODE, priKey);
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+        //RSA 解密的字节大小最多是128,将需要解密的内容,按128位拆开解密
+
+		int inputLen = encryptByte.length;
+
+		int offSet = 0;
+		byte[] cache;
+		int i = 0;
+		// 对数据分段解密
+		while (inputLen - offSet > 0) {
+			if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
+				cache = cipher.doFinal(encryptByte, offSet, MAX_DECRYPT_BLOCK);
+			} else {
+				cache = cipher.doFinal(encryptByte, offSet, inputLen - offSet);
+			}
+			out.write(cache, 0, cache.length);
+			i++;
+			offSet = i * MAX_DECRYPT_BLOCK;
+		}
+		byte[] plainByte = out.toByteArray();
+		out.close();
+
+        return new String(plainByte);
+    }
+
+
+
+
+
+}

+ 85 - 0
src/main/java/com/emato/eoss/util/sign/Test.java

@@ -0,0 +1,85 @@
+package com.emato.eoss.util.sign;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+
+/**
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-12-28
+ */
+public class Test {
+
+
+    public static void main(String[] args) throws Exception {
+
+        System.out.println("//-------------- DSA begin ----------");
+        Instant timeBeg = Instant.now();
+        System.out.println("now beg: " + timeBeg);
+
+        System.out.println();
+        String msg = "Hello World";
+        Map<String, Object> map = Dsa.initKey();
+
+
+        String privateKeyStr = Dsa.getPrivateKeyStr(map);
+        System.out.println("生成私钥:" + privateKeyStr);
+
+        String signStr = Dsa.sign(msg, privateKeyStr, "UTF-8");
+        System.out.println("签名值为:" + signStr);
+
+
+        String publicKeyStr = Dsa.getPublicKeyStr(map);
+        System.out.println("生成公钥:" + publicKeyStr);
+        boolean bl = Dsa.verify(msg, signStr, publicKeyStr, "UTF-8");
+
+        String result = bl ? "数字签名匹配" : "数字签名不匹配";
+        System.out.println("数字签名:" + signStr);
+        System.out.println("验证结果:" + result);
+
+        Instant timeEnd = Instant.now();
+        System.out.println("now beg: " + timeEnd);
+
+        Long btw = Duration.between(timeBeg, timeEnd).toMillis();
+        System.out.println("Duration is: " + btw);
+
+        System.out.println("//-------------- DSA end ----------");
+
+
+
+        System.out.println("//-------------- RSA begin ----------");
+        Instant timeBeg2 = Instant.now();
+        System.out.println("now beg: " + timeBeg2);
+
+        System.out.println();
+        String msg2 = "Hello World";
+        Map<String, Object> map2 = Rsa.initKey();
+
+
+        String privateKeyStr2 = Rsa.getPrivateKeyStr(map2);
+        System.out.println("生成私钥:" + privateKeyStr2);
+
+        String signStr2 = Rsa.sign(msg2, privateKeyStr2, "UTF-8");
+        System.out.println("签名值为:" + signStr2);
+
+
+        String publicKeyStr2 = Rsa.getPublicKeyStr(map2);
+        System.out.println("生成公钥:" + publicKeyStr2);
+        boolean bl2 = Rsa.verify(msg2, signStr2, publicKeyStr2, "UTF-8");
+
+        String result2 = bl2 ? "数字签名匹配" : "数字签名不匹配";
+        System.out.println("数字签名:" + signStr2);
+        System.out.println("验证结果:" + result2);
+
+        Instant timeEnd2 = Instant.now();
+        System.out.println("now beg: " + timeEnd2);
+
+        Long btw2 = Duration.between(timeBeg2, timeEnd2).toMillis();
+        System.out.println("Duration is: " + btw2);
+
+        System.out.println("//-------------- RSA end ----------");
+
+    }
+
+}

+ 30 - 0
src/main/java/com/emato/eoss/web/OssFilter.java

@@ -0,0 +1,30 @@
+package com.emato.eoss.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.*;
+import java.io.IOException;
+
+/**
+ * @author Scott Chen
+ * @date 2021-03-29
+ */
+public class OssFilter implements Filter {
+    private static final Logger logger = LoggerFactory.getLogger(OssFilter.class);
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        Filter.super.init(filterConfig);
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+
+    }
+
+    @Override
+    public void destroy() {
+        Filter.super.destroy();
+    }
+}

+ 19 - 0
src/main/resources/application-dev.yml

@@ -0,0 +1,19 @@
+server:
+    servlet:
+        context-path: /dev-api
+
+
+# 当前环境
+spring:
+    config:
+        activate:
+            on-profile: dev
+
+
+# OSS Object Storage Server 对象存储配置
+oss-confg:
+    host: 3f352e8584.qicp.vip
+    port: 15633
+    accesskeyId: minio
+    secretAccessKey: jP*K*WapB1
+

+ 18 - 0
src/main/resources/application-test.yml

@@ -0,0 +1,18 @@
+server:
+    servlet:
+        context-path: /test-api
+
+# 当前环境
+spring:
+    config:
+        activate:
+            on-profile: test
+
+
+# OSS Object Storage Server 对象存储配置
+oss-confg:
+    host: 3f352e8584.qicp.vip
+    port: 15633
+    accesskeyId: minio
+    secretAccessKey: jP*K*WapB1
+

+ 49 - 0
src/main/resources/application.yml

@@ -0,0 +1,49 @@
+#---------- OSS 项目 ----------#
+#---
+
+# 项目配置
+app:
+    name: Spring Boot
+    description: ${app.name} is a Spring Boot application
+
+# 服务配置
+server:
+    address: 127.0.0.1
+    port: 9090
+    servlet:
+        context-path: /api
+
+
+# OSS Object Storage Server 对象存储配置
+oss-confg:
+    host: 3f352e8584.qicp.vip
+    port: 15633
+    accesskeyId: minio
+    secretAccessKey: jP*K*WapB1
+
+spring:
+    profiles:
+        active: dev
+    # 使用CGLIB实现AOP
+    aop:
+        # true:基于类的代理, false基于接口的代理
+        proxy-target-class: true
+
+
+# --------------------   --------------------
+
+# OSS Object Storage Server 对象存储
+minio:
+    # true:scheme为https,false:scheme为http
+    secure: false
+    endpoint: ${oss-confg.host}
+    port: ${oss-confg.port}
+    accesskeyId: ${oss-confg.accesskeyId}
+    secretAccessKey: ${oss-confg.secretAccessKey}
+    # 当stoage下的存储桶 buckets
+    buckets:
+        # 跟具体业务相关名称,对应如下的 buckets值
+        product: product
+
+
+

+ 232 - 0
src/main/resources/logback.xml

@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
+scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
+debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
+-->
+<configuration scan="false" scanPeriod="60 seconds" debug="false">
+
+    <property name="LOG_HOME" value="/data/logs/oss/"/>
+    <!-- 定义日志的根目录 -->
+    <property name="TRACE_DIR" value="trace" />
+    <property name="DEBUG_DIR" value="debug" />
+    <property name="INFO_DIR" value="info" />
+    <property name="WARN_DIR" value="warn" />
+    <property name="ERROR_DIR" value="error" />
+    <!-- 定义日志文件名称 -->
+    <property name="TRACE_FILE_NAME" value="oss-trace"></property>
+    <property name="DEBUG_FILE_NAME" value="oss-debug"></property>
+    <property name="INFO_FILE_NAME" value="oss-info"></property>
+    <property name="WARN_FILE_NAME" value="oss-warn"></property>
+    <property name="ERROR_FILE_NAME" value="oss-error"></property>
+
+    <!-- 定义日志级别颜色 -->
+    <!-- 控制台显示 -->
+    <property name="STD_CONSOLE_LOG_PATTERN"
+              value="%d{yyyy-MM-dd HH:mm:ss.SSS}[%yellow(%thread)]-[%highlight(%-5level)][%green(%logger{70}):%cyan(%line)] - %msg%n"/>
+
+    <!-- 日志文件打印 -->
+    <property name="CONSOLE_LOG_PATTERN"
+              value="%d{yyyy-MM-dd HH:mm:ss.SSS}[%thread]-[%-5level][%logger{70}:%line] - %msg%n"/>
+
+    <!-- ConsoleAppender 控制台输出 appender -->
+    <appender name="stdoutAppender" class="ch.qos.logback.core.ConsoleAppender">
+        <!--
+        日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度
+        %logger{70} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符
+        -->
+        <encoder>
+            <pattern>${STD_CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+
+    <!-- TRACE 日志 appender  -->
+    <appender name="traceAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${TRACE_DIR}/${TRACE_FILE_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${TRACE_DIR}/${TRACE_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <MaxHistory>365</MaxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>TRACE</level>
+            <!--<onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+
+    <!-- DEBUG 日志 appender  -->
+    <appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${DEBUG_DIR}/${DEBUG_FILE_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${DEBUG_DIR}/${DEBUG_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <MaxHistory>365</MaxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>DEBUG</level>
+            <!--<onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+
+    <!-- phrase 日志 appender  -->
+    <appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${INFO_DIR}/${INFO_FILE_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${INFO_DIR}/${INFO_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <MaxHistory>365</MaxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+            <!--<onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+
+    <!-- WARN 日志 appender  -->
+    <appender name="warnAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${WARN_DIR}/${WARN_FILE_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${WARN_DIR}/${WARN_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <MaxHistory>365</MaxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>WARN</level>
+            <!--<onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+
+    <!-- ERROR 日志 appender  -->
+    <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 指定日志文件的名称 -->
+        <file>${LOG_HOME}/${ERROR_DIR}/${ERROR_FILE_NAME}.log</file>
+        <!--
+        当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
+        TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
+        -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!--
+            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
+            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
+            -->
+            <fileNamePattern>${LOG_HOME}/${ERROR_DIR}/${ERROR_FILE_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
+            <!--
+            可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
+            且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
+            那些为了归档而创建的目录也会被删除。
+            -->
+            <MaxHistory>365</MaxHistory>
+            <!--
+            当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
+            -->
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <!--
+        日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{70} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符
+        -->
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!--
+        过滤器返回枚举:DENY,NEUTRAL,ACCEPT。
+        返回DENY,日志将立即被抛弃不再经过其他过滤器;返回NEUTRAL,有序列表里的下个过滤器过接着处理日志;返回ACCEPT,日志会被立即处理不再经过剩余过滤器。
+        LevelFilter: 级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。节点:level,onMatch,onMismatch
+        -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 只打印错误日志 -->
+            <!-- 设置过滤级别 -->
+            <level>ERROR</level>
+            <!-- 配置符合过滤条件的操作
+            <onMatch>ACCEPT</onMatch>
+            &lt;!&ndash; 配置不符合过滤条件的操作 &ndash;&gt;
+            <onMismatch>DENY</onMismatch>-->
+        </filter>
+    </appender>
+
+    <!--
+        logger主要用于存放日志对象,也可以定义日志类型、级别
+        name:表示匹配的logger类型前缀,也就是包的前半部分
+        level:要记录的日志级别,大小写无关,包括 TRACE,DEBUG,INFO,WARN,ERROR,ALL 和 OFF。
+        additivity:是否向上级loger传递打印信息。默认是true。 作用在于children-logger是否向上级root-logger配置的appender传递打印信息,false:不传递,true:传递
+    -->
+    <!--
+        没设置level,继承他的上级<root>的日志级别;
+        没有设置additivity,默认为true,将此loger的打印信息向上级<root>传递;
+        没有设置appender,此loger本身不打印任何信息;
+        子<logger>向<root>传递信息后,日志level 完全由子级别的level 决定;
+    -->
+
+    <logger name="org.apache.shiro" level="DEBUG" additivity="false" />
+    <logger name="com.zaxxer.hikari" level="ERROR" additivity="false" />
+    <logger name="org.apache" level="ERROR" />
+    <logger name="org.springframework.context.annotation.ClassPathBeanDefinitionScanner" level="INFO" />
+    <logger name="org.springframework.beans.factory.support.DefaultListableBeanFactory" level="INFO" />
+    <logger name="org.springframework.data.convert.CustomConversions" level="INFO"/>
+    <logger name="org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener" level="INFO" />
+    <logger name="org.springframework.boot.autoconfigure.web.servlet.WelcomePageHandlerMapping" level="INFO" />
+    <logger name="org.springframework" level="DEBUG" />
+    <logger name="io.netty" level="INFO" />
+    <logger name="org.mybatis" level="INFO" />
+    <logger name="org.hibernate" level="INFO" />
+    <logger name="io.lettuce" level="INFO" />
+    <logger name="springfox.documentation" level="INFO" />
+
+    <logger name="com.netflix.discovery" level="ERROR" />
+    <logger name="com.netflix.eureka" level="ERROR" />
+    <logger name="org.springframework.security" level="ERROR" />
+
+    <logger name="com.songmao" level="DEBUG"/>
+
+    <!--
+        logger优先,如果没有,则使用root,任何一个类只会匹配logger或root二者之一,
+        找到这个logger或root后,再logger的level和appender。
+    -->
+    <root level="DEBUG" >
+        <appender-ref ref="stdoutAppender" />
+        <!--<appender-ref ref="traceAppender" />-->
+        <appender-ref ref="debugAppender" />
+        <!--<appender-ref ref="infoAppender" />-->
+        <!--<appender-ref ref="warnAppender" />-->
+        <!--<appender-ref ref="errorAppender" />-->
+    </root>
+</configuration>