diff --git a/.gitignore b/.gitignore
index a1c2a238..c456c4a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,25 @@
-# Compiled class file
-*.class
+/target/
+!.mvn/wrapper/maven-wrapper.jar
-# Log file
-*.log
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
-# BlueJ files
-*.ctxt
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.nar
-*.ear
-*.zip
-*.tar.gz
-*.rar
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/README.md b/README.md
index f122b38a..3d04a52c 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,45 @@
-# SOP
+# SOP(Simple Open Platform)
-#### 介绍
-一个简单易用的接口开放平台(Simple Open Platform),可实现类似于淘宝开放平台的调用方式。
+一个开放平台解决方案项目,基于Spring Cloud实现,目标是能够让用户快速得搭建起自己的开放平台。
-#### 软件架构
-软件架构说明
+SOP提供了两种接口调用方式,分别是:[支付宝开放平台](https://docs.open.alipay.com/api)的调用方式和[淘宝开放平台](http://open.taobao.com/api.htm?docId=285&docType=2)的调用方式。
+通过简单的配置后,你的项目就具备了和支付宝开放平台的一样的接口提供能力。
+
+SOP封装了开放平台大部分功能包括:签名验证、统一异常处理、统一返回内容 、业务参数验证(JSR-303)、秘钥管理等,未来还会实现更多功能。
+
+## 项目特点
+
+- 接入方式简单,与老项目不冲突,老项目注册到注册中心,然后的方法上加上注解即可。
+- 架构松耦合,业务代码实现在各自微服务上,SOP不参与业务实现,这也是Spring Cloud微服务体系带来的好处。
+- 扩展简单,开放平台对应的功能各自独立,可以自定义实现自己的需求,如:更改参数,更改签名规则等。
+
+## 谁能使用这个项目
+
+- 有现成的项目,想改造成开放平台供他人调用
+- 有现成的项目,想暴露其中几个接口并通过开放平台供他人调用
+- 想搭一个开放平台新项目,并结合微服务的方式去维护
+- 对开放平台感兴趣的朋友
+
+以上情况都可以考虑使用SOP
+
+## 架构图
-#### 安装教程
-1. xxxx
-2. xxxx
-3. xxxx
+## 工程说明
-#### 使用说明
+- sop-registry:注册中心,eureka实现
+- sop-gateway:网关,zuul实现,统一访问入口
+- sop-gateway-common:网关公共模块,封装常用功能,包含签名校验等功能。
+- sop-server-common:微服务端公共模块,封装配套功能
+- sop-story:示例,story服务,同时作为Provider提供服务
+- sop-book:示例,book服务,也是Consumer,调用story提供的服务
+- sop-test:测试用例
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### 参与贡献
-
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
+## 相关文档
-#### 码云特技
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
-5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
\ No newline at end of file
+## 沟通交流
+
+Q群:328419269
diff --git a/mvnw b/mvnw
new file mode 100755
index 00000000..5551fde8
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ 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
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ 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
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ wget "$jarUrl" -O "$wrapperJarPath"
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ curl -o "$wrapperJarPath" "$jarUrl"
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 00000000..e5cfb0ae
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ echo Found %WRAPPER_JAR%
+) else (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+ echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..7804453f
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,20 @@
+
+
+ 4.0.0
+ com.gitee.sop
+ sop-parent
+ 1.0.0-SNAPSHOT
+ pom
+
+
+ sop-registry
+ sop-gateway
+ sop-gateway-common
+ sop-server-common
+ sop-story
+ sop-book
+ sop-test
+
+
\ No newline at end of file
diff --git a/sop-book/pom.xml b/sop-book/pom.xml
new file mode 100644
index 00000000..6a343033
--- /dev/null
+++ b/sop-book/pom.xml
@@ -0,0 +1,15 @@
+
+
+ 4.0.0
+ com.gitee.sop
+ sop-book-parent
+ 1.0.0-SNAPSHOT
+ pom
+
+
+ sop-book-api
+ sop-book-web
+
+
\ No newline at end of file
diff --git a/sop-book/sop-book-api/.gitignore b/sop-book/sop-book-api/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/sop-book/sop-book-api/.gitignore
@@ -0,0 +1,25 @@
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/sop-book/sop-book-api/pom.xml b/sop-book/sop-book-api/pom.xml
new file mode 100644
index 00000000..c47d23c4
--- /dev/null
+++ b/sop-book/sop-book-api/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ com.gitee.sop
+ sop-book-api
+ 1.0-SNAPSHOT
+
+
+ 1.8
+ Greenwich.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+ provided
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
\ No newline at end of file
diff --git a/sop-book/sop-book-api/src/main/java/com/hhdd/book/api/domain/Book.java b/sop-book/sop-book-api/src/main/java/com/hhdd/book/api/domain/Book.java
new file mode 100644
index 00000000..a286924a
--- /dev/null
+++ b/sop-book/sop-book-api/src/main/java/com/hhdd/book/api/domain/Book.java
@@ -0,0 +1,12 @@
+package com.gitee.book.api.domain;
+
+import lombok.Data;
+
+/**
+ * @author tanghc
+ */
+@Data
+public class Book {
+ private int id;
+ private String name;
+}
diff --git a/sop-book/sop-book-api/src/main/java/com/hhdd/book/api/service/BookService.java b/sop-book/sop-book-api/src/main/java/com/hhdd/book/api/service/BookService.java
new file mode 100644
index 00000000..1076be9d
--- /dev/null
+++ b/sop-book/sop-book-api/src/main/java/com/hhdd/book/api/service/BookService.java
@@ -0,0 +1,14 @@
+package com.hhdd.book.api.service;
+
+import com.gitee.book.api.domain.Book;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * @author tanghc
+ */
+@RequestMapping("/book")
+public interface BookService {
+ @RequestMapping("/getBook")
+ Book getBook(@RequestParam("id") int id);
+}
diff --git a/sop-book/sop-book-web/.gitignore b/sop-book/sop-book-web/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/sop-book/sop-book-web/.gitignore
@@ -0,0 +1,25 @@
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/sop-book/sop-book-web/.mvn/wrapper/maven-wrapper.jar b/sop-book/sop-book-web/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 00000000..01e67997
Binary files /dev/null and b/sop-book/sop-book-web/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/sop-book/sop-book-web/.mvn/wrapper/maven-wrapper.properties b/sop-book/sop-book-web/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 00000000..cd0d451c
--- /dev/null
+++ b/sop-book/sop-book-web/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
diff --git a/sop-book/sop-book-web/mvnw b/sop-book/sop-book-web/mvnw
new file mode 100755
index 00000000..5551fde8
--- /dev/null
+++ b/sop-book/sop-book-web/mvnw
@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ 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
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ 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
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ wget "$jarUrl" -O "$wrapperJarPath"
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ curl -o "$wrapperJarPath" "$jarUrl"
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/sop-book/sop-book-web/mvnw.cmd b/sop-book/sop-book-web/mvnw.cmd
new file mode 100644
index 00000000..e5cfb0ae
--- /dev/null
+++ b/sop-book/sop-book-web/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ echo Found %WRAPPER_JAR%
+) else (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+ echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/sop-book/sop-book-web/pom.xml b/sop-book/sop-book-web/pom.xml
new file mode 100644
index 00000000..793e2484
--- /dev/null
+++ b/sop-book/sop-book-web/pom.xml
@@ -0,0 +1,84 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ com.gitee.sop
+ sop-book-web
+ 0.0.1-SNAPSHOT
+ sop-story
+ Demo project for Spring Boot
+
+
+ 1.8
+ Greenwich.RELEASE
+
+
+
+
+ com.gitee.sop
+ sop-book-api
+ 1.0-SNAPSHOT
+
+
+ com.gitee.sop
+ sop-story-api
+ 1.0-SNAPSHOT
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+ compile
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
+
diff --git a/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/SopBookApplication.java b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/SopBookApplication.java
new file mode 100644
index 00000000..cfb16be7
--- /dev/null
+++ b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/SopBookApplication.java
@@ -0,0 +1,20 @@
+package com.gitee.sop.bookweb;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+// 允许调用其他服务
+@EnableFeignClients
+// 服务注册
+@EnableDiscoveryClient
+@SpringBootApplication
+public class SopBookApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SopBookApplication.class, args);
+ }
+
+}
+
diff --git a/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/consumer/StoryServiceConsumer.java b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/consumer/StoryServiceConsumer.java
new file mode 100644
index 00000000..cce5945b
--- /dev/null
+++ b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/consumer/StoryServiceConsumer.java
@@ -0,0 +1,14 @@
+package com.gitee.sop.bookweb.consumer;
+
+import com.gitee.sop.story.api.service.StoryService;
+import org.springframework.cloud.openfeign.FeignClient;
+
+/**
+ * 调用story服务
+ *
+ * @author tanghc
+ */
+// value对应的spring.application.name
+@FeignClient("story-service")
+public interface StoryServiceConsumer extends StoryService {
+}
diff --git a/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/controller/ApiResult.java b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/controller/ApiResult.java
new file mode 100644
index 00000000..a39434b8
--- /dev/null
+++ b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/controller/ApiResult.java
@@ -0,0 +1,74 @@
+package com.gitee.sop.bookweb.controller;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.Data;
+
+/**
+ * 默认的结果封装类.
+ *
+ *
+ * xml返回结果:
+ *
+ * 50
+ * Remote service error
+ * isv.invalid-parameter
+ * 非法参数
+ *
+ * 成功情况:
+ *
+ * 0
+ * 成功消息
+ *
+ * ...返回内容
+ *
+ *
+ *
+ * json返回格式:
+ * {
+ * "code":"50",
+ * "msg":"Remote service error",
+ * "sub_code":"isv.invalid-parameter",
+ * "sub_msg":"非法参数"
+ * }
+ * 成功情况:
+ * {
+ * "code":"0",
+ * "msg":"成功消息内容。。。",
+ * "data":{
+ * ...返回内容
+ * }
+ * }
+ *
+ *
+ * 字段说明:
+ * code:网关异常码
+ * msg:网关异常信息
+ * sub_code:业务异常码
+ * sub_msg:业务异常信息
+ *
+ * @author tanghc
+ */
+@XStreamAlias("response")
+@Data
+public class ApiResult {
+
+ /**
+ * 网关异常信息
+ */
+ private String msg;
+
+ /**
+ * 业务异常码
+ */
+ private String sub_msg;
+
+ /**
+ * 业务异常信息
+ */
+ private String sub_code;
+
+ /**
+ * 返回结果
+ */
+ private Object data;
+}
diff --git a/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/controller/BookController.java b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/controller/BookController.java
new file mode 100644
index 00000000..bde5634e
--- /dev/null
+++ b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/controller/BookController.java
@@ -0,0 +1,73 @@
+package com.gitee.sop.bookweb.controller;
+
+import com.gitee.book.api.domain.Book;
+import com.gitee.sop.bookweb.consumer.StoryServiceConsumer;
+import com.gitee.sop.bookweb.param.BookParam;
+import com.gitee.sop.story.api.domain.Story;
+import com.hhdd.book.api.service.BookService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Arrays;
+
+/**
+ * book服务
+ *
+ * @author tanghc
+ */
+@RestController
+public class BookController implements BookService {
+
+ @Autowired
+ StoryServiceConsumer storyServiceConsumer;
+
+ @Override
+ public Book getBook(int id) {
+ Book book = new Book();
+ book.setId(id);
+ book.setName("汪汪队");
+ return book;
+ }
+
+ @RequestMapping("listBookAndStory")
+ public Object listBookAndStory(int id) {
+ Book book = new Book();
+ book.setId(id);
+ book.setName("汪汪队");
+
+ // 调用story服务
+ Story story = storyServiceConsumer.getStory(id);
+
+ return Arrays.asList(book, story);
+ }
+
+ @RequestMapping("getBook2")
+ public Object getBookError(int id) {
+ if (id == 0) {
+ throw new RuntimeException("id不能为空");
+ }
+ Book book = new Book();
+ book.setId(id);
+ book.setName("汪汪队");
+ return Arrays.asList(book);
+ }
+
+
+
+ @RequestMapping("getBook3")
+ public Object getBook3(@RequestBody BookParam param) {
+ if (param.getId() == 0) {
+ throw new RuntimeException("id不能为空");
+ }
+ Book book = new Book();
+ book.setId(param.getId());
+ book.setName("小马宝莉");
+
+ ApiResult apiResult = new ApiResult();
+ apiResult.setData(book);
+ return apiResult;
+ }
+
+}
diff --git a/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/param/BookParam.java b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/param/BookParam.java
new file mode 100644
index 00000000..47703b09
--- /dev/null
+++ b/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/param/BookParam.java
@@ -0,0 +1,11 @@
+package com.gitee.sop.bookweb.param;
+
+import lombok.Data;
+
+/**
+ * @author tanghc
+ */
+@Data
+public class BookParam {
+ private int id;
+}
diff --git a/sop-book/sop-book-web/src/main/resources/application.properties b/sop-book/sop-book-web/src/main/resources/application.properties
new file mode 100644
index 00000000..c163532c
--- /dev/null
+++ b/sop-book/sop-book-web/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+server.port=3333
+spring.application.name=book-service
+eureka.host=localhost
+eureka.port=1111
+eureka.client.serviceUrl.defaultZone=http://${eureka.host}:${eureka.port}/eureka/
+
diff --git a/sop-book/sop-book-web/src/test/java/com/gitee/sop/bookweb/SopBookApplicationTests.java b/sop-book/sop-book-web/src/test/java/com/gitee/sop/bookweb/SopBookApplicationTests.java
new file mode 100644
index 00000000..6fa12ac2
--- /dev/null
+++ b/sop-book/sop-book-web/src/test/java/com/gitee/sop/bookweb/SopBookApplicationTests.java
@@ -0,0 +1,17 @@
+package com.gitee.sop.bookweb;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SopBookApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
+
diff --git a/sop-gateway-common/.gitignore b/sop-gateway-common/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/sop-gateway-common/.gitignore
@@ -0,0 +1,25 @@
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/sop-gateway-common/pom.xml b/sop-gateway-common/pom.xml
new file mode 100644
index 00000000..9c1c6bf9
--- /dev/null
+++ b/sop-gateway-common/pom.xml
@@ -0,0 +1,131 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ com.gitee.sop
+ sop-gateway-common
+ 1.0.0-SNAPSHOT
+ sop-gateway-common
+ sop-gateway-common
+
+
+ 1.8
+ Greenwich.RELEASE
+
+ 1.0.13
+ 1.7.5
+
+
+ 4.11
+
+ 1.2.15
+ 2.5
+ 1.3.3
+ 3.2.2
+ 1.4.7
+ 1.7
+ 6.0.10.Final
+ 0.31
+ 3.2.0
+ 18.0
+ 1.8.8.RELEASE
+ 4.1.25.Final
+ 2.0.5.Final
+ 5.5.12
+ 5.5.8
+ 5.2.0
+ 6.2
+ 1.11
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-zuul
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.thoughtworks.xstream
+ xstream
+ ${xstream.version}
+ compile
+
+
+ com.alibaba
+ fastjson
+ ${fastjson.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ commons-collections
+ commons-collections
+ ${commons-collection.version}
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+ commons-fileupload
+ commons-fileupload
+ ${commons-fileupload.version}
+
+
+ commons-codec
+ commons-codec
+ ${commons-codec.version}
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+ provided
+
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
+
diff --git a/sop-gateway-common/readme.md b/sop-gateway-common/readme.md
new file mode 100644
index 00000000..05c58128
--- /dev/null
+++ b/sop-gateway-common/readme.md
@@ -0,0 +1,3 @@
+# sop-gateway-common
+
+zuul通用组件,主要封装网关的一些功能。
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
new file mode 100644
index 00000000..47168791
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
@@ -0,0 +1,116 @@
+package com.gitee.sop.gatewaycommon.bean;
+
+import com.gitee.sop.gatewaycommon.configuration.BaseZuulController;
+import com.gitee.sop.gatewaycommon.param.ApiParamParser;
+import com.gitee.sop.gatewaycommon.param.ParamParser;
+import com.gitee.sop.gatewaycommon.result.ApiResultExecutor;
+import com.gitee.sop.gatewaycommon.result.JsonResultSerializer;
+import com.gitee.sop.gatewaycommon.result.ResultExecutor;
+import com.gitee.sop.gatewaycommon.result.ResultSerializer;
+import com.gitee.sop.gatewaycommon.result.XmlResultSerializer;
+import com.gitee.sop.gatewaycommon.secret.AppSecretManager;
+import com.gitee.sop.gatewaycommon.secret.CacheAppSecretManager;
+import com.gitee.sop.gatewaycommon.session.ApiSessionManager;
+import com.gitee.sop.gatewaycommon.session.SessionManager;
+import com.gitee.sop.gatewaycommon.validate.ApiEncrypter;
+import com.gitee.sop.gatewaycommon.validate.ApiSigner;
+import com.gitee.sop.gatewaycommon.validate.ApiValidator;
+import com.gitee.sop.gatewaycommon.validate.Encrypter;
+import com.gitee.sop.gatewaycommon.validate.Signer;
+import com.gitee.sop.gatewaycommon.validate.Validator;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author tanghc
+ */
+@Data
+public class ApiConfig {
+
+ private static ApiConfig instance = new ApiConfig();
+
+ private ApiConfig() {
+ }
+
+ /**
+ * 合并结果处理
+ */
+ private ResultExecutor resultExecutor = new ApiResultExecutor();
+
+ /**
+ * json序列化
+ */
+ private ResultSerializer jsonResultSerializer = new JsonResultSerializer();
+ /**
+ * xml序列化
+ */
+ private ResultSerializer xmlResultSerializer = new XmlResultSerializer();
+
+ /**
+ * app秘钥管理
+ */
+ private AppSecretManager appSecretManager = new CacheAppSecretManager();
+
+ /**
+ * 加密工具
+ */
+ private Encrypter encrypter = new ApiEncrypter();
+
+ /**
+ * 签名工具
+ */
+ private Signer signer = new ApiSigner();
+
+ /**
+ * 参数解析
+ */
+ private ParamParser paramParser = new ApiParamParser();
+
+ /**
+ * 验证
+ */
+ private Validator validator = new ApiValidator();
+
+ /**
+ * session管理
+ */
+ private SessionManager sessionManager = new ApiSessionManager();
+
+ /**
+ * 错误模块
+ */
+ private List i18nModules = new ArrayList();
+
+ /**
+ * 基础Controller
+ */
+ private BaseZuulController baseZuulController = new BaseZuulController();
+
+
+ // -------- fields ---------
+ /**
+ * 忽略验证
+ */
+ private boolean ignoreValidate;
+
+ /**
+ * 超时时间
+ */
+ private int timeoutSeconds = 60 * 5;
+
+ public void addAppSecret(Map appSecretPair) {
+ this.appSecretManager.addAppSecret(appSecretPair);
+ }
+
+ public static ApiConfig getInstance() {
+ return instance;
+ }
+
+ public static void setInstance(ApiConfig apiConfig) {
+ instance = apiConfig;
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiContext.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiContext.java
new file mode 100644
index 00000000..72ff62d7
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiContext.java
@@ -0,0 +1,186 @@
+package com.gitee.sop.gatewaycommon.bean;
+
+import com.gitee.sop.gatewaycommon.param.UploadContext;
+import com.netflix.zuul.context.RequestContext;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import com.gitee.sop.gatewaycommon.session.SessionManager;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.util.Locale;
+
+/**
+ * 应用上下文,方便获取信息
+ *
+ * @author tanghc
+ *
+ */
+public class ApiContext {
+ private static final String ATTR_PARAM = "zuul.common.api.param";
+ private static final String ATTR_UPLOAD_CONTEXT = "zuul.common.api.upload_context";
+
+ private ApiContext(){}
+
+ private static void setAttr(String name, Object val) {
+ HttpServletRequest request = getRequest();
+ if (request != null) {
+ request.setAttribute(name, val);
+ }
+ }
+
+ private static Object getAttr(String name) {
+ HttpServletRequest request = getRequest();
+ if (request == null) {
+ return null;
+ }
+ return request.getAttribute(name);
+ }
+
+ /**
+ * 获取随机码
+ * @return 返回随机码
+ */
+ public static String getRandomKey() {
+ HttpSession session = getSession();
+ if (session == null) {
+ return null;
+ }
+ return (String) session.getAttribute(SopConstants.RANDOM_KEY_NAME);
+ }
+
+
+
+ /**
+ * 获取HttpServletRequest
+ *
+ * @return HttpServletRequest
+ */
+ public static HttpServletRequest getRequest() {
+ return RequestContext.getCurrentContext().getRequest();
+ }
+
+ /**
+ * 返回默认的HttpServletRequest.getSession();
+ *
+ * @return 没有返回null
+ */
+ public static HttpSession getSession() {
+ HttpServletRequest req = getRequest();
+ if (req == null) {
+ return null;
+ } else {
+ return req.getSession();
+ }
+ }
+
+ /**
+ * 获取session管理器
+ * @return 返回SessionManager
+ */
+ public static SessionManager getSessionManager() {
+ return getApiConfig().getSessionManager();
+ }
+
+ /**
+ * 返回自定义的session,被SessionManager管理
+ *
+ * @return 如果sessionId为null,则返回null
+ */
+ public static HttpSession getManagedSession() {
+ String sessionId = getSessionId();
+ if (sessionId != null) {
+ return getSessionManager().getSession(sessionId);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * 同getSessionId()
+ * @return 返回accessToken,没有返回null
+ */
+ public static String getAccessToken() {
+ return getSessionId();
+ }
+
+
+ /**
+ * 获取登陆的token
+ *
+ * @return 没有返回null
+ */
+ public static String getSessionId() {
+ ApiParam apiParam = getApiParam();
+ if (apiParam == null) {
+ return null;
+ }
+ return apiParam.fetchAccessToken();
+ }
+
+ /**
+ * 获取本地化,从HttpServletRequest中获取,没有则返回Locale.SIMPLIFIED_CHINESE
+ *
+ * @return Locale
+ */
+ public static Locale getLocale() {
+ HttpServletRequest req = getRequest();
+ if (req == null) {
+ return Locale.SIMPLIFIED_CHINESE;
+ }
+ return req.getLocale();
+ }
+
+ public static void setApiParam(ApiParam apiParam) {
+ setAttr(ATTR_PARAM, apiParam);
+ }
+
+ /**
+ * 获取系统参数
+ *
+ * @return 返回ApiParam
+ */
+ public static ApiParam getApiParam() {
+ return (ApiParam) getAttr(ATTR_PARAM);
+ }
+
+ public static ApiConfig getApiConfig() {
+ return ApiConfig.getInstance();
+ }
+
+ public static void setApiConfig(ApiConfig apiConfig) {
+ ApiConfig.setInstance(apiConfig);
+ }
+
+
+ public static ServletContext getServletContext() {
+ ServletContext ctx = null;
+ HttpSession session = getSession();
+ if (session != null) {
+ ctx = session.getServletContext();
+ }
+ return ctx;
+ }
+
+ /**
+ * 获取上传文件,如果客户端有文件上传,从这里取。
+ * @return 如果没有文件上传,返回null
+ */
+ public static UploadContext getUploadContext() {
+ return (UploadContext) getAttr(ATTR_UPLOAD_CONTEXT);
+ }
+
+ public static void setUploadContext(UploadContext uploadCtx) {
+ setAttr(ATTR_UPLOAD_CONTEXT, uploadCtx);
+ }
+
+ /**
+ * 获取response
+ * @return 返回response
+ */
+ public static HttpServletResponse getResponse() {
+ return RequestContext.getCurrentContext().getResponse();
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RequestMode.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RequestMode.java
new file mode 100644
index 00000000..8fb274a8
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RequestMode.java
@@ -0,0 +1,17 @@
+package com.gitee.sop.gatewaycommon.bean;
+
+/**
+ * @author tanghc
+ */
+public enum RequestMode {
+
+ /**
+ * 数字签名请求模式
+ */
+ SIGNATURE,
+ /**
+ * 公私钥加密模式,这样请求和返回的数据都经过加密处理
+ */
+ ENCRYPT
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ServiceApiInfo.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ServiceApiInfo.java
new file mode 100644
index 00000000..854ef7f0
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ServiceApiInfo.java
@@ -0,0 +1,29 @@
+package com.gitee.sop.gatewaycommon.bean;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author tanghc
+ */
+@Data
+public class ServiceApiInfo {
+ private String md5;
+ private String appName;
+ private List apis;
+
+ @Data
+ public static class ApiMeta {
+ /** 接口名 */
+ private String name;
+ /** 请求path */
+ private String path;
+ /** 版本号 */
+ private String version;
+
+ public String fetchNameVersion() {
+ return name + version;
+ }
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java
new file mode 100644
index 00000000..d6fd6123
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java
@@ -0,0 +1,33 @@
+package com.gitee.sop.gatewaycommon.bean;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author tanghc
+ */
+public class SopConstants {
+
+ public static final String NULL = "null";
+ public static final String RANDOM_KEY_NAME = "ssl_randomKey";
+ public static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
+ public static final String FORMAT_JSON = "json";
+ public static final String FORMAT_XML = "xml";
+ public static final String AUTHORIZATION = "Authorization";
+ public static final String PREFIX_BEARER = "Bearer ";
+ public static final String YMDHMS = "yyyy-MM-dd HH:mm:ss";
+
+ public static final String DEFAULT_SIGN_METHOD = "md5";
+
+ public static final String CONTENT_TYPE_NAME = "Content-Type";
+ public static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8";
+
+ public static final String LINE = "\n";
+
+ public static final String EMPTY_JSON = "{}";
+
+ public static final String SORT_DESC = "DESC";
+
+ public static final String REST_PARAM_NAME = "_REST_PARAM_NAME_";
+ public static final String REST_PARAM_VERSION = "_REST_PARAM_VERSION_";
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/AlipayZuulConfiguration.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/AlipayZuulConfiguration.java
new file mode 100644
index 00000000..0c341582
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/AlipayZuulConfiguration.java
@@ -0,0 +1,16 @@
+package com.gitee.sop.gatewaycommon.configuration;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySigner;
+
+/**
+ * 具备支付宝开放平台能力配置 https://docs.open.alipay.com/api
+ *
+ * @author tanghc
+ */
+public class AlipayZuulConfiguration extends BaseZuulConfiguration {
+
+ static {
+ ApiContext.getApiConfig().setSigner(new AlipaySigner());
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/BaseZuulConfiguration.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/BaseZuulConfiguration.java
new file mode 100644
index 00000000..704ed496
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/BaseZuulConfiguration.java
@@ -0,0 +1,112 @@
+package com.gitee.sop.gatewaycommon.configuration;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.filter.ErrorFilter;
+import com.gitee.sop.gatewaycommon.filter.PostResultFilter;
+import com.gitee.sop.gatewaycommon.filter.PreValidateFilter;
+import com.gitee.sop.gatewaycommon.manager.ApiMetaChangeListener;
+import com.gitee.sop.gatewaycommon.manager.ApiMetaContext;
+import com.gitee.sop.gatewaycommon.manager.ApiMetaManager;
+import com.gitee.sop.gatewaycommon.manager.DefaultApiMetaContext;
+import com.gitee.sop.gatewaycommon.manager.DefaultApiMetaManager;
+import com.gitee.sop.gatewaycommon.manager.SopRouteLocator;
+import com.gitee.sop.gatewaycommon.message.ErrorFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
+import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
+import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.listener.PatternTopic;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @author tanghc
+ */
+public class BaseZuulConfiguration {
+
+ public static final String API_CHANGE_CHANNEL = "channel.sop.api.change";
+
+
+ @Autowired
+ protected ZuulProperties zuulProperties;
+
+ @Autowired
+ protected ServerProperties server;
+
+ @Autowired
+ protected ApiMetaManager apiMetaManager;
+
+ @Bean
+ public ApiMetaContext apiMetaContext() {
+ return new DefaultApiMetaContext();
+ }
+
+ @Bean
+ public ApiMetaManager apiMetaManager(StringRedisTemplate stringRedisTemplate, ApiMetaContext apiMetaContext) {
+ return new DefaultApiMetaManager(stringRedisTemplate, apiMetaContext);
+ }
+
+ @Bean
+ PreValidateFilter preValidateFilter() {
+ return new PreValidateFilter();
+ }
+
+ @Bean
+ public PreDecorationFilter preDecorationFilter(ApiMetaContext apiMetaContext, ProxyRequestHelper proxyRequestHelper) {
+ SopRouteLocator routeLocator = new SopRouteLocator(apiMetaContext);
+ return new PreDecorationFilter(routeLocator,
+ this.server.getServlet().getContextPath(),
+ this.zuulProperties,
+ proxyRequestHelper);
+ }
+
+ @Bean
+ ErrorFilter errorFilter() {
+ return new ErrorFilter();
+ }
+
+ @Bean
+ PostResultFilter postResultFilter() {
+ return new PostResultFilter();
+ }
+
+ /**
+ * 配置redis事件订阅
+ *
+ * @param apiMetaManager
+ * @param redisTemplate
+ * @return
+ */
+ @Bean
+ RedisMessageListenerContainer container(ApiMetaManager apiMetaManager, StringRedisTemplate redisTemplate) {
+ RedisMessageListenerContainer container = new RedisMessageListenerContainer();
+ container.setConnectionFactory(redisTemplate.getConnectionFactory());
+ ApiMetaChangeListener apiMetaChangeListener = new ApiMetaChangeListener(apiMetaManager, redisTemplate);
+ container.addMessageListener(apiMetaChangeListener, new PatternTopic(API_CHANGE_CHANNEL));
+ return container;
+ }
+
+ @Bean
+ BaseZuulController baseZuulController() {
+ return ApiContext.getApiConfig().getBaseZuulController();
+ }
+
+ @PostConstruct
+ public void after() {
+ doAfter();
+ }
+
+ protected void doAfter() {
+ initMessage();
+ apiMetaManager.refresh();
+ }
+
+ protected void initMessage() {
+ ErrorFactory.initMessageSource(ApiContext.getApiConfig().getI18nModules());
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/BaseZuulController.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/BaseZuulController.java
new file mode 100644
index 00000000..399db035
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/BaseZuulController.java
@@ -0,0 +1,44 @@
+package com.gitee.sop.gatewaycommon.configuration;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.result.ResultExecutor;
+import com.netflix.zuul.context.RequestContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 处理网关自身异常
+ *
+ * @author tanghc
+ */
+@Controller
+@Slf4j
+public class BaseZuulController implements ErrorController {
+
+ public static final String ERROR_PATH = "/error";
+
+ // 错误最终会到这里来
+ @RequestMapping(ERROR_PATH)
+ @ResponseBody
+ public Object error(HttpServletRequest request, HttpServletResponse response) {
+ RequestContext ctx = RequestContext.getCurrentContext();
+ Throwable throwable = ctx.getThrowable();
+ return this.buildResult(throwable);
+ }
+
+ protected Object buildResult(Throwable throwable) {
+ ResultExecutor resultExecutor = ApiContext.getApiConfig().getResultExecutor();
+ return resultExecutor.mergeError(throwable);
+ }
+
+ @Override
+ public String getErrorPath() {
+ return ERROR_PATH;
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/TaobaoZuulConfiguration.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/TaobaoZuulConfiguration.java
new file mode 100644
index 00000000..ef3dd3e2
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/TaobaoZuulConfiguration.java
@@ -0,0 +1,23 @@
+package com.gitee.sop.gatewaycommon.configuration;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.param.ParamNames;
+import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner;
+
+/**
+ * 具备淘宝开放平台能力配置
+ * 淘宝开放平台:http://open.taobao.com/doc.htm
+ * @author tanghc
+ */
+public class TaobaoZuulConfiguration extends BaseZuulConfiguration {
+
+ static {
+ ParamNames.APP_KEY_NAME = "app_key";
+ ParamNames.SIGN_TYPE_NAME = "sign_method";
+ ParamNames.VERSION_NAME = "v";
+ ParamNames.APP_AUTH_TOKEN_NAME = "session";
+
+ ApiContext.getApiConfig().setSigner(new TaobaoSigner());
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenRoute.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenRoute.java
new file mode 100644
index 00000000..7a827c0f
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenRoute.java
@@ -0,0 +1,20 @@
+package com.gitee.sop.gatewaycommon.easyopen;
+
+import org.springframework.cloud.netflix.zuul.filters.Route;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * @author tanghc
+ */
+public class EasyopenRoute extends Route {
+ public EasyopenRoute(String id, String path, String location, String prefix, Boolean retryable, Set ignoredHeaders) {
+ super(id, path, location, prefix, retryable, ignoredHeaders);
+ }
+
+ public EasyopenRoute(String id, String location) {
+ this(id, "/" + location + "/", location, "", false, Collections.emptySet());
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfig.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfig.java
new file mode 100644
index 00000000..50f42a78
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfig.java
@@ -0,0 +1,74 @@
+package com.gitee.sop.gatewaycommon.easyopen;
+
+import com.gitee.sop.gatewaycommon.filter.ErrorFilter;
+import com.gitee.sop.gatewaycommon.filter.PostResultFilter;
+import com.gitee.sop.gatewaycommon.easyopen.filter.PostEasyopenResultFilter;
+import com.gitee.sop.gatewaycommon.filter.PreValidateFilter;
+import com.gitee.sop.gatewaycommon.manager.ApiMetaContext;
+import com.gitee.sop.gatewaycommon.manager.ApiMetaManager;
+import com.gitee.sop.gatewaycommon.manager.DefaultApiMetaContext;
+import com.gitee.sop.gatewaycommon.manager.DefaultApiMetaManager;
+import com.gitee.sop.gatewaycommon.manager.SopRouteLocator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
+import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
+import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @author tanghc
+ */
+public class EasyopenZuulConfig {
+
+ @Autowired
+ protected ZuulProperties zuulProperties;
+
+ @Autowired
+ protected ServerProperties server;
+
+ @Autowired
+ protected ApiMetaManager apiMetaManager;
+
+ @Bean
+ public ApiMetaContext apiMetaContext() {
+ return new DefaultApiMetaContext();
+ }
+
+ @Bean
+ public ApiMetaManager apiMetaManager(StringRedisTemplate stringRedisTemplate, ApiMetaContext apiMetaContext) {
+ return new DefaultApiMetaManager(stringRedisTemplate, apiMetaContext);
+ }
+
+ @Bean
+ public PreDecorationFilter preDecorationFilter(ApiMetaContext apiMetaContext, ProxyRequestHelper proxyRequestHelper) {
+ SopRouteLocator routeLocator = new SopRouteLocator(apiMetaContext);
+ return new PreDecorationFilter(routeLocator,
+ this.server.getServlet().getContextPath(),
+ this.zuulProperties,
+ proxyRequestHelper);
+ }
+
+ @Bean
+ PreValidateFilter preValidateFilter() {
+ return new PreValidateFilter();
+ }
+
+ @Bean
+ ErrorFilter errorFilter() {
+ return new ErrorFilter();
+ }
+
+ @Bean
+ PostResultFilter postResultFilter() {
+ return new PostEasyopenResultFilter();
+ }
+
+ @PostConstruct
+ public void after() {
+ apiMetaManager.refresh();
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/filter/PostEasyopenResultFilter.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/filter/PostEasyopenResultFilter.java
new file mode 100644
index 00000000..38316134
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/filter/PostEasyopenResultFilter.java
@@ -0,0 +1,46 @@
+package com.gitee.sop.gatewaycommon.easyopen.filter;
+
+import com.gitee.sop.gatewaycommon.filter.PostResultFilter;
+
+/**
+ * 合并微服务结果,统一返回格式
+ *
+ * @author tanghc
+ */
+public class PostEasyopenResultFilter extends PostResultFilter {
+ private static final String EASYOPEN_SUCCESS_CODE = "0";
+//
+// protected Result processServiceResult(InputStream responseDataStream, ResultBuilder resultBuilder) throws IOException {
+// String responseBody = IOUtils.toString(responseDataStream, SopConstants.CHARSET_UTF8);
+// ApiResult result = this.parseApiResult(responseBody);
+// Result finalResult;
+// if (EASYOPEN_SUCCESS_CODE.equals(result.getCode())) {
+// finalResult = resultBuilder.buildSuccessResult(GATEWAY_SUCCESS_CODE, null, result.getData());
+// } else {
+// // 业务出错
+// finalResult = resultBuilder.buildServiceError(result.getCode(), result.getMsg());
+// }
+// return finalResult;
+// }
+//
+// protected ApiResult parseApiResult(String responseBody) {
+// ApiParam apiParam = ApiContext.getApiParam();
+// String format = apiParam.fetchFormat();
+// return SopConstants.FORMAT_XML.equalsIgnoreCase(format)
+// ? XmlUtil.unserialize(responseBody, ApiResult.class)
+// : JSON.parseObject(responseBody, ApiResult.class);
+// }
+//
+// protected ResultSerializer buildResultSerializer(HttpServletRequest request, ApiConfig apiConfig) {
+// ApiParam apiParam = ApiContext.getApiParam();
+// String format = apiParam.fetchFormat();
+// if (SopConstants.FORMAT_JSON.equalsIgnoreCase(format)) {
+// return apiConfig.getJsonResultSerializer();
+// } else if (SopConstants.FORMAT_XML.equalsIgnoreCase(format)) {
+// // xml格式输出
+// return apiConfig.getXmlResultSerializer();
+// } else {
+// throw ErrorEnum.isv_invalid_format.getErrorMeta().getException();
+// }
+// }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/exception/ApiException.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/exception/ApiException.java
new file mode 100644
index 00000000..ae6bb9ac
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/exception/ApiException.java
@@ -0,0 +1,26 @@
+package com.gitee.sop.gatewaycommon.exception;
+
+
+import com.gitee.sop.gatewaycommon.message.Error;
+
+/**
+ * @author tanghc
+ */
+public class ApiException extends RuntimeException {
+
+ private Error error;
+
+ public ApiException(Throwable cause, Error error) {
+ super(cause);
+ this.error = error;
+ }
+
+ public ApiException(Error error) {
+ super(error.toString());
+ this.error = error;
+ }
+
+ public Error getError() {
+ return error;
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/BaseZuulFilter.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/BaseZuulFilter.java
new file mode 100644
index 00000000..78dbc74b
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/BaseZuulFilter.java
@@ -0,0 +1,96 @@
+package com.gitee.sop.gatewaycommon.filter;
+
+import com.alibaba.fastjson.JSON;
+import com.gitee.sop.gatewaycommon.result.ApiResult;
+import com.netflix.zuul.ZuulFilter;
+import com.netflix.zuul.context.RequestContext;
+import com.netflix.zuul.exception.ZuulException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author tanghc
+ */
+public abstract class BaseZuulFilter extends ZuulFilter {
+
+ protected Logger log = LoggerFactory.getLogger(getClass());
+
+ private Integer filterOrder;
+
+ protected abstract FilterType getFilterType();
+
+ protected abstract int getFilterOrder();
+
+ protected abstract Object doRun(RequestContext requestContext) throws ZuulException;
+
+ /**
+ * 设置过滤器顺序
+ *
+ * @param filterOrder 顺序,值越小优先执行
+ * @return 返回自身对象
+ */
+ public BaseZuulFilter order(int filterOrder) {
+ this.filterOrder = filterOrder;
+ return this;
+ }
+
+ @Override
+ public int filterOrder() {
+ return filterOrder != null ? filterOrder : this.getFilterOrder();
+ }
+
+ @Override
+ public String filterType() {
+ return this.getFilterType().getType();
+ }
+
+ @Override
+ public boolean shouldFilter() {
+ return true;
+ }
+
+ @Override
+ public Object run() throws ZuulException {
+ return this.doRun(RequestContext.getCurrentContext());
+ }
+
+ /**
+ * 过滤该请求,不往下级服务去转发请求,到此结束。并填充responseBody
+ *
+ * @param requestContext
+ * @param result
+ */
+ public static void stopRouteAndReturn(RequestContext requestContext, Object result) {
+ requestContext.setSendZuulResponse(false);
+ requestContext.setResponseBody(JSON.toJSONString(result));
+ }
+
+ public static void main(String[] args) {
+ System.out.println(JSON.toJSONString(new ApiResult()));
+ }
+
+ /**
+ * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
+ * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
+ * We also support a "static" type for static responses see StaticResponseFilter.
+ * Any filterType made be created or added and doRun by calling FilterProcessor.runFilters(type)
+ */
+ public enum FilterType {
+ PRE("pre"),
+ ROUTE("route"),
+ POST("post"),
+ ERROR("error"),
+ STATIC("static"),
+ ;
+
+ FilterType(String type) {
+ this.type = type;
+ }
+
+ private String type;
+
+ public String getType() {
+ return type;
+ }
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/ErrorFilter.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/ErrorFilter.java
new file mode 100644
index 00000000..e476c7d6
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/ErrorFilter.java
@@ -0,0 +1,59 @@
+package com.gitee.sop.gatewaycommon.filter;
+
+import com.netflix.zuul.FilterProcessor;
+import com.netflix.zuul.ZuulFilter;
+import com.netflix.zuul.context.RequestContext;
+import com.netflix.zuul.exception.ZuulException;
+import org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter;
+import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
+
+/**
+ * @author tanghc
+ */
+public class ErrorFilter extends SendErrorFilter {
+
+ public static final String FAILED_FILTER = "failed.filter";
+
+ private int filterOrder = 10;
+
+ public ErrorFilter() {
+ initFilterProcessor();
+ }
+
+ public void initFilterProcessor() {
+ FilterProcessor instance = FilterProcessor.getInstance();
+ if (!(instance instanceof MyFilterProcessor)) {
+ FilterProcessor.setProcessor(new MyFilterProcessor());
+ }
+ }
+
+ @Override
+ public int filterOrder() {
+ return filterOrder;
+ }
+
+ @Override
+ public boolean shouldFilter() {
+ // 判断:仅处理来自post过滤器引起的异常
+ RequestContext ctx = RequestContext.getCurrentContext();
+ ZuulFilter failedFilter = (ZuulFilter) ctx.get(FAILED_FILTER);
+ return failedFilter != null && failedFilter.filterType().equals(FilterConstants.POST_TYPE);
+ }
+
+ public static class MyFilterProcessor extends FilterProcessor {
+ @Override
+ public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
+ try {
+ return super.processZuulFilter(filter);
+ } catch (ZuulException e) {
+ RequestContext ctx = RequestContext.getCurrentContext();
+ ctx.set(FAILED_FILTER, filter);
+ throw e;
+ }
+ }
+ }
+
+ public void setFilterOrder(int filterOrder) {
+ this.filterOrder = filterOrder;
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PostResultFilter.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PostResultFilter.java
new file mode 100644
index 00000000..9bb819c7
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PostResultFilter.java
@@ -0,0 +1,49 @@
+package com.gitee.sop.gatewaycommon.filter;
+
+import com.gitee.sop.gatewaycommon.bean.ApiConfig;
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.bean.SopConstants;
+import com.gitee.sop.gatewaycommon.result.ResultExecutor;
+import com.netflix.zuul.context.RequestContext;
+import com.netflix.zuul.exception.ZuulException;
+import org.apache.commons.io.IOUtils;
+import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
+
+import java.io.InputStream;
+
+/**
+ * 合并微服务结果,统一返回格式
+ *
+ * @author tanghc
+ */
+public class PostResultFilter extends BaseZuulFilter {
+
+ @Override
+ protected FilterType getFilterType() {
+ return FilterType.POST;
+ }
+
+ @Override
+ protected int getFilterOrder() {
+ return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
+ }
+
+ @Override
+ protected Object doRun(RequestContext requestContext) throws ZuulException {
+ int responseStatusCode = requestContext.getResponseStatusCode();
+ InputStream responseDataStream = requestContext.getResponseDataStream();
+ ApiConfig apiConfig = ApiContext.getApiConfig();
+ ResultExecutor resultExecutor = apiConfig.getResultExecutor();
+ String serviceResult;
+ try {
+ serviceResult = IOUtils.toString(responseDataStream, SopConstants.CHARSET_UTF8);
+ } catch (Exception e) {
+ log.error("业务方无数据返回", e);
+ serviceResult = SopConstants.EMPTY_JSON;
+ }
+ String finalResult = resultExecutor.mergeResult(responseStatusCode, serviceResult);
+ requestContext.setResponseBody(finalResult);
+ return null;
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PreTokenFilter.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PreTokenFilter.java
new file mode 100644
index 00000000..49fa27a9
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PreTokenFilter.java
@@ -0,0 +1,41 @@
+package com.gitee.sop.gatewaycommon.filter;
+
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.netflix.zuul.context.RequestContext;
+import com.netflix.zuul.exception.ZuulException;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author tanghc
+ */
+public class PreTokenFilter extends BaseZuulFilter {
+
+ @Override
+ protected FilterType getFilterType() {
+ return FilterType.PRE;
+ }
+
+ @Override
+ protected int getFilterOrder() {
+ return FilterConstants.PRE_DECORATION_FILTER_ORDER + 1;
+ }
+
+ @Override
+ protected Object doRun(RequestContext ctx) throws ZuulException {
+ Object serviceId = ctx.get(FilterConstants.SERVICE_ID_KEY);
+ log.info("serviceId:{}", serviceId);
+ HttpServletRequest request = ctx.getRequest();
+
+ log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());
+
+ String accessToken = request.getParameter("access_token");
+ if (StringUtils.isBlank(accessToken)) {
+ throw ErrorEnum.AOP_INVALID_APP_AUTH_TOKEN.getErrorMeta().getException();
+ }
+ return null;
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PreValidateFilter.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PreValidateFilter.java
new file mode 100644
index 00000000..6f28cb11
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PreValidateFilter.java
@@ -0,0 +1,48 @@
+package com.gitee.sop.gatewaycommon.filter;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.bean.ApiConfig;
+import com.gitee.sop.gatewaycommon.exception.ApiException;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import com.gitee.sop.gatewaycommon.validate.Validator;
+import com.netflix.zuul.context.RequestContext;
+import com.netflix.zuul.exception.ZuulException;
+import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 前置校验
+ * @author tanghc
+ */
+public class PreValidateFilter extends BaseZuulFilter {
+ @Override
+ protected FilterType getFilterType() {
+ return FilterType.PRE;
+ }
+
+ @Override
+ protected int getFilterOrder() {
+ // 在org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter前面
+ return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
+ }
+
+ @Override
+ protected Object doRun(RequestContext requestContext) throws ZuulException {
+ HttpServletRequest request = requestContext.getRequest();
+ ApiConfig apiConfig = ApiContext.getApiConfig();
+ // 解析参数
+ ApiParam param = apiConfig.getParamParser().parse(request);
+ ApiContext.setApiParam(param);
+ // 验证操作,这里有负责验证签名参数
+ Validator validator = apiConfig.getValidator();
+ try {
+ validator.validate(param);
+ } catch (ApiException e) {
+ log.error("签名验证失败,params:{}", param.toJSONString(), e);
+ throw e;
+ }
+ return null;
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaChangeListener.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaChangeListener.java
new file mode 100644
index 00000000..e95baaf1
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaChangeListener.java
@@ -0,0 +1,25 @@
+package com.gitee.sop.gatewaycommon.manager;
+
+import org.springframework.data.redis.connection.Message;
+import org.springframework.data.redis.connection.MessageListener;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+public class ApiMetaChangeListener implements MessageListener {
+
+ private ApiMetaManager apiMetaManager;
+ private StringRedisTemplate redisTemplate;
+
+ public ApiMetaChangeListener(ApiMetaManager apiMetaManager, StringRedisTemplate redisTemplate) {
+ this.apiMetaManager = apiMetaManager;
+ this.redisTemplate = redisTemplate;
+ }
+
+ @Override
+ public void onMessage(Message message, byte[] bytes) {
+ RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
+ String msg = stringSerializer.deserialize(message.getBody());
+ this.apiMetaManager.onChange(msg);
+ }
+
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaConfig.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaConfig.java
new file mode 100644
index 00000000..da879a3a
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaConfig.java
@@ -0,0 +1,20 @@
+package com.gitee.sop.gatewaycommon.manager;
+
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+/**
+ * @author tanghc
+ */
+public class ApiMetaConfig {
+
+ private StringRedisTemplate redisTemplate;
+
+ public ApiMetaConfig(StringRedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
+
+ public void loadApiMetas() {
+
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaContext.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaContext.java
new file mode 100644
index 00000000..2e5ff58d
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaContext.java
@@ -0,0 +1,13 @@
+package com.gitee.sop.gatewaycommon.manager;
+
+import com.gitee.sop.gatewaycommon.bean.ServiceApiInfo;
+import org.springframework.cloud.netflix.zuul.filters.Route;
+
+/**
+ * @author tanghc
+ */
+public interface ApiMetaContext {
+ void reload(String serviceId, ServiceApiInfo serviceApiInfo);
+
+ Route getRoute(String nameVersion);
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaManager.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaManager.java
new file mode 100644
index 00000000..855ccf70
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaManager.java
@@ -0,0 +1,22 @@
+package com.gitee.sop.gatewaycommon.manager;
+
+/**
+ * 管理各服务接口信息
+ * @author tanghc
+ */
+public interface ApiMetaManager {
+
+ String API_STORE_KEY = "com.gitee.sop.api";
+
+ /**
+ * 刷新素有的微服务接口信息
+ */
+ void refresh();
+
+ /**
+ * 某个服务接口更改时触发
+ * @param serviceApiInfoJson 接口信息
+ */
+ void onChange(String serviceApiInfoJson);
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultApiMetaContext.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultApiMetaContext.java
new file mode 100644
index 00000000..2565119b
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultApiMetaContext.java
@@ -0,0 +1,62 @@
+package com.gitee.sop.gatewaycommon.manager;
+
+import com.gitee.sop.gatewaycommon.bean.ServiceApiInfo;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.netflix.zuul.filters.Route;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author tanghc
+ */
+@Slf4j
+public class DefaultApiMetaContext implements ApiMetaContext {
+
+ // key:nameVersion
+ private Map nameVersionServiceIdMap = new HashMap<>(128);
+
+ // key: serviceId , value: md5
+ private Map serviceIdMd5Map = new HashMap<>(16);
+
+ @Override
+ public void reload(String serviceId, ServiceApiInfo serviceApiInfo) {
+ String md5 = serviceIdMd5Map.get(serviceId);
+ if (md5 != null && md5.equals(serviceApiInfo.getMd5())) {
+ log.info("MD5相同,无需更改本地接口信息,appName:{}, md5:{}", serviceApiInfo.getAppName(), serviceApiInfo.getMd5());
+ return;
+ }
+ log.info("更新本地接口信息,appName:{}, md5:{}", serviceApiInfo.getAppName(), serviceApiInfo.getMd5());
+ // 移除原来的
+ Iterator> iterator = nameVersionServiceIdMap.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ if (entry.getValue().getLocation().equals(serviceId)) {
+ iterator.remove();
+ }
+ }
+ List apis = serviceApiInfo.getApis();
+ for (ServiceApiInfo.ApiMeta apiMeta : apis) {
+ Route route = this.buildRoute(serviceId, apiMeta);
+ nameVersionServiceIdMap.put(apiMeta.fetchNameVersion(), route);
+ }
+ serviceIdMd5Map.put(serviceId, serviceApiInfo.getMd5());
+ }
+
+ @Override
+ public Route getRoute(String nameVersion) {
+ Route route = nameVersionServiceIdMap.get(nameVersion);
+ if (route == null) {
+ throw ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException();
+ }
+ return route;
+ }
+
+ protected Route buildRoute(String serviceId, ServiceApiInfo.ApiMeta apiMeta) {
+ return new Route(apiMeta.getName(), apiMeta.getPath(), serviceId, null, false, null);
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultApiMetaManager.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultApiMetaManager.java
new file mode 100644
index 00000000..44ef79c1
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultApiMetaManager.java
@@ -0,0 +1,51 @@
+package com.gitee.sop.gatewaycommon.manager;
+
+import com.alibaba.fastjson.JSON;
+import com.gitee.sop.gatewaycommon.bean.ServiceApiInfo;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import java.util.Map;
+
+/**
+ * 保存在redis中,结构为HSET。格式如下:
+ *
+ * com.gitee.sop.api
+ * <serviceId>:{ md5:"xxx", apis:[{name:"", version:""}] }
+ *
+ * @author tanghc
+ */
+@Getter
+@Slf4j
+public class DefaultApiMetaManager implements ApiMetaManager {
+
+ private StringRedisTemplate redisTemplate;
+ private ApiMetaContext apiMetaContext;
+
+ public DefaultApiMetaManager(StringRedisTemplate redisTemplate, ApiMetaContext apiMetaContext) {
+ this.redisTemplate = redisTemplate;
+ this.apiMetaContext = apiMetaContext;
+ }
+
+ @Override
+ public void refresh() {
+ log.info("刷新本地接口信息");
+ Map entries = redisTemplate.opsForHash().entries(API_STORE_KEY);
+ for (Map.Entry entry : entries.entrySet()) {
+ log.info("更新微服务接口,appName:{}", entry.getKey());
+ String serviceId = entry.getKey().toString();
+ String serviceApiInfoJson = entry.getValue().toString();
+ ServiceApiInfo serviceApiInfo = JSON.parseObject(serviceApiInfoJson, ServiceApiInfo.class);
+ apiMetaContext.reload(serviceId, serviceApiInfo);
+ }
+ }
+
+ @Override
+ public void onChange(String serviceApiInfoJson) {
+ ServiceApiInfo serviceApiInfo = JSON.parseObject(serviceApiInfoJson, ServiceApiInfo.class);
+ log.info("Redis订阅推送接口信息,appName:{}, md5:{}", serviceApiInfo.getAppName(), serviceApiInfo.getMd5());
+ this.apiMetaContext.reload(serviceApiInfo.getAppName(), serviceApiInfo);
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/SopRouteLocator.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/SopRouteLocator.java
new file mode 100644
index 00000000..ffae417f
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/SopRouteLocator.java
@@ -0,0 +1,45 @@
+package com.gitee.sop.gatewaycommon.manager;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import org.springframework.cloud.netflix.zuul.filters.Route;
+import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
+import org.springframework.core.Ordered;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author tanghc
+ */
+public class SopRouteLocator implements RouteLocator, Ordered {
+
+ private ApiMetaContext apiMetaContext;
+
+ public SopRouteLocator(ApiMetaContext apiMetaContext) {
+ this.apiMetaContext = apiMetaContext;
+ }
+
+ @Override
+ public Collection getIgnoredPaths() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getRoutes() {
+ return null;
+ }
+
+ @Override
+ public Route getMatchingRoute(String path) {
+ ApiParam param = ApiContext.getApiParam();
+ String nameVersion = param.fetchNameVersion();
+ return apiMetaContext.getRoute(nameVersion);
+ }
+
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/Error.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/Error.java
new file mode 100644
index 00000000..fe928099
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/Error.java
@@ -0,0 +1,48 @@
+package com.gitee.sop.gatewaycommon.message;
+
+/**
+ * 定义错误返回
+ * code(返回码)
+ * msg(返回码描述)
+ * sub_code(明细返回码)
+ * sub_msg(明细返回码描述)
+ * 解决方案
+ * @author tanghc
+ */
+public interface Error {
+ /**
+ * 获取网关状态码
+ *
+ * @return 返回状态码
+ */
+ String getCode();
+
+ /**
+ * 获取网关错误信息
+ *
+ * @return 返回错误信息
+ */
+ String getMsg();
+
+ /**
+ * sub_code(明细返回码)
+ * @return sub_code(明细返回码)
+ */
+ String getSub_code();
+
+ /**
+ * sub_msg(明细返回码描述)
+ * @return sub_msg(明细返回码描述)
+ */
+ String getSub_msg();
+
+ /**
+ * 解决方案
+ * @return 解决方案
+ */
+ String getSolution();
+
+
+
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorEnum.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorEnum.java
new file mode 100644
index 00000000..e963f4f9
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorEnum.java
@@ -0,0 +1,66 @@
+package com.gitee.sop.gatewaycommon.message;
+
+/**
+ * @author tanghc
+ */
+public enum ErrorEnum {
+ SUCCESS("10000", ""),
+
+ ISP_UNKNOW_ERROR("20000", "isp.unknow-error"),
+ AOP_UNKNOW_ERROR("20000", "aop.unknow-error"),
+
+ AOP_INVALID_AUTH_TOKEN("20001", "aop.invalid-auth-token"),
+ AOP_INVALID_AUTH_OKEN("20001", "aop.invalid-auth-token"),
+ AOP_AUTH_TOKEN_TIME_OUT("20001", "aop.auth-token-time-out"),
+ AOP_INVALID_APP_AUTH_TOKEN("20001", "aop.invalid-app-auth-token"),
+ AOP_INVALID_APP_AUTH_TOKEN_NO_API("20001", "aop.invalid-app-auth-token-no-api"),
+ AOP_APP_AUTH_TOKEN_TIME_OUT("20001", "aop.app-auth-token-time-out"),
+ AOP_NO_PRODUCT_REG_BY_PARTNER("20001", "aop.no-product-reg-by-partner"),
+
+ ISV_MISSING_METHOD("40001", "isv.missing-method"),
+ ISV_MISSING_SIGNATURE("40001", "isv.missing-signature"),
+ ISV_MISSING_SIGNATURE_TYPE("40001", "isv.missing-signature-type"),
+ ISV_MISSING_SIGNATURE_KEY("40001", "isv.missing-signature-key"),
+ ISV_MISSING_APP_ID("40001", "isv.missing-app-id"),
+ ISV_MISSING_TIMESTAMP("40001", "isv.missing-timestamp"),
+ ISV_MISSING_VERSION("40001", "isv.missing-version"),
+ ISV_DECRYPTION_ERROR_MISSING_ENCRYPT_TYPE("40001", "isv.decryption-error-missing-encrypt-type"),
+
+ ISV_INVALID_PARAMETER("40002", "isv.invalid-parameter"),
+ ISV_UPLOAD_FAIL("40002", "isv.upload-fail"),
+ ISV_INVALID_FILE_EXTENSION("40002", "isv.invalid-file-extension"),
+ ISV_INVALID_FILE_SIZE("40002", "isv.invalid-file-size"),
+ ISV_INVALID_METHOD("40002", "isv.invalid-method"),
+ ISV_INVALID_FORMAT("40002", "isv.invalid-format"),
+ ISV_INVALID_SIGNATURE_TYPE("40002", "isv.invalid-signature-type"),
+ ISV_INVALID_SIGNATURE("40002", "isv.invalid-signature"),
+ ISV_INVALID_ENCRYPT_TYPE("40002", "isv.invalid-encrypt-type"),
+ ISV_INVALID_ENCRYPT("40002", "isv.invalid-encrypt"),
+ ISV_INVALID_APP_ID("40002", "isv.invalid-app-id"),
+ ISV_INVALID_TIMESTAMP("40002", "isv.invalid-timestamp"),
+ ISV_INVALID_CHARSET("40002", "isv.invalid-charset"),
+ ISV_INVALID_DIGEST("40002", "isv.invalid-digest"),
+ ISV_DECRYPTION_ERROR_NOT_VALID_ENCRYPT_TYPE("40002", "isv.decryption-error-not-valid-encrypt-type"),
+ ISV_DECRYPTION_ERROR_NOT_VALID_ENCRYPT_KEY("40002", "isv.decryption-error-not-valid-encrypt-key"),
+ ISV_DECRYPTION_ERROR_UNKNOWN("40002", "isv.decryption-error-unknown"),
+ ISV_MISSING_SIGNATURE_CONFIG("40002", "isv.missing-signature-config"),
+ ISV_NOT_SUPPORT_APP_AUTH("40002", "isv.not-support-app-auth"),
+ ISV_SUSPECTED_ATTACK("40002", "isv.suspected-attack"),
+ ISV_INVALID_CONTENT_TYPE("40002", "isv.invalid-content-type"),
+
+ BIZ_ERROR("40004", ""),
+
+ ISV_INSUFFICIENT_ISV_PERMISSIONS("40006", "isv.insufficient-isv-permissions"),
+ ISV_INSUFFICIENT_USER_PERMISSIONS("40006", "isv.insufficient-user-permissions"),
+
+ ;
+ private ErrorMeta errorMeta;
+
+ ErrorEnum(String code, String sub_code) {
+ this.errorMeta = new ErrorMeta("open.error_", code, sub_code);
+ }
+
+ public ErrorMeta getErrorMeta() {
+ return errorMeta;
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorFactory.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorFactory.java
new file mode 100644
index 00000000..c6df7fbc
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorFactory.java
@@ -0,0 +1,120 @@
+package com.gitee.sop.gatewaycommon.message;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.context.support.ResourceBundleMessageSource;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 负责构建错误消息
+ *
+ * @author tanghc
+ */
+@Slf4j
+public class ErrorFactory {
+
+ public static final String SYS_ERR = "系统错误";
+
+ private static final String I18N_OPEN_ERROR = "i18n/open/error";
+ public static final String UNDERLINE = "_";
+
+ private static Set noModuleCache = new HashSet();
+
+ private static Map errorCache = new HashMap<>(64);
+
+ /**
+ * 错误信息的国际化信息
+ */
+ private static MessageSourceAccessor errorMessageSourceAccessor;
+
+ /**
+ * 设置国际化资源信息
+ */
+ public static void initMessageSource(List isvModules) {
+ HashSet baseNamesSet = new HashSet();
+ baseNamesSet.add(I18N_OPEN_ERROR);
+
+ if (!isvModules.isEmpty()) {
+ baseNamesSet.addAll(isvModules);
+ }
+
+ String[] totalBaseNames = baseNamesSet.toArray(new String[0]);
+
+ log.info("加载错误码国际化资源:{}", StringUtils.arrayToCommaDelimitedString(totalBaseNames));
+ ResourceBundleMessageSource bundleMessageSource = new ResourceBundleMessageSource();
+ bundleMessageSource.setBasenames(totalBaseNames);
+ MessageSourceAccessor messageSourceAccessor = new MessageSourceAccessor(bundleMessageSource);
+ setErrorMessageSourceAccessor(messageSourceAccessor);
+ }
+
+ /**
+ * 通过ErrorMeta,Locale,params构建国际化错误消息
+ *
+ * @param errorMeta 错误信息
+ * @param locale 本地化
+ * @param params 参数
+ * @return 如果没有配置国际化消息,则直接返回errorMeta中的信息
+ */
+ public static Error getError(ErrorMeta errorMeta, Locale locale, Object... params) {
+ String key = errorMeta.getModulePrefix() + errorMeta.getCode() + errorMeta.getSubCode() + locale.toString();
+ Error error = errorCache.get(key);
+ if (error == null) {
+ Assert.notNull(locale, "未设置Locale");
+ String modulePrefix = errorMeta.getModulePrefix();
+ String code = errorMeta.getCode();
+ // open.error_20000=Service is temporarily unavailable
+ String msg = getErrorMessage(modulePrefix + code, locale);
+ String subCode = errorMeta.getSubCode();
+ // open.error_20000_isp.unknow-error=Service is temporarily unavailable
+ String subMsg = getErrorMessage(modulePrefix + code + UNDERLINE + subCode, locale, params);
+ if (StringUtils.isEmpty(msg)) {
+ msg = SYS_ERR;
+ }
+ if (StringUtils.isEmpty(subMsg)) {
+ subMsg = SYS_ERR;
+ }
+ // solution暂未实现,如果要实现,可以这样配置:
+ // open.error_20000_isp.unknow-error_solution=Service is temporarily unavailable
+ // String solution = getErrorMessage(modulePrefix + code + UNDERLINE + subCode + "_solution", locale, params);
+ error = new ErrorImpl(code, msg, subCode, subMsg, null);
+ errorCache.put(key, error);
+ }
+ return error;
+ }
+
+
+ public static void setErrorMessageSourceAccessor(MessageSourceAccessor errorMessageSourceAccessor) {
+ ErrorFactory.errorMessageSourceAccessor = errorMessageSourceAccessor;
+ }
+
+ /**
+ * 返回本地化信息
+ *
+ * @param module 错误模块
+ * @param locale 本地化
+ * @param params 参数
+ * @return 返回信息
+ */
+ public static String getErrorMessage(String module, Locale locale, Object... params) {
+ if (noModuleCache.contains(module)) {
+ return null;
+ }
+ try {
+ return errorMessageSourceAccessor.getMessage(module, params, locale);
+ } catch (Exception e) {
+ noModuleCache.add(module);
+ return null;
+ }
+ }
+
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorImpl.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorImpl.java
new file mode 100644
index 00000000..6fb66b87
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorImpl.java
@@ -0,0 +1,23 @@
+package com.gitee.sop.gatewaycommon.message;
+
+import lombok.Data;
+
+@Data
+public class ErrorImpl implements Error {
+ private String code;
+ private String msg;
+ private String sub_code;
+ private String sub_msg;
+ private String solution;
+
+ public ErrorImpl() {
+ }
+
+ public ErrorImpl(String code, String msg, String sub_code, String sub_msg, String solution) {
+ this.code = code;
+ this.msg = msg;
+ this.sub_code = sub_code;
+ this.sub_msg = sub_msg;
+ this.solution = solution;
+ }
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorMeta.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorMeta.java
new file mode 100644
index 00000000..5bb5bc8a
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorMeta.java
@@ -0,0 +1,47 @@
+package com.gitee.sop.gatewaycommon.message;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.exception.ApiException;
+import lombok.Getter;
+
+/**
+ * 错误对象
+ *
+ * @author tanghc
+ */
+@Getter
+public class ErrorMeta {
+
+ private String modulePrefix;
+ private String code;
+ private String subCode;
+
+ public ErrorMeta(String modulePrefix, String code, String subCode) {
+ this.modulePrefix = modulePrefix;
+ this.code = code;
+ this.subCode = subCode;
+ }
+
+ public Error getError() {
+ return ErrorFactory.getError(this, ApiContext.getLocale());
+ }
+
+ /**
+ * 返回网关exception
+ *
+ * @param params 参数
+ * @return 返回exception
+ */
+ public ApiException getException(Object... params) {
+ if (params != null && params.length == 1) {
+ Object param = params[0];
+ if (param instanceof Throwable) {
+ Error error = ErrorFactory.getError(this, ApiContext.getLocale());
+ return new ApiException((Throwable) param, error);
+ }
+ }
+ Error error = ErrorFactory.getError(this, ApiContext.getLocale(), params);
+ return new ApiException(error);
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java
new file mode 100644
index 00000000..d2433980
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java
@@ -0,0 +1,197 @@
+package com.gitee.sop.gatewaycommon.param;
+
+import com.alibaba.fastjson.JSONObject;
+import com.gitee.sop.gatewaycommon.bean.SopConstants;
+import com.netflix.zuul.context.RequestContext;
+import org.apache.commons.lang.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+/**
+ * 客户端传来的参数放在这里.
+ *
+ * @author tanghc
+ */
+public class ApiParam extends JSONObject implements Param {
+ public ApiParam(Map map) {
+ super(map);
+ }
+
+ private boolean ignoreSign;
+ private boolean ignoreValidate;
+
+ private String restName;
+ private String restVersion;
+
+ /**
+ * 获取sign,并从param中删除
+ *
+ * @return 返回sign内容
+ */
+ public String fetchSignAndRemove() {
+ String sign = this.fetchSign();
+ this.remove(ParamNames.SIGN_NAME);
+ return sign;
+ }
+
+ public HttpServletRequest fetchRequest() {
+ return RequestContext.getCurrentContext().getRequest();
+ }
+
+ /**
+ * 是否忽略验证签名
+ *
+ * @return 返回true,忽略签名
+ */
+ public boolean fetchIgnoreSign() {
+ return ignoreSign;
+ }
+
+ public void setIgnoreSign(boolean ignoreSign) {
+ this.ignoreSign = ignoreSign;
+ }
+
+ public boolean fetchIgnoreValidate() {
+ return ignoreValidate;
+ }
+
+ public void setIgnoreValidate(boolean ignoreValidate) {
+ this.ignoreValidate = ignoreValidate;
+ }
+
+ /**
+ * 接口名,如:goods.list
+ */
+ @Override
+ public String fetchName() {
+ String name = getString(ParamNames.API_NAME);
+ if (name == null) {
+ name = this.restName;
+ }
+ return name;
+ }
+
+ public void setName(String name) {
+ this.restName = name;
+ }
+
+ public String fetchNameVersion() {
+ return buildNameVersion(this.fetchName(), this.fetchVersion());
+ }
+
+ public static String buildNameVersion(String name, String version) {
+ if (StringUtils.isEmpty(version)) {
+ return name;
+ } else {
+ return name + version;
+ }
+ }
+
+ /**
+ * 版本号
+ */
+ @Override
+ public String fetchVersion() {
+ String version = getString(ParamNames.VERSION_NAME);
+ if (version == null) {
+ version = this.restVersion;
+ }
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.restVersion = version;
+ }
+
+ /**
+ * 接入应用ID
+ */
+ @Override
+ public String fetchAppKey() {
+ return getString(ParamNames.APP_KEY_NAME);
+ }
+
+ public void setAppKey(String appKey) {
+ put(ParamNames.APP_KEY_NAME, appKey);
+ }
+
+ /**
+ * 参数,urlencode后的
+ */
+ @Override
+ public String fetchData() {
+ return getString(ParamNames.BIZ_CONTENT_NAME);
+ }
+
+ public void setData(String json) {
+ put(ParamNames.BIZ_CONTENT_NAME, json);
+ }
+
+ /**
+ * 时间戳,格式为yyyy-MM-dd HH:mm:ss,例如:2015-01-01 12:00:00
+ */
+ @Override
+ public String fetchTimestamp() {
+ return getString(ParamNames.TIMESTAMP_NAME);
+ }
+
+ public void setTimestamp(String timestamp) {
+ put(ParamNames.TIMESTAMP_NAME, timestamp);
+ }
+
+ /**
+ * 签名串
+ */
+ @Override
+ public String fetchSign() {
+ return getString(ParamNames.SIGN_NAME);
+ }
+
+ public void setSign(String sign) {
+ put(ParamNames.SIGN_NAME, sign);
+ }
+
+ @Override
+ public String fetchFormat() {
+ String format = getString(ParamNames.FORMAT_NAME);
+ if (format == null || "".equals(format)) {
+ return SopConstants.FORMAT_JSON;
+ }
+ return format;
+ }
+
+ public void setFormat(String format) {
+ put(ParamNames.FORMAT_NAME, format);
+ }
+
+ @Override
+ public String fetchAccessToken() {
+ return getString(ParamNames.APP_AUTH_TOKEN_NAME);
+ }
+
+ @Override
+ public String fetchSignMethod() {
+ String signMethod = getString(ParamNames.SIGN_TYPE_NAME);
+ if (signMethod == null) {
+ return SopConstants.DEFAULT_SIGN_METHOD;
+ } else {
+ return signMethod;
+ }
+ }
+
+ @Override
+ public String fetchCharset() {
+ return getString(ParamNames.CHARSET_NAME);
+ }
+
+ @Override
+ public ApiParam clone() {
+ ApiParam param = new ApiParam(this);
+ param.ignoreSign = this.ignoreSign;
+ param.ignoreValidate = this.ignoreValidate;
+ return param;
+ }
+
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParamParser.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParamParser.java
new file mode 100644
index 00000000..d2834b50
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParamParser.java
@@ -0,0 +1,104 @@
+package com.gitee.sop.gatewaycommon.param;
+
+import com.alibaba.fastjson.JSON;
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.util.RequestUtil;
+import com.netflix.zuul.http.HttpServletRequestWrapper;
+import org.springframework.http.MediaType;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.multipart.commons.CommonsMultipartResolver;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 参数解析默认实现
+ *
+ * @author tanghc
+ */
+public class ApiParamParser implements ParamParser {
+
+ private static final String CONTENT_TYPE_MULTIPART = MediaType.MULTIPART_FORM_DATA_VALUE;
+ private static final String CONTENT_TYPE_JSON = MediaType.APPLICATION_JSON_VALUE;
+ private static final String CONTENT_TYPE_TEXT = MediaType.TEXT_PLAIN_VALUE;
+
+ @Override
+ public ApiParam parse(HttpServletRequest request) {
+ try {
+ Map params = this.getJson(request);
+ return new ApiParam(params);
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_PARAMETER.getErrorMeta().getException();
+ }
+ }
+
+ public Map getJson(HttpServletRequest request) throws Exception {
+ // zuul会做一层包装
+ if (request instanceof HttpServletRequestWrapper) {
+ HttpServletRequestWrapper req = (HttpServletRequestWrapper) request;
+ request = req.getRequest();
+ }
+ Map params = null;
+
+ if (RequestUtil.isGetRequest(request)) {
+ params = RequestUtil.convertRequestParamsToMap(request);
+ } else {
+ String contectType = request.getContentType();
+
+ if (contectType == null) {
+ contectType = "";
+ }
+
+ contectType = contectType.toLowerCase();
+
+ // json或者纯文本形式
+ if (contectType.contains(CONTENT_TYPE_JSON) || contectType.contains(CONTENT_TYPE_TEXT)) {
+ String txt = RequestUtil.getText(request);
+ params = JSON.parseObject(txt);
+ } else if (contectType.contains(CONTENT_TYPE_MULTIPART)) {
+ // 上传文件形式
+ params = this.parseUploadRequest(request);
+ } else {
+ params = RequestUtil.convertRequestParamsToMap(request);
+ }
+ }
+
+ return params;
+ }
+
+ /**
+ * 解析文件上传请求
+ *
+ * @param request
+ * @return 返回json字符串
+ */
+ protected Map parseUploadRequest(HttpServletRequest request) {
+ CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(
+ request.getSession().getServletContext());
+ // 检查form中是否有enctype="multipart/form-data"
+ if (multipartResolver.isMultipart(request)) {
+ MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
+ Map fileMap = multiRequest.getFileMap();
+ Map finalMap = new HashMap<>(fileMap.size());
+
+ Set keys = fileMap.keySet();
+ for (String name : keys) {
+ MultipartFile file = fileMap.get(name);
+ if (file.getSize() > 0) {
+ finalMap.put(name, file);
+ }
+ }
+ if (finalMap.size() > 0) {
+ // 保存上传文件
+ ApiContext.setUploadContext(new ApiUploadContext(finalMap));
+ }
+ }
+
+ return RequestUtil.convertRequestParamsToMap(request);
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiUploadContext.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiUploadContext.java
new file mode 100644
index 00000000..6fb56403
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiUploadContext.java
@@ -0,0 +1,42 @@
+package com.gitee.sop.gatewaycommon.param;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 存放上传文件
+ *
+ * @author tanghc
+ */
+public class ApiUploadContext implements UploadContext {
+
+ private Map fileMap;
+ private List allFile;
+
+ public ApiUploadContext(Map map) {
+ if (map == null) {
+ map = Collections.emptyMap();
+ }
+ this.fileMap = map;
+ this.allFile = new ArrayList<>(map.values());
+ }
+
+ @Override
+ public MultipartFile getFile(int index) {
+ return this.allFile.get(index);
+ }
+
+ @Override
+ public MultipartFile getFile(String name) {
+ return fileMap.get(name);
+ }
+
+ @Override
+ public List getAllFile() {
+ return this.allFile;
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/Param.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/Param.java
new file mode 100644
index 00000000..b2a6387c
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/Param.java
@@ -0,0 +1,70 @@
+package com.gitee.sop.gatewaycommon.param;
+
+import java.io.Serializable;
+
+/**
+ * @author tanghc
+ */
+public interface Param extends Serializable {
+
+ /**
+ * 获取接口名
+ * @return 返回接口名
+ */
+ String fetchName();
+
+ /**
+ * 获取版本号
+ * @return 返回版本号
+ */
+ String fetchVersion();
+
+ /**
+ * 获取appKey
+ * @return 返回appKey
+ */
+ String fetchAppKey();
+
+ /**
+ * 获取业务参数
+ * @return 返回业务参数
+ */
+ String fetchData();
+
+ /**
+ * 获取时间戳
+ * @return 返回时间戳
+ */
+ String fetchTimestamp();
+
+ /**
+ * 获取签名串
+ * @return 返回签名串
+ */
+ String fetchSign();
+
+ /**
+ * 获取格式化类型
+ * @return 返回格式化类型
+ */
+ String fetchFormat();
+
+ /**
+ * 获取accessToken
+ * @return 返回accessToken
+ */
+ String fetchAccessToken();
+
+ /**
+ * 获取签名方式
+ * @return 返回签名方式
+ */
+ String fetchSignMethod();
+
+ /**
+ * 请求使用的编码格式,如utf-8,gbk,gb2312等
+ * @return 请求使用的编码格式,如utf-8,gbk,gb2312等
+ */
+ String fetchCharset();
+
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ParamNames.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ParamNames.java
new file mode 100644
index 00000000..5d5e0928
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ParamNames.java
@@ -0,0 +1,46 @@
+package com.gitee.sop.gatewaycommon.param;
+
+/**
+ * 请求参数名定义
+ *
+ * 参数 类型 是否必填 最大长度 描述 示例值
+ * app_id String 是 32 支付宝分配给开发者的应用ID 2014072300007148
+ * method String 是 128 接口名称 alipay.trade.fastpay.refund.query
+ * format String 否 40 仅支持JSON JSON
+ * charset String 是 10 请求使用的编码格式,如utf-8,gbk,gb2312等 utf-8
+ * sign_type String 是 10 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 RSA2
+ * sign String 是 344 商户请求参数的签名串,详见签名 详见示例
+ * timestamp String 是 19 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" 2014-07-24 03:07:50
+ * version String 是 3 调用的接口版本,固定为:1.0 1.0
+ * app_auth_token String 否 40 详见应用授权概述
+ * biz_content String 是 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
+ *
+ * @author tanghc
+ */
+public class ParamNames {
+ /** 分配给开发者的应用ID */
+ public static String APP_KEY_NAME = "app_id";
+ /** 接口名称 */
+ public static String API_NAME = "method";
+ /** 仅支持JSON */
+ public static String FORMAT_NAME = "format";
+ /** 请求使用的编码格式 */
+ public static String CHARSET_NAME = "charset";
+ /** 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 */
+ public static String SIGN_TYPE_NAME = "sign_type";
+ /** 商户请求参数的签名串 */
+ public static String SIGN_NAME = "sign";
+ /** 发送请求的时间 */
+ public static String TIMESTAMP_NAME = "timestamp";
+ /** 调用的接口版本 */
+ public static String VERSION_NAME = "version";
+ /** OAuth 2.0授权token */
+ public static String APP_AUTH_TOKEN_NAME = "app_auth_token";
+ /** 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档 */
+ public static String BIZ_CONTENT_NAME = "biz_content";
+
+ /** */
+ public static String TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ParamParser.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ParamParser.java
new file mode 100644
index 00000000..3edceb3d
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ParamParser.java
@@ -0,0 +1,19 @@
+package com.gitee.sop.gatewaycommon.param;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 负责解析参数
+ * @author tanghc
+ *
+ */
+public interface ParamParser {
+
+ /**
+ * 从request提取参数
+ * @param request
+ * @return 返回ApiParam
+ * @throws Exception
+ */
+ ApiParam parse(HttpServletRequest request);
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/UploadContext.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/UploadContext.java
new file mode 100644
index 00000000..bb72a2d7
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/UploadContext.java
@@ -0,0 +1,36 @@
+package com.gitee.sop.gatewaycommon.param;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 获取上传文件
+ *
+ * @author tanghc
+ */
+public interface UploadContext {
+ /**
+ * 根据索引获取上传文件,从0开始
+ *
+ * @param index
+ * @return 返回上传文件信息
+ */
+ MultipartFile getFile(int index);
+
+ /**
+ * 根据表单名获取上传文件
+ *
+ * @param name
+ * 表单名称
+ * @return 返回上传文件信息
+ */
+ MultipartFile getFile(String name);
+
+ /**
+ * 获取所有的上传文件
+ *
+ * @return 返回所有的上传文件
+ */
+ List getAllFile();
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ApiResult.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ApiResult.java
new file mode 100644
index 00000000..9247cc76
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ApiResult.java
@@ -0,0 +1,77 @@
+package com.gitee.sop.gatewaycommon.result;
+
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.Data;
+
+/**
+ * 默认的结果封装类.
+ *
+ *
+ * xml返回结果:
+ *
+ * 50
+ * Remote service error
+ * isv.invalid-parameter
+ * 非法参数
+ *
+ * 成功情况:
+ *
+ * 0
+ * 成功消息
+ *
+ * ...返回内容
+ *
+ *
+ *
+ * json返回格式:
+ * {
+ * "code":"50",
+ * "msg":"Remote service error",
+ * "sub_code":"isv.invalid-parameter",
+ * "sub_msg":"非法参数"
+ * }
+ * 成功情况:
+ * {
+ * "code":"0",
+ * "msg":"成功消息内容。。。",
+ * "data":{
+ * ...返回内容
+ * }
+ * }
+ *
+ *
+ * 字段说明:
+ * code:网关异常码
+ * msg:网关异常信息
+ * sub_code:业务异常码
+ * sub_msg:业务异常信息
+ *
+ * @author tanghc
+ */
+@XStreamAlias("response")
+@Data
+public class ApiResult implements Result {
+
+ /**
+ * 网关异常码,范围0~100 成功返回"0"
+ */
+ private String code = ErrorEnum.SUCCESS.getErrorMeta().getCode();
+
+ /**
+ * 网关异常信息
+ */
+ private String msg;
+
+ /**
+ * 业务异常码
+ */
+ private String sub_msg;
+
+ /**
+ * 业务异常信息
+ */
+ private String sub_code;
+
+ private String sign;
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ApiResultExecutor.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ApiResultExecutor.java
new file mode 100644
index 00000000..f8bf8c7e
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ApiResultExecutor.java
@@ -0,0 +1,125 @@
+package com.gitee.sop.gatewaycommon.result;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.exception.ApiException;
+import com.gitee.sop.gatewaycommon.message.Error;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.message.ErrorMeta;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import com.gitee.sop.gatewaycommon.param.ParamNames;
+import com.netflix.zuul.exception.ZuulException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+
+/**
+ * @author tanghc
+ */
+@Slf4j
+public class ApiResultExecutor implements ResultExecutor {
+
+ private static final ErrorMeta SUCCESS_META = ErrorEnum.SUCCESS.getErrorMeta();
+ private static final ErrorMeta ISP_UNKNOW_ERROR_META = ErrorEnum.ISP_UNKNOW_ERROR.getErrorMeta();
+ private static final ErrorMeta ISP_BIZ_ERROR = ErrorEnum.BIZ_ERROR.getErrorMeta();
+
+
+ public static final int BIZ_ERROR_STATUS = 4000;
+ private static final char DOT = '.';
+ private static final char UNDERLINE = '_';
+ public static final String GATEWAY_CODE_NAME = "code";
+ public static final String GATEWAY_MSG_NAME = "msg";
+ public static final String DATA_SUFFIX = "_response";
+
+ @Override
+ public String mergeResult(int responseStatus, String responseData) {
+ if (responseStatus == HttpStatus.OK.value() || responseStatus == BIZ_ERROR_STATUS) {
+ return mergeSuccess(responseStatus, responseData);
+ } else {
+ // 微服务端有可能返回500错误
+ // {"path":"/book/getBook3","error":"Internal Server Error","message":"id不能为空","timestamp":"2019-02-13T07:41:00.495+0000","status":500}
+ return mergeError(responseData);
+ }
+ }
+
+ @Override
+ public String mergeError(Throwable throwable) {
+ Error error = null;
+ if (throwable instanceof ZuulException) {
+ ZuulException ex = (ZuulException) throwable;
+ Throwable cause = ex.getCause();
+ if (cause instanceof ApiException) {
+ ApiException apiException = (ApiException) cause;
+ error = apiException.getError();
+ }
+ }
+ if (error == null) {
+ error = ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getError();
+ }
+ JSONObject jsonObject = (JSONObject)JSON.toJSON(error);
+ return this.merge(jsonObject);
+ }
+
+ /*
+ 成功示例
+ {
+ "alipay_trade_fastpay_refund_query_response": {
+ "code": "10000",
+ "msg": "Success",
+ "trade_no": "2014112611001004680073956707",
+ "out_trade_no": "20150320010101001",
+ "out_request_no": "20150320010101001",
+ "refund_reason": "用户退款请求",
+ "total_amount": 100.2,
+ "refund_amount": 12.33
+ },
+ "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
+ }
+ */
+ public String mergeSuccess(int responseStatus, String serviceResult) {
+ JSONObject jsonObjectService;
+ // 如果是业务出错
+ if (responseStatus == BIZ_ERROR_STATUS) {
+ jsonObjectService = JSON.parseObject(serviceResult);
+ jsonObjectService.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode());
+ jsonObjectService.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError().getMsg());
+ } else {
+ // 200正常返回
+ jsonObjectService = JSON.parseObject(serviceResult);
+ jsonObjectService.put(GATEWAY_CODE_NAME, SUCCESS_META.getCode());
+ jsonObjectService.put(GATEWAY_MSG_NAME, SUCCESS_META.getError().getMsg());
+ }
+ return this.merge(jsonObjectService);
+ }
+
+ /*
+ 异常示例
+ {
+ "alipay_trade_fastpay_refund_query_response": {
+ "code": "20000",
+ "msg": "Service Currently Unavailable",
+ "sub_code": "isp.unknow-error",
+ "sub_msg": "系统繁忙"
+ },
+ "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
+ }
+ */
+ public String mergeError(String serviceResult) {
+ JSONObject jsonObjectService = new JSONObject();
+ jsonObjectService.put(GATEWAY_CODE_NAME, ISP_UNKNOW_ERROR_META.getCode());
+ jsonObjectService.put(GATEWAY_MSG_NAME, ISP_UNKNOW_ERROR_META.getError().getMsg());
+ return this.merge(jsonObjectService);
+ }
+
+ private String merge(JSONObject jsonObjectService) {
+ JSONObject ret = new JSONObject();
+ ApiParam apiParam = ApiContext.getApiParam();
+ // 点换成下划线
+ String apiName = apiParam.fetchName().replace(DOT, UNDERLINE);
+ ret.put(apiName + DATA_SUFFIX, jsonObjectService);
+ ret.put(ParamNames.SIGN_NAME, apiParam.fetchSign());
+ return ret.toJSONString();
+ }
+
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/JsonResultSerializer.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/JsonResultSerializer.java
new file mode 100644
index 00000000..e1d14797
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/JsonResultSerializer.java
@@ -0,0 +1,16 @@
+package com.gitee.sop.gatewaycommon.result;
+
+import com.alibaba.fastjson.JSON;
+
+/**
+ * 序列化json
+ * @author tanghc
+ */
+public class JsonResultSerializer implements ResultSerializer {
+
+ @Override
+ public String serialize(Object obj) {
+ return JSON.toJSONString(obj);
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/Result.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/Result.java
new file mode 100644
index 00000000..8c1a7a7d
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/Result.java
@@ -0,0 +1,12 @@
+package com.gitee.sop.gatewaycommon.result;
+
+import java.io.Serializable;
+
+/**
+ * 返回结果,后续自定义的返回类需要实现这个接口。
+ *
+ * @author tanghc
+ */
+public interface Result extends Serializable {
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultBuilder.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultBuilder.java
new file mode 100644
index 00000000..a2bf66e5
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultBuilder.java
@@ -0,0 +1,27 @@
+package com.gitee.sop.gatewaycommon.result;
+
+import com.gitee.sop.gatewaycommon.message.Error;
+
+/**
+ * @author tanghc
+ */
+public interface ResultBuilder {
+
+
+ /**
+ * 构建网关错误返回结果
+ *
+ * @param throwable 异常
+ * @return 返回最终结果
+ */
+ Result buildGatewayError(Throwable throwable);
+
+ /**
+ * 构建网关错误返回结果
+ *
+ * @param error error
+ * @return 返回最终结果
+ */
+ Result buildGatewayError(Error error);
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutor.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutor.java
new file mode 100644
index 00000000..afdd2a5f
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutor.java
@@ -0,0 +1,10 @@
+package com.gitee.sop.gatewaycommon.result;
+
+/**
+ * @author tanghc
+ */
+public interface ResultExecutor {
+ String mergeResult(int responseStatus, String responseData);
+
+ String mergeError(Throwable throwable);
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultSerializer.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultSerializer.java
new file mode 100644
index 00000000..a92efffb
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultSerializer.java
@@ -0,0 +1,16 @@
+package com.gitee.sop.gatewaycommon.result;
+
+/**
+ * 对象序列化
+ * @author tanghc
+ *
+ */
+public interface ResultSerializer {
+ /**
+ * 序列化
+ *
+ * @param obj
+ * @return 返回序列化后的结果
+ */
+ String serialize(Object obj);
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/XmlResultSerializer.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/XmlResultSerializer.java
new file mode 100644
index 00000000..fdacbf55
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/XmlResultSerializer.java
@@ -0,0 +1,16 @@
+package com.gitee.sop.gatewaycommon.result;
+
+import com.gitee.sop.gatewaycommon.util.XmlUtil;
+
+/**
+ * 序列化成xml
+ * @author tanghc
+ */
+public class XmlResultSerializer implements ResultSerializer {
+
+ @Override
+ public String serialize(Object obj) {
+ return XmlUtil.serialize(obj);
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/AppSecretManager.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/AppSecretManager.java
new file mode 100644
index 00000000..3777215b
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/AppSecretManager.java
@@ -0,0 +1,32 @@
+package com.gitee.sop.gatewaycommon.secret;
+
+import java.util.Map;
+
+/**
+ * 负责秘钥管理
+ * @author tanghc
+ */
+public interface AppSecretManager {
+
+ /**
+ * 初始化秘钥数据
+ * @param appSecretStore
+ */
+ void addAppSecret(Map appSecretStore);
+
+ /**
+ * 获取应用程序的密钥
+ *
+ * @param appKey
+ * @return 返回秘钥
+ */
+ String getSecret(String appKey);
+
+ /**
+ * 是否是合法的appKey
+ *
+ * @param appKey
+ * @return 返回appKey
+ */
+ boolean isValidAppKey(String appKey);
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/CacheAppSecretManager.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/CacheAppSecretManager.java
new file mode 100644
index 00000000..59060361
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/CacheAppSecretManager.java
@@ -0,0 +1,33 @@
+package com.gitee.sop.gatewaycommon.secret;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * appkey,secret默认管理,简单放在map中,如果要放在redis中,可以参照此方式实现AppSecretManager,然后在ApiConfig中setAppSecretManager()
+ * @author tanghc
+ *
+ */
+public class CacheAppSecretManager implements AppSecretManager {
+
+ private Map secretMap = new HashMap(64);
+
+ @Override
+ public void addAppSecret(Map appSecretStore) {
+ secretMap.putAll(appSecretStore);
+ }
+
+ @Override
+ public String getSecret(String appKey) {
+ return secretMap.get(appKey);
+ }
+
+ @Override
+ public boolean isValidAppKey(String appKey) {
+ if (appKey == null) {
+ return false;
+ }
+ return getSecret(appKey) != null;
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/FileAppSecretManager.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/FileAppSecretManager.java
new file mode 100644
index 00000000..bfbc46ff
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/FileAppSecretManager.java
@@ -0,0 +1,56 @@
+package com.gitee.sop.gatewaycommon.secret;
+
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * appkey,secret文件管理,功能同CacheAppSecretManager,这个是将appKey,secret放在属性文件中
+ * key为appKey,value为secret
+ * @author tanghc
+ *
+ */
+public class FileAppSecretManager implements AppSecretManager {
+
+ private String appSecretFile = "appSecret.properties";
+
+ private Properties properties;
+
+ @Override
+ public void addAppSecret(Map appSecretStore) {
+ properties.putAll(appSecretStore);
+ }
+
+ @Override
+ public String getSecret(String appKey) {
+ if (properties == null) {
+ try {
+ // 默认加载class根目录的appSecret.properties文件
+ DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
+ Resource resource = resourceLoader.getResource(appSecretFile);
+ properties = PropertiesLoaderUtils.loadProperties(resource);
+ } catch (IOException e) {
+ throw new RuntimeException("在类路径下找不到appSecret.properties的应用密钥的属性文件");
+ }
+ }
+
+ return properties.getProperty(appKey);
+ }
+
+ public void setAppSecretFile(String appSecretFile) {
+ this.appSecretFile = appSecretFile;
+ }
+
+ @Override
+ public boolean isValidAppKey(String appKey) {
+ if(appKey == null){
+ return false;
+ }
+ return getSecret(appKey) != null;
+ }
+}
+
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiHttpSession.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiHttpSession.java
new file mode 100644
index 00000000..9cf28712
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiHttpSession.java
@@ -0,0 +1,242 @@
+package com.gitee.sop.gatewaycommon.session;
+
+import org.springframework.util.Assert;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionContext;
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.Vector;
+
+/**
+ * @author tanghc
+ */
+@SuppressWarnings("deprecation")
+public class ApiHttpSession implements HttpSession, Serializable {
+ private static final long serialVersionUID = 946272038219216222L;
+
+ private final String id;
+
+ private final long creationTime = System.currentTimeMillis();
+
+ private int maxInactiveInterval;
+
+ private long lastAccessedTime = System.currentTimeMillis();
+
+ private final ServletContext servletContext;
+
+ private final Map attributes = new LinkedHashMap();
+
+ private boolean invalid = false;
+
+ private boolean isNew = true;
+
+ /**
+ * Create a new ApiHttpSession
+ */
+ public ApiHttpSession() {
+ this(null);
+ }
+
+ /**
+ * Create a new ApiHttpSession.
+ *
+ * @param servletContext
+ * the ServletContext that the session runs in
+ */
+ public ApiHttpSession(ServletContext servletContext) {
+ this(servletContext, null);
+ }
+
+ /**
+ * Create a new ApiHttpSession.
+ *
+ * @param servletContext
+ * the ServletContext that the session runs in
+ * @param id
+ * a unique identifier for this session
+ */
+ public ApiHttpSession(ServletContext servletContext, String id) {
+ this.servletContext = servletContext;
+ this.id = this.buildId(id);
+ }
+
+ protected String buildId(String id) {
+ return (id != null ? id : UUID.randomUUID().toString().replace("-", "").toUpperCase());
+ }
+
+ @Override
+ public long getCreationTime() {
+ return this.creationTime;
+ }
+
+ @Override
+ public String getId() {
+ return this.id;
+ }
+
+ public void access() {
+ this.lastAccessedTime = System.currentTimeMillis();
+ this.isNew = false;
+ }
+
+ @Override
+ public long getLastAccessedTime() {
+ return this.lastAccessedTime;
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return this.servletContext;
+ }
+
+ @Override
+ public void setMaxInactiveInterval(int interval) {
+ this.maxInactiveInterval = interval;
+ }
+
+ @Override
+ public int getMaxInactiveInterval() {
+ return this.maxInactiveInterval;
+ }
+
+ @Override
+ public HttpSessionContext getSessionContext() {
+ throw new UnsupportedOperationException("getSessionContext");
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ Assert.notNull(name, "Attribute name must not be null");
+ return this.attributes.get(name);
+ }
+
+ @Override
+ public Object getValue(String name) {
+ return getAttribute(name);
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ return new Vector(this.attributes.keySet()).elements();
+ }
+
+ @Override
+ public String[] getValueNames() {
+ return this.attributes.keySet().toArray(new String[this.attributes.size()]);
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ Assert.notNull(name, "Attribute name must not be null");
+ if (value != null) {
+ this.attributes.put(name, value);
+ if (value instanceof HttpSessionBindingListener) {
+ ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value));
+ }
+ } else {
+ removeAttribute(name);
+ }
+ }
+
+ @Override
+ public void putValue(String name, Object value) {
+ setAttribute(name, value);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ Assert.notNull(name, "Attribute name must not be null");
+ Object value = this.attributes.remove(name);
+ if (value instanceof HttpSessionBindingListener) {
+ ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
+ }
+ }
+
+ @Override
+ public void removeValue(String name) {
+ removeAttribute(name);
+ }
+
+ /**
+ * Clear all of this session's attributes.
+ */
+ public void clearAttributes() {
+ for (Iterator> it = this.attributes.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = it.next();
+ String name = entry.getKey();
+ Object value = entry.getValue();
+ it.remove();
+ if (value instanceof HttpSessionBindingListener) {
+ ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
+ }
+ }
+ }
+
+ @Override
+ public void invalidate() {
+ this.invalid = true;
+ clearAttributes();
+ }
+
+ public boolean isInvalid() {
+ return this.invalid;
+ }
+
+ public void setNew(boolean value) {
+ this.isNew = value;
+ }
+
+ @Override
+ public boolean isNew() {
+ return this.isNew;
+ }
+
+ /**
+ * Serialize the attributes of this session into an object that can be
+ * turned into a byte array with standard Java serialization.
+ *
+ * @return a representation of this session's serialized state
+ */
+ public Serializable serializeState() {
+ HashMap state = new HashMap();
+ for (Iterator> it = this.attributes.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = it.next();
+ String name = entry.getKey();
+ Object value = entry.getValue();
+ it.remove();
+ if (value instanceof Serializable) {
+ state.put(name, (Serializable) value);
+ } else {
+ // Not serializable... Servlet containers usually automatically
+ // unbind the attribute in this case.
+ if (value instanceof HttpSessionBindingListener) {
+ ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
+ }
+ }
+ }
+ return state;
+ }
+
+ /**
+ * Deserialize the attributes of this session from a state object created by
+ * {@link #serializeState()}.
+ *
+ * @param state
+ * a representation of this session's serialized state
+ */
+ @SuppressWarnings("unchecked")
+ public void deserializeState(Serializable state) {
+ Assert.isTrue(state instanceof Map, "Serialized state needs to be of type [java.util.Map]");
+ this.attributes.putAll((Map) state);
+ }
+
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiRedisTemplate.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiRedisTemplate.java
new file mode 100644
index 00000000..b9295f44
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiRedisTemplate.java
@@ -0,0 +1,30 @@
+package com.gitee.sop.gatewaycommon.session;
+
+import org.springframework.data.redis.connection.DefaultStringRedisConnection;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * @author tanghc
+ */
+public class ApiRedisTemplate extends RedisTemplate {
+ public ApiRedisTemplate() {
+ RedisSerializer stringSerializer = new StringRedisSerializer();
+ setKeySerializer(stringSerializer);
+ setHashKeySerializer(stringSerializer);
+ }
+
+ public ApiRedisTemplate(RedisConnectionFactory connectionFactory) {
+ this();
+ setConnectionFactory(connectionFactory);
+ afterPropertiesSet();
+ }
+
+ @Override
+ protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
+ return new DefaultStringRedisConnection(connection);
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiSessionManager.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiSessionManager.java
new file mode 100644
index 00000000..9246a5d0
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiSessionManager.java
@@ -0,0 +1,100 @@
+package com.gitee.sop.gatewaycommon.session;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * session管理,默认存放的是{@link ApiHttpSession}。采用谷歌guava缓存实现。
+ *
+ * @author tanghc
+ *
+ */
+public class ApiSessionManager implements SessionManager {
+ private static Logger logger = LoggerFactory.getLogger(ApiSessionManager.class);
+
+ private int sessionTimeout = 20;
+
+ private LoadingCache cache;
+
+ public ApiSessionManager() {
+ cache = this.buildCache();
+ }
+
+ @Override
+ public HttpSession getSession(String sessionId) {
+ if(sessionId == null) {
+ return this.createSession(sessionId);
+ }
+ try {
+ HttpSession session = cache.get(sessionId);
+ return session;
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ throw ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getException();
+ }
+ }
+
+ /**
+ * 创建一个session
+ *
+ * @param sessionId 传null将返回一个新session
+ * @return 返回session
+ */
+ protected HttpSession createSession(String sessionId) {
+ ServletContext servletContext = getServletContext();
+ HttpSession session = this.newSession(sessionId, servletContext);
+ session.setMaxInactiveInterval(getSessionTimeout());
+ this.cache.put(session.getId(), session);
+ return session;
+ }
+
+ /**
+ * 返回新的session实例
+ *
+ * @param sessionId
+ * @param servletContext
+ * @return 返回session
+ */
+ protected HttpSession newSession(String sessionId, ServletContext servletContext) {
+ return new ApiHttpSession(servletContext, sessionId);
+ }
+
+ protected ServletContext getServletContext() {
+ return ApiContext.getServletContext();
+ }
+
+ protected LoadingCache buildCache() {
+ return CacheBuilder.newBuilder().expireAfterAccess(getSessionTimeout(), TimeUnit.MINUTES)
+ .build(new CacheLoader() {
+ // 找不到sessionId对应的HttpSession时,进入这个方法
+ // 找不到就新建一个
+ @Override
+ public HttpSession load(String sessionId) throws Exception {
+ return createSession(sessionId);
+ }
+ });
+ }
+
+ public void setSessionTimeout(int sessionTimeout) {
+ this.sessionTimeout = sessionTimeout;
+ }
+
+ /**
+ * 过期时间,分钟,默认20分钟
+ *
+ * @return 返回过期时间
+ */
+ public int getSessionTimeout() {
+ return sessionTimeout;
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/RedisHttpSession.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/RedisHttpSession.java
new file mode 100644
index 00000000..ac23f5fe
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/RedisHttpSession.java
@@ -0,0 +1,264 @@
+package com.gitee.sop.gatewaycommon.session;
+
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.util.Assert;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * RedisHttpSession
+ *
+ * @author tanghc
+ */
+@SuppressWarnings("deprecation")
+public class RedisHttpSession implements HttpSession, Serializable {
+ private static final long serialVersionUID = -8081963657251144855L;
+
+ private static final int SEC60 = 60;
+
+ private static final String SESSION_ATTR = "session_attr:";
+ private static final String CREATION_TIME = "creationTime";
+ private static final String LAST_ACCESSED_TIME = "lastAccessedTime";
+ private static final String MAX_INACTIVE_INTERVAL = "maxInactiveInterval";
+
+ /**
+ * 存入redis的key
+ */
+ private String key;
+
+ /**
+ * sessionId
+ */
+ private String id;
+
+ private ServletContext servletContext;
+
+ private RedisTemplate redisTemplate;
+
+ private RedisHttpSession() {
+ }
+
+
+ protected static String buildId(String id) {
+ return (id != null ? id : UUID.randomUUID().toString().replace("-", "").toUpperCase());
+ }
+
+ public static String buildKey(String keyPrefix, String sessionId) {
+ Assert.notNull(keyPrefix, "sessionPrefix不能为null");
+ return keyPrefix + sessionId;
+ }
+
+ /**
+ * 创建新的session
+ *
+ * @param servletContext
+ * @param sessionId
+ * @param sessionTimeout 过期时间,单位秒
+ * @param redisTemplate redis客户端
+ * @param keyPrefix 存入的key前缀
+ * @return 返回session
+ */
+ public static RedisHttpSession createNewSession(ServletContext servletContext, String sessionId, int sessionTimeout, RedisTemplate redisTemplate, String keyPrefix) {
+ Assert.notNull(redisTemplate, "redisTemplate can not null.");
+ Assert.notNull(sessionId, "sessionId can not null.");
+ Assert.notNull(keyPrefix, "keyPrefix can not null.");
+ RedisHttpSession redisHttpSession = new RedisHttpSession();
+ redisHttpSession.setId(sessionId);
+ redisHttpSession.setKey(buildKey(keyPrefix, sessionId));
+ redisHttpSession.setRedisTemplate(redisTemplate);
+ redisHttpSession.setServletContext(servletContext);
+
+ long creationTime = System.currentTimeMillis();
+ // 过期时间,分转换成秒
+ int maxInactiveInterval = sessionTimeout * SEC60;
+
+ redisHttpSession.setCreationTime(creationTime);
+ redisHttpSession.setLastAccessedTime(creationTime);
+ redisHttpSession.setMaxInactiveInterval(maxInactiveInterval);
+
+ redisHttpSession.refresh();
+
+ return redisHttpSession;
+ }
+
+ /**
+ * 创建已经存在的session,数据在redis里面
+ *
+ * @param sessionId
+ * @param servletContext
+ * @param redisTemplate redis客户端
+ * @param keyPrefix 存入的key前缀
+ * @return 返回session
+ */
+ public static RedisHttpSession createExistSession(String sessionId, ServletContext servletContext, RedisTemplate redisTemplate, String keyPrefix) {
+ Assert.notNull(redisTemplate, "redisTemplate can not null.");
+ Assert.notNull(sessionId, "sessionId can not null.");
+ Assert.notNull(keyPrefix, "keyPrefix can not null.");
+ RedisHttpSession redisHttpSession = new RedisHttpSession();
+ redisHttpSession.setId(sessionId);
+ redisHttpSession.setKey(buildKey(keyPrefix, sessionId));
+ redisHttpSession.setRedisTemplate(redisTemplate);
+ redisHttpSession.setServletContext(servletContext);
+
+ redisHttpSession.refresh();
+
+ return redisHttpSession;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+
+ public void setServletContext(ServletContext servletContext) {
+ this.servletContext = servletContext;
+ }
+
+ public void setCreationTime(long creationTime) {
+ this.redisTemplate.opsForHash().put(key, CREATION_TIME, String.valueOf(creationTime));
+ }
+
+ @Override
+ public long getCreationTime() {
+ Object createTime = this.redisTemplate.opsForHash().get(key, CREATION_TIME);
+ return Long.valueOf(String.valueOf(createTime));
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public long getLastAccessedTime() {
+ Object lastAccessedTime = this.redisTemplate.opsForHash().get(key, LAST_ACCESSED_TIME);
+ return Long.valueOf(String.valueOf(lastAccessedTime));
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return servletContext;
+ }
+
+ @Override
+ public void setMaxInactiveInterval(int interval) {
+ this.redisTemplate.opsForHash().put(key, MAX_INACTIVE_INTERVAL, String.valueOf(interval));
+ }
+
+ @Override
+ public int getMaxInactiveInterval() {
+ Object maxInactiveInterval = this.redisTemplate.opsForHash().get(key, MAX_INACTIVE_INTERVAL);
+ return Integer.valueOf(String.valueOf(maxInactiveInterval));
+ }
+
+ @Override
+ public HttpSessionContext getSessionContext() {
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return this.redisTemplate.opsForHash().get(key, SESSION_ATTR + name);
+ }
+
+ @Override
+ public Object getValue(String name) {
+ return getAttribute(name);
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ return Collections.enumeration(getAttributeKeys());
+ }
+
+ private Set getAttributeKeys() {
+ Set keys = this.redisTemplate.opsForHash().keys(key);
+ Set attrNames = new HashSet<>();
+ for (Object key : keys) {
+ String k = String.valueOf(key);
+ if (k.startsWith(SESSION_ATTR)) {
+ attrNames.add(k.substring(SESSION_ATTR.length()));
+ }
+ }
+ return attrNames;
+ }
+
+
+ @Override
+ public String[] getValueNames() {
+ return getAttributeKeys().toArray(new String[0]);
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ this.redisTemplate.opsForHash().put(key, SESSION_ATTR + name, value);
+ }
+
+ @Override
+ public void putValue(String name, Object value) {
+ setAttribute(name, value);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ this.redisTemplate.opsForHash().delete(key, name);
+ }
+
+ @Override
+ public void removeValue(String name) {
+ removeAttribute(name);
+ }
+
+ @Override
+ public void invalidate() {
+ this.redisTemplate.delete(key);
+ }
+
+ @Override
+ public boolean isNew() {
+ return false;
+ }
+
+ /**
+ * update expireTime,accessTime
+ */
+ public void refresh() {
+ // token更新过期时间
+ this.redisTemplate.expire(key, getMaxInactiveInterval(), TimeUnit.SECONDS);
+ // 设置访问时间
+ this.setLastAccessedTime(System.currentTimeMillis());
+ }
+
+
+ public void setLastAccessedTime(long lastAccessedTime) {
+ this.redisTemplate.opsForHash().put(key, LAST_ACCESSED_TIME, String.valueOf(lastAccessedTime));
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public boolean isInvalidated() {
+ return !this.redisTemplate.hasKey(key);
+ }
+
+
+ public void setRedisTemplate(RedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
+
+
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/RedisSessionManager.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/RedisSessionManager.java
new file mode 100644
index 00000000..ebf96ceb
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/RedisSessionManager.java
@@ -0,0 +1,100 @@
+package com.gitee.sop.gatewaycommon.session;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.util.Assert;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import java.util.UUID;
+
+/**
+ * SessionManager的redis实现,使用redis管理session
+ *
+ * @author tanghc
+ *
+ */
+public class RedisSessionManager implements SessionManager {
+
+ private ApiRedisTemplate redisTemplate;
+
+ /** 过期时间,30分钟 */
+ private int sessionTimeout = 30;
+ /** 存入redis中key的前缀 */
+ private String keyPrefix = "session:";
+
+ public RedisSessionManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
+ Assert.notNull(redisTemplate, "RedisSessionManager中的redisTemplate不能为null");
+ this.redisTemplate = new ApiRedisTemplate(redisTemplate.getConnectionFactory());
+ }
+
+ @Override
+ public HttpSession getSession(String sessionId) {
+ return this.getSession(sessionId, this.keyPrefix);
+ }
+
+ public HttpSession getSession(String sessionId, String keyPrefix) {
+ if (this.hasKey(sessionId)) {
+ return RedisHttpSession.createExistSession(sessionId, getServletContext(), redisTemplate, keyPrefix);
+ } else {
+ sessionId = this.buildSessionId(sessionId);
+ return RedisHttpSession.createNewSession(getServletContext(), sessionId, this.getSessionTimeout(),
+ redisTemplate, keyPrefix);
+ }
+ }
+
+ /**
+ * 构建sessionId
+ * @param id
+ * @return 返回sessionid
+ */
+ public String buildSessionId(String id) {
+ return (id != null ? id : UUID.randomUUID().toString().replace("-", "").toUpperCase());
+ }
+
+ public boolean hasKey(String sessionId) {
+ if (sessionId == null) {
+ return false;
+ } else {
+ String key = RedisHttpSession.buildKey(this.keyPrefix, sessionId);
+ return redisTemplate.hasKey(key);
+ }
+ }
+
+ public ServletContext getServletContext() {
+ return ApiContext.getServletContext();
+ }
+
+ public int getSessionTimeout() {
+ return sessionTimeout;
+ }
+
+ /**
+ * 设置session过期时间,单位分钟
+ * @param sessionTimeout
+ */
+ public void setSessionTimeout(int sessionTimeout) {
+ this.sessionTimeout = sessionTimeout;
+ }
+
+ public ApiRedisTemplate getRedisTemplate() {
+ return redisTemplate;
+ }
+
+ public void setRedisTemplate(ApiRedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
+
+ public String getKeyPrefix() {
+ return keyPrefix;
+ }
+
+ /**
+ * 设置存入redis中key的前缀,默认为"session:"
+ * @param keyPrefix
+ */
+ public void setKeyPrefix(String keyPrefix) {
+ this.keyPrefix = keyPrefix;
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/SessionManager.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/SessionManager.java
new file mode 100644
index 00000000..e899e725
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/SessionManager.java
@@ -0,0 +1,20 @@
+package com.gitee.sop.gatewaycommon.session;
+
+import javax.servlet.http.HttpSession;
+
+/**
+ * session管理
+ *
+ * @author tanghc
+ *
+ */
+public interface SessionManager {
+
+ /**
+ * 根据sessionId获取session
+ *
+ * @param sessionId 客户端传过来的sessionId,为null时创建一个新session
+ * @return 返回session
+ */
+ HttpSession getSession(String sessionId);
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/AESUtil.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/AESUtil.java
new file mode 100644
index 00000000..f8b6beb8
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/AESUtil.java
@@ -0,0 +1,146 @@
+package com.gitee.sop.gatewaycommon.util;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * AES-128 ECB加密.
+ *
+ *
+ * 字符集:UTF-8
+ * 算法模式:ECB
+ * 数据块:128位
+ * 补码方式:PKCS5Padding
+ * 加密结果编码方式:Base64
+ *
+ *
+ * @author tanghc
+ *
+ */
+public class AESUtil {
+ private static final String UTF8 = "UTF-8";
+ private static final String ALGORITHM = "AES";
+ /** 默认的加密算法 */
+ private static final String ALGORITHM_CIPHER = "AES/ECB/PKCS5Padding";
+
+ private static final int LIMIT_LEN = 16;
+
+ /**
+ * 生成一个SecretKey
+ * @param password 长度必须小于等于16
+ * @return 返回SecretKey
+ */
+ public static SecretKey getSecretKey(String password) {
+ byte[] passwordData = password.getBytes();
+ if(passwordData.length > LIMIT_LEN) {
+ throw new IllegalArgumentException("password 长度必须小于等于16");
+ }
+ // 创建一个空的16位字节数组(默认值为0),16byte(128bit)
+ byte[] keyData = new byte[16];
+ System.arraycopy(passwordData, 0, keyData, 0, passwordData.length);
+
+ return new SecretKeySpec(keyData, ALGORITHM);
+ }
+
+ /**
+ * 加密
+ * @param data 待加密数据
+ * @param password 密码
+ * @return 返回加密成功后数据
+ * @throws Exception
+ */
+ public static byte[] encrypt(byte[] data, String password) throws Exception {
+ SecretKey secretKey = getSecretKey(password);
+ // Ciphr完成加密或解密工作类
+ Cipher cipher = Cipher.getInstance(ALGORITHM_CIPHER);
+ // 对Cipher初始化,解密模式
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ // 加密data
+ return cipher.doFinal(data);
+ }
+
+ /**
+ * 解密
+ * @param data 待解密数据
+ * @param password 密码
+ * @return 返回解密后的数据
+ * @throws Exception
+ */
+ public static byte[] decrypt(byte[] data, String password) throws Exception {
+ SecretKey secretKey = getSecretKey(password);
+ // Cipher完成加密或解密工作类
+ Cipher cipher = Cipher.getInstance(ALGORITHM_CIPHER);
+ // 对Cipher初始化,解密模式
+ cipher.init(Cipher.DECRYPT_MODE, secretKey);
+ // 解密data
+ return cipher.doFinal(data);
+ }
+
+ /**
+ * 文本加密
+ * @param content 明文
+ * @param password 密码
+ * @return 返回base64内容
+ * @throws Exception
+ */
+ public static String encryptToBase64String(String content, String password) throws Exception {
+ byte[] data = content.getBytes(UTF8);
+ byte[] result = encrypt(data, password);
+ return Base64.encodeBase64String(result);
+ }
+
+ /**
+ * 文本解密
+ * @param base64String 待解密文本
+ * @param password 密码
+ * @return 返回明文
+ * @throws Exception
+ */
+ public static String decryptFromBase64String(String base64String, String password) throws Exception {
+ byte[] data = Base64.decodeBase64(base64String);
+ byte[] contentData = decrypt(data, password);
+ return new String(contentData, UTF8);
+ }
+
+ /**
+ * 文本加密
+ * @param content 明文
+ * @param password 密码
+ * @return 返回16进制内容
+ * @throws Exception
+ */
+ public static String encryptToHex(String content, String password) throws Exception {
+ byte[] data = content.getBytes(UTF8);
+ byte[] result = encrypt(data, password);
+ return Hex.encodeHexString(result);
+ }
+
+ /**
+ * 文本解密
+ * @param hex 待解密文本
+ * @param password 密码
+ * @return 返回明文
+ * @throws Exception
+ */
+ public static String decryptFromHex(String hex, String password) throws Exception {
+ byte[] data = Hex.decodeHex(hex);
+ byte[] contentData = decrypt(data, password);
+ return new String(contentData,UTF8);
+ }
+
+ /*public static void main(String[] args) throws Exception {
+ String content = "我爱你";
+ String password = "1234567890123456";
+ System.out.println("password:" + password);
+
+ String ret2 = encryptToBase64String(content, password);
+ System.out.println("密文:" + ret2);
+ String content3 = decryptFromBase64String(ret2, password);
+ System.out.println(content.equals(content3));
+ }*/
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/KeyStore.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/KeyStore.java
new file mode 100644
index 00000000..5e926ba9
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/KeyStore.java
@@ -0,0 +1,25 @@
+package com.gitee.sop.gatewaycommon.util;
+
+/**
+ * @author tanghc
+ */
+public class KeyStore {
+ private String publicKey;
+ private String privateKey;
+
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ public void setPublicKey(String publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ public String getPrivateKey() {
+ return privateKey;
+ }
+
+ public void setPrivateKey(String privateKey) {
+ this.privateKey = privateKey;
+ }
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RSANewUtil.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RSANewUtil.java
new file mode 100644
index 00000000..3461e6cf
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RSANewUtil.java
@@ -0,0 +1,208 @@
+package com.gitee.sop.gatewaycommon.util;
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * RSA加解密工具
+ * @author tanghc
+ */
+public class RSANewUtil {
+ public static final String RSA_ALGORITHM = "RSA";
+ public static final String UTF8 = "UTF-8";
+
+ /**
+ * 创建公钥私钥
+ *
+ * @return 返回公私钥对
+ * @throws Exception
+ */
+ public static KeyStore createKeys() throws Exception {
+ KeyPairGenerator keyPairGeno = KeyPairGenerator.getInstance(RSA_ALGORITHM);
+ keyPairGeno.initialize(1024);
+ KeyPair keyPair = keyPairGeno.generateKeyPair();
+
+ RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+ RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+
+ KeyStore keyStore = new KeyStore();
+ keyStore.setPublicKey(Base64.encodeBase64String(publicKey.getEncoded()));
+ keyStore.setPrivateKey(Base64.encodeBase64String(privateKey.getEncoded()));
+ return keyStore;
+ }
+
+ /**
+ * 获取公钥对象
+ *
+ * @param pubKeyData 公钥数据
+ * @return 公钥对象
+ * @throws Exception
+ */
+ public static RSAPublicKey getPublicKey(byte[] pubKeyData) throws Exception {
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKeyData);
+ KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
+ return (RSAPublicKey) keyFactory.generatePublic(keySpec);
+ }
+
+ /**
+ * 获取公钥对象
+ *
+ * @param pubKey
+ * 公钥
+ * @return 返回公钥对象
+ * @throws Exception
+ */
+ public static RSAPublicKey getPublicKey(String pubKey) throws Exception {
+ return getPublicKey(Base64.decodeBase64(pubKey));
+
+ }
+
+ /**
+ * 获取私钥对象
+ *
+ * @param priKey
+ * 私钥
+ * @return 返回私钥对象
+ * @throws Exception
+ */
+ public static RSAPrivateKey getPrivateKey(String priKey) throws Exception {
+ return getPrivateKey(Base64.decodeBase64(priKey));
+ }
+
+ /**
+ * 通过私钥byte[]将公钥还原,适用于RSA算法
+ *
+ * @param keyBytes 私钥数据
+ * @return 返回公钥对象
+ * @throws Exception
+ */
+ public static RSAPrivateKey getPrivateKey(byte[] keyBytes) throws Exception {
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
+ return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
+
+ }
+
+ public static String encryptByPublicKey(String data, String publicKey) throws Exception {
+ return encryptByPublicKey(data, getPublicKey(publicKey));
+ }
+
+ /**
+ * 公钥加密
+ *
+ * @param data 内容
+ * @param publicKey 公钥
+ * @return 返回密文
+ * @throws Exception
+ */
+ public static String encryptByPublicKey(String data, RSAPublicKey publicKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ byte[] bytes = cipher.doFinal(data.getBytes(UTF8));
+ return Base64.encodeBase64String(bytes);
+ }
+
+ public static String decryptByPublicKey(String data, String rsaPublicKey) throws Exception {
+ return decryptByPublicKey(data, getPublicKey(rsaPublicKey));
+ }
+
+ /**
+ * 公钥解密
+ *
+ * @param data 待解密内容
+ * @param rsaPublicKey 公钥
+ * @return 返回明文
+ * @throws Exception
+ */
+ public static String decryptByPublicKey(String data, RSAPublicKey rsaPublicKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
+ byte[] inputData = Base64.decodeBase64(data);
+ byte[] bytes = cipher.doFinal(inputData);
+ return new String(bytes, UTF8);
+ }
+
+ public static String encryptByPrivateKey(String data, String privateKey) throws Exception {
+ return encryptByPrivateKey(data, getPrivateKey(privateKey));
+ }
+
+ /**
+ * 私钥加密
+ *
+ * @param data 内容
+ * @param privateKey 私钥
+ * @return 返回密文
+ * @throws Exception
+ */
+ public static String encryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, privateKey);
+ byte[] bytes = cipher.doFinal(data.getBytes(UTF8));
+ return Base64.encodeBase64String(bytes);
+ }
+
+ public static String decryptByPrivateKey(String data, String privateKey) throws Exception {
+ return decryptByPrivateKey(data, getPrivateKey(privateKey));
+ }
+
+ /**
+ * 私钥解密
+ *
+ * @param data 待解密内容
+ * @param privateKey 私钥
+ * @return 返回明文
+ * @throws Exception
+ */
+ public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ byte[] inputData = Base64.decodeBase64(data);
+ byte[] bytes = cipher.doFinal(inputData);
+ return new String(bytes, UTF8);
+ }
+
+
+ /*
+ pubKey:
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCG/iIZZzb16PxKqslkDMYa4tVFb3IVPBpLj4BgHQmDfe843sG4gkJIPXCm7+t6QxIbfDfynBpqZJLvu0c6E7TqlCtynBIlRFOBZrQVNEFkaanR2Kln3vd3CIidR571UstOC32XDyqAQNlvjD19zeIDVfmLa0Q+Or0zaxY99QwBHwIDAQAB
+priKey:
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIb+IhlnNvXo/EqqyWQMxhri1UVvchU8GkuPgGAdCYN97zjewbiCQkg9cKbv63pDEht8N/KcGmpkku+7RzoTtOqUK3KcEiVEU4FmtBU0QWRpqdHYqWfe93cIiJ1HnvVSy04LfZcPKoBA2W+MPX3N4gNV+YtrRD46vTNrFj31DAEfAgMBAAECgYBiNPQdwwcq86rHr2QAE4L0AF3ju+YlKKqAmg9s3PMU5ENq/jO0xZ7u6zPPXu/S7IR51m7lY0ecazqyiW6SA9AzYH7ImWWkZ4stZ03beTB2US3cSeJIkugoexoN5fQRAGZiZezTLs91CeJivESOZyDKnnQdgJ49mveBV5OvievD8QJBAMztpqiWWavdR4tqQ+plat+rwYoXqejsK3Hyfg0pVJqEdazve2sr74rla7yI9P47ZAh1sklCv0CO//ctICv366UCQQCoop3T0FeZtbKJG+fHzZvpAe63tXpdhLMaQvTBuXLG8vi78Wyfhg5r7HOWR0Z1V7nzF1gzMywL53Pmkq9tB65zAkAiHu/A4kfL9ewTqn3kaT6CP3baJ1aDEc+qCVYzms4bbDKruLQ0A/y+g7SMj8E7E2h0gCRPTm3JsgWsgjb5Gy6BAkAA8mjQd6sGQe7utilnBdCKTmh4v5wgSk53J0kYjWIHm/WpmIFzo90Q3hMIFP5gSk3Q/6CPKQpmRrZv5QL3KcPhAkEAuMoQbij/7hyLlIxRHZs2SMXxfHPiZgDc6rVi1KNxeq8HXTlERi7Npc2Uz5TeWN4JwBBx9uA50zowk9iS05nclQ==
+用公钥加密mi : c3B0jtMdvkqrgaPxHZCK2cXMUQC2QzLud2ouLMNx0nBAj9k2/ytOuVJViTGe/DozB/ky5jvl4spD9Ey6aTMrwLHfQVhn0gRJ+wHcmx/51dXQDIgsldt6bf7YpdPdnghBjQz2+P5RhqSkeFDbTZKkl2BNaLE78a/OyWWeCGwN+4s=
+true
+用私钥加密mi2 : QU5vDnQ1ukj8GsauokFlgcB/g61U882tj82wHGrrqHEnvaga+4cXjML9RhjpZtKqwDGZTCujsmpynDk4qek6IGOQ/oxdWLwV4ZNjfa/oqA8OFDothVUT8wpqCu9kOYHrTdGybmXD0dB2Iy1/AMQTAgPNNXXiRXdvsz9xWYTV6z8=
+true
+ */
+ /*public static void main(String[] args) throws Exception {
+ KeyStore keys = createKeys();
+ String pubKey = keys.getPublicKey();
+ System.out.println("pubKey:");
+ System.out.println(pubKey);
+ String priKey = keys.getPrivateKey();
+ System.out.println("priKey:");
+ System.out.println(priKey);
+
+ String ming = "1234567890123456";
+ // 用公钥加密
+ String mi = encryptByPublicKey(ming, pubKey);
+ System.out.println("用公钥加密mi : " + mi);
+ // 用私钥解密
+ System.out.println(ming.equals(decryptByPrivateKey(mi, priKey)));
+
+ // 用私钥加密
+ String mi2 = encryptByPrivateKey(ming, priKey);
+
+ System.out.println("用私钥加密mi2 : " + mi2);
+ // 用公钥解密
+ String ming2 = decryptByPublicKey(mi2, pubKey);
+ System.out.println(ming.equals(ming2));
+ }*/
+
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RSAUtil.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RSAUtil.java
new file mode 100644
index 00000000..4b36c739
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RSAUtil.java
@@ -0,0 +1,312 @@
+package com.gitee.sop.gatewaycommon.util;
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * RSA加解密工具
+ * @author tanghc
+ */
+public class RSAUtil {
+ public static String RSA_ALGORITHM = "RSA";
+
+ /**
+ * 创建公钥私钥
+ *
+ * @return 返回公私钥对
+ * @throws Exception
+ */
+ public static KeyStore createKeys() throws Exception {
+ KeyPairGenerator keyPairGeno = KeyPairGenerator.getInstance(RSA_ALGORITHM);
+ keyPairGeno.initialize(1024);
+ KeyPair keyPair = keyPairGeno.generateKeyPair();
+
+ RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+ RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+
+ KeyStore keyStore = new KeyStore();
+ keyStore.setPublicKey(Base64.encodeBase64String(publicKey.getEncoded()));
+ keyStore.setPrivateKey(Base64.encodeBase64String(privateKey.getEncoded()));
+ return keyStore;
+ }
+
+ /**
+ * 获取公钥对象
+ *
+ * @param pubKeyData 公钥
+ * @return 返回公钥对象
+ * @throws Exception
+ */
+ public static RSAPublicKey getPublicKey(byte[] pubKeyData) throws Exception {
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKeyData);
+ KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
+ return (RSAPublicKey) keyFactory.generatePublic(keySpec);
+ }
+
+ /**
+ * 获取公钥对象
+ *
+ * @param pubKey
+ * 公钥
+ * @return 返回私钥对象
+ * @throws Exception
+ */
+ public static RSAPublicKey getPublicKey(String pubKey) throws Exception {
+ return getPublicKey(Base64.decodeBase64(pubKey));
+
+ }
+
+ /**
+ * 获取私钥对象
+ *
+ * @param priKey
+ * 私钥
+ * @return 私钥对象
+ * @throws Exception
+ */
+ public static RSAPrivateKey getPrivateKey(String priKey) throws Exception {
+ return getPrivateKey(Base64.decodeBase64(priKey));
+ }
+
+ /**
+ * 通过私钥byte[]将公钥还原,适用于RSA算法
+ *
+ * @param keyBytes
+ * @return 返回私钥
+ * @throws Exception
+ */
+ public static RSAPrivateKey getPrivateKey(byte[] keyBytes) throws Exception {
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
+ return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
+
+ }
+
+ /**
+ * 公钥加密
+ *
+ * @param data 待加密内容
+ * @param publicKey 公钥
+ * @return 返回密文
+ * @throws Exception
+ */
+ public static String encryptByPublicKey(String data, RSAPublicKey publicKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ // 模长
+ int key_len = publicKey.getModulus().bitLength() / 8;
+ // 加密数据长度 <= 模长-11
+ String[] datas = splitString(data, key_len - 11);
+ String mi = "";
+ // 如果明文长度大于模长-11则要分组加密
+ for (String s : datas) {
+ mi += bcd2Str(cipher.doFinal(s.getBytes()));
+ }
+ return mi;
+ }
+ public static String encryptByPrivateKey(String data, String privateKey) throws Exception {
+ return encryptByPrivateKey(data, getPrivateKey(privateKey));
+ }
+
+ /**
+ * 私钥加密
+ *
+ * @param data 待加密数据
+ * @param privateKey 私钥
+ * @return 返回密文
+ * @throws Exception
+ */
+ public static String encryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, privateKey);
+ // 模长
+ int key_len = privateKey.getModulus().bitLength() / 8;
+ // 加密数据长度 <= 模长-11
+ String[] datas = splitString(data, key_len - 11);
+ String mi = "";
+ // 如果明文长度大于模长-11则要分组加密
+ for (String s : datas) {
+ mi += bcd2Str(cipher.doFinal(s.getBytes()));
+ }
+ return mi;
+ }
+
+ public static String decryptByPrivateKey(String data, String privateKey) throws Exception {
+ return decryptByPrivateKey(data, getPrivateKey(privateKey));
+ }
+
+ /**
+ * 私钥解密
+ *
+ * @param data 待解密内容
+ * @param privateKey 私钥
+ * @return 返回明文
+ * @throws Exception
+ */
+ public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ // 模长
+ int key_len = privateKey.getModulus().bitLength() / 8;
+ byte[] bytes = data.getBytes();
+ byte[] bcd = ASCII_To_BCD(bytes, bytes.length);
+ // 如果密文长度大于模长则要分组解密
+ String ming = "";
+ byte[][] arrays = splitArray(bcd, key_len);
+ for (byte[] arr : arrays) {
+ ming += new String(cipher.doFinal(arr));
+ }
+ return ming;
+ }
+
+ /**
+ * 公钥解密
+ *
+ * @param data 待解密内容
+ * @param rsaPublicKey 公钥
+ * @return 返回明文
+ * @throws Exception
+ */
+ public static String decryptByPublicKey(String data, RSAPublicKey rsaPublicKey) throws Exception {
+ Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
+ // 模长
+ int key_len = rsaPublicKey.getModulus().bitLength() / 8;
+ byte[] bytes = data.getBytes();
+ byte[] bcd = ASCII_To_BCD(bytes, bytes.length);
+ // 如果密文长度大于模长则要分组解密
+ String ming = "";
+ byte[][] arrays = splitArray(bcd, key_len);
+ for (byte[] arr : arrays) {
+ ming += new String(cipher.doFinal(arr));
+ }
+ return ming;
+ }
+
+
+ /**
+ * ASCII码转BCD码
+ *
+ */
+ public static byte[] ASCII_To_BCD(byte[] ascii, int asc_len) {
+ byte[] bcd = new byte[asc_len / 2];
+ int j = 0;
+ for (int i = 0; i < (asc_len + 1) / 2; i++) {
+ bcd[i] = asc_to_bcd(ascii[j++]);
+ bcd[i] = (byte) (((j >= asc_len) ? 0x00 : asc_to_bcd(ascii[j++]) & 0xff) + (bcd[i] << 4));
+ }
+ return bcd;
+ }
+
+ public static byte asc_to_bcd(byte asc) {
+ byte bcd;
+
+ if ((asc >= '0') && (asc <= '9')) {
+ bcd = (byte) (asc - '0');
+ } else if ((asc >= 'A') && (asc <= 'F')) {
+ bcd = (byte) (asc - 'A' + 10);
+ } else if ((asc >= 'a') && (asc <= 'f')) {
+ bcd = (byte) (asc - 'a' + 10);
+ } else {
+ bcd = (byte) (asc - 48);
+ }
+ return bcd;
+ }
+
+ /**
+ * BCD转字符串
+ */
+ public static String bcd2Str(byte[] bytes) {
+ char[] temp = new char[bytes.length * 2];
+ char val;
+
+ for (int i = 0; i < bytes.length; i++) {
+ val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
+ temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
+
+ val = (char) (bytes[i] & 0x0f);
+ temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
+ }
+ return new String(temp);
+ }
+
+ /**
+ * 拆分字符串
+ */
+ public static String[] splitString(String string, int len) {
+ int x = string.length() / len;
+ int y = string.length() % len;
+ int z = 0;
+ if (y != 0) {
+ z = 1;
+ }
+ String[] strings = new String[x + z];
+ String str = "";
+ for (int i = 0; i < x + z; i++) {
+ if (i == x + z - 1 && y != 0) {
+ str = string.substring(i * len, i * len + y);
+ } else {
+ str = string.substring(i * len, i * len + len);
+ }
+ strings[i] = str;
+ }
+ return strings;
+ }
+
+ /**
+ * 拆分数组
+ */
+ public static byte[][] splitArray(byte[] data, int len) {
+ int x = data.length / len;
+ int y = data.length % len;
+ int z = 0;
+ if (y != 0) {
+ z = 1;
+ }
+ byte[][] arrays = new byte[x + z][];
+ byte[] arr;
+ for (int i = 0; i < x + z; i++) {
+ arr = new byte[len];
+ if (i == x + z - 1 && y != 0) {
+ System.arraycopy(data, i * len, arr, 0, y);
+ } else {
+ System.arraycopy(data, i * len, arr, 0, len);
+ }
+ arrays[i] = arr;
+ }
+ return arrays;
+ }
+
+ /*public static void main(String[] args) throws Exception {
+ KeyStore keys = createKeys();
+ String pubKey = keys.getPublicKey();
+ System.out.println("pubKey:");
+ System.out.println(pubKey);
+ String priKey = keys.getPrivateKey();
+ System.out.println("priKey:");
+ System.out.println(priKey);
+
+ String ming = "6460201d23954f8e90cf79b818844ca0";
+ // 用公钥加密
+ String mi = encryptByPublicKey(ming, getPublicKey(pubKey));
+ System.out.println("mi : " + mi);
+ // 用私钥解密
+ System.out.println("ming : " + decryptByPrivateKey(mi, getPrivateKey(priKey)));
+
+ // 用私钥加密
+ String mi2 = encryptByPrivateKey(ming, getPrivateKey(priKey));
+
+ System.out.println("mi2 : " + mi2);
+ // 用公钥解密
+ System.out.println("ming2 : " + decryptByPublicKey(mi2, getPublicKey(pubKey)));
+ }*/
+
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RedisLockUtil.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RedisLockUtil.java
new file mode 100644
index 00000000..2221badc
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RedisLockUtil.java
@@ -0,0 +1,139 @@
+package com.gitee.sop.gatewaycommon.util;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.RedisScript;
+
+import java.util.Collections;
+
+/**
+ * redis分布式锁,详见:https://blog.csdn.net/thc1987/article/details/80355155
+ * 思路:
+ *
+ * 用SETNX命令,SETNX只有在key不存在时才返回成功。这意味着只有一个线程可以成功运行SETNX命令,而其他线程会失败,然后不断重试,直到它们能建立锁。
+ * 然后使用脚本来创建锁,因为一个redis脚本同一时刻只能运行一次。
+ * 创建锁代码:
+ *
+-- KEYS[1] key,
+-- ARGV[1] value,
+-- ARGV[2] expireTimeMilliseconds
+
+if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then
+ redis.call('pexpire', KEYS[1], ARGV[2])
+ return 1
+else
+ return 0
+end
+ *
+ * 最后使用脚本来解锁。
+ * 解锁代码:
+ *
+ *
+-- KEYS[1] key,
+-- ARGV[1] value
+if redis.call("get", KEYS[1]) == ARGV[1]
+then
+ return redis.call("del", KEYS[1])
+else
+ return 0
+end
+ *
+ *
+ *
+ * @author tanghc
+ */
+public class RedisLockUtil {
+
+ private static final Long SUCCESS = 1L;
+
+ /** 加锁脚本 */
+ private static final String SCRIPT_LOCK = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('pexpire', KEYS[1], ARGV[2]) return 1 else return 0 end";
+ /** 解锁脚本 */
+ private static final String SCRIPT_UNLOCK = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
+ /** 加锁脚本sha1值 */
+ private static final String SCRIPT_LOCK_SHA1 = DigestUtils.sha1Hex(SCRIPT_LOCK);
+ /** 解锁脚本sha1值 */
+ private static final String SCRIPT_UNLOCK_SHA1 = DigestUtils.sha1Hex(SCRIPT_UNLOCK);
+
+ /**
+ * 尝试获取分布式锁
+ *
+ * @param redisTemplate
+ * Redis客户端
+ * @param lockKey
+ * 锁
+ * @param requestId
+ * 请求标识
+ * @param expireTimeMilliseconds
+ * 超期时间,多少毫秒后这把锁自动释放
+ * @return 返回true表示拿到锁
+ */
+ @SuppressWarnings("unchecked")
+ public static boolean tryGetDistributedLock(@SuppressWarnings("rawtypes") final RedisTemplate redisTemplate,
+ final String lockKey, final String requestId, final int expireTimeMilliseconds) {
+
+ Object result = redisTemplate.execute(new RedisScript() {
+ @Override
+ public String getSha1() {
+ return SCRIPT_LOCK_SHA1;
+ }
+
+ @Override
+ public Class getResultType() {
+ return Long.class;
+ }
+
+ @Override
+ public String getScriptAsString() {
+ return SCRIPT_LOCK;
+ }
+
+ },
+ // KEYS[1]
+ Collections.singletonList(lockKey),
+ // ARGV[1]
+ requestId,
+ // ARGV[2]
+ String.valueOf(expireTimeMilliseconds)
+ );
+
+ return SUCCESS.equals(result);
+ }
+
+ /**
+ * 释放分布式锁
+ *
+ * @param redisTemplate
+ * Redis客户端
+ * @param lockKey
+ * 锁
+ * @param requestId
+ * 请求标识
+ * @return 返回true表示释放锁成功
+ */
+ @SuppressWarnings("unchecked")
+ public static boolean releaseDistributedLock(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate,
+ String lockKey, String requestId) {
+
+ Object result = redisTemplate.execute(new RedisScript() {
+ @Override
+ public String getSha1() {
+ return SCRIPT_UNLOCK_SHA1;
+ }
+
+ @Override
+ public Class getResultType() {
+ return Long.class;
+ }
+
+ @Override
+ public String getScriptAsString() {
+ return SCRIPT_UNLOCK;
+ }
+
+ }, Collections.singletonList(lockKey), requestId);
+
+ return SUCCESS.equals(result);
+ }
+
+}
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RequestUtil.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RequestUtil.java
new file mode 100644
index 00000000..aec2b561
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RequestUtil.java
@@ -0,0 +1,137 @@
+package com.gitee.sop.gatewaycommon.util;
+
+import com.alibaba.fastjson.JSON;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * @author tanghc
+ */
+public class RequestUtil {
+
+ private static final String CONTENT_TYPE_URLENCODED = "application/x-www-form-urlencoded";
+ private static final String CONTENT_TYPE_JSON = "application/json";
+ private static final String CONTENT_TYPE_TEXT = "text/plain";
+
+ private static final String UTF8 = "UTF-8";
+ private static final String GET = "get";
+
+ private static final String UNKOWN = "unknown";
+ private static final String LOCAL_IP = "127.0.0.1";
+ private static final int IP_LEN = 15;
+
+ public static String getText(HttpServletRequest request) throws Exception {
+ return IOUtils.toString(request.getInputStream(), UTF8);
+ }
+
+ /**
+ * 从request中获取json。如果提交方式是application/x-www-form-urlencoded,则组装成json格式。
+ *
+ * @param request request对象
+ * @return 返回json
+ * @throws IOException
+ */
+ public static String getJson(HttpServletRequest request) throws Exception {
+ String requestJson = null;
+ String contectType = request.getContentType();
+ if (StringUtils.isBlank(contectType)) {
+ throw ErrorEnum.ISV_INVALID_CONTENT_TYPE.getErrorMeta().getException(contectType);
+ }
+
+ contectType = contectType.toLowerCase();
+
+ if (contectType.contains(CONTENT_TYPE_JSON) || contectType.contains(CONTENT_TYPE_TEXT)) {
+ requestJson = getText(request);
+ } else if (contectType.contains(CONTENT_TYPE_URLENCODED)) {
+ Map params = convertRequestParamsToMap(request);
+ requestJson = JSON.toJSONString(params);
+ } else {
+ throw ErrorEnum.ISV_INVALID_CONTENT_TYPE.getErrorMeta().getException(contectType);
+ }
+ return requestJson;
+ }
+
+ /**
+ * request中的参数转换成map
+ *
+ * @param request request对象
+ * @return 返回参数键值对
+ */
+ public static Map convertRequestParamsToMap(HttpServletRequest request) {
+ Map paramMap = request.getParameterMap();
+ if(paramMap == null || paramMap.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ Map retMap = new HashMap(paramMap.size());
+
+ Set> entrySet = paramMap.entrySet();
+
+ for (Entry entry : entrySet) {
+ String name = entry.getKey();
+ String[] values = entry.getValue();
+ if (values.length == 1) {
+ retMap.put(name, values[0]);
+ }
+ }
+ return retMap;
+ }
+
+ /**
+ * 获取客户端真实IP
+ *
+ * @param request request对象
+ * @return 返回ip
+ */
+ public static String getClientIP(HttpServletRequest request) {
+ String ipAddress = request.getHeader("x-forwarded-for");
+ if (ipAddress == null || ipAddress.length() == 0 || UNKOWN.equalsIgnoreCase(ipAddress)) {
+ ipAddress = request.getHeader("Proxy-Client-IP");
+ }
+ if (ipAddress == null || ipAddress.length() == 0 || UNKOWN.equalsIgnoreCase(ipAddress)) {
+ ipAddress = request.getHeader("WL-Proxy-Client-IP");
+ }
+ if (ipAddress == null || ipAddress.length() == 0 || UNKOWN.equalsIgnoreCase(ipAddress)) {
+ ipAddress = request.getRemoteAddr();
+ if (LOCAL_IP.equals(ipAddress)) {
+ // 根据网卡取本机配置的IP
+ try {
+ InetAddress inet = InetAddress.getLocalHost();
+ ipAddress = inet.getHostAddress();
+ } catch (UnknownHostException e) {
+ // ignore
+ }
+ }
+
+ }
+
+ // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+ if (ipAddress != null && ipAddress.length() > IP_LEN) {
+ if (ipAddress.indexOf(",") > 0) {
+ ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
+ }
+ }
+ return ipAddress;
+
+ }
+
+ /**
+ * 是否是get请求
+ * @param request request对象
+ * @return true,是
+ */
+ public static boolean isGetRequest(HttpServletRequest request) {
+ return GET.equalsIgnoreCase(request.getMethod());
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/ResponseUtil.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/ResponseUtil.java
new file mode 100644
index 00000000..5169243a
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/ResponseUtil.java
@@ -0,0 +1,119 @@
+package com.gitee.sop.gatewaycommon.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author tanghc
+ */
+public class ResponseUtil {
+ public static final String UTF_8 = "UTF-8";
+
+ private static Logger log = LoggerFactory.getLogger(ResponseUtil.class);
+
+ public static void writeJson(HttpServletResponse response, Object result) {
+ response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
+ response.setCharacterEncoding(UTF_8);
+ try {
+ response.getWriter().write(JSON.toJSONString(result));
+ } catch (IOException e) {
+ log.error("doWriter", e);
+ }
+ }
+
+ /**
+ * map转成xml
+ *
+ * @param parameters
+ * @return
+ */
+ public static String mapToXml(JSONObject parameters) {
+ String content = doMap2xml(parameters);
+ return content;
+ }
+
+ private static String doMap2xml(JSONObject parameters) {
+ StringBuffer sb = new StringBuffer();
+ Set es = parameters.entrySet();
+ Iterator it = es.iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String k = (String) entry.getKey();
+ Object v = entry.getValue();
+ if (v instanceof JSONObject) {
+ sb.append("<").append(k).append(">")
+ .append(doMap2xml((JSONObject) v))
+ .append("").append(k).append(">");
+ } else if (v instanceof JSONArray) {
+ JSONArray collection = (JSONArray) v;
+ String items = buildItems(k + "_item", collection);
+ sb.append("<").append(k).append(">")
+ .append(items)
+ .append("").append(k).append(">");
+ } else {
+ sb.append("<").append(k).append(">").append(k).append(">");
+ }
+ }
+ return sb.toString();
+ }
+
+ private static String buildItems(String key, JSONArray collection) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < collection.size(); i++) {
+ Object jsonObject = collection.get(i);
+ sb.append("<").append(key).append(">");
+ if (jsonObject instanceof JSONObject) {
+ sb.append(doMap2xml((JSONObject) jsonObject));
+ } else {
+ sb.append(jsonObject);
+ }
+ sb.append("").append(key).append(">");
+ }
+ return sb.toString();
+ }
+
+ public static void main(String[] args) {
+ Persion persion = new Persion();
+ persion.setId(1);
+ persion.setName("aaa");
+
+ String jsonString = JSON.toJSONString(persion);
+ JSONObject jsonObject = JSON.parseObject(jsonString);
+
+ String xml = mapToXml(jsonObject);
+ System.out.println(xml);
+
+ }
+
+ @Data
+ public static class Persion {
+ int id;
+ String name;
+ List items = Arrays.asList("item1", "item2");
+ List child = Arrays.asList(new Man("Jim"), new Man("Tom"));
+ }
+
+ @Data
+ public static class Man{
+ String name;
+
+ public Man(String name) {
+ this.name = name;
+ }
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/XmlUtil.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/XmlUtil.java
new file mode 100644
index 00000000..7c62ff1b
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/XmlUtil.java
@@ -0,0 +1,32 @@
+package com.gitee.sop.gatewaycommon.util;
+
+import com.gitee.sop.gatewaycommon.result.ApiResult;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.naming.NoNameCoder;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+
+/**
+ * @author tanghc
+ */
+public class XmlUtil {
+ private static XStream xStream = new XStream(new StaxDriver(new NoNameCoder()));
+ static {
+ xStream.processAnnotations(ApiResult.class);
+ xStream.aliasSystemAttribute(null, "class");
+ }
+
+ public static String serialize(Object obj) {
+ return getXStream().toXML(obj);
+ }
+
+ public static T unserialize(String xml, Class clazz) {
+ xStream.processAnnotations(clazz);
+ Object object = xStream.fromXML(xml);
+ T cast = clazz.cast(object);
+ return cast;
+ }
+
+ public static XStream getXStream() {
+ return xStream;
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/AbstractSigner.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/AbstractSigner.java
new file mode 100644
index 00000000..e06236a0
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/AbstractSigner.java
@@ -0,0 +1,36 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author tanghc
+ */
+@Slf4j
+public abstract class AbstractSigner implements Signer {
+
+ /**
+ * 构建服务端签名串
+ *
+ * @param params
+ * @param secret
+ * @return
+ */
+ protected abstract String buildServerSign(ApiParam params, String secret);
+
+ @Override
+ public boolean checkSign(HttpServletRequest request, String secret) {
+ ApiParam apiParam = ApiContext.getApiParam();
+ String clientSign = apiParam.fetchSignAndRemove();
+ if (StringUtils.isBlank(clientSign)) {
+ throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException();
+ }
+ String serverSign = buildServerSign(apiParam, secret);
+ return clientSign.equals(serverSign);
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiEncrypter.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiEncrypter.java
new file mode 100644
index 00000000..e41c9372
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiEncrypter.java
@@ -0,0 +1,60 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import com.gitee.sop.gatewaycommon.util.AESUtil;
+import com.gitee.sop.gatewaycommon.util.RSANewUtil;
+import com.gitee.sop.gatewaycommon.util.RSAUtil;
+
+/**
+ * 负责各类加解密
+ * @author tanghc
+ *
+ */
+public class ApiEncrypter implements Encrypter {
+
+ @Override
+ public String aesEncryptToHex(String content, String password) throws Exception {
+ return AESUtil.encryptToHex(content, password);
+ }
+
+ @Override
+ public String aesDecryptFromHex(String hex, String password) throws Exception {
+ return AESUtil.decryptFromHex(hex, password);
+ }
+
+ @Override
+ public String aesEncryptToBase64String(String content, String password) throws Exception {
+ return AESUtil.encryptToBase64String(content, password);
+ }
+
+ @Override
+ public String aesDecryptFromBase64String(String base64String, String password) throws Exception {
+ return AESUtil.decryptFromBase64String(base64String, password);
+ }
+
+ @Override
+ public String rsaDecryptByPrivateKey(String data, String privateKey) throws Exception {
+ return RSAUtil.decryptByPrivateKey(data, privateKey);
+ }
+
+ @Override
+ public String rsaEncryptByPrivateKey(String data, String privateKey) throws Exception {
+ return RSAUtil.encryptByPrivateKey(data, privateKey);
+ }
+
+ @Override
+ public String rsaDecryptByPrivateKeyNew(String data, String privateKey) throws Exception {
+ return RSANewUtil.decryptByPrivateKey(data, privateKey);
+ }
+
+ @Override
+ public String rsaEncryptByPrivateKeyNew(String data, String privateKey) throws Exception {
+ return RSANewUtil.encryptByPrivateKey(data, privateKey);
+ }
+
+ @Override
+ public String md5(String value) {
+ return DigestUtils.md5Hex(value);
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiSigner.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiSigner.java
new file mode 100644
index 00000000..133fa1f0
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiSigner.java
@@ -0,0 +1,56 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import org.apache.tomcat.util.buf.HexUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 签名验证实现
+ *
+ * @author tanghc
+ */
+public class ApiSigner extends AbstractSigner {
+
+ private Map signEncipherMap = new HashMap<>();
+
+ public ApiSigner() {
+ signEncipherMap.put("md5", new SignEncipherMD5());
+ signEncipherMap.put("hmac", new SignEncipherHMAC_MD5());
+ }
+
+
+ @Override
+ public String buildServerSign(ApiParam param, String secret) {
+ String signMethod = param.fetchSignMethod();
+ SignEncipher signEncipher = signEncipherMap.get(signMethod);
+ if (signEncipher == null) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException(signMethod);
+ }
+
+ // 第一步:参数排序
+ Set keySet = param.keySet();
+ List paramNames = new ArrayList(keySet);
+ Collections.sort(paramNames);
+
+ // 第二步:把所有参数名和参数值串在一起
+ StringBuilder paramNameValue = new StringBuilder();
+ for (String paramName : paramNames) {
+ paramNameValue.append(paramName).append(param.get(paramName));
+ }
+
+ // 第三步:使用MD5/HMAC加密
+ String source = paramNameValue.toString();
+ byte[] bytes = signEncipher.encrypt(source, secret);
+
+ // 第四步:把二进制转化为大写的十六进制
+ return HexUtils.toHexString(bytes).toUpperCase();
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java
new file mode 100644
index 00000000..38e937ca
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java
@@ -0,0 +1,150 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+import com.gitee.sop.gatewaycommon.bean.ApiConfig;
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import com.gitee.sop.gatewaycommon.param.ParamNames;
+import com.gitee.sop.gatewaycommon.param.UploadContext;
+import com.gitee.sop.gatewaycommon.secret.AppSecretManager;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 负责校验,校验工作都在这里
+ *
+ * @author tanghc
+ */
+public class ApiValidator implements Validator {
+ private static final Logger logger = LoggerFactory.getLogger(ApiValidator.class);
+
+ private static final int MILLISECOND_OF_ONE_SECOND = 1000;
+
+
+ private static List FORMAT_LIST = Arrays.asList("json", "xml");
+
+
+ @Override
+ public void validate(ApiParam param) {
+ ApiConfig apiConfig = ApiContext.getApiConfig();
+ if (apiConfig.isIgnoreValidate() || param.fetchIgnoreValidate()) {
+ logger.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion());
+ return;
+ }
+ if (param.fetchIgnoreSign()) {
+ logger.debug("忽略签名验证, name:{}, version:{}", param.fetchName(), param.fetchVersion());
+ } else {
+ // 需要验证签名
+ checkAppKey(param);
+ checkSign(param);
+ }
+ checkUploadFile(param);
+ checkTimeout(param);
+ checkFormat(param);
+ }
+
+ /**
+ * 校验上传文件内容
+ *
+ * @param param
+ */
+ protected void checkUploadFile(ApiParam param) {
+ UploadContext uploadContext = ApiContext.getUploadContext();
+ if (uploadContext != null) {
+ try {
+ List files = uploadContext.getAllFile();
+ for (MultipartFile file : files) {
+ // 客户端传来的文件md5
+ String clientMd5 = param.getString(file.getName());
+ if (clientMd5 != null) {
+ String fileMd5 = DigestUtils.md5Hex(file.getBytes());
+ if (!clientMd5.equals(fileMd5)) {
+ throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
+ }
+ }
+ }
+ } catch (IOException e) {
+ logger.error("验证上传文件MD5错误", e);
+ throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
+ }
+ }
+
+ }
+
+ protected void checkTimeout(ApiParam param) {
+ int timeoutSeconds = ApiContext.getApiConfig().getTimeoutSeconds();
+ // 如果设置为0,表示不校验
+ if (timeoutSeconds == 0) {
+ return;
+ }
+ if (timeoutSeconds < 0) {
+ throw new IllegalArgumentException("服务端timeoutSeconds设置错误");
+ }
+ String requestTime = param.fetchTimestamp();
+ try {
+ Date requestDate = new SimpleDateFormat(ParamNames.TIMESTAMP_PATTERN).parse(requestTime);
+ long requestMilliseconds = requestDate.getTime();
+ if (System.currentTimeMillis() - requestMilliseconds > timeoutSeconds * MILLISECOND_OF_ONE_SECOND) {
+ throw ErrorEnum.ISV_INVALID_TIMESTAMP.getErrorMeta().getException();
+ }
+ } catch (ParseException e) {
+ throw ErrorEnum.ISV_INVALID_TIMESTAMP.getErrorMeta().getException(param.fetchNameVersion());
+ }
+ }
+
+ protected void checkAppKey(ApiParam param) {
+ if (StringUtils.isEmpty(param.fetchAppKey())) {
+ throw ErrorEnum.ISV_MISSING_APP_ID.getErrorMeta().getException();
+ }
+ AppSecretManager appSecretManager = ApiContext.getApiConfig().getAppSecretManager();
+ Assert.notNull(appSecretManager, "appSecretManager未初始化");
+ boolean isTrueAppKey = appSecretManager.isValidAppKey(param.fetchAppKey());
+ if (!isTrueAppKey) {
+ throw ErrorEnum.ISV_INVALID_APP_ID.getErrorMeta().getException();
+ }
+ }
+
+ protected void checkSign(ApiParam param) {
+ String clientSign = param.fetchSign();
+ try {
+ if (StringUtils.isEmpty(param.fetchSign())) {
+ throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion(), ParamNames.SIGN_NAME);
+ }
+ String secret = ApiContext.getApiConfig().getAppSecretManager().getSecret(param.fetchAppKey());
+ if (StringUtils.isEmpty(secret)) {
+ throw ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG.getErrorMeta().getException();
+ }
+ Signer signer = ApiContext.getApiConfig().getSigner();
+ boolean isRightSign = signer.checkSign(ApiContext.getRequest(), secret);
+ // 错误的sign
+ if (!isRightSign) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion());
+ }
+ } finally {
+ // 校验过程中会移除sign,这里需要重新设置进去
+ param.setSign(clientSign);
+ }
+ }
+
+
+ protected void checkFormat(ApiParam param) {
+ String format = param.fetchFormat();
+ boolean contains = FORMAT_LIST.contains(format.toLowerCase());
+
+ if (!contains) {
+ throw ErrorEnum.ISV_INVALID_FORMAT.getErrorMeta().getException(param.fetchNameVersion(), format);
+ }
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Encrypter.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Encrypter.java
new file mode 100644
index 00000000..649304a2
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Encrypter.java
@@ -0,0 +1,95 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+/**
+ * 负责加解密
+ *
+ * @author tanghc
+ */
+public interface Encrypter {
+
+ /**
+ * AES文本加密
+ *
+ * @param content 明文
+ * @param password 密码
+ * @return 返回16进制内容
+ * @throws Exception
+ */
+ String aesEncryptToHex(String content, String password) throws Exception;
+
+ /**
+ * AES文本解密
+ *
+ * @param hex 待解密文本,16进制内容
+ * @param password 密码
+ * @return 返回明文
+ * @throws Exception
+ */
+ String aesDecryptFromHex(String hex, String password) throws Exception;
+
+ /**
+ * AES文本加密
+ *
+ * @param content 明文
+ * @param password 密码
+ * @return 返回base64内容
+ * @throws Exception
+ */
+ String aesEncryptToBase64String(String content, String password) throws Exception;
+
+ /**
+ * AES文本解密
+ *
+ * @param base64String 待解密文本,16进制内容
+ * @param password 密码
+ * @return 返回明文
+ * @throws Exception
+ */
+ String aesDecryptFromBase64String(String base64String, String password) throws Exception;
+
+ /**
+ * RSA私钥解密
+ *
+ * @param data 解密内容
+ * @param privateKey 私钥
+ * @return 返回明文
+ * @throws Exception
+ */
+ String rsaDecryptByPrivateKey(String data, String privateKey) throws Exception;
+
+ /**
+ * 新版rsa私钥解密
+ * @param data 解密内容
+ * @param privateKey 私钥
+ * @return 返回明文
+ * @throws Exception
+ */
+ String rsaDecryptByPrivateKeyNew(String data, String privateKey) throws Exception;
+
+ /**
+ * RSA私钥加密
+ *
+ * @param data 明文
+ * @param privateKey 私钥
+ * @return 返回密文
+ * @throws Exception
+ */
+ String rsaEncryptByPrivateKey(String data, String privateKey) throws Exception;
+
+ /**
+ * 新版rsa私钥加密
+ * @param data 明文
+ * @param privateKey 私钥
+ * @return 返回密文
+ * @throws Exception
+ */
+ String rsaEncryptByPrivateKeyNew(String data, String privateKey) throws Exception;
+
+ /**
+ * md5加密,全部小写
+ *
+ * @param value
+ * @return 返回md5内容
+ */
+ String md5(String value);
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/SignEncipher.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/SignEncipher.java
new file mode 100644
index 00000000..b9aaf532
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/SignEncipher.java
@@ -0,0 +1,13 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+/**
+ * @author tanghc
+ */
+public interface SignEncipher {
+ /**
+ * 签名的摘要算法
+ * @param input 待签名数据
+ * @return 返回加密后的数据
+ */
+ byte[] encrypt(String input, String secret);
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/SignEncipherHMAC_MD5.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/SignEncipherHMAC_MD5.java
new file mode 100644
index 00000000..a08fb7a4
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/SignEncipherHMAC_MD5.java
@@ -0,0 +1,37 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+import com.gitee.sop.gatewaycommon.bean.SopConstants;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * HMAC_MD5加密
+ * @author tanghc
+ */
+@Slf4j
+public class SignEncipherHMAC_MD5 implements SignEncipher {
+
+ public static final String HMAC_MD5 = "HmacMD5";
+
+ @Override
+ public byte[] encrypt(String input, String secret) {
+ try {
+ SecretKey secretKey = new SecretKeySpec(secret.getBytes(SopConstants.CHARSET_UTF8), HMAC_MD5);
+ Mac mac = Mac.getInstance(secretKey.getAlgorithm());
+ mac.init(secretKey);
+ return mac.doFinal(input.getBytes(SopConstants.CHARSET_UTF8));
+ } catch (NoSuchAlgorithmException e) {
+ log.error("HMAC_MD5加密加密失败NoSuchAlgorithmException", e);
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
+ } catch (InvalidKeyException e) {
+ log.error("HMAC_MD5加密加密失败InvalidKeyException", e);
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
+ }
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/SignEncipherMD5.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/SignEncipherMD5.java
new file mode 100644
index 00000000..b5b88503
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/SignEncipherMD5.java
@@ -0,0 +1,16 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author tanghc
+ */
+public class SignEncipherMD5 implements SignEncipher {
+ @Override
+ public byte[] encrypt(String input, String secret) {
+ String source = secret + input + secret;
+ return DigestUtils.md5(source.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Signer.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Signer.java
new file mode 100644
index 00000000..53d27a1c
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Signer.java
@@ -0,0 +1,20 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 负责签名校验
+ * @author tanghc
+ *
+ */
+public interface Signer {
+
+ /**
+ * 签名校验
+ * @param request
+ * @param secret 秘钥
+ * @return true签名正确
+ */
+ boolean checkSign(HttpServletRequest request, String secret);
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Validator.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Validator.java
new file mode 100644
index 00000000..de7e194e
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Validator.java
@@ -0,0 +1,18 @@
+package com.gitee.sop.gatewaycommon.validate;
+
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+
+/**
+ * 校验接口
+ *
+ * @author tanghc
+ *
+ */
+public interface Validator {
+ /**
+ * 接口验证
+ * @param param 接口参数
+ */
+ void validate(ApiParam param);
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipayConstants.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipayConstants.java
new file mode 100644
index 00000000..ceb140f8
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipayConstants.java
@@ -0,0 +1,97 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.gatewaycommon.validate.alipay;
+
+/**
+ *
+ * @author runzhi
+ */
+public class AlipayConstants {
+
+ public static final String SIGN_TYPE = "sign_type";
+
+ public static final String SIGN_TYPE_RSA = "RSA";
+
+ /**
+ * sha256WithRsa 算法请求类型
+ */
+ public static final String SIGN_TYPE_RSA2 = "RSA2";
+
+ public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
+
+ public static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";
+
+ public static final String ENCRYPT_TYPE_AES = "AES";
+
+ public static final String APP_ID = "app_id";
+
+ public static final String FORMAT = "format";
+
+ public static final String METHOD = "method";
+
+ public static final String TIMESTAMP = "timestamp";
+
+ public static final String VERSION = "version";
+
+ public static final String SIGN = "sign";
+
+ public static final String ALIPAY_SDK = "alipay_sdk";
+
+ public static final String ACCESS_TOKEN = "auth_token";
+
+ public static final String APP_AUTH_TOKEN = "app_auth_token";
+
+ public static final String TERMINAL_TYPE = "terminal_type";
+
+ public static final String TERMINAL_INFO = "terminal_info";
+
+ public static final String CHARSET = "charset";
+
+ public static final String NOTIFY_URL = "notify_url";
+
+ public static final String RETURN_URL = "return_url";
+
+ public static final String ENCRYPT_TYPE = "encrypt_type";
+
+ //-----===-------///
+
+ public static final String BIZ_CONTENT_KEY = "biz_content";
+
+ /** 默认时间格式 **/
+ public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ /** Date默认时区 **/
+ public static final String DATE_TIMEZONE = "GMT+8";
+
+ /** UTF-8字符集 **/
+ public static final String CHARSET_UTF8 = "UTF-8";
+
+ /** GBK字符集 **/
+ public static final String CHARSET_GBK = "GBK";
+
+ /** JSON 应格式 */
+ public static final String FORMAT_JSON = "json";
+
+ /** XML 应格式 */
+ public static final String FORMAT_XML = "xml";
+
+ /** SDK版本号 */
+ public static final String SDK_VERSION = "alipay-sdk-java-3.6.0.ALL";
+
+ public static final String PROD_CODE = "prod_code";
+
+ /** 老版本失败节点 */
+ public static final String ERROR_RESPONSE = "error_response";
+
+ /** 新版本节点后缀 */
+ public static final String RESPONSE_SUFFIX = "_response";
+
+ /** 加密后XML返回报文的节点名字 */
+ public static final String RESPONSE_XML_ENCRYPT_NODE_NAME = "response_encrypted";
+
+ /** 批量请求id **/
+ public static final String BATCH_REQUEST_ID = "batch_request_id";
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySignature.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySignature.java
new file mode 100644
index 00000000..7ba2f51e
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySignature.java
@@ -0,0 +1,616 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.gatewaycommon.validate.alipay;
+
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author runzhi
+ */
+public class AlipaySignature {
+
+ /**
+ * RSA最大加密明文大小
+ */
+ private static final int MAX_ENCRYPT_BLOCK = 117;
+
+ /**
+ * RSA最大解密密文大小
+ */
+ private static final int MAX_DECRYPT_BLOCK = 128;
+
+
+ /**
+ * @param sortedParams
+ * @return
+ */
+ public static String getSignContent(Map sortedParams) {
+ StringBuffer content = new StringBuffer();
+ List keys = new ArrayList(sortedParams.keySet());
+ Collections.sort(keys);
+ int index = 0;
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ String value = String.valueOf(sortedParams.get(key));
+ if (StringUtils.areNotEmpty(key, value)) {
+ content.append((index == 0 ? "" : "&") + key + "=" + value);
+ index++;
+ }
+ }
+ return content.toString();
+ }
+
+ /**
+ * rsa内容签名
+ *
+ * @param content
+ * @param publicKey
+ * @param charset
+ * @return
+ */
+ public static String rsaSign(String content, String publicKey, String charset,
+ String signType) {
+
+ if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
+
+ return rsaSign(content, publicKey, charset);
+ } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) {
+
+ return rsa256Sign(content, publicKey, charset);
+ } else {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException();
+// throw new AlipayApiException("Sign Type is Not Support : signType=" + signType);
+ }
+
+ }
+
+ /**
+ * sha256WithRsa 加签
+ *
+ * @param content
+ * @param publicKey
+ * @param charset
+ * @return
+ */
+ public static String rsa256Sign(String content, String publicKey,
+ String charset) {
+
+ try {
+ PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(publicKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS);
+
+ signature.initSign(priKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ byte[] signed = signature.sign();
+
+ return new String(Base64.encodeBase64(signed));
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e);
+ }
+
+ }
+
+ /**
+ * sha1WithRsa 加签
+ *
+ * @param content
+ * @param publicKey
+ * @param charset
+ * @return
+ */
+ public static String rsaSign(String content, String publicKey,
+ String charset) {
+ try {
+ PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(publicKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_ALGORITHMS);
+
+ signature.initSign(priKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ byte[] signed = signature.sign();
+
+ return new String(Base64.encodeBase64(signed));
+ } catch (InvalidKeySpecException ie) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException(ie);
+// throw new AlipayApiException("RSA私钥格式不正确,请检查是否正确配置了PKCS8格式的私钥", ie);
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e);
+ }
+ }
+
+ public static String rsaSign(Map params, String publicKey,
+ String charset, String signType) {
+ String signContent = getSignContent(params);
+
+ return rsaSign(signContent, publicKey, charset, signType);
+
+ }
+
+ public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
+ InputStream ins) throws Exception {
+ if (ins == null || StringUtils.isEmpty(algorithm)) {
+ return null;
+ }
+
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+
+ byte[] encodedKey = StreamUtil.readText(ins, "UTF-8").getBytes();
+
+ encodedKey = Base64.decodeBase64(encodedKey);
+
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
+ }
+
+ public static String getSignCheckContentV1(Map params) {
+ if (params == null) {
+ return null;
+ }
+
+ params.remove("sign");
+ params.remove("sign_type");
+
+ StringBuffer content = new StringBuffer();
+ List keys = new ArrayList(params.keySet());
+ Collections.sort(keys);
+
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ String value = params.get(key);
+ content.append((i == 0 ? "" : "&") + key + "=" + value);
+ }
+
+ return content.toString();
+ }
+
+ public static String getSignCheckContentV2(Map params) {
+ if (params == null) {
+ return null;
+ }
+
+ params.remove("sign");
+
+ StringBuffer content = new StringBuffer();
+ List keys = new ArrayList(params.keySet());
+ Collections.sort(keys);
+
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ String value = String.valueOf(params.get(key));
+ content.append((i == 0 ? "" : "&") + key + "=" + value);
+ }
+
+ return content.toString();
+ }
+
+ public static boolean rsaCheckV1(Map params, String publicKey,
+ String charset) {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV1(params);
+
+ return rsaCheckContent(content, sign, publicKey, charset);
+ }
+
+ public static boolean rsaCheckV1(Map params, String publicKey,
+ String charset, String signType) {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV1(params);
+
+ return rsaCheck(content, sign, publicKey, charset, signType);
+ }
+
+ public static boolean rsaCheckV2(Map params, String publicKey,
+ String charset) {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV2(params);
+
+ return rsaCheckContent(content, sign, publicKey, charset);
+ }
+
+ public static boolean rsaCheckV2(Map params, String publicKey,
+ String charset, String signType) {
+ String sign = String.valueOf(params.get("sign"));
+ String content = getSignCheckContentV2(params);
+
+ return rsaCheck(content, sign, publicKey, charset, signType);
+ }
+
+ public static boolean rsaCheck(String content, String sign, String publicKey, String charset,
+ String signType) {
+
+ if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
+
+ return rsaCheckContent(content, sign, publicKey, charset);
+
+ } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) {
+
+ return rsa256CheckContent(content, sign, publicKey, charset);
+
+ } else {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException();
+// throw new AlipayApiException("Sign Type is Not Support : signType=" + signType);
+ }
+
+ }
+
+ public static boolean rsa256CheckContent(String content, String sign, String publicKey,
+ String charset) {
+ try {
+ PublicKey pubKey = getPublicKeyFromX509("RSA",
+ new ByteArrayInputStream(publicKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS);
+
+ signature.initVerify(pubKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ return signature.verify(Base64.decodeBase64(sign.getBytes()));
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException(
+// "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
+ }
+ }
+
+ public static boolean rsaCheckContent(String content, String sign, String publicKey,
+ String charset) {
+ try {
+ PublicKey pubKey = getPublicKeyFromX509("RSA",
+ new ByteArrayInputStream(publicKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_ALGORITHMS);
+
+ signature.initVerify(pubKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ return signature.verify(Base64.decodeBase64(sign.getBytes()));
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException(
+// "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
+ }
+ }
+
+ public static PublicKey getPublicKeyFromX509(String algorithm,
+ InputStream ins) throws Exception {
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+
+ StringWriter writer = new StringWriter();
+ StreamUtil.io(new InputStreamReader(ins), writer);
+
+ byte[] encodedKey = writer.toString().getBytes();
+
+ encodedKey = Base64.decodeBase64(encodedKey);
+
+ return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
+ }
+
+ /**
+ * 验签并解密
+ *
+ * 目前适用于公众号
+ * params参数示例:
+ * {
+ * biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
+ * sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
+ * sign_type=RSA,
+ * charset=UTF-8
+ * }
+ *
+ *
+ * @param params
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param isCheckSign 是否验签
+ * @param isDecrypt 是否解密
+ * @return 解密后明文,验签失败则异常抛出
+ */
+ public static String checkSignAndDecrypt(Map params, String alipayPublicKey,
+ String cusPrivateKey, boolean isCheckSign,
+ boolean isDecrypt) {
+ String charset = params.get("charset");
+ String bizContent = params.get("biz_content");
+ if (isCheckSign) {
+ if (!rsaCheckV2(params, alipayPublicKey, charset)) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
+// throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
+ }
+ }
+
+ if (isDecrypt) {
+ return rsaDecrypt(bizContent, cusPrivateKey, charset);
+ }
+
+ return bizContent;
+ }
+
+ /**
+ * 验签并解密
+ *
+ * 目前适用于公众号
+ * params参数示例:
+ * {
+ * biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
+ * sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
+ * sign_type=RSA,
+ * charset=UTF-8
+ * }
+ *
+ *
+ * @param params
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param isCheckSign 是否验签
+ * @param isDecrypt 是否解密
+ * @return 解密后明文,验签失败则异常抛出
+ */
+ public static String checkSignAndDecrypt(Map params, String alipayPublicKey,
+ String cusPrivateKey, boolean isCheckSign,
+ boolean isDecrypt, String signType) {
+ String charset = params.get("charset");
+ String bizContent = params.get("biz_content");
+ if (isCheckSign) {
+ if (!rsaCheckV2(params, alipayPublicKey, charset, signType)) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
+// throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
+ }
+ }
+
+ if (isDecrypt) {
+ return rsaDecrypt(bizContent, cusPrivateKey, charset);
+ }
+
+ return bizContent;
+ }
+
+ /**
+ * 加密并签名
+ * 目前适用于公众号
+ *
+ * @param bizContent 待加密、签名内容
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @param isEncrypt 是否加密,true-加密 false-不加密
+ * @param isSign 是否签名,true-签名 false-不签名
+ * @return 加密、签名后xml内容字符串
+ *
+ * 返回示例:
+ *
+ * 密文
+ * RSA
+ * sign
+ * RSA
+ *
+ *
+ */
+ public static String encryptAndSign(String bizContent, String alipayPublicKey,
+ String cusPrivateKey, String charset, boolean isEncrypt,
+ boolean isSign) {
+ StringBuilder sb = new StringBuilder();
+ if (StringUtils.isEmpty(charset)) {
+ charset = AlipayConstants.CHARSET_GBK;
+ }
+ sb.append("");
+ if (isEncrypt) {// 加密
+ sb.append("");
+ String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
+ sb.append("" + encrypted + " ");
+ sb.append("RSA ");
+ if (isSign) {
+ String sign = rsaSign(encrypted, cusPrivateKey, charset);
+ sb.append("" + sign + " ");
+ sb.append("RSA ");
+ }
+ sb.append(" ");
+ } else if (isSign) {// 不加密,但需要签名
+ sb.append("");
+ sb.append("" + bizContent + " ");
+ String sign = rsaSign(bizContent, cusPrivateKey, charset);
+ sb.append("" + sign + " ");
+ sb.append("RSA ");
+ sb.append(" ");
+ } else {// 不加密,不加签
+ sb.append(bizContent);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 加密并签名
+ * 目前适用于公众号
+ *
+ * @param bizContent 待加密、签名内容
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @param isEncrypt 是否加密,true-加密 false-不加密
+ * @param isSign 是否签名,true-签名 false-不签名
+ * @return 加密、签名后xml内容字符串
+ *
+ * 返回示例:
+ *
+ * 密文
+ * RSA
+ * sign
+ * RSA
+ *
+ *
+ */
+ public static String encryptAndSign(String bizContent, String alipayPublicKey,
+ String cusPrivateKey, String charset, boolean isEncrypt,
+ boolean isSign, String signType) {
+ StringBuilder sb = new StringBuilder();
+ if (StringUtils.isEmpty(charset)) {
+ charset = AlipayConstants.CHARSET_GBK;
+ }
+ sb.append("");
+ if (isEncrypt) {// 加密
+ sb.append("");
+ String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
+ sb.append("" + encrypted + " ");
+ sb.append("RSA ");
+ if (isSign) {
+ String sign = rsaSign(encrypted, cusPrivateKey, charset, signType);
+ sb.append("" + sign + " ");
+ sb.append("");
+ sb.append(signType);
+ sb.append(" ");
+ }
+ sb.append(" ");
+ } else if (isSign) {// 不加密,但需要签名
+ sb.append("");
+ sb.append("" + bizContent + " ");
+ String sign = rsaSign(bizContent, cusPrivateKey, charset, signType);
+ sb.append("" + sign + " ");
+ sb.append("");
+ sb.append(signType);
+ sb.append(" ");
+ sb.append(" ");
+ } else {// 不加密,不加签
+ sb.append(bizContent);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 公钥加密
+ *
+ * @param content 待加密内容
+ * @param publicKey 公钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @return 密文内容
+ */
+ public static String rsaEncrypt(String content, String publicKey,
+ String charset) {
+ try {
+ PublicKey pubKey = getPublicKeyFromX509(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(publicKey.getBytes()));
+ Cipher cipher = Cipher.getInstance(AlipayConstants.SIGN_TYPE_RSA);
+ cipher.init(Cipher.ENCRYPT_MODE, pubKey);
+ byte[] data = StringUtils.isEmpty(charset) ? content.getBytes()
+ : content.getBytes(charset);
+ int inputLen = data.length;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int offSet = 0;
+ byte[] cache;
+ int i = 0;
+ // 对数据分段加密
+ while (inputLen - offSet > 0) {
+ if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
+ cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
+ } else {
+ cache = cipher.doFinal(data, offSet, inputLen - offSet);
+ }
+ out.write(cache, 0, cache.length);
+ i++;
+ offSet = i * MAX_ENCRYPT_BLOCK;
+ }
+ byte[] encryptedData = Base64.encodeBase64(out.toByteArray());
+ out.close();
+
+ return StringUtils.isEmpty(charset) ? new String(encryptedData)
+ : new String(encryptedData, charset);
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException("EncryptContent = " + content + ",charset = " + charset,
+// e);
+ }
+ }
+
+ /**
+ * 私钥解密
+ *
+ * @param content 待解密内容
+ * @param publicKey 私钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @return 明文内容
+ */
+ public static String rsaDecrypt(String content, String publicKey,
+ String charset) {
+ try {
+ PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(publicKey.getBytes()));
+ Cipher cipher = Cipher.getInstance(AlipayConstants.SIGN_TYPE_RSA);
+ cipher.init(Cipher.DECRYPT_MODE, priKey);
+ byte[] encryptedData = StringUtils.isEmpty(charset)
+ ? Base64.decodeBase64(content.getBytes())
+ : Base64.decodeBase64(content.getBytes(charset));
+ int inputLen = encryptedData.length;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int offSet = 0;
+ byte[] cache;
+ int i = 0;
+ // 对数据分段解密
+ while (inputLen - offSet > 0) {
+ if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
+ cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
+ } else {
+ cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
+ }
+ out.write(cache, 0, cache.length);
+ i++;
+ offSet = i * MAX_DECRYPT_BLOCK;
+ }
+ byte[] decryptedData = out.toByteArray();
+ out.close();
+
+ return StringUtils.isEmpty(charset) ? new String(decryptedData)
+ : new String(decryptedData, charset);
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException("EncodeContent = " + content + ",charset = " + charset, e);
+ }
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySigner.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySigner.java
new file mode 100644
index 00000000..6f690e55
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySigner.java
@@ -0,0 +1,34 @@
+package com.gitee.sop.gatewaycommon.validate.alipay;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import com.gitee.sop.gatewaycommon.validate.Signer;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 支付宝签名验证实现。
+ *
+ * @author tanghc
+ * @see 支付宝签名
+ */
+public class AlipaySigner implements Signer {
+
+ @Override
+ public boolean checkSign(HttpServletRequest request, String secret) {
+ // 服务端存的是公钥
+ String publicKey = secret;
+ ApiParam apiParam = ApiContext.getApiParam();
+ String charset = apiParam.fetchCharset();
+ String signType = apiParam.fetchSignMethod();
+ if (signType == null) {
+ throw ErrorEnum.ISV_DECRYPTION_ERROR_MISSING_ENCRYPT_TYPE.getErrorMeta().getException();
+ }
+ if (charset == null) {
+ throw ErrorEnum.ISV_INVALID_CHARSET.getErrorMeta().getException();
+ }
+ return AlipaySignature.rsaCheckV2(apiParam, publicKey, charset, signType);
+ }
+
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/StreamUtil.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/StreamUtil.java
new file mode 100644
index 00000000..343b0ae5
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/StreamUtil.java
@@ -0,0 +1,134 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.gatewaycommon.validate.alipay;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ *
+ * @author runzhi
+ */
+public class StreamUtil {
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ public static void io(InputStream in, OutputStream out) throws IOException {
+ io(in, out, -1);
+ }
+
+ public static void io(InputStream in, OutputStream out, int bufferSize) throws IOException {
+ if (bufferSize == -1) {
+ bufferSize = DEFAULT_BUFFER_SIZE;
+ }
+
+ byte[] buffer = new byte[bufferSize];
+ int amount;
+
+ while ((amount = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, amount);
+ }
+ }
+
+ public static void io(Reader in, Writer out) throws IOException {
+ io(in, out, -1);
+ }
+
+ public static void io(Reader in, Writer out, int bufferSize) throws IOException {
+ if (bufferSize == -1) {
+ bufferSize = DEFAULT_BUFFER_SIZE >> 1;
+ }
+
+ char[] buffer = new char[bufferSize];
+ int amount;
+
+ while ((amount = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, amount);
+ }
+ }
+
+ public static OutputStream synchronizedOutputStream(OutputStream out) {
+ return new SynchronizedOutputStream(out);
+ }
+
+ public static OutputStream synchronizedOutputStream(OutputStream out, Object lock) {
+ return new SynchronizedOutputStream(out, lock);
+ }
+
+ public static String readText(InputStream in) throws IOException {
+ return readText(in, null, -1);
+ }
+
+ public static String readText(InputStream in, String encoding) throws IOException {
+ return readText(in, encoding, -1);
+ }
+
+ public static String readText(InputStream in, String encoding, int bufferSize)
+ throws IOException {
+ Reader reader = (encoding == null) ? new InputStreamReader(in) : new InputStreamReader(in,
+ encoding);
+
+ return readText(reader, bufferSize);
+ }
+
+ public static String readText(Reader reader) throws IOException {
+ return readText(reader, -1);
+ }
+
+ public static String readText(Reader reader, int bufferSize) throws IOException {
+ StringWriter writer = new StringWriter();
+
+ io(reader, writer, bufferSize);
+ return writer.toString();
+ }
+
+ private static class SynchronizedOutputStream extends OutputStream {
+ private OutputStream out;
+ private Object lock;
+
+ SynchronizedOutputStream(OutputStream out) {
+ this(out, out);
+ }
+
+ SynchronizedOutputStream(OutputStream out, Object lock) {
+ this.out = out;
+ this.lock = lock;
+ }
+
+ public void write(int datum) throws IOException {
+ synchronized (lock) {
+ out.write(datum);
+ }
+ }
+
+ public void write(byte[] data) throws IOException {
+ synchronized (lock) {
+ out.write(data);
+ }
+ }
+
+ public void write(byte[] data, int offset, int length) throws IOException {
+ synchronized (lock) {
+ out.write(data, offset, length);
+ }
+ }
+
+ public void flush() throws IOException {
+ synchronized (lock) {
+ out.flush();
+ }
+ }
+
+ public void close() throws IOException {
+ synchronized (lock) {
+ out.close();
+ }
+ }
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/StringUtils.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/StringUtils.java
new file mode 100644
index 00000000..0b3c01e7
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/StringUtils.java
@@ -0,0 +1,167 @@
+package com.gitee.sop.gatewaycommon.validate.alipay;
+
+/**
+ * 字符串工具类。
+ *
+ * @author carver.gu
+ * @since 1.0, Sep 12, 2009
+ */
+public abstract class StringUtils {
+
+ private StringUtils() {}
+
+ /**
+ * 检查指定的字符串是否为空。
+ *
+ * SysUtils.isEmpty(null) = true
+ * SysUtils.isEmpty("") = true
+ * SysUtils.isEmpty(" ") = true
+ * SysUtils.isEmpty("abc") = false
+ *
+ *
+ * @param value 待检查的字符串
+ * @return true/false
+ */
+ public static boolean isEmpty(String value) {
+ int strLen;
+ if (value == null || (strLen = value.length()) == 0) {
+ return true;
+ }
+ for (int i = 0; i < strLen; i++) {
+ if ((Character.isWhitespace(value.charAt(i)) == false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 检查对象是否为数字型字符串,包含负数开头的。
+ */
+ public static boolean isNumeric(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ char[] chars = obj.toString().toCharArray();
+ int length = chars.length;
+ if(length < 1)
+ return false;
+
+ int i = 0;
+ if(length > 1 && chars[0] == '-')
+ i = 1;
+
+ for (; i < length; i++) {
+ if (!Character.isDigit(chars[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 检查指定的字符串列表是否不为空。
+ */
+ public static boolean areNotEmpty(String... values) {
+ boolean result = true;
+ if (values == null || values.length == 0) {
+ result = false;
+ } else {
+ for (String value : values) {
+ result &= !isEmpty(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 把通用字符编码的字符串转化为汉字编码。
+ */
+ public static String unicodeToChinese(String unicode) {
+ StringBuilder out = new StringBuilder();
+ if (!isEmpty(unicode)) {
+ for (int i = 0; i < unicode.length(); i++) {
+ out.append(unicode.charAt(i));
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * 过滤不可见字符
+ */
+ public static String stripNonValidXMLCharacters(String input) {
+ if (input == null || ("".equals(input)))
+ return "";
+ StringBuilder out = new StringBuilder();
+ char current;
+ for (int i = 0; i < input.length(); i++) {
+ current = input.charAt(i);
+ if ((current == 0x9) || (current == 0xA) || (current == 0xD)
+ || ((current >= 0x20) && (current <= 0xD7FF))
+ || ((current >= 0xE000) && (current <= 0xFFFD))
+ || ((current >= 0x10000) && (current <= 0x10FFFF)))
+ out.append(current);
+ }
+ return out.toString();
+ }
+
+ public static String leftPad(String str, int size, char padChar) {
+ if (str == null) {
+ return null;
+ } else {
+ int pads = size - str.length();
+ if (pads <= 0) {
+ return str;
+ } else {
+ return pads > 8192 ? leftPad(str, size, String.valueOf(padChar)) : padding(pads, padChar).concat(str);
+ }
+ }
+ }
+
+ public static String leftPad(String str, int size, String padStr) {
+ if (str == null) {
+ return null;
+ } else {
+ if (isEmpty(padStr)) {
+ padStr = " ";
+ }
+
+ int padLen = padStr.length();
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ } else if (padLen == 1 && pads <= 8192) {
+ return leftPad(str, size, padStr.charAt(0));
+ } else if (pads == padLen) {
+ return padStr.concat(str);
+ } else if (pads < padLen) {
+ return padStr.substring(0, pads).concat(str);
+ } else {
+ char[] padding = new char[pads];
+ char[] padChars = padStr.toCharArray();
+
+ for(int i = 0; i < pads; ++i) {
+ padding[i] = padChars[i % padLen];
+ }
+
+ return (new String(padding)).concat(str);
+ }
+ }
+ }
+
+ private static String padding(int repeat, char padChar) throws IndexOutOfBoundsException {
+ if (repeat < 0) {
+ throw new IndexOutOfBoundsException("Cannot pad a negative amount: " + repeat);
+ } else {
+ char[] buf = new char[repeat];
+
+ for(int i = 0; i < buf.length; ++i) {
+ buf[i] = padChar;
+ }
+
+ return new String(buf);
+ }
+ }
+}
diff --git a/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/taobao/TaobaoSigner.java b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/taobao/TaobaoSigner.java
new file mode 100644
index 00000000..21a1a620
--- /dev/null
+++ b/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/taobao/TaobaoSigner.java
@@ -0,0 +1,64 @@
+package com.gitee.sop.gatewaycommon.validate.taobao;
+
+
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import com.gitee.sop.gatewaycommon.validate.AbstractSigner;
+import com.gitee.sop.gatewaycommon.validate.SignEncipher;
+import com.gitee.sop.gatewaycommon.validate.SignEncipherHMAC_MD5;
+import com.gitee.sop.gatewaycommon.validate.SignEncipherMD5;
+import org.apache.commons.lang.StringUtils;
+import org.apache.tomcat.util.buf.HexUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 淘宝开放平台签名验证实现,http://open.taobao.com/doc.htm?docId=101617&docType=1
+ *
+ * @author tanghc
+ */
+public class TaobaoSigner extends AbstractSigner {
+
+ private Map signEncipherMap = new HashMap<>();
+
+ public TaobaoSigner() {
+ signEncipherMap.put("md5", new SignEncipherMD5());
+ signEncipherMap.put("hmac", new SignEncipherHMAC_MD5());
+ }
+
+
+ @Override
+ public String buildServerSign(ApiParam param, String secret) {
+ String signMethod = param.fetchSignMethod();
+ SignEncipher signEncipher = signEncipherMap.get(signMethod);
+ if (signEncipher == null) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException(signMethod);
+ }
+
+ // 第一步:参数排序
+ Set keySet = param.keySet();
+ List paramNames = new ArrayList(keySet);
+ Collections.sort(paramNames);
+
+ // 第二步:把所有参数名和参数值串在一起
+ StringBuilder paramNameValue = new StringBuilder();
+ for (String paramName : paramNames) {
+ Object val = param.get(paramName);
+ if (val != null && StringUtils.isNotBlank(String.valueOf(val))) {
+ paramNameValue.append(paramName).append(val);
+ }
+ }
+
+ // 第三步:使用MD5/HMAC加密
+ String source = paramNameValue.toString();
+ byte[] bytes = signEncipher.encrypt(source, secret);
+
+ // 第四步:把二进制转化为大写的十六进制
+ return HexUtils.toHexString(bytes).toUpperCase();
+ }
+}
diff --git a/sop-gateway-common/src/main/resources/i18n/open/error_en.properties b/sop-gateway-common/src/main/resources/i18n/open/error_en.properties
new file mode 100644
index 00000000..6f2ed50d
--- /dev/null
+++ b/sop-gateway-common/src/main/resources/i18n/open/error_en.properties
@@ -0,0 +1,55 @@
+# 网关错误配置
+
+open.error_10000=Success
+
+open.error_20000=Service is temporarily unavailable
+open.error_20000_isp.unknow-error=Service is temporarily unavailable
+open.error_20000_aop.unknow-error=Service is temporarily unavailable
+
+open.error_20001=Insufficient authorization authority
+open.error_20001_aop.invalid-auth-token=Invalid access token
+open.error_20001_aop.auth-token-time-out=The access token has expired
+open.error_20001_aop.invalid-app-auth-token=Invalid application authorization token
+open.error_20001_aop.invalid-app-auth-token-no-api=Merchant does not authorize current interface
+open.error_20001_aop.app-auth-token-time-out=The application authorization token has expired
+open.error_20001_aop.no-product-reg-by-partner=The merchant has not signed any product
+
+open.error_40001=Missing required parameters
+open.error_40001_isv.missing-method=Method name parameter is missing
+open.error_40001_isv.missing-signature=Lack of signature parameter
+open.error_40001_isv.missing-signature-type=Missing signature type parameter
+open.error_40001_isv.missing-signature-key=Lack of signature configuration
+open.error_40001_isv.missing-app-id=Missing appId parameter
+open.error_40001_isv.missing-timestamp=Missing timestamp parameter
+open.error_40001_isv.missing-version=Missing version parameter
+open.error_40001_isv.decryption-error-missing-encrypt-type=Decryption error, no encryption algorithm specified
+
+open.error_40002=Invalid parameter
+open.error_40002_isv.invalid-parameter=Parameter is invalid
+open.error_40002_isv.upload-fail=File upload failed
+open.error_40002_isv.invalid-file-extension=Invalid file extension
+open.error_40002_isv.invalid-file-size=Invalid file size
+open.error_40002_isv.invalid-method=Nonexistent method name
+open.error_40002_isv.invalid-format=Invalid data format
+open.error_40002_isv.invalid-signature-type=Invalid signature type
+open.error_40002_isv.invalid-signature=Invalid signature
+open.error_40002_isv.invalid-encrypt-type=Invalid encryption type
+open.error_40002_isv.invalid-encrypt=Decryption exception
+open.error_40002_isv.invalid-app-id=Invalid appId parameter
+open.error_40002_isv.invalid-timestamp=Invalid timestamp parameter
+open.error_40002_isv.invalid-charset=Character set error
+open.error_40002_isv.invalid-digest=Digest error
+open.error_40002_isv.decryption-error-not-valid-encrypt-type=Decryption error, unsupported encryption algorithm
+open.error_40002_isv.decryption-error-not-valid-encrypt-key=Decryption error, unconfigured encryption key or encryption key format error
+open.error_40002_isv.decryption-error-unknown=Decryption error, unknown exception
+open.error_40002_isv.missing-signature-config=Signature verification error, no public key or certificate of the corresponding signature algorithm is configured
+open.error_40002_isv.not-support-app-auth=This interface does not support third-party proxy calls
+open.error_40002_isv.suspected-attack=Suspicious attack requests
+open.error_40002_isv.invalid-content-type=Invalid content type
+
+open.error_40004=Business processing failure
+open.error_40004_=Business processing failure
+
+open.error_40006=Insufficient permissions
+open.error_40006_isv.insufficient-isv-permissions=Insufficient ISV permissions
+open.error_40006_isv.insufficient-user-permissions=Insufficient user permissions
\ No newline at end of file
diff --git a/sop-gateway-common/src/main/resources/i18n/open/error_zh_CN.properties b/sop-gateway-common/src/main/resources/i18n/open/error_zh_CN.properties
new file mode 100644
index 00000000..49e4969e
--- /dev/null
+++ b/sop-gateway-common/src/main/resources/i18n/open/error_zh_CN.properties
@@ -0,0 +1,107 @@
+# 网关错误配置
+
+#open.error_20000=服务不可用
+#open.error_20000_isp.unknow-error=服务暂不可用
+#open.error_20000_aop.unknow-error=服务暂不可用
+#
+#open.error_20001=授权权限不足
+#open.error_20001_aop.invalid-auth-token=无效的访问令牌
+#open.error_20001_aop.auth-token-time-out=访问令牌已过期
+#open.error_20001_aop.invalid-app-auth-token=无效的应用授权令牌
+#open.error_20001_aop.invalid-app-auth-token-no-api=商户未授权当前接口
+#open.error_20001_aop.app-auth-token-time-out=应用授权令牌已过期
+#open.error_20001_aop.no-product-reg-by-partner=商户未签约任何产品
+#
+#open.error_40001=缺少必选参数
+#open.error_40001_isv.missing-method=缺少方法名参数
+#open.error_40001_isv.missing-signature=缺少签名参数
+#open.error_40001_isv.missing-signature-type=缺少签名类型参数
+#open.error_40001_isv.missing-signature-key=缺少签名配置
+#open.error_40001_isv.missing-app-id=缺少appId参数
+#open.error_40001_isv.missing-timestamp=缺少时间戳参数
+#open.error_40001_isv.missing-version=缺少版本参数
+#open.error_40001_isv.decryption-error-missing-encrypt-type=解密出错, 未指定加密算法
+#
+#open.error_40002=非法的参数
+#open.error_40002_isv.invalid-parameter=参数无效
+#open.error_40002_isv.upload-fail=文件上传失败
+#open.error_40002_isv.invalid-file-extension=文件扩展名无效
+#open.error_40002_isv.invalid-file-size=文件大小无效
+#open.error_40002_isv.invalid-method=不存在的方法名
+#open.error_40002_isv.invalid-format=无效的数据格式
+#open.error_40002_isv.invalid-signature-type=无效的签名类型
+#open.error_40002_isv.invalid-signature=无效签名
+#open.error_40002_isv.invalid-encrypt-type=无效的加密类型
+#open.error_40002_isv.invalid-encrypt=解密异常
+#open.error_40002_isv.invalid-app-id=无效的appId参数
+#open.error_40002_isv.invalid-timestamp=非法的时间戳参数
+#open.error_40002_isv.invalid-charset=字符集错误
+#open.error_40002_isv.invalid-digest=摘要错误
+#open.error_40002_isv.decryption-error-not-valid-encrypt-type=解密出错,不支持的加密算法
+#open.error_40002_isv.decryption-error-not-valid-encrypt-key=解密出错, 未配置加密密钥或加密密钥格式错误
+#open.error_40002_isv.decryption-error-unknown=解密出错,未知异常
+#open.error_40002_isv.missing-signature-config=验签出错, 未配置对应签名算法的公钥或者证书
+#open.error_40002_isv.not-support-app-auth=本接口不支持第三方代理调用
+#open.error_40002_isv.suspected-attack=可疑的攻击请求
+#open.error_40002_isv.invalid-content-type=无效的content-type
+#
+#open.error_40004=业务处理失败
+#open.error_40004_=业务处理失败
+#
+#open.error_40006=权限不足
+#open.error_40006_isv.insufficient-isv-permissions=请检查配置的账户是否有当前接口权限
+#open.error_40006_isv.insufficient-user-permissions=代理的商户没有当前接口权限
+
+open.error_10000=Success
+
+open.error_20000=\u670d\u52a1\u4e0d\u53ef\u7528
+open.error_20000_isp.unknow-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
+open.error_20000_aop.unknow-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
+
+open.error_20001=\u6388\u6743\u6743\u9650\u4e0d\u8db3
+open.error_20001_aop.invalid-auth-token=\u65e0\u6548\u7684\u8bbf\u95ee\u4ee4\u724c
+open.error_20001_aop.auth-token-time-out=\u8bbf\u95ee\u4ee4\u724c\u5df2\u8fc7\u671f
+open.error_20001_aop.invalid-app-auth-token=\u65e0\u6548\u7684\u5e94\u7528\u6388\u6743\u4ee4\u724c
+open.error_20001_aop.invalid-app-auth-token-no-api=\u5546\u6237\u672a\u6388\u6743\u5f53\u524d\u63a5\u53e3
+open.error_20001_aop.app-auth-token-time-out=\u5e94\u7528\u6388\u6743\u4ee4\u724c\u5df2\u8fc7\u671f
+open.error_20001_aop.no-product-reg-by-partner=\u5546\u6237\u672a\u7b7e\u7ea6\u4efb\u4f55\u4ea7\u54c1
+
+open.error_40001=\u7f3a\u5c11\u5fc5\u9009\u53c2\u6570
+open.error_40001_isv.missing-method=\u7f3a\u5c11\u65b9\u6cd5\u540d\u53c2\u6570
+open.error_40001_isv.missing-signature=\u7f3a\u5c11\u7b7e\u540d\u53c2\u6570
+open.error_40001_isv.missing-signature-type=\u7f3a\u5c11\u7b7e\u540d\u7c7b\u578b\u53c2\u6570
+open.error_40001_isv.missing-signature-key=\u7f3a\u5c11\u7b7e\u540d\u914d\u7f6e
+open.error_40001_isv.missing-app-id=\u7f3a\u5c11appId\u53c2\u6570
+open.error_40001_isv.missing-timestamp=\u7f3a\u5c11\u65f6\u95f4\u6233\u53c2\u6570
+open.error_40001_isv.missing-version=\u7f3a\u5c11\u7248\u672c\u53c2\u6570
+open.error_40001_isv.decryption-error-missing-encrypt-type=\u89e3\u5bc6\u51fa\u9519, \u672a\u6307\u5b9a\u52a0\u5bc6\u7b97\u6cd5
+
+open.error_40002=\u975e\u6cd5\u7684\u53c2\u6570
+open.error_40002_isv.invalid-parameter=\u53c2\u6570\u65e0\u6548
+open.error_40002_isv.upload-fail=\u6587\u4ef6\u4e0a\u4f20\u5931\u8d25
+open.error_40002_isv.invalid-file-extension=\u6587\u4ef6\u6269\u5c55\u540d\u65e0\u6548
+open.error_40002_isv.invalid-file-size=\u6587\u4ef6\u5927\u5c0f\u65e0\u6548
+open.error_40002_isv.invalid-method=\u4e0d\u5b58\u5728\u7684\u65b9\u6cd5\u540d
+open.error_40002_isv.invalid-format=\u65e0\u6548\u7684\u6570\u636e\u683c\u5f0f
+open.error_40002_isv.invalid-signature-type=\u65e0\u6548\u7684\u7b7e\u540d\u7c7b\u578b
+open.error_40002_isv.invalid-signature=\u65e0\u6548\u7b7e\u540d
+open.error_40002_isv.invalid-encrypt-type=\u65e0\u6548\u7684\u52a0\u5bc6\u7c7b\u578b
+open.error_40002_isv.invalid-encrypt=\u89e3\u5bc6\u5f02\u5e38
+open.error_40002_isv.invalid-app-id=\u65e0\u6548\u7684appId\u53c2\u6570
+open.error_40002_isv.invalid-timestamp=\u975e\u6cd5\u7684\u65f6\u95f4\u6233\u53c2\u6570
+open.error_40002_isv.invalid-charset=\u5b57\u7b26\u96c6\u9519\u8bef
+open.error_40002_isv.invalid-digest=\u6458\u8981\u9519\u8bef
+open.error_40002_isv.decryption-error-not-valid-encrypt-type=\u89e3\u5bc6\u51fa\u9519\uff0c\u4e0d\u652f\u6301\u7684\u52a0\u5bc6\u7b97\u6cd5
+open.error_40002_isv.decryption-error-not-valid-encrypt-key=\u89e3\u5bc6\u51fa\u9519, \u672a\u914d\u7f6e\u52a0\u5bc6\u5bc6\u94a5\u6216\u52a0\u5bc6\u5bc6\u94a5\u683c\u5f0f\u9519\u8bef
+open.error_40002_isv.decryption-error-unknown=\u89e3\u5bc6\u51fa\u9519\uff0c\u672a\u77e5\u5f02\u5e38
+open.error_40002_isv.missing-signature-config=\u9a8c\u7b7e\u51fa\u9519, \u672a\u914d\u7f6e\u5bf9\u5e94\u7b7e\u540d\u7b97\u6cd5\u7684\u516c\u94a5\u6216\u8005\u8bc1\u4e66
+open.error_40002_isv.not-support-app-auth=\u672c\u63a5\u53e3\u4e0d\u652f\u6301\u7b2c\u4e09\u65b9\u4ee3\u7406\u8c03\u7528
+open.error_40002_isv.suspected-attack=\u53ef\u7591\u7684\u653b\u51fb\u8bf7\u6c42
+open.error_40002_isv.invalid-content-type=\u65e0\u6548\u7684content-type
+
+open.error_40004=\u4e1a\u52a1\u5904\u7406\u5931\u8d25
+open.error_40004_=\u4e1a\u52a1\u5904\u7406\u5931\u8d25
+
+open.error_40006=\u6743\u9650\u4e0d\u8db3
+open.error_40006_isv.insufficient-isv-permissions=\u8bf7\u68c0\u67e5\u914d\u7f6e\u7684\u8d26\u6237\u662f\u5426\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650
+open.error_40006_isv.insufficient-user-permissions=\u4ee3\u7406\u7684\u5546\u6237\u6ca1\u6709\u5f53\u524d\u63a5\u53e3\u6743\u9650
diff --git a/sop-gateway/.gitignore b/sop-gateway/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/sop-gateway/.gitignore
@@ -0,0 +1,25 @@
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/sop-gateway/.mvn/wrapper/maven-wrapper.jar b/sop-gateway/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 00000000..01e67997
Binary files /dev/null and b/sop-gateway/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/sop-gateway/.mvn/wrapper/maven-wrapper.properties b/sop-gateway/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 00000000..cd0d451c
--- /dev/null
+++ b/sop-gateway/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
diff --git a/sop-gateway/mvnw b/sop-gateway/mvnw
new file mode 100755
index 00000000..5551fde8
--- /dev/null
+++ b/sop-gateway/mvnw
@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ 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
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ 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
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ wget "$jarUrl" -O "$wrapperJarPath"
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ curl -o "$wrapperJarPath" "$jarUrl"
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/sop-gateway/mvnw.cmd b/sop-gateway/mvnw.cmd
new file mode 100644
index 00000000..e5cfb0ae
--- /dev/null
+++ b/sop-gateway/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ echo Found %WRAPPER_JAR%
+) else (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+ echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/sop-gateway/pom.xml b/sop-gateway/pom.xml
new file mode 100644
index 00000000..c1a78a50
--- /dev/null
+++ b/sop-gateway/pom.xml
@@ -0,0 +1,74 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ com.gitee.sop
+ sop-gateway
+ 1.0.0-SNAPSHOT
+ sop-gateway
+ Demo project for Spring Boot
+
+
+ 1.8
+ Greenwich.RELEASE
+
+
+
+
+ com.gitee.sop
+ sop-gateway-common
+ 1.0.0-SNAPSHOT
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-zuul
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
+
diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/SopGatewayApplication.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/SopGatewayApplication.java
new file mode 100644
index 00000000..cc7af2ed
--- /dev/null
+++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/SopGatewayApplication.java
@@ -0,0 +1,17 @@
+package com.gitee.sop.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
+
+// 开启网关功能
+@EnableZuulProxy
+@SpringBootApplication
+public class SopGatewayApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SopGatewayApplication.class, args);
+ }
+
+}
+
diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java
new file mode 100644
index 00000000..e9ccd367
--- /dev/null
+++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java
@@ -0,0 +1,35 @@
+package com.gitee.sop.gateway.config;
+
+import com.gitee.sop.gatewaycommon.bean.ApiContext;
+import com.gitee.sop.gatewaycommon.configuration.AlipayZuulConfiguration;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 开通支付宝开放平台能力
+ * @author tanghc
+ */
+@Configuration
+public class ZuulConfig extends AlipayZuulConfiguration {
+
+ {
+ Map appSecretStore = new HashMap();
+ appSecretStore.put("alipay_test", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyb9aUBaljQP/vjmBFe1mF8HsWSvyfC2NTlpT/V9E+sBxTr8TSkbzJCeeeOEm4LCaVXL0Qz63MZoT24v7AIXTuMdj4jyiM/WJ4tjrWAgnmohNOegfntTto16C3l234vXz4ryWZMR/7W+MXy5B92wPGQEJ0LKFwNEoLspDEWZ7RdE53VH7w6y6sIZUfK+YkXWSwehfKPKlx+lDw3zRJ3/yvMF+U+BAdW/MfECe1GuBnCFKnlMRh3UKczWyXWkL6ItOpYHHJi/jx85op5BWDje2pY9QowzfN94+0DB3T7UvZeweu3zlP6diwAJDzLaFQX8ULfWhY+wfKxIRgs9NoiSAQIDAQAB");
+ ApiContext.getApiConfig().addAppSecret(appSecretStore);
+ }
+}
+
+/**
+ * 开通淘宝开放平能力
+ */
+//@Configuration
+//public class ZuulConfig extends TaobaoZuulConfiguration {
+//
+// {
+// Map appSecretStore = new HashMap();
+// appSecretStore.put("taobao_test", "G9w0BAQEFAAOCAQ8AMIIBCgKCA");
+// ApiContext.getApiConfig().addAppSecret(appSecretStore);
+// }
+//}
diff --git a/sop-gateway/src/main/resources/application.properties b/sop-gateway/src/main/resources/application.properties
new file mode 100644
index 00000000..85c1a89c
--- /dev/null
+++ b/sop-gateway/src/main/resources/application.properties
@@ -0,0 +1,28 @@
+server.port=8081
+spring.application.name=api-gateway
+
+# 注册中心
+eureka.host=localhost
+eureka.port=1111
+eureka.client.serviceUrl.defaultZone=http://${eureka.host}:${eureka.port}/eureka/
+
+# 入口地址,默认是/zuul
+zuul.servlet-path=/api
+
+# 路由配置
+# story服务路由配置
+# 内置有个默认的:
+# zuul.routes..path=/<服务名>/**
+# zuul.routes..serviceId=<服务名>
+# 等同于:
+# zuul.routes.<服务名>=/<服务名>/**
+
+
+# Redis数据库索引(默认为0)
+spring.redis.database=0
+# Redis服务器地址
+spring.redis.host=127.0.0.1
+# Redis服务器连接端口
+spring.redis.port=6379
+# Redis服务器连接密码(默认为空)
+spring.redis.password=
diff --git a/sop-gateway/src/test/java/com/gitee/sop/gateway/PostTest.java b/sop-gateway/src/test/java/com/gitee/sop/gateway/PostTest.java
new file mode 100644
index 00000000..13879db2
--- /dev/null
+++ b/sop-gateway/src/test/java/com/gitee/sop/gateway/PostTest.java
@@ -0,0 +1,127 @@
+package com.gitee.sop.gateway;
+
+import com.alibaba.fastjson.JSON;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.security.MessageDigest;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class PostTest extends TestBase {
+
+ String url = "http://localhost:8081/zuul";
+ String appKey = "test";
+ String secret = "123456";
+
+ @Test
+ public void testPost() throws Exception {
+ // 业务参数
+ Map jsonMap = new HashMap();
+ jsonMap.put("id", "1");
+ jsonMap.put("id", "葫芦娃");
+
+ String json = JSON.toJSONString(jsonMap);
+ json = URLEncoder.encode(json, "utf-8");
+
+ // 系统参数
+ Map param = new HashMap();
+ param.put("name", "story.get");
+ param.put("app_key", appKey);
+ param.put("data", json);
+ param.put("timestamp", getTime());
+ param.put("version", "2.0");
+
+ String sign = buildSign(param, secret);
+
+ param.put("sign", sign);
+
+ System.out.println("=====请求数据=====");
+ String postJson = JSON.toJSONString(param);
+ System.out.println(postJson);
+
+ post(url, param); // 发送请求
+ }
+
+
+
+ /**
+ * 构建签名
+ *
+ * @param paramsMap
+ * 参数
+ * @param secret
+ * 密钥
+ * @return
+ * @throws IOException
+ */
+ public static String buildSign(Map paramsMap, String secret) throws IOException {
+ Set keySet = paramsMap.keySet();
+ List paramNames = new ArrayList(keySet);
+
+ Collections.sort(paramNames);
+
+ StringBuilder paramNameValue = new StringBuilder();
+
+ for (String paramName : paramNames) {
+ paramNameValue.append(paramName).append(paramsMap.get(paramName));
+ }
+
+ String source = secret + paramNameValue.toString() + secret;
+
+ return md5(source);
+ }
+
+ /**
+ * 生成md5,全部大写
+ *
+ * @param message
+ * @return
+ */
+ public static String md5(String message) {
+ try {
+ // 1 创建一个提供信息摘要算法的对象,初始化为md5算法对象
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ // 2 将消息变成byte数组
+ byte[] input = message.getBytes();
+
+ // 3 计算后获得字节数组,这就是那128位了
+ byte[] buff = md.digest(input);
+
+ // 4 把数组每一字节(一个字节占八位)换成16进制连成md5字符串
+ return byte2hex(buff);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 二进制转十六进制字符串
+ *
+ * @param bytes
+ * @return
+ */
+ private static String byte2hex(byte[] bytes) {
+ StringBuilder sign = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ String hex = Integer.toHexString(bytes[i] & 0xFF);
+ if (hex.length() == 1) {
+ sign.append("0");
+ }
+ sign.append(hex.toUpperCase());
+ }
+ return sign.toString();
+ }
+
+ public String getTime() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
+ }
+}
diff --git a/sop-gateway/src/test/java/com/gitee/sop/gateway/SopGatewayApplicationTests.java b/sop-gateway/src/test/java/com/gitee/sop/gateway/SopGatewayApplicationTests.java
new file mode 100644
index 00000000..8db99012
--- /dev/null
+++ b/sop-gateway/src/test/java/com/gitee/sop/gateway/SopGatewayApplicationTests.java
@@ -0,0 +1,17 @@
+package com.gitee.sop.gateway;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SopGatewayApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
+
diff --git a/sop-gateway/src/test/java/com/gitee/sop/gateway/SopPostTest.java b/sop-gateway/src/test/java/com/gitee/sop/gateway/SopPostTest.java
new file mode 100644
index 00000000..ce374de7
--- /dev/null
+++ b/sop-gateway/src/test/java/com/gitee/sop/gateway/SopPostTest.java
@@ -0,0 +1,121 @@
+package com.gitee.sop.gateway;
+
+import com.alibaba.fastjson.JSON;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class SopPostTest extends TestBase {
+
+
+ String url = "http://localhost:8081/zuul";
+ String appKey = "test";
+ String secret = "123456";
+
+ @Test
+ public void testPost() throws Exception {
+
+ // 系统参数
+ Map param = new HashMap();
+ param.put("method", "story.get");
+ param.put("app_key", appKey);
+ param.put("timestamp", getTime());
+ param.put("version", "2.0");
+
+ // 业务参数
+ param.put("id", "1");
+ param.put("name", "葫芦娃");
+
+ String sign = buildSign(param, secret);
+
+ param.put("sign", sign);
+
+ System.out.println("=====请求数据=====");
+ String postJson = JSON.toJSONString(param);
+ System.out.println(postJson);
+
+ post(url, param); // 发送请求
+ }
+
+ /**
+ * 构建签名
+ *
+ * @param paramsMap 参数
+ * @param secret 密钥
+ * @return
+ * @throws IOException
+ */
+ public static String buildSign(Map paramsMap, String secret) throws IOException {
+ Set keySet = paramsMap.keySet();
+ List paramNames = new ArrayList(keySet);
+
+ Collections.sort(paramNames);
+
+ StringBuilder paramNameValue = new StringBuilder();
+
+ for (String paramName : paramNames) {
+ paramNameValue.append(paramName).append(paramsMap.get(paramName));
+ }
+
+ String source = secret + paramNameValue.toString() + secret;
+
+ return md5(source);
+ }
+
+ /**
+ * 生成md5,全部大写
+ *
+ * @param message
+ * @return
+ */
+ public static String md5(String message) {
+ try {
+ // 1 创建一个提供信息摘要算法的对象,初始化为md5算法对象
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ // 2 将消息变成byte数组
+ byte[] input = message.getBytes(StandardCharsets.UTF_8);
+
+ // 3 计算后获得字节数组,这就是那128位了
+ byte[] buff = md.digest(input);
+
+ // 4 把数组每一字节(一个字节占八位)换成16进制连成md5字符串
+ return byte2hex(buff);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 二进制转十六进制字符串
+ *
+ * @param bytes
+ * @return
+ */
+ private static String byte2hex(byte[] bytes) {
+ StringBuilder sign = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ String hex = Integer.toHexString(bytes[i] & 0xFF);
+ if (hex.length() == 1) {
+ sign.append("0");
+ }
+ sign.append(hex.toUpperCase());
+ }
+ return sign.toString();
+ }
+
+ public String getTime() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
+ }
+
+}
diff --git a/sop-gateway/src/test/java/com/gitee/sop/gateway/TestBase.java b/sop-gateway/src/test/java/com/gitee/sop/gateway/TestBase.java
new file mode 100644
index 00000000..16593289
--- /dev/null
+++ b/sop-gateway/src/test/java/com/gitee/sop/gateway/TestBase.java
@@ -0,0 +1,102 @@
+package com.gitee.sop.gateway;
+
+import junit.framework.TestCase;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author tanghc
+ */
+public class TestBase extends TestCase {
+ public void post(String url, String postJson) throws IOException {
+ HttpClient httpClient = HttpClientBuilder.create().build();
+ HttpPost post = new HttpPost(url);
+ // 构造消息头
+
+ post.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
+ // 构建消息实体
+ StringEntity entity = new StringEntity(postJson, Charset.forName("UTF-8"));
+ entity.setContentEncoding("UTF-8");
+ // 发送Json格式的数据请求
+ entity.setContentType("application/json");
+ post.setEntity(entity);
+
+ HttpResponse response = httpClient.execute(post);
+ HttpEntity responseEntity = response.getEntity();
+ String content = IOUtils.toString(responseEntity.getContent(), "UTF-8");
+ System.out.println(content);
+ }
+
+ /**
+ * 发送POST请求
+ * @param url
+ * @return JSON或者字符串
+ * @throws Exception
+ */
+ public static Object post(String url, Map params) throws Exception{
+ CloseableHttpClient client = null;
+ CloseableHttpResponse response = null;
+ try{
+ /**
+ * 创建一个httpclient对象
+ */
+ client = HttpClients.createDefault();
+ /**
+ * 创建一个post对象
+ */
+ HttpPost post = new HttpPost(url);
+ List nameValuePairs = params.entrySet().stream().map(entry -> {
+ return new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue()));
+ }).collect(Collectors.toList());
+ /**
+ * 包装成一个Entity对象
+ */
+ UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nameValuePairs, "UTF-8");
+ /**
+ * 设置请求的内容
+ */
+ post.setEntity(entity);
+ /**
+ * 设置请求的报文头部的编码
+ */
+ post.setHeader(new BasicHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"));
+ /**
+ * 执行post请求
+ */
+ response = client.execute(post);
+ /**
+ * 通过EntityUitls获取返回内容
+ */
+ String result = EntityUtils.toString(response.getEntity(),"UTF-8");
+ System.out.println(result);
+ return result;
+ }catch (Exception e){
+ e.printStackTrace();
+ }finally {
+ IOUtils.closeQuietly(client);
+ IOUtils.closeQuietly(response);
+ }
+ return null;
+ }
+
+
+}
diff --git a/sop-registry/.gitignore b/sop-registry/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/sop-registry/.gitignore
@@ -0,0 +1,25 @@
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/sop-registry/.mvn/wrapper/maven-wrapper.jar b/sop-registry/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 00000000..01e67997
Binary files /dev/null and b/sop-registry/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/sop-registry/.mvn/wrapper/maven-wrapper.properties b/sop-registry/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 00000000..cd0d451c
--- /dev/null
+++ b/sop-registry/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
diff --git a/sop-registry/mvnw b/sop-registry/mvnw
new file mode 100755
index 00000000..5551fde8
--- /dev/null
+++ b/sop-registry/mvnw
@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ 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
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ 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
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ wget "$jarUrl" -O "$wrapperJarPath"
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ curl -o "$wrapperJarPath" "$jarUrl"
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/sop-registry/mvnw.cmd b/sop-registry/mvnw.cmd
new file mode 100644
index 00000000..e5cfb0ae
--- /dev/null
+++ b/sop-registry/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ echo Found %WRAPPER_JAR%
+) else (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+ echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/sop-registry/pom.xml b/sop-registry/pom.xml
new file mode 100644
index 00000000..2518b0e9
--- /dev/null
+++ b/sop-registry/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ com.gitee.sop
+ sop-registry
+ 1.0.0-SNAPSHOT
+ sop-registry
+ 注册中心
+
+
+ 1.8
+ Greenwich.RELEASE
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-server
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
+
diff --git a/sop-registry/src/main/java/com/gitee/sop/registry/SopRegistryApplication.java b/sop-registry/src/main/java/com/gitee/sop/registry/SopRegistryApplication.java
new file mode 100644
index 00000000..42662cd4
--- /dev/null
+++ b/sop-registry/src/main/java/com/gitee/sop/registry/SopRegistryApplication.java
@@ -0,0 +1,16 @@
+package com.gitee.sop.registry;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
+
+@EnableEurekaServer
+@SpringBootApplication
+public class SopRegistryApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SopRegistryApplication.class, args);
+ }
+
+}
+
diff --git a/sop-registry/src/main/resources/application.properties b/sop-registry/src/main/resources/application.properties
new file mode 100644
index 00000000..da1d10a4
--- /dev/null
+++ b/sop-registry/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+spring.application.name=sop-registry
+server.port=1111
+
+# ---- eureka注册中心 ----
+# 不注册自己
+eureka.client.register-with-eureka=false
+eureka.client.fetch-registry=false
+# 注册中心地址
+eureka.host=localhost
+eureka.port=1111
+eureka.client.serviceUrl.defaultZone=http://${eureka.host}:${eureka.port}/eureka/
+
diff --git a/sop-registry/src/test/java/com/gitee/sop/registry/SopRegistryApplicationTests.java b/sop-registry/src/test/java/com/gitee/sop/registry/SopRegistryApplicationTests.java
new file mode 100644
index 00000000..da1deda9
--- /dev/null
+++ b/sop-registry/src/test/java/com/gitee/sop/registry/SopRegistryApplicationTests.java
@@ -0,0 +1,17 @@
+package com.gitee.sop.registry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SopRegistryApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
+
diff --git a/sop-server-common/.gitignore b/sop-server-common/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/sop-server-common/.gitignore
@@ -0,0 +1,25 @@
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/sop-server-common/pom.xml b/sop-server-common/pom.xml
new file mode 100644
index 00000000..6843c42d
--- /dev/null
+++ b/sop-server-common/pom.xml
@@ -0,0 +1,92 @@
+
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ com.gitee.sop
+ sop-server-common
+ sop-server-common
+ 1.0.0-SNAPSHOT
+
+
+ UTF-8
+ Greenwich.RELEASE
+ 1.8
+ 1.8
+ 1.8.8.RELEASE
+ 1.2.15
+
+
+
+
+ com.alibaba
+ fastjson
+ ${fastjson.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ 9.0.14
+ provided
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+ provided
+
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
diff --git a/sop-server-common/readme.md b/sop-server-common/readme.md
new file mode 100644
index 00000000..1335082e
--- /dev/null
+++ b/sop-server-common/readme.md
@@ -0,0 +1,3 @@
+# sop-server-common
+
+微服务端公共组件,每个微服务需要依赖这个jar,先把这个工程mvn deploy到maven私服。
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiAbility.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiAbility.java
new file mode 100644
index 00000000..91f2af05
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiAbility.java
@@ -0,0 +1,23 @@
+package com.gitee.sop.servercommon.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 设置接口能力。
+ * 如果想把已经存在的接口开放出去,可用此注解。
+ * 作用于Controller类上或方法上。如果作用在类上,则类中的所有方法将具备开放平台接口提供能力。
+ * @author tanghc
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ApiAbility {
+ /**
+ * 版本号,如:1.0
+ */
+ String version() default "";
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiMapping.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiMapping.java
new file mode 100644
index 00000000..5a5a16d0
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiMapping.java
@@ -0,0 +1,64 @@
+package com.gitee.sop.servercommon.annotation;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 接口申明注解,使用方式同RequestMapping一样,多了一个版本号属性,
+ * 用了此注解具备开放平台接口提供能力。
+ * @author tanghc
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping
+public @interface ApiMapping {
+
+ /**
+ * 版本号,默认版本号是""
+ * 改默认版本号:ServiceContext.getSopServerConfig().setDefaultVersion("1.0");
+ */
+ String version() default "";
+
+ /**
+ * 接口名
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+
+ @AliasFor(annotation = RequestMapping.class)
+ RequestMethod[] method() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ParamNames.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ParamNames.java
new file mode 100644
index 00000000..8694daf2
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ParamNames.java
@@ -0,0 +1,46 @@
+package com.gitee.sop.servercommon.bean;
+
+/**
+ * 请求参数名定义
+ *
+ * 参数 类型 是否必填 最大长度 描述 示例值
+ * app_id String 是 32 支付宝分配给开发者的应用ID 2014072300007148
+ * method String 是 128 接口名称 alipay.trade.fastpay.refund.query
+ * format String 否 40 仅支持JSON JSON
+ * charset String 是 10 请求使用的编码格式,如utf-8,gbk,gb2312等 utf-8
+ * sign_type String 是 10 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 RSA2
+ * sign String 是 344 商户请求参数的签名串,详见签名 详见示例
+ * timestamp String 是 19 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" 2014-07-24 03:07:50
+ * version String 是 3 调用的接口版本,固定为:1.0 1.0
+ * app_auth_token String 否 40 详见应用授权概述
+ * biz_content String 是 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
+ *
+ * @author tanghc
+ */
+public class ParamNames {
+ /** 分配给开发者的应用ID */
+ public static String APP_KEY_NAME = "app_id";
+ /** 接口名称 */
+ public static String API_NAME = "method";
+ /** 仅支持JSON */
+ public static String FORMAT_NAME = "format";
+ /** 请求使用的编码格式 */
+ public static String CHARSET_NAME = "charset";
+ /** 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 */
+ public static String SIGN_TYPE_NAME = "sign_type";
+ /** 商户请求参数的签名串 */
+ public static String SIGN_NAME = "sign";
+ /** 发送请求的时间 */
+ public static String TIMESTAMP_NAME = "timestamp";
+ /** 调用的接口版本 */
+ public static String VERSION_NAME = "version";
+ /** OAuth 2.0授权token */
+ public static String APP_AUTH_TOKEN_NAME = "app_auth_token";
+ /** 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档 */
+ public static String BIZ_CONTENT_NAME = "biz_content";
+
+ /** */
+ public static String TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceApiInfo.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceApiInfo.java
new file mode 100644
index 00000000..3b67f8a3
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceApiInfo.java
@@ -0,0 +1,39 @@
+package com.gitee.sop.servercommon.bean;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author tanghc
+ */
+@Data
+public class ServiceApiInfo {
+ private String md5;
+ private String appName;
+ private List apis;
+
+ @Data
+ public static class ApiMeta {
+ /** 接口名 */
+ private String name;
+ /** 请求path */
+ private String path;
+ /** 版本号 */
+ private String version;
+
+ public ApiMeta() {
+ }
+
+ public ApiMeta(String name, String path, String version) {
+ this.name = name;
+ this.path = path;
+ this.version = version;
+ }
+
+ public String fetchNameVersion() {
+ return this.name + this.version;
+ }
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConfig.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConfig.java
new file mode 100644
index 00000000..a6292458
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConfig.java
@@ -0,0 +1,57 @@
+package com.gitee.sop.servercommon.bean;
+
+import com.gitee.sop.servercommon.configuration.DefaultGlobalExceptionHandler;
+import com.gitee.sop.servercommon.configuration.GlobalExceptionHandler;
+import com.gitee.sop.servercommon.param.ApiArgumentResolver;
+import com.gitee.sop.servercommon.result.DefaultServiceResultBuilder;
+import com.gitee.sop.servercommon.result.ServiceResultBuilder;
+import lombok.Data;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author tanghc
+ */
+@Data
+public class ServiceConfig {
+
+ private static ServiceConfig instance = new ServiceConfig();
+
+ private ServiceConfig() {
+ }
+
+ /**
+ * 默认版本号
+ */
+ private String defaultVersion = "";
+
+ /**
+ * 错误模块
+ */
+ private List i18nModules = new ArrayList();
+
+ /**
+ * 解析业务参数
+ */
+ private HandlerMethodArgumentResolver methodArgumentResolver = new ApiArgumentResolver();
+
+ /**
+ * 返回结果处理
+ */
+ private ServiceResultBuilder serviceResultBuilder = new DefaultServiceResultBuilder();
+
+ /**
+ * 全局异常处理
+ */
+ private GlobalExceptionHandler globalExceptionHandler = new DefaultGlobalExceptionHandler();
+
+ public static ServiceConfig getInstance() {
+ return instance;
+ }
+
+ public static void setInstance(ServiceConfig instance) {
+ ServiceConfig.instance = instance;
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceContext.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceContext.java
new file mode 100644
index 00000000..aba95ea6
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceContext.java
@@ -0,0 +1,139 @@
+package com.gitee.sop.servercommon.bean;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Locale;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ServiceContext extends ConcurrentHashMap {
+
+ public static final String REQUEST_KEY = "request";
+ public static final String RESPONSE_KEY = "response";
+ protected static Class extends ServiceContext> contextClass = ServiceContext.class;
+
+ protected static final ThreadLocal extends ServiceContext> threadLocal = new ThreadLocal() {
+ @Override
+ protected ServiceContext initialValue() {
+ try {
+ return contextClass.newInstance();
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+
+ public ServiceContext() {
+ super();
+ }
+
+ public Locale getLocale() {
+ return getRequest().getLocale();
+ }
+
+ /**
+ * Override the default ServiceContext
+ *
+ * @param clazz
+ */
+ public static void setContextClass(Class extends ServiceContext> clazz) {
+ contextClass = clazz;
+ }
+
+
+ /**
+ * Get the current ServiceContext
+ *
+ * @return the current ServiceContext
+ */
+ public static ServiceContext getCurrentContext() {
+ return threadLocal.get();
+ }
+
+ /**
+ * Convenience method to return a boolean value for a given key
+ *
+ * @param key
+ * @return true or false depending what was set. default is false
+ */
+ public boolean getBoolean(String key) {
+ return getBoolean(key, false);
+ }
+
+ /**
+ * Convenience method to return a boolean value for a given key
+ *
+ * @param key
+ * @param defaultResponse
+ * @return true or false depending what was set. default defaultResponse
+ */
+ public boolean getBoolean(String key, boolean defaultResponse) {
+ Boolean b = (Boolean) get(key);
+ if (b != null) {
+ return b.booleanValue();
+ }
+ return defaultResponse;
+ }
+
+ /**
+ * sets a key value to Boolen.TRUE
+ *
+ * @param key
+ */
+ public void set(String key) {
+ put(key, Boolean.TRUE);
+ }
+
+ /**
+ * puts the key, value into the map. a null value will remove the key from the map
+ *
+ * @param key
+ * @param value
+ */
+ public void set(String key, Object value) {
+ if (value != null) put(key, value);
+ else remove(key);
+ }
+
+ /**
+ * @return the HttpServletRequest from the "request" key
+ */
+ public HttpServletRequest getRequest() {
+ return (HttpServletRequest) get(REQUEST_KEY);
+ }
+
+ /**
+ * sets the HttpServletRequest into the "request" key
+ *
+ * @param request
+ */
+ public void setRequest(HttpServletRequest request) {
+ put(REQUEST_KEY, request);
+ }
+
+ /**
+ * @return the HttpServletResponse from the "response" key
+ */
+ public HttpServletResponse getResponse() {
+ return (HttpServletResponse) get(RESPONSE_KEY);
+ }
+
+ /**
+ * sets the "response" key to the HttpServletResponse passed in
+ *
+ * @param response
+ */
+ public void setResponse(HttpServletResponse response) {
+ set(RESPONSE_KEY, response);
+ }
+
+
+ /**
+ * unsets the threadLocal context. Done at the end of the request.
+ */
+ public void unset() {
+ threadLocal.remove();
+ }
+
+
+}
\ No newline at end of file
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/AlipayServerConfiguration.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/AlipayServerConfiguration.java
new file mode 100644
index 00000000..e6623169
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/AlipayServerConfiguration.java
@@ -0,0 +1,24 @@
+package com.gitee.sop.servercommon.configuration;
+
+import com.gitee.sop.servercommon.bean.ServiceConfig;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+
+import java.util.List;
+
+/**
+ * 具备支付宝开放平台服务提供能力
+ * @author tanghc
+ */
+public class AlipayServerConfiguration extends BaseServerConfiguration {
+
+ public AlipayServerConfiguration() {
+ // 默认版本号为1.0
+ ServiceConfig.getInstance().setDefaultVersion("1.0");
+ }
+
+ @Override
+ protected void addArgumentResolvers(List argumentResolvers) {
+ argumentResolvers.add(ServiceConfig.getInstance().getMethodArgumentResolver());
+ }
+
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/BaseServerConfiguration.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/BaseServerConfiguration.java
new file mode 100644
index 00000000..f4c32c01
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/BaseServerConfiguration.java
@@ -0,0 +1,78 @@
+package com.gitee.sop.servercommon.configuration;
+
+import com.gitee.sop.servercommon.bean.ServiceConfig;
+import com.gitee.sop.servercommon.exception.ServiceException;
+import com.gitee.sop.servercommon.interceptor.ServiceContextInterceptor;
+import com.gitee.sop.servercommon.manager.ApiMetaManager;
+import com.gitee.sop.servercommon.manager.DefaultRequestMappingEvent;
+import com.gitee.sop.servercommon.manager.RedisApiMetaManager;
+import com.gitee.sop.servercommon.mapping.ApiMappingHandlerMapping;
+import com.gitee.sop.servercommon.message.ServiceErrorFactory;
+import com.gitee.sop.servercommon.result.ServiceResultBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.env.Environment;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author tanghc
+ */
+public class BaseServerConfiguration extends WebMvcConfigurationSupport {
+
+ public BaseServerConfiguration() {
+ ServiceConfig.getInstance().getI18nModules().add("i18n/isp/bizerror");
+ }
+
+ @Autowired
+ private StringRedisTemplate redisTemplate;
+
+ @Autowired
+ private Environment environment;
+
+ @Override
+ protected void addInterceptors(InterceptorRegistry registry) {
+ // 添加拦截器
+ registry.addInterceptor(new ServiceContextInterceptor());
+ super.addInterceptors(registry);
+ }
+
+ // 自定义Mapping,详见@ApiMapping
+ @Override
+ protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
+ ApiMetaManager apiMetaManager = new RedisApiMetaManager(redisTemplate);
+ DefaultRequestMappingEvent requestMappingEvent = new DefaultRequestMappingEvent(apiMetaManager, environment);
+ return new ApiMappingHandlerMapping(requestMappingEvent);
+ }
+
+ @Bean
+ GlobalExceptionHandler globalExceptionHandler() {
+ return ServiceConfig.getInstance().getGlobalExceptionHandler();
+ }
+
+ @PostConstruct
+ public void after() {
+ doAfter();
+ }
+
+ protected void doAfter() {
+ initMessage();
+ }
+
+ protected void initMessage() {
+ ServiceErrorFactory.initMessageSource(ServiceConfig.getInstance().getI18nModules());
+ }
+
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/DefaultGlobalExceptionHandler.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/DefaultGlobalExceptionHandler.java
new file mode 100644
index 00000000..9395fc41
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/DefaultGlobalExceptionHandler.java
@@ -0,0 +1,46 @@
+package com.gitee.sop.servercommon.configuration;
+
+import com.gitee.sop.servercommon.bean.ServiceConfig;
+import com.gitee.sop.servercommon.exception.ServiceException;
+import com.gitee.sop.servercommon.result.ServiceResultBuilder;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 全局异常处理
+ * @author tanghc
+ */
+@ControllerAdvice
+public class DefaultGlobalExceptionHandler implements GlobalExceptionHandler {
+
+ @RequestMapping("/error")
+ @ResponseBody
+ public Object error(HttpServletRequest request, HttpServletResponse response) {
+ ServiceResultBuilder serviceResultBuilder = ServiceConfig.getInstance().getServiceResultBuilder();
+ return serviceResultBuilder.buildError(request, response, new RuntimeException("系统繁忙"));
+ }
+
+ @ExceptionHandler(ServiceException.class)
+ @ResponseBody
+ public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) throws Exception {
+ return this.processError(request, response, exception);
+ }
+
+ /**
+ * 处理异常
+ *
+ * @param request
+ * @param response
+ * @param exception
+ * @return
+ */
+ protected Object processError(HttpServletRequest request, HttpServletResponse response, Exception exception) {
+ ServiceResultBuilder serviceResultBuilder = ServiceConfig.getInstance().getServiceResultBuilder();
+ return serviceResultBuilder.buildError(request, response, exception);
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/GlobalExceptionHandler.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/GlobalExceptionHandler.java
new file mode 100644
index 00000000..822ae86d
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/GlobalExceptionHandler.java
@@ -0,0 +1,7 @@
+package com.gitee.sop.servercommon.configuration;
+
+/**
+ * @author tanghc
+ */
+public interface GlobalExceptionHandler {
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/TaobaoServerConfiguration.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/TaobaoServerConfiguration.java
new file mode 100644
index 00000000..50990e0d
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/configuration/TaobaoServerConfiguration.java
@@ -0,0 +1,9 @@
+package com.gitee.sop.servercommon.configuration;
+
+/**
+ * 具备淘宝开放平台服务提供能力
+ * @author tanghc
+ */
+public class TaobaoServerConfiguration extends BaseServerConfiguration {
+
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/exception/ServiceException.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/exception/ServiceException.java
new file mode 100644
index 00000000..86d5d84e
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/exception/ServiceException.java
@@ -0,0 +1,26 @@
+package com.gitee.sop.servercommon.exception;
+
+import com.gitee.sop.servercommon.message.ServiceError;
+import com.gitee.sop.servercommon.message.ServiceErrorImpl;
+
+/**
+ * @author tanghc
+ */
+public class ServiceException extends RuntimeException {
+
+ private ServiceError error;
+
+ public ServiceException(String subCode, String subMsg) {
+ super(subMsg);
+ this.error = new ServiceErrorImpl(subCode, subMsg);
+ }
+
+ public ServiceException(ServiceError error) {
+ super(error.toString());
+ this.error = error;
+ }
+
+ public ServiceError getError() {
+ return error;
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/interceptor/ServiceContextInterceptor.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/interceptor/ServiceContextInterceptor.java
new file mode 100644
index 00000000..7ff9495f
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/interceptor/ServiceContextInterceptor.java
@@ -0,0 +1,25 @@
+package com.gitee.sop.servercommon.interceptor;
+
+import com.gitee.sop.servercommon.bean.ServiceContext;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author tanghc
+ */
+public class ServiceContextInterceptor extends HandlerInterceptorAdapter {
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ ServiceContext context = ServiceContext.getCurrentContext();
+ context.setRequest(request);
+ context.setResponse(response);
+ return true;
+ }
+
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+ ServiceContext.getCurrentContext().unset();
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaManager.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaManager.java
new file mode 100644
index 00000000..225074ce
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaManager.java
@@ -0,0 +1,12 @@
+package com.gitee.sop.servercommon.manager;
+
+import com.gitee.sop.servercommon.bean.ServiceApiInfo;
+
+/**
+ * @author tanghc
+ */
+public interface ApiMetaManager {
+ String API_STORE_KEY = "com.gitee.sop.api";
+
+ void uploadApi(ServiceApiInfo serviceApiInfo);
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/DefaultRequestMappingEvent.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/DefaultRequestMappingEvent.java
new file mode 100644
index 00000000..06d29b3f
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/DefaultRequestMappingEvent.java
@@ -0,0 +1,100 @@
+package com.gitee.sop.servercommon.manager;
+
+import com.gitee.sop.servercommon.bean.ServiceApiInfo;
+import com.gitee.sop.servercommon.mapping.ApiMappingHandlerMapping;
+import com.gitee.sop.servercommon.mapping.ApiMappingInfo;
+import com.gitee.sop.servercommon.mapping.ApiMappingRequestCondition;
+import lombok.Getter;
+import org.springframework.core.env.Environment;
+import org.springframework.util.DigestUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.condition.RequestCondition;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author tanghc
+ */
+@Getter
+public class DefaultRequestMappingEvent implements RequestMappingEvent {
+
+ private ApiMetaManager apiMetaManager;
+ private Environment environment;
+
+ public DefaultRequestMappingEvent(ApiMetaManager apiMetaManager, Environment environment) {
+ this.apiMetaManager = apiMetaManager;
+ this.environment = environment;
+ }
+
+ @Override
+ public void onRegisterSuccess(ApiMappingHandlerMapping apiMappingHandlerMapping) {
+ Map handlerMethods = apiMappingHandlerMapping.getHandlerMethods();
+ Set requestMappingInfos = handlerMethods.keySet();
+ List store = new ArrayList<>(requestMappingInfos.size());
+ List apis = new ArrayList<>(requestMappingInfos.size());
+
+ for (RequestMappingInfo requestMappingInfo : requestMappingInfos) {
+ Set patterns = requestMappingInfo.getPatternsCondition().getPatterns();
+ RequestCondition> customCondition = requestMappingInfo.getCustomCondition();
+ if (customCondition instanceof ApiMappingRequestCondition) {
+ ApiMappingRequestCondition condition = (ApiMappingRequestCondition) customCondition;
+ ApiMappingInfo apiMappingInfo = condition.getApiMappingInfo();
+ String name = apiMappingInfo.getName();
+ String version = apiMappingInfo.getVersion();
+ String path = patterns.iterator().next();
+ // 不是ApiMapping注解的接口,name属性是null
+ if (name == null) {
+ name = buildName(path);
+ }
+ String key = path + version;
+ if (store.contains(key)) {
+ throw new RuntimeException("重复申明接口,请检查path和version,path:" + path + ", version:" + version);
+ } else {
+ store.add(key);
+ }
+ apis.add(new ServiceApiInfo.ApiMeta(name, path, version));
+ }
+ }
+
+ String appName = environment.getProperty("spring.application.name");
+ if (appName == null) {
+ throw new RuntimeException("请在application.properties中指定spring.application.name属性");
+ }
+ // 排序
+ Collections.sort(apis, new Comparator() {
+ @Override
+ public int compare(ServiceApiInfo.ApiMeta o1, ServiceApiInfo.ApiMeta o2) {
+ return o1.fetchNameVersion().compareTo(o2.fetchNameVersion());
+ }
+ });
+
+ ServiceApiInfo serviceApiInfo = new ServiceApiInfo();
+ serviceApiInfo.setAppName(appName);
+ serviceApiInfo.setApis(apis);
+ serviceApiInfo.setMd5(getMd5(apis));
+
+ apiMetaManager.uploadApi(serviceApiInfo);
+ }
+
+ protected String buildName(String path) {
+ path = StringUtils.trimLeadingCharacter(path, '/');
+ path = StringUtils.trimTrailingCharacter(path, '/');
+ path = path.replace("/", ".");
+ return path;
+ }
+
+ protected String getMd5(List apis) {
+ StringBuilder sb = new StringBuilder();
+ for (ServiceApiInfo.ApiMeta api : apis) {
+ sb.append(api.fetchNameVersion());
+ }
+ return DigestUtils.md5DigestAsHex(sb.toString().getBytes());
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/RedisApiMetaManager.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/RedisApiMetaManager.java
new file mode 100644
index 00000000..7deb94c9
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/RedisApiMetaManager.java
@@ -0,0 +1,29 @@
+package com.gitee.sop.servercommon.manager;
+
+import com.alibaba.fastjson.JSON;
+import com.gitee.sop.servercommon.bean.ServiceApiInfo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+/**
+ * @author tanghc
+ */
+@Slf4j
+public class RedisApiMetaManager implements ApiMetaManager {
+ public static final String API_CHANGE_CHANNEL = "channel.sop.api.change";
+
+ private StringRedisTemplate redisTemplate;
+
+ public RedisApiMetaManager(StringRedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
+
+ @Override
+ public void uploadApi(ServiceApiInfo serviceApiInfo) {
+ log.info("上传接口信息到Redis,appName:{}, md5:{}, 接口数量:{}", serviceApiInfo.getAppName(), serviceApiInfo.getMd5(), serviceApiInfo.getApis().size());
+ String serviceApiInfoJson = JSON.toJSONString(serviceApiInfo);
+ redisTemplate.opsForHash().put(API_STORE_KEY, serviceApiInfo.getAppName(), serviceApiInfoJson);
+ // 发送订阅事件
+ redisTemplate.convertAndSend(API_CHANGE_CHANNEL, serviceApiInfoJson);
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/RequestMappingEvent.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/RequestMappingEvent.java
new file mode 100644
index 00000000..e9031dcd
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/manager/RequestMappingEvent.java
@@ -0,0 +1,10 @@
+package com.gitee.sop.servercommon.manager;
+
+import com.gitee.sop.servercommon.mapping.ApiMappingHandlerMapping;
+
+/**
+ * @author tanghc
+ */
+public interface RequestMappingEvent {
+ void onRegisterSuccess(ApiMappingHandlerMapping apiMappingHandlerMapping);
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java
new file mode 100644
index 00000000..8f9f7a96
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java
@@ -0,0 +1,76 @@
+package com.gitee.sop.servercommon.mapping;
+
+import com.gitee.sop.servercommon.annotation.ApiMapping;
+import com.gitee.sop.servercommon.annotation.ApiAbility;
+import com.gitee.sop.servercommon.bean.ServiceConfig;
+import com.gitee.sop.servercommon.manager.RequestMappingEvent;
+import org.springframework.core.PriorityOrdered;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.StringValueResolver;
+import org.springframework.web.servlet.mvc.condition.RequestCondition;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.lang.reflect.Method;
+
+public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping implements PriorityOrdered {
+
+ private static StringValueResolver stringValueResolver = new ApiMappingStringValueResolver();
+
+ private RequestMappingEvent requestMappingEvent;
+
+ public ApiMappingHandlerMapping(RequestMappingEvent requestMappingEvent) {
+ this.requestMappingEvent = requestMappingEvent;
+ }
+
+ @Override
+ protected RequestMappingInfo getMappingForMethod(Method method, Class> handlerType) {
+ ApiMapping apiMapping = method.getAnnotation(ApiMapping.class);
+ StringValueResolver valueResolver = null;
+ if (apiMapping != null) {
+ valueResolver = stringValueResolver;
+ }
+ this.setEmbeddedValueResolver(valueResolver);
+ return super.getMappingForMethod(method, handlerType);
+ }
+
+ @Override
+ protected RequestCondition> getCustomMethodCondition(Method method) {
+ method.setAccessible(true);
+ String name = null;
+ String version;
+ ApiMapping apiMapping = method.getAnnotation(ApiMapping.class);
+ if (apiMapping != null) {
+ name = apiMapping.value()[0];
+ version = apiMapping.version();
+ } else {
+ ApiAbility apiAbility = this.findApiAbilityAnnotation(method);
+ if (apiAbility != null) {
+ version = apiAbility.version();
+ } else {
+ return super.getCustomMethodCondition(method);
+ }
+ }
+ if ("".equals(version)) {
+ version = ServiceConfig.getInstance().getDefaultVersion();
+ }
+ ApiMappingInfo apiMappingInfo = new ApiMappingInfo(name, version);
+ logger.info("注册接口,method:" + method + ", version:" + version);
+ return new ApiMappingRequestCondition(apiMappingInfo);
+ }
+
+ protected ApiAbility findApiAbilityAnnotation(Method method) {
+ ApiAbility apiAbility = method.getAnnotation(ApiAbility.class);
+ if (apiAbility == null) {
+ Class> controllerClass = method.getDeclaringClass();
+ apiAbility = AnnotationUtils.findAnnotation(controllerClass, ApiAbility.class);
+ }
+ return apiAbility;
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ super.afterPropertiesSet();
+ this.requestMappingEvent.onRegisterSuccess(this);
+ }
+}
\ No newline at end of file
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingInfo.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingInfo.java
new file mode 100644
index 00000000..3f14ba6c
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingInfo.java
@@ -0,0 +1,17 @@
+package com.gitee.sop.servercommon.mapping;
+
+import lombok.Data;
+
+/**
+ * @author tanghc
+ */
+@Data
+public class ApiMappingInfo {
+ private String name;
+ private String version;
+
+ public ApiMappingInfo(String name, String version) {
+ this.name = name;
+ this.version = version;
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingRequestCondition.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingRequestCondition.java
new file mode 100644
index 00000000..c11c32f7
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingRequestCondition.java
@@ -0,0 +1,54 @@
+package com.gitee.sop.servercommon.mapping;
+
+import com.gitee.sop.servercommon.bean.ServiceConfig;
+import com.gitee.sop.servercommon.bean.ParamNames;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.servlet.mvc.condition.RequestCondition;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Getter
+@Slf4j
+public class ApiMappingRequestCondition implements RequestCondition {
+
+ public static final String UTF_8 = "UTF-8";
+ private String defaultVersion = ServiceConfig.getInstance().getDefaultVersion();
+
+ private ApiMappingInfo apiMappingInfo;
+
+ public ApiMappingRequestCondition(ApiMappingInfo apiMappingInfo) {
+ this.apiMappingInfo = apiMappingInfo;
+ }
+
+ @Override
+ public ApiMappingRequestCondition combine(ApiMappingRequestCondition other) {
+ return this;
+ }
+
+ // 如果版本号跟当前对象中的版本号匹配,则表是命中的对应的方法
+ // 否则返回null,表示不匹配
+ @Override
+ public ApiMappingRequestCondition getMatchingCondition(HttpServletRequest request) {
+ String version = this.getVersion(request);
+ if (version.equals(this.apiMappingInfo.getVersion())) {
+ return this;
+ }
+ return null;
+ }
+
+ protected String getVersion(HttpServletRequest request) {
+ String version = request.getParameter(ParamNames.VERSION_NAME);
+ return version == null ? defaultVersion : version;
+ }
+
+
+ // 对两个RequestCondition对象进行比较,这里主要是如果存在两个注册的一样的Mapping,那么就会对
+ // 这两个Mapping进行排序,以判断哪个Mapping更适合处理当前request请求
+ @Override
+ public int compareTo(ApiMappingRequestCondition other, HttpServletRequest request) {
+ return null != apiMappingInfo && null == other.apiMappingInfo ? 1
+ : null == apiMappingInfo && null != other.apiMappingInfo ? -1 : 0;
+ }
+
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingStringValueResolver.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingStringValueResolver.java
new file mode 100644
index 00000000..d17ce61a
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingStringValueResolver.java
@@ -0,0 +1,19 @@
+package com.gitee.sop.servercommon.mapping;
+
+import org.springframework.util.StringValueResolver;
+
+/**
+ * @author tanghc
+ */
+public class ApiMappingStringValueResolver implements StringValueResolver {
+
+ private static final String END_CHAR = "/";
+
+ @Override
+ public String resolveStringValue(String strVal) {
+ if (strVal != null && !strVal.endsWith(END_CHAR)) {
+ return strVal + END_CHAR;
+ }
+ return null;
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceError.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceError.java
new file mode 100644
index 00000000..13018f59
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceError.java
@@ -0,0 +1,25 @@
+package com.gitee.sop.servercommon.message;
+
+/**
+ * 定义错误返回
+ * sub_code(明细返回码)
+ * sub_msg(明细返回码描述)
+ * 解决方案
+ * @author tanghc
+ */
+public interface ServiceError {
+
+ /**
+ * sub_code(明细返回码)
+ * @return sub_code(明细返回码)
+ */
+ String getSub_code();
+
+ /**
+ * sub_msg(明细返回码描述)
+ * @return sub_msg(明细返回码描述)
+ */
+ String getSub_msg();
+
+
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorEnum.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorEnum.java
new file mode 100644
index 00000000..2bcb981f
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorEnum.java
@@ -0,0 +1,20 @@
+package com.gitee.sop.servercommon.message;
+
+/**
+ * @author tanghc
+ */
+public enum ServiceErrorEnum {
+ /** 系统繁忙 */
+ ISP_UNKNOW_ERROR("isp.unknow-error"),
+ ISP_PARAM_ERROR("isv.invalid-parameter"),
+ ;
+ private ServiceErrorMeta errorMeta;
+
+ ServiceErrorEnum(String sub_code) {
+ this.errorMeta = new ServiceErrorMeta("isp.error_", sub_code);
+ }
+
+ public ServiceErrorMeta getErrorMeta() {
+ return errorMeta;
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorFactory.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorFactory.java
new file mode 100644
index 00000000..5553684e
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorFactory.java
@@ -0,0 +1,110 @@
+package com.gitee.sop.servercommon.message;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.context.support.ResourceBundleMessageSource;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 负责构建错误消息
+ *
+ * @author tanghc
+ */
+@Slf4j
+public class ServiceErrorFactory {
+
+ public static final String SYS_ERR = "系统错误";
+
+ private static final String I18N_OPEN_ERROR = "i18n/isp/error";
+
+ private static Set noModuleCache = new HashSet();
+
+ private static Map errorCache = new HashMap<>(64);
+
+ /**
+ * 错误信息的国际化信息
+ */
+ private static MessageSourceAccessor errorMessageSourceAccessor;
+
+ /**
+ * 设置国际化资源信息
+ */
+ public static void initMessageSource(List isvModules) {
+ HashSet baseNamesSet = new HashSet();
+ baseNamesSet.add(I18N_OPEN_ERROR);
+
+ if (!isvModules.isEmpty()) {
+ baseNamesSet.addAll(isvModules);
+ }
+
+ String[] totalBaseNames = baseNamesSet.toArray(new String[0]);
+
+ log.info("加载错误码国际化资源:{}", StringUtils.arrayToCommaDelimitedString(totalBaseNames));
+ ResourceBundleMessageSource bundleMessageSource = new ResourceBundleMessageSource();
+ bundleMessageSource.setBasenames(totalBaseNames);
+ MessageSourceAccessor messageSourceAccessor = new MessageSourceAccessor(bundleMessageSource);
+ setErrorMessageSourceAccessor(messageSourceAccessor);
+ }
+
+ /**
+ * 通过ErrorMeta,Locale,params构建国际化错误消息
+ *
+ * @param errorMeta 错误信息
+ * @param locale 本地化
+ * @param params 参数
+ * @return 如果没有配置国际化消息,则直接返回errorMeta中的信息
+ */
+ public static ServiceError getError(ServiceErrorMeta errorMeta, Locale locale, Object... params) {
+ String key = errorMeta.getModulePrefix() + errorMeta.getSubCode() + locale.toString();
+ ServiceError error = errorCache.get(key);
+ if (error == null) {
+ Assert.notNull(locale, "未设置Locale");
+ String modulePrefix = errorMeta.getModulePrefix();
+ String subCode = errorMeta.getSubCode();
+ // isp.error_sp.unknow-error=Service is temporarily unavailable
+ String subMsg = getErrorMessage(modulePrefix + subCode, locale, params);
+ if (StringUtils.isEmpty(subMsg)) {
+ subMsg = SYS_ERR;
+ }
+ error = new ServiceErrorImpl(subCode, subMsg);
+ errorCache.put(key, error);
+ }
+ return error;
+ }
+
+
+ public static void setErrorMessageSourceAccessor(MessageSourceAccessor errorMessageSourceAccessor) {
+ ServiceErrorFactory.errorMessageSourceAccessor = errorMessageSourceAccessor;
+ }
+
+ /**
+ * 返回本地化信息
+ *
+ * @param module 错误模块
+ * @param locale 本地化
+ * @param params 参数
+ * @return 返回信息
+ */
+ public static String getErrorMessage(String module, Locale locale, Object... params) {
+ if (noModuleCache.contains(module)) {
+ return null;
+ }
+ try {
+ return errorMessageSourceAccessor.getMessage(module, params, locale);
+ } catch (Exception e) {
+ noModuleCache.add(module);
+ return null;
+ }
+ }
+
+
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorImpl.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorImpl.java
new file mode 100644
index 00000000..c8abba9f
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorImpl.java
@@ -0,0 +1,17 @@
+package com.gitee.sop.servercommon.message;
+
+import lombok.Data;
+
+@Data
+public class ServiceErrorImpl implements ServiceError {
+ private String sub_code;
+ private String sub_msg;
+
+ public ServiceErrorImpl() {
+ }
+
+ public ServiceErrorImpl(String sub_code, String sub_msg) {
+ this.sub_code = sub_code;
+ this.sub_msg = sub_msg;
+ }
+}
\ No newline at end of file
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorMeta.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorMeta.java
new file mode 100644
index 00000000..c8816bc8
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorMeta.java
@@ -0,0 +1,38 @@
+package com.gitee.sop.servercommon.message;
+
+import com.gitee.sop.servercommon.bean.ServiceContext;
+import com.gitee.sop.servercommon.exception.ServiceException;
+import lombok.Getter;
+
+/**
+ * 错误对象
+ *
+ * @author tanghc
+ */
+@Getter
+public class ServiceErrorMeta {
+
+ private String modulePrefix;
+ private String subCode;
+
+ public ServiceErrorMeta(String modulePrefix, String subCode) {
+ this.modulePrefix = modulePrefix;
+ this.subCode = subCode;
+ }
+
+ public ServiceError getError() {
+ return ServiceErrorFactory.getError(this, ServiceContext.getCurrentContext().getLocale());
+ }
+
+ /**
+ * 返回网关exception
+ *
+ * @param params 参数
+ * @return 返回exception
+ */
+ public ServiceException getException(Object... params) {
+ ServiceError error = ServiceErrorFactory.getError(this, ServiceContext.getCurrentContext().getLocale(), params);
+ return new ServiceException(error);
+ }
+
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java
new file mode 100644
index 00000000..076367a8
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java
@@ -0,0 +1,39 @@
+package com.gitee.sop.servercommon.param;
+
+import com.alibaba.fastjson.JSON;
+import com.gitee.sop.servercommon.bean.ParamNames;
+import lombok.Data;
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+/**
+ * 解析request参数中的业务参数,隐射到方法参数上
+ * @author tanghc
+ */
+@Data
+public class ApiArgumentResolver implements HandlerMethodArgumentResolver {
+
+ private ParamValidator paramValidator = new ServiceParamValidator();
+
+ @Override
+ public boolean supportsParameter(MethodParameter methodParameter) {
+ return methodParameter != null;
+ }
+
+ @Override
+ public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
+ String bizContent = nativeWebRequest.getParameter(ParamNames.BIZ_CONTENT_NAME);
+ if (bizContent != null) {
+ Class> parameterType = methodParameter.getParameterType();
+ Object paramObj = JSON.parseObject(bizContent, parameterType);
+ // JSR-303验证
+ paramValidator.validateBizParam(paramObj);
+ return paramObj;
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/param/ParamValidator.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/param/ParamValidator.java
new file mode 100644
index 00000000..8fe5df53
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/param/ParamValidator.java
@@ -0,0 +1,12 @@
+package com.gitee.sop.servercommon.param;
+
+/**
+ * @author tanghc
+ */
+public interface ParamValidator {
+ /**
+ * 验证业务参数
+ * @param obj
+ */
+ void validateBizParam(Object obj);
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/param/ServiceParamValidator.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/param/ServiceParamValidator.java
new file mode 100644
index 00000000..62f3b57a
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/param/ServiceParamValidator.java
@@ -0,0 +1,67 @@
+package com.gitee.sop.servercommon.param;
+
+import com.gitee.sop.servercommon.bean.ServiceContext;
+import com.gitee.sop.servercommon.exception.ServiceException;
+import com.gitee.sop.servercommon.message.ServiceErrorEnum;
+import com.gitee.sop.servercommon.message.ServiceErrorFactory;
+import org.springframework.util.CollectionUtils;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.ValidatorFactory;
+import java.util.Set;
+
+/**
+ * 参数验证,JSR-303
+ * @author tanghc
+ */
+public class ServiceParamValidator implements ParamValidator {
+ private static final String LEFT_TOKEN = "{";
+ private static final String RIGHT_TOKEN = "}";
+ public static final String EQ = "=";
+ public static final String COMMA = ",";
+ private static Object[] EMPTY_OBJ_ARRAY = {};
+
+ private static ValidatorFactory factory;
+ private static javax.validation.Validator validator;
+
+ static {
+ factory = Validation.buildDefaultValidatorFactory();
+ validator = factory.getValidator();
+ }
+
+ @Override
+ public void validateBizParam(Object obj) {
+ if (obj == null) {
+ return;
+ }
+ Set> set = validator.validate(obj);
+ if (!CollectionUtils.isEmpty(set)) {
+ ConstraintViolation oneError = set.iterator().next();
+ String errorMsg = oneError.getMessage();
+ throw this.getValidateBizParamException(errorMsg);
+ }
+ }
+
+ private RuntimeException getValidateBizParamException(String errorMsg) {
+ String subCode = ServiceErrorEnum.ISP_PARAM_ERROR.getErrorMeta().getSubCode();
+ String[] msgToken = errorMsg.split(EQ);
+ String msg = msgToken[0];
+ if (msg.startsWith(LEFT_TOKEN) && msg.endsWith(RIGHT_TOKEN)) {
+ String module = msg.substring(1, msg.length() - 1);
+ Object[] params = this.buildParams(msgToken);
+ String error = ServiceErrorFactory.getErrorMessage(module, ServiceContext.getCurrentContext().getLocale(), params);
+ return new ServiceException(subCode, error);
+ } else {
+ return new ServiceException(subCode, errorMsg);
+ }
+ }
+
+ private Object[] buildParams(String[] msgToken) {
+ if (msgToken.length == 2) {
+ return msgToken[1].split(COMMA);
+ } else {
+ return EMPTY_OBJ_ARRAY;
+ }
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/result/DefaultServiceResultBuilder.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/result/DefaultServiceResultBuilder.java
new file mode 100644
index 00000000..c5b909b1
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/result/DefaultServiceResultBuilder.java
@@ -0,0 +1,49 @@
+package com.gitee.sop.servercommon.result;
+
+import com.gitee.sop.servercommon.exception.ServiceException;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 处理业务返回结果
+ * @author tanghc
+ */
+@Slf4j
+public class DefaultServiceResultBuilder implements ServiceResultBuilder {
+
+ public static final String ISP_UNKNOWN_ERROR = "isp.unknown-error";
+ /** 与网关约定好的状态码,表示业务出错 */
+ public static final int BIZ_ERROR_CODE = 4000;
+
+ @Override
+ public Object buildError(HttpServletRequest request, HttpServletResponse response, Throwable throwable) {
+ response.setStatus(BIZ_ERROR_CODE);
+ String subCode, subMsg;
+ if (throwable instanceof ServiceException) {
+ ServiceException ex = (ServiceException) throwable;
+ subCode = ex.getError().getSub_code();
+ subMsg = ex.getError().getSub_msg();
+ } else {
+ subCode = ISP_UNKNOWN_ERROR;
+ subMsg = throwable.getMessage();
+ }
+ return this.buildError(subCode, subMsg);
+ }
+
+ @Override
+ public Object buildError(String subCode, String subMsg) {
+ AlipayResult result = new AlipayResult();
+ result.setSub_code(subCode);
+ result.setSub_msg(subMsg);
+ return result;
+ }
+
+ @Data
+ public static class AlipayResult {
+ private String sub_code;
+ private String sub_msg;
+ }
+}
diff --git a/sop-server-common/src/main/java/com/gitee/sop/servercommon/result/ServiceResultBuilder.java b/sop-server-common/src/main/java/com/gitee/sop/servercommon/result/ServiceResultBuilder.java
new file mode 100644
index 00000000..bab79e6c
--- /dev/null
+++ b/sop-server-common/src/main/java/com/gitee/sop/servercommon/result/ServiceResultBuilder.java
@@ -0,0 +1,29 @@
+package com.gitee.sop.servercommon.result;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author tanghc
+ */
+public interface ServiceResultBuilder {
+
+
+ /**
+ * 构建错误返回结果
+ *
+ * @param throwable 异常
+ * @return 返回最终结果
+ */
+ Object buildError(HttpServletRequest request, HttpServletResponse response, Throwable throwable);
+
+
+ /**
+ * 构建错误返回结果
+ * @param subCode
+ * @param subMsg
+ * @return
+ */
+ Object buildError(String subCode, String subMsg);
+
+}
diff --git a/sop-server-common/src/main/resources/i18n/isp/error_en.properties b/sop-server-common/src/main/resources/i18n/isp/error_en.properties
new file mode 100644
index 00000000..8560312c
--- /dev/null
+++ b/sop-server-common/src/main/resources/i18n/isp/error_en.properties
@@ -0,0 +1,4 @@
+# 错误配置
+
+isp.error_isp.unknow-error=Service is temporarily unavailable
+isp.error_isv.invalid-parameter=Invalid parameter
\ No newline at end of file
diff --git a/sop-server-common/src/main/resources/i18n/isp/error_zh_CN.properties b/sop-server-common/src/main/resources/i18n/isp/error_zh_CN.properties
new file mode 100644
index 00000000..42616e36
--- /dev/null
+++ b/sop-server-common/src/main/resources/i18n/isp/error_zh_CN.properties
@@ -0,0 +1,5 @@
+# 错误配置
+
+isp.error_isp.unknow-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
+# 参数无效
+isp.error_isv.invalid-parameter=\u53c2\u6570\u65e0\u6548
diff --git a/sop-story/pom.xml b/sop-story/pom.xml
new file mode 100644
index 00000000..b2da4384
--- /dev/null
+++ b/sop-story/pom.xml
@@ -0,0 +1,15 @@
+
+
+ 4.0.0
+ com.gitee.sop-story
+ sop-story-parent
+ 1.0.0-SNAPSHOT
+ pom
+
+
+ sop-story-api
+ sop-story-web
+
+
\ No newline at end of file
diff --git a/sop-story/sop-story-api/.gitignore b/sop-story/sop-story-api/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/sop-story/sop-story-api/.gitignore
@@ -0,0 +1,25 @@
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/sop-story/sop-story-api/pom.xml b/sop-story/sop-story-api/pom.xml
new file mode 100644
index 00000000..103848c1
--- /dev/null
+++ b/sop-story/sop-story-api/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ com.gitee.sop
+ sop-story-api
+ 1.0-SNAPSHOT
+
+
+ 1.8
+ Greenwich.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+ provided
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
\ No newline at end of file
diff --git a/sop-story/sop-story-api/src/main/java/com/gitee/sop/story/api/domain/Story.java b/sop-story/sop-story-api/src/main/java/com/gitee/sop/story/api/domain/Story.java
new file mode 100644
index 00000000..d2cfbae0
--- /dev/null
+++ b/sop-story/sop-story-api/src/main/java/com/gitee/sop/story/api/domain/Story.java
@@ -0,0 +1,18 @@
+package com.gitee.sop.story.api.domain;
+
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author tanghc
+ */
+@Data
+public class Story {
+ private int id;
+
+ @NotBlank(message = "name不能为空")
+ @Length(max = 20, message = "name长度不能超过20")
+ private String name;
+}
diff --git a/sop-story/sop-story-api/src/main/java/com/gitee/sop/story/api/service/StoryService.java b/sop-story/sop-story-api/src/main/java/com/gitee/sop/story/api/service/StoryService.java
new file mode 100644
index 00000000..0b3c9794
--- /dev/null
+++ b/sop-story/sop-story-api/src/main/java/com/gitee/sop/story/api/service/StoryService.java
@@ -0,0 +1,15 @@
+package com.gitee.sop.story.api.service;
+
+import com.gitee.sop.story.api.domain.Story;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * @author tanghc
+ */
+@RequestMapping("/story")
+public interface StoryService {
+ @RequestMapping("/getStory")
+ Story getStory(@RequestParam("id")/* 必须指定@RequestParam,且value不能少 */
+ int id);
+}
diff --git a/sop-story/sop-story-web/.gitignore b/sop-story/sop-story-web/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/sop-story/sop-story-web/.gitignore
@@ -0,0 +1,25 @@
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
diff --git a/sop-story/sop-story-web/mvnw b/sop-story/sop-story-web/mvnw
new file mode 100755
index 00000000..5551fde8
--- /dev/null
+++ b/sop-story/sop-story-web/mvnw
@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ 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
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ 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
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ wget "$jarUrl" -O "$wrapperJarPath"
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ curl -o "$wrapperJarPath" "$jarUrl"
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/sop-story/sop-story-web/mvnw.cmd b/sop-story/sop-story-web/mvnw.cmd
new file mode 100644
index 00000000..e5cfb0ae
--- /dev/null
+++ b/sop-story/sop-story-web/mvnw.cmd
@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ echo Found %WRAPPER_JAR%
+) else (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+ echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/sop-story/sop-story-web/pom.xml b/sop-story/sop-story-web/pom.xml
new file mode 100644
index 00000000..d6db8302
--- /dev/null
+++ b/sop-story/sop-story-web/pom.xml
@@ -0,0 +1,78 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ com.gitee.sop
+ sop-story-web
+ 0.0.1-SNAPSHOT
+ sop-story
+ Demo project for Spring Boot
+
+
+ 1.8
+ Greenwich.RELEASE
+
+
+
+
+ com.gitee.sop
+ sop-server-common
+ 1.0.0-SNAPSHOT
+
+
+ com.gitee.sop
+ sop-story-api
+ 1.0-SNAPSHOT
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+
+
+
diff --git a/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/SopStoryApplication.java b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/SopStoryApplication.java
new file mode 100644
index 00000000..16617166
--- /dev/null
+++ b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/SopStoryApplication.java
@@ -0,0 +1,16 @@
+package com.gitee.sop.bookweb;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+@EnableDiscoveryClient
+@SpringBootApplication
+public class SopStoryApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SopStoryApplication.class, args);
+ }
+
+}
+
diff --git a/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/config/OpenServerConfig.java b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/config/OpenServerConfig.java
new file mode 100644
index 00000000..6c1a7f1a
--- /dev/null
+++ b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/config/OpenServerConfig.java
@@ -0,0 +1,23 @@
+package com.gitee.sop.bookweb.config;
+
+import com.gitee.sop.servercommon.configuration.AlipayServerConfiguration;
+import com.gitee.sop.servercommon.configuration.TaobaoServerConfiguration;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 使用支付宝开放平台功能
+ * @author tanghc
+ */
+@Configuration
+public class OpenServerConfig extends AlipayServerConfiguration {
+
+}
+
+/**
+ * 使用淘宝开放平台功能
+ * @author tanghc
+ */
+//@Configuration
+//public class OpenServerConfig extends TaobaoServerConfiguration {
+//
+//}
diff --git a/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java
new file mode 100644
index 00000000..7f9e86d2
--- /dev/null
+++ b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java
@@ -0,0 +1,28 @@
+package com.gitee.sop.bookweb.controller;
+
+import com.gitee.sop.bookweb.message.StoryErrorEnum;
+import com.gitee.sop.servercommon.annotation.ApiMapping;
+import com.gitee.sop.story.api.domain.Story;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 支付宝服务端,假设签名验证通过后,到达这里进行具体的业务处理。
+ * 这里演示如何接受业务参数。
+ * @author tanghc
+ */
+@RestController
+public class AlipayController {
+
+ @ApiMapping(value = "alipay.story.get")
+ public Story getStory(Story story) {
+ if (story.getId() == 0) {
+ throw StoryErrorEnum.param_error.getErrorMeta().getException("id无效");
+ }
+ return story;
+ }
+
+ @ApiMapping(value = "alipay.story.get", version = "1.2")
+ public Story getStory11(Story story) {
+ return story;
+ }
+}
diff --git a/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/Story2Controller.java b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/Story2Controller.java
new file mode 100644
index 00000000..6f37e27a
--- /dev/null
+++ b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/Story2Controller.java
@@ -0,0 +1,46 @@
+package com.gitee.sop.bookweb.controller;
+
+import com.gitee.sop.servercommon.annotation.ApiAbility;
+import com.gitee.sop.servercommon.annotation.ApiMapping;
+import com.gitee.sop.story.api.domain.Story;
+import com.gitee.sop.story.api.service.StoryService;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author tanghc
+ */
+@ApiAbility // 放在这里,下面所有的接口都具备接口提供能力
+@RestController
+@RequestMapping("story2")
+public class Story2Controller{
+
+ @RequestMapping("getStory4")
+ public Story getStory4() {
+ Story story = new Story();
+ story.setId(1);
+ story.setName("海底小纵队(getStory4)");
+ return story;
+ }
+
+ // 优先使用方法上@ApiAbility
+ @ApiAbility(version = "1.4")
+ @RequestMapping("getStory4")
+ public Story storyget() {
+ Story story = new Story();
+ story.setId(1);
+ story.setName("海底小纵队(1.4)");
+ return story;
+ }
+
+ // 优先使用@ApiMapping
+ @ApiMapping(value = "story.get2")
+ public Story getStory2() {
+ Story story = new Story();
+ story.setId(1);
+ story.setName("海底小纵队story.get2");
+ return story;
+ }
+
+
+}
diff --git a/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/StoryController.java b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/StoryController.java
new file mode 100644
index 00000000..d2233e6f
--- /dev/null
+++ b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/StoryController.java
@@ -0,0 +1,85 @@
+package com.gitee.sop.bookweb.controller;
+
+import com.gitee.sop.servercommon.annotation.ApiMapping;
+import com.gitee.sop.servercommon.annotation.ApiAbility;
+import com.gitee.sop.story.api.domain.Story;
+import com.gitee.sop.story.api.service.StoryService;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author tanghc
+ */
+@RestController
+public class StoryController implements StoryService {
+
+ // 提供给Feign的服务
+ @Override
+ public Story getStory(int id) {
+ Story story = new Story();
+ story.setId(id);
+ story.setName("海底小纵队(Feign)");
+ return story;
+ }
+
+ // http://localhost:2222/story/story_get
+ // 原生的接口,可正常调用
+ @RequestMapping("story_get")
+ public Story getStory4() {
+ Story story = new Story();
+ story.setId(1);
+ story.setName("海底小纵队(原生)");
+ return story;
+ }
+
+ // http://localhost:2222/story/story.get/
+ // 接口名,使用默认版本号
+ @ApiMapping(value = "story.get")
+ public Story storyget() {
+ Story story = new Story();
+ story.setId(1);
+ story.setName("海底小纵队(默认版本号)");
+ return story;
+ }
+
+ // http://localhost:2222/story/story.get/?version=1.1
+ // 接口名 + 版本号
+ @ApiMapping(value = "story.get", version = "1.1")
+ public Story getStory2() {
+ Story story = new Story();
+ story.setId(1);
+ story.setName("海底小纵队1.0");
+ return story;
+ }
+
+ // http://localhost:2222/story/story.get/?version=2.0
+ // 接口名 + 版本号
+ @ApiMapping(value = "story.get", version = "2.0")
+ public Story getStory20(Story story) {
+ return story;
+ }
+
+ // http://localhost:2222/story/getStory2
+ // 遗留接口具备开放平台能力
+ @ApiAbility
+ @RequestMapping("getStory2")
+ public Story getStory2_0() {
+ Story story = new Story();
+ story.setId(1);
+ story.setName("海底小纵队(默认版本号)");
+ return story;
+ }
+
+ // http://localhost:2222/story/getStory2?version=2.1
+ // 在原来的基础上加版本号
+ @ApiAbility(version = "2.1")
+ @RequestMapping("getStory2")
+ public Story getStory2_1() {
+ Story story = new Story();
+ story.setId(1);
+ story.setName("海底小纵队2.1");
+ return story;
+ }
+
+
+}
diff --git a/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/message/StoryErrorEnum.java b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/message/StoryErrorEnum.java
new file mode 100644
index 00000000..5aae79ef
--- /dev/null
+++ b/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/message/StoryErrorEnum.java
@@ -0,0 +1,21 @@
+package com.gitee.sop.bookweb.message;
+
+import com.gitee.sop.servercommon.message.ServiceErrorMeta;
+
+/**
+ * @author tanghc
+ */
+public enum StoryErrorEnum {
+ /** 参数错误 */
+ param_error("isv.invalid-parameter"),
+ ;
+ private ServiceErrorMeta errorMeta;
+
+ StoryErrorEnum(String sub_code) {
+ this.errorMeta = new ServiceErrorMeta("isp.error_", sub_code);
+ }
+
+ public ServiceErrorMeta getErrorMeta() {
+ return errorMeta;
+ }
+}
diff --git a/sop-story/sop-story-web/src/main/resources/application.properties b/sop-story/sop-story-web/src/main/resources/application.properties
new file mode 100644
index 00000000..cb731b6f
--- /dev/null
+++ b/sop-story/sop-story-web/src/main/resources/application.properties
@@ -0,0 +1,15 @@
+server.port=2222
+spring.application.name=story-service
+
+eureka.host=localhost
+eureka.port=1111
+eureka.client.serviceUrl.defaultZone=http://${eureka.host}:${eureka.port}/eureka/
+
+# Redis数据库索引(默认为0)
+spring.redis.database=0
+# Redis服务器地址
+spring.redis.host=127.0.0.1
+# Redis服务器连接端口
+spring.redis.port=6379
+# Redis服务器连接密码(默认为空)
+spring.redis.password=
diff --git a/sop-story/sop-story-web/src/main/resources/i18n/isp/bizerror_en.properties b/sop-story/sop-story-web/src/main/resources/i18n/isp/bizerror_en.properties
new file mode 100644
index 00000000..76ef051e
--- /dev/null
+++ b/sop-story/sop-story-web/src/main/resources/i18n/isp/bizerror_en.properties
@@ -0,0 +1,3 @@
+# 错误配置
+
+isp.error_isv.invalid-parameter=Invalid parameter, {0}
\ No newline at end of file
diff --git a/sop-story/sop-story-web/src/main/resources/i18n/isp/bizerror_zh_CN.properties b/sop-story/sop-story-web/src/main/resources/i18n/isp/bizerror_zh_CN.properties
new file mode 100644
index 00000000..4c215059
--- /dev/null
+++ b/sop-story/sop-story-web/src/main/resources/i18n/isp/bizerror_zh_CN.properties
@@ -0,0 +1,4 @@
+# 错误配置
+
+# 参数无效
+isp.error_isv.invalid-parameter=\u53c2\u6570\u65e0\u6548, {0}
diff --git a/sop-story/sop-story-web/src/test/java/com/gitee/sop/bookweb/SopStoryApplicationTests.java b/sop-story/sop-story-web/src/test/java/com/gitee/sop/bookweb/SopStoryApplicationTests.java
new file mode 100644
index 00000000..ba442149
--- /dev/null
+++ b/sop-story/sop-story-web/src/test/java/com/gitee/sop/bookweb/SopStoryApplicationTests.java
@@ -0,0 +1,17 @@
+package com.gitee.sop.bookweb;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SopStoryApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
+
diff --git a/sop-test/pom.xml b/sop-test/pom.xml
new file mode 100644
index 00000000..3d508cb9
--- /dev/null
+++ b/sop-test/pom.xml
@@ -0,0 +1,102 @@
+
+
+
+
+ sop-parent
+ com.gitee.sop
+ 1.0.0-SNAPSHOT
+
+ 4.0.0
+
+ sop-test
+
+ sop-test
+
+ http://www.example.com
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.6
+ test
+
+
+ commons-io
+ commons-io
+ 2.5
+ test
+
+
+ com.alibaba
+ fastjson
+ 1.2.47
+ test
+
+
+ commons-codec
+ commons-codec
+ 1.11
+ compile
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
diff --git a/sop-test/readme.md b/sop-test/readme.md
new file mode 100644
index 00000000..1dcc85cb
--- /dev/null
+++ b/sop-test/readme.md
@@ -0,0 +1,3 @@
+# 测试
+
+查看测试用例
\ No newline at end of file
diff --git a/sop-test/src/main/java/com/gitee/sop/App.java b/sop-test/src/main/java/com/gitee/sop/App.java
new file mode 100644
index 00000000..5712fbcd
--- /dev/null
+++ b/sop-test/src/main/java/com/gitee/sop/App.java
@@ -0,0 +1,13 @@
+package com.gitee.sop;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/sop-test/src/main/java/com/gitee/sop/alipay/AlipayApiException.java b/sop-test/src/main/java/com/gitee/sop/alipay/AlipayApiException.java
new file mode 100644
index 00000000..18aa9ea8
--- /dev/null
+++ b/sop-test/src/main/java/com/gitee/sop/alipay/AlipayApiException.java
@@ -0,0 +1,49 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.alipay;
+
+
+/**
+ *
+ * @author runzhi
+ */
+public class AlipayApiException extends Exception {
+
+ private static final long serialVersionUID = -238091758285157331L;
+
+ private String errCode;
+ private String errMsg;
+
+ public AlipayApiException() {
+ super();
+ }
+
+ public AlipayApiException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public AlipayApiException(String message) {
+ super(message);
+ }
+
+ public AlipayApiException(Throwable cause) {
+ super(cause);
+ }
+
+ public AlipayApiException(String errCode, String errMsg) {
+ super(errCode + ":" + errMsg);
+ this.errCode = errCode;
+ this.errMsg = errMsg;
+ }
+
+ public String getErrCode() {
+ return this.errCode;
+ }
+
+ public String getErrMsg() {
+ return this.errMsg;
+ }
+
+}
\ No newline at end of file
diff --git a/sop-test/src/main/java/com/gitee/sop/alipay/AlipayConstants.java b/sop-test/src/main/java/com/gitee/sop/alipay/AlipayConstants.java
new file mode 100644
index 00000000..276a3022
--- /dev/null
+++ b/sop-test/src/main/java/com/gitee/sop/alipay/AlipayConstants.java
@@ -0,0 +1,97 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.alipay;
+
+/**
+ *
+ * @author runzhi
+ */
+public class AlipayConstants {
+
+ public static final String SIGN_TYPE = "sign_type";
+
+ public static final String SIGN_TYPE_RSA = "RSA";
+
+ /**
+ * sha256WithRsa 算法请求类型
+ */
+ public static final String SIGN_TYPE_RSA2 = "RSA2";
+
+ public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
+
+ public static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";
+
+ public static final String ENCRYPT_TYPE_AES = "AES";
+
+ public static final String APP_ID = "app_id";
+
+ public static final String FORMAT = "format";
+
+ public static final String METHOD = "method";
+
+ public static final String TIMESTAMP = "timestamp";
+
+ public static final String VERSION = "version";
+
+ public static final String SIGN = "sign";
+
+ public static final String ALIPAY_SDK = "alipay_sdk";
+
+ public static final String ACCESS_TOKEN = "auth_token";
+
+ public static final String APP_AUTH_TOKEN = "app_auth_token";
+
+ public static final String TERMINAL_TYPE = "terminal_type";
+
+ public static final String TERMINAL_INFO = "terminal_info";
+
+ public static final String CHARSET = "charset";
+
+ public static final String NOTIFY_URL = "notify_url";
+
+ public static final String RETURN_URL = "return_url";
+
+ public static final String ENCRYPT_TYPE = "encrypt_type";
+
+ //-----===-------///
+
+ public static final String BIZ_CONTENT_KEY = "biz_content";
+
+ /** 默认时间格式 **/
+ public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ /** Date默认时区 **/
+ public static final String DATE_TIMEZONE = "GMT+8";
+
+ /** UTF-8字符集 **/
+ public static final String CHARSET_UTF8 = "UTF-8";
+
+ /** GBK字符集 **/
+ public static final String CHARSET_GBK = "GBK";
+
+ /** JSON 应格式 */
+ public static final String FORMAT_JSON = "json";
+
+ /** XML 应格式 */
+ public static final String FORMAT_XML = "xml";
+
+ /** SDK版本号 */
+ public static final String SDK_VERSION = "alipay-sdk-java-3.6.0.ALL";
+
+ public static final String PROD_CODE = "prod_code";
+
+ /** 老版本失败节点 */
+ public static final String ERROR_RESPONSE = "error_response";
+
+ /** 新版本节点后缀 */
+ public static final String RESPONSE_SUFFIX = "_response";
+
+ /** 加密后XML返回报文的节点名字 */
+ public static final String RESPONSE_XML_ENCRYPT_NODE_NAME = "response_encrypted";
+
+ /** 批量请求id **/
+ public static final String BATCH_REQUEST_ID = "batch_request_id";
+
+}
diff --git a/sop-test/src/main/java/com/gitee/sop/alipay/AlipaySignature.java b/sop-test/src/main/java/com/gitee/sop/alipay/AlipaySignature.java
new file mode 100644
index 00000000..1eef3163
--- /dev/null
+++ b/sop-test/src/main/java/com/gitee/sop/alipay/AlipaySignature.java
@@ -0,0 +1,611 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.alipay;
+
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author runzhi
+ */
+public class AlipaySignature {
+
+ /** RSA最大加密明文大小 */
+ private static final int MAX_ENCRYPT_BLOCK = 117;
+
+ /** RSA最大解密密文大小 */
+ private static final int MAX_DECRYPT_BLOCK = 128;
+
+
+
+ /**
+ *
+ * @param sortedParams
+ * @return
+ */
+ public static String getSignContent(Map sortedParams) {
+ StringBuffer content = new StringBuffer();
+ List keys = new ArrayList(sortedParams.keySet());
+ Collections.sort(keys);
+ int index = 0;
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ String value = sortedParams.get(key);
+ if (StringUtils.areNotEmpty(key, value)) {
+ content.append((index == 0 ? "" : "&") + key + "=" + value);
+ index++;
+ }
+ }
+ return content.toString();
+ }
+
+ /**
+ * rsa内容签名
+ *
+ * @param content
+ * @param privateKey
+ * @param charset
+ * @return
+ * @throws AlipayApiException
+ */
+ public static String rsaSign(String content, String privateKey, String charset,
+ String signType) throws AlipayApiException {
+
+ if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
+
+ return rsaSign(content, privateKey, charset);
+ } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) {
+
+ return rsa256Sign(content, privateKey, charset);
+ } else {
+
+ throw new AlipayApiException("Sign Type is Not Support : signType=" + signType);
+ }
+
+ }
+
+ /**
+ * sha256WithRsa 加签
+ *
+ * @param content
+ * @param privateKey
+ * @param charset
+ * @return
+ * @throws AlipayApiException
+ */
+ public static String rsa256Sign(String content, String privateKey,
+ String charset) throws AlipayApiException {
+
+ try {
+ PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(privateKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS);
+
+ signature.initSign(priKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ byte[] signed = signature.sign();
+
+ return new String(Base64.encodeBase64(signed));
+ } catch (Exception e) {
+ throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e);
+ }
+
+ }
+
+ /**
+ * sha1WithRsa 加签
+ *
+ * @param content
+ * @param privateKey
+ * @param charset
+ * @return
+ * @throws AlipayApiException
+ */
+ public static String rsaSign(String content, String privateKey,
+ String charset) throws AlipayApiException {
+ try {
+ PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(privateKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_ALGORITHMS);
+
+ signature.initSign(priKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ byte[] signed = signature.sign();
+
+ return new String(Base64.encodeBase64(signed));
+ } catch (InvalidKeySpecException ie) {
+ throw new AlipayApiException("RSA私钥格式不正确,请检查是否正确配置了PKCS8格式的私钥", ie);
+ } catch (Exception e) {
+ throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e);
+ }
+ }
+
+ public static String rsaSign(Map params, String privateKey,
+ String charset) throws AlipayApiException {
+ String signContent = getSignContent(params);
+
+ return rsaSign(signContent, privateKey, charset);
+
+ }
+
+ public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
+ InputStream ins) throws Exception {
+ if (ins == null || StringUtils.isEmpty(algorithm)) {
+ return null;
+ }
+
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+
+ byte[] encodedKey = StreamUtil.readText(ins).getBytes();
+
+ encodedKey = Base64.decodeBase64(encodedKey);
+
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
+ }
+
+ public static String getSignCheckContentV1(Map params) {
+ if (params == null) {
+ return null;
+ }
+
+ params.remove("sign");
+ params.remove("sign_type");
+
+ StringBuffer content = new StringBuffer();
+ List keys = new ArrayList(params.keySet());
+ Collections.sort(keys);
+
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ String value = params.get(key);
+ content.append((i == 0 ? "" : "&") + key + "=" + value);
+ }
+
+ return content.toString();
+ }
+
+ public static String getSignCheckContentV2(Map params) {
+ if (params == null) {
+ return null;
+ }
+
+ params.remove("sign");
+
+ StringBuffer content = new StringBuffer();
+ List keys = new ArrayList(params.keySet());
+ Collections.sort(keys);
+
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ String value = params.get(key);
+ content.append((i == 0 ? "" : "&") + key + "=" + value);
+ }
+
+ return content.toString();
+ }
+
+ public static boolean rsaCheckV1(Map params, String publicKey,
+ String charset) throws AlipayApiException {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV1(params);
+
+ return rsaCheckContent(content, sign, publicKey, charset);
+ }
+
+ public static boolean rsaCheckV1(Map params, String publicKey,
+ String charset,String signType) throws AlipayApiException {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV1(params);
+
+ return rsaCheck(content, sign, publicKey, charset,signType);
+ }
+
+ public static boolean rsaCheckV2(Map params, String publicKey,
+ String charset) throws AlipayApiException {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV2(params);
+
+ return rsaCheckContent(content, sign, publicKey, charset);
+ }
+
+ public static boolean rsaCheckV2(Map params, String publicKey,
+ String charset,String signType) throws AlipayApiException {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV2(params);
+
+ return rsaCheck(content, sign, publicKey, charset,signType);
+ }
+
+ public static boolean rsaCheck(String content, String sign, String publicKey, String charset,
+ String signType) throws AlipayApiException {
+
+ if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
+
+ return rsaCheckContent(content, sign, publicKey, charset);
+
+ } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) {
+
+ return rsa256CheckContent(content, sign, publicKey, charset);
+
+ } else {
+
+ throw new AlipayApiException("Sign Type is Not Support : signType=" + signType);
+ }
+
+ }
+
+ public static boolean rsa256CheckContent(String content, String sign, String publicKey,
+ String charset) throws AlipayApiException {
+ try {
+ PublicKey pubKey = getPublicKeyFromX509("RSA",
+ new ByteArrayInputStream(publicKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS);
+
+ signature.initVerify(pubKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ return signature.verify(Base64.decodeBase64(sign.getBytes()));
+ } catch (Exception e) {
+ throw new AlipayApiException(
+ "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
+ }
+ }
+
+ public static boolean rsaCheckContent(String content, String sign, String publicKey,
+ String charset) throws AlipayApiException {
+ try {
+ PublicKey pubKey = getPublicKeyFromX509("RSA",
+ new ByteArrayInputStream(publicKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_ALGORITHMS);
+
+ signature.initVerify(pubKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ return signature.verify(Base64.decodeBase64(sign.getBytes()));
+ } catch (Exception e) {
+ throw new AlipayApiException(
+ "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
+ }
+ }
+
+ public static PublicKey getPublicKeyFromX509(String algorithm,
+ InputStream ins) throws Exception {
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+
+ StringWriter writer = new StringWriter();
+ StreamUtil.io(new InputStreamReader(ins), writer);
+
+ byte[] encodedKey = writer.toString().getBytes();
+
+ encodedKey = Base64.decodeBase64(encodedKey);
+
+ return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
+ }
+
+ /**
+ * 验签并解密
+ *
+ * 目前适用于公众号
+ * params参数示例:
+ * {
+ * biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
+ * sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
+ * sign_type=RSA,
+ * charset=UTF-8
+ * }
+ *
+ * @param params
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param isCheckSign 是否验签
+ * @param isDecrypt 是否解密
+ * @return 解密后明文,验签失败则异常抛出
+ * @throws AlipayApiException
+ */
+ public static String checkSignAndDecrypt(Map params, String alipayPublicKey,
+ String cusPrivateKey, boolean isCheckSign,
+ boolean isDecrypt) throws AlipayApiException {
+ String charset = params.get("charset");
+ String bizContent = params.get("biz_content");
+ if (isCheckSign) {
+ if (!rsaCheckV2(params, alipayPublicKey, charset)) {
+ throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
+ }
+ }
+
+ if (isDecrypt) {
+ return rsaDecrypt(bizContent, cusPrivateKey, charset);
+ }
+
+ return bizContent;
+ }
+
+ /**
+ * 验签并解密
+ *
+ * 目前适用于公众号
+ * params参数示例:
+ * {
+ * biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
+ * sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
+ * sign_type=RSA,
+ * charset=UTF-8
+ * }
+ *
+ * @param params
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param isCheckSign 是否验签
+ * @param isDecrypt 是否解密
+ * @return 解密后明文,验签失败则异常抛出
+ * @throws AlipayApiException
+ */
+ public static String checkSignAndDecrypt(Map params, String alipayPublicKey,
+ String cusPrivateKey, boolean isCheckSign,
+ boolean isDecrypt, String signType) throws AlipayApiException {
+ String charset = params.get("charset");
+ String bizContent = params.get("biz_content");
+ if (isCheckSign) {
+ if (!rsaCheckV2(params, alipayPublicKey, charset,signType)) {
+ throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
+ }
+ }
+
+ if (isDecrypt) {
+ return rsaDecrypt(bizContent, cusPrivateKey, charset);
+ }
+
+ return bizContent;
+ }
+
+ /**
+ * 加密并签名
+ * 目前适用于公众号
+ * @param bizContent 待加密、签名内容
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @param isEncrypt 是否加密,true-加密 false-不加密
+ * @param isSign 是否签名,true-签名 false-不签名
+ * @return 加密、签名后xml内容字符串
+ *
+ * 返回示例:
+ *
+ * 密文
+ * RSA
+ * sign
+ * RSA
+ *
+ *
+ * @throws AlipayApiException
+ */
+ public static String encryptAndSign(String bizContent, String alipayPublicKey,
+ String cusPrivateKey, String charset, boolean isEncrypt,
+ boolean isSign) throws AlipayApiException {
+ StringBuilder sb = new StringBuilder();
+ if (StringUtils.isEmpty(charset)) {
+ charset = AlipayConstants.CHARSET_GBK;
+ }
+ sb.append("");
+ if (isEncrypt) {// 加密
+ sb.append("");
+ String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
+ sb.append("" + encrypted + " ");
+ sb.append("RSA ");
+ if (isSign) {
+ String sign = rsaSign(encrypted, cusPrivateKey, charset);
+ sb.append("" + sign + " ");
+ sb.append("RSA ");
+ }
+ sb.append(" ");
+ } else if (isSign) {// 不加密,但需要签名
+ sb.append("");
+ sb.append("" + bizContent + " ");
+ String sign = rsaSign(bizContent, cusPrivateKey, charset);
+ sb.append("" + sign + " ");
+ sb.append("RSA ");
+ sb.append(" ");
+ } else {// 不加密,不加签
+ sb.append(bizContent);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 加密并签名
+ * 目前适用于公众号
+ * @param bizContent 待加密、签名内容
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @param isEncrypt 是否加密,true-加密 false-不加密
+ * @param isSign 是否签名,true-签名 false-不签名
+ * @return 加密、签名后xml内容字符串
+ *
+ * 返回示例:
+ *
+ * 密文
+ * RSA
+ * sign
+ * RSA
+ *
+ *
+ * @throws AlipayApiException
+ */
+ public static String encryptAndSign(String bizContent, String alipayPublicKey,
+ String cusPrivateKey, String charset, boolean isEncrypt,
+ boolean isSign,String signType) throws AlipayApiException {
+ StringBuilder sb = new StringBuilder();
+ if (StringUtils.isEmpty(charset)) {
+ charset = AlipayConstants.CHARSET_GBK;
+ }
+ sb.append("");
+ if (isEncrypt) {// 加密
+ sb.append("");
+ String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
+ sb.append("" + encrypted + " ");
+ sb.append("RSA ");
+ if (isSign) {
+ String sign = rsaSign(encrypted, cusPrivateKey, charset, signType);
+ sb.append("" + sign + " ");
+ sb.append("");
+ sb.append(signType);
+ sb.append(" ");
+ }
+ sb.append(" ");
+ } else if (isSign) {// 不加密,但需要签名
+ sb.append("");
+ sb.append("" + bizContent + " ");
+ String sign = rsaSign(bizContent, cusPrivateKey, charset, signType);
+ sb.append("" + sign + " ");
+ sb.append("");
+ sb.append(signType);
+ sb.append(" ");
+ sb.append(" ");
+ } else {// 不加密,不加签
+ sb.append(bizContent);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 公钥加密
+ *
+ * @param content 待加密内容
+ * @param publicKey 公钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @return 密文内容
+ * @throws AlipayApiException
+ */
+ public static String rsaEncrypt(String content, String publicKey,
+ String charset) throws AlipayApiException {
+ try {
+ PublicKey pubKey = getPublicKeyFromX509(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(publicKey.getBytes()));
+ Cipher cipher = Cipher.getInstance(AlipayConstants.SIGN_TYPE_RSA);
+ cipher.init(Cipher.ENCRYPT_MODE, pubKey);
+ byte[] data = StringUtils.isEmpty(charset) ? content.getBytes()
+ : content.getBytes(charset);
+ int inputLen = data.length;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int offSet = 0;
+ byte[] cache;
+ int i = 0;
+ // 对数据分段加密
+ while (inputLen - offSet > 0) {
+ if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
+ cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
+ } else {
+ cache = cipher.doFinal(data, offSet, inputLen - offSet);
+ }
+ out.write(cache, 0, cache.length);
+ i++;
+ offSet = i * MAX_ENCRYPT_BLOCK;
+ }
+ byte[] encryptedData = Base64.encodeBase64(out.toByteArray());
+ out.close();
+
+ return StringUtils.isEmpty(charset) ? new String(encryptedData)
+ : new String(encryptedData, charset);
+ } catch (Exception e) {
+ throw new AlipayApiException("EncryptContent = " + content + ",charset = " + charset,
+ e);
+ }
+ }
+
+ /**
+ * 私钥解密
+ *
+ * @param content 待解密内容
+ * @param privateKey 私钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @return 明文内容
+ * @throws AlipayApiException
+ */
+ public static String rsaDecrypt(String content, String privateKey,
+ String charset) throws AlipayApiException {
+ try {
+ PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(privateKey.getBytes()));
+ Cipher cipher = Cipher.getInstance(AlipayConstants.SIGN_TYPE_RSA);
+ cipher.init(Cipher.DECRYPT_MODE, priKey);
+ byte[] encryptedData = StringUtils.isEmpty(charset)
+ ? Base64.decodeBase64(content.getBytes())
+ : Base64.decodeBase64(content.getBytes(charset));
+ int inputLen = encryptedData.length;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int offSet = 0;
+ byte[] cache;
+ int i = 0;
+ // 对数据分段解密
+ while (inputLen - offSet > 0) {
+ if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
+ cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
+ } else {
+ cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
+ }
+ out.write(cache, 0, cache.length);
+ i++;
+ offSet = i * MAX_DECRYPT_BLOCK;
+ }
+ byte[] decryptedData = out.toByteArray();
+ out.close();
+
+ return StringUtils.isEmpty(charset) ? new String(decryptedData)
+ : new String(decryptedData, charset);
+ } catch (Exception e) {
+ throw new AlipayApiException("EncodeContent = " + content + ",charset = " + charset, e);
+ }
+ }
+
+}
diff --git a/sop-test/src/main/java/com/gitee/sop/alipay/StreamUtil.java b/sop-test/src/main/java/com/gitee/sop/alipay/StreamUtil.java
new file mode 100644
index 00000000..23cddb1c
--- /dev/null
+++ b/sop-test/src/main/java/com/gitee/sop/alipay/StreamUtil.java
@@ -0,0 +1,134 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.alipay;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ *
+ * @author runzhi
+ */
+public class StreamUtil {
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ public static void io(InputStream in, OutputStream out) throws IOException {
+ io(in, out, -1);
+ }
+
+ public static void io(InputStream in, OutputStream out, int bufferSize) throws IOException {
+ if (bufferSize == -1) {
+ bufferSize = DEFAULT_BUFFER_SIZE;
+ }
+
+ byte[] buffer = new byte[bufferSize];
+ int amount;
+
+ while ((amount = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, amount);
+ }
+ }
+
+ public static void io(Reader in, Writer out) throws IOException {
+ io(in, out, -1);
+ }
+
+ public static void io(Reader in, Writer out, int bufferSize) throws IOException {
+ if (bufferSize == -1) {
+ bufferSize = DEFAULT_BUFFER_SIZE >> 1;
+ }
+
+ char[] buffer = new char[bufferSize];
+ int amount;
+
+ while ((amount = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, amount);
+ }
+ }
+
+ public static OutputStream synchronizedOutputStream(OutputStream out) {
+ return new SynchronizedOutputStream(out);
+ }
+
+ public static OutputStream synchronizedOutputStream(OutputStream out, Object lock) {
+ return new SynchronizedOutputStream(out, lock);
+ }
+
+ public static String readText(InputStream in) throws IOException {
+ return readText(in, null, -1);
+ }
+
+ public static String readText(InputStream in, String encoding) throws IOException {
+ return readText(in, encoding, -1);
+ }
+
+ public static String readText(InputStream in, String encoding, int bufferSize)
+ throws IOException {
+ Reader reader = (encoding == null) ? new InputStreamReader(in) : new InputStreamReader(in,
+ encoding);
+
+ return readText(reader, bufferSize);
+ }
+
+ public static String readText(Reader reader) throws IOException {
+ return readText(reader, -1);
+ }
+
+ public static String readText(Reader reader, int bufferSize) throws IOException {
+ StringWriter writer = new StringWriter();
+
+ io(reader, writer, bufferSize);
+ return writer.toString();
+ }
+
+ private static class SynchronizedOutputStream extends OutputStream {
+ private OutputStream out;
+ private Object lock;
+
+ SynchronizedOutputStream(OutputStream out) {
+ this(out, out);
+ }
+
+ SynchronizedOutputStream(OutputStream out, Object lock) {
+ this.out = out;
+ this.lock = lock;
+ }
+
+ public void write(int datum) throws IOException {
+ synchronized (lock) {
+ out.write(datum);
+ }
+ }
+
+ public void write(byte[] data) throws IOException {
+ synchronized (lock) {
+ out.write(data);
+ }
+ }
+
+ public void write(byte[] data, int offset, int length) throws IOException {
+ synchronized (lock) {
+ out.write(data, offset, length);
+ }
+ }
+
+ public void flush() throws IOException {
+ synchronized (lock) {
+ out.flush();
+ }
+ }
+
+ public void close() throws IOException {
+ synchronized (lock) {
+ out.close();
+ }
+ }
+ }
+}
diff --git a/sop-test/src/main/java/com/gitee/sop/alipay/StringUtils.java b/sop-test/src/main/java/com/gitee/sop/alipay/StringUtils.java
new file mode 100644
index 00000000..f666f7a1
--- /dev/null
+++ b/sop-test/src/main/java/com/gitee/sop/alipay/StringUtils.java
@@ -0,0 +1,167 @@
+package com.gitee.sop.alipay;
+
+/**
+ * 字符串工具类。
+ *
+ * @author carver.gu
+ * @since 1.0, Sep 12, 2009
+ */
+public abstract class StringUtils {
+
+ private StringUtils() {}
+
+ /**
+ * 检查指定的字符串是否为空。
+ *
+ * SysUtils.isEmpty(null) = true
+ * SysUtils.isEmpty("") = true
+ * SysUtils.isEmpty(" ") = true
+ * SysUtils.isEmpty("abc") = false
+ *
+ *
+ * @param value 待检查的字符串
+ * @return true/false
+ */
+ public static boolean isEmpty(String value) {
+ int strLen;
+ if (value == null || (strLen = value.length()) == 0) {
+ return true;
+ }
+ for (int i = 0; i < strLen; i++) {
+ if ((Character.isWhitespace(value.charAt(i)) == false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 检查对象是否为数字型字符串,包含负数开头的。
+ */
+ public static boolean isNumeric(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ char[] chars = obj.toString().toCharArray();
+ int length = chars.length;
+ if(length < 1)
+ return false;
+
+ int i = 0;
+ if(length > 1 && chars[0] == '-')
+ i = 1;
+
+ for (; i < length; i++) {
+ if (!Character.isDigit(chars[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 检查指定的字符串列表是否不为空。
+ */
+ public static boolean areNotEmpty(String... values) {
+ boolean result = true;
+ if (values == null || values.length == 0) {
+ result = false;
+ } else {
+ for (String value : values) {
+ result &= !isEmpty(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 把通用字符编码的字符串转化为汉字编码。
+ */
+ public static String unicodeToChinese(String unicode) {
+ StringBuilder out = new StringBuilder();
+ if (!isEmpty(unicode)) {
+ for (int i = 0; i < unicode.length(); i++) {
+ out.append(unicode.charAt(i));
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * 过滤不可见字符
+ */
+ public static String stripNonValidXMLCharacters(String input) {
+ if (input == null || ("".equals(input)))
+ return "";
+ StringBuilder out = new StringBuilder();
+ char current;
+ for (int i = 0; i < input.length(); i++) {
+ current = input.charAt(i);
+ if ((current == 0x9) || (current == 0xA) || (current == 0xD)
+ || ((current >= 0x20) && (current <= 0xD7FF))
+ || ((current >= 0xE000) && (current <= 0xFFFD))
+ || ((current >= 0x10000) && (current <= 0x10FFFF)))
+ out.append(current);
+ }
+ return out.toString();
+ }
+
+ public static String leftPad(String str, int size, char padChar) {
+ if (str == null) {
+ return null;
+ } else {
+ int pads = size - str.length();
+ if (pads <= 0) {
+ return str;
+ } else {
+ return pads > 8192 ? leftPad(str, size, String.valueOf(padChar)) : padding(pads, padChar).concat(str);
+ }
+ }
+ }
+
+ public static String leftPad(String str, int size, String padStr) {
+ if (str == null) {
+ return null;
+ } else {
+ if (isEmpty(padStr)) {
+ padStr = " ";
+ }
+
+ int padLen = padStr.length();
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ } else if (padLen == 1 && pads <= 8192) {
+ return leftPad(str, size, padStr.charAt(0));
+ } else if (pads == padLen) {
+ return padStr.concat(str);
+ } else if (pads < padLen) {
+ return padStr.substring(0, pads).concat(str);
+ } else {
+ char[] padding = new char[pads];
+ char[] padChars = padStr.toCharArray();
+
+ for(int i = 0; i < pads; ++i) {
+ padding[i] = padChars[i % padLen];
+ }
+
+ return (new String(padding)).concat(str);
+ }
+ }
+ }
+
+ private static String padding(int repeat, char padChar) throws IndexOutOfBoundsException {
+ if (repeat < 0) {
+ throw new IndexOutOfBoundsException("Cannot pad a negative amount: " + repeat);
+ } else {
+ char[] buf = new char[repeat];
+
+ for(int i = 0; i < buf.length; ++i) {
+ buf[i] = padChar;
+ }
+
+ return new String(buf);
+ }
+ }
+}
diff --git a/sop-test/src/main/java/com/gitee/sop/taobao/Constants.java b/sop-test/src/main/java/com/gitee/sop/taobao/Constants.java
new file mode 100644
index 00000000..222f33b8
--- /dev/null
+++ b/sop-test/src/main/java/com/gitee/sop/taobao/Constants.java
@@ -0,0 +1,10 @@
+package com.gitee.sop.taobao;
+
+/**
+ * @author tanghc
+ */
+public class Constants {
+ public static final String SIGN_METHOD_HMAC = "hmac";
+ public static final String SIGN_METHOD_MD5 = "md5";
+ public static final String CHARSET_UTF8 = "UTF-8";
+}
diff --git a/sop-test/src/main/java/com/gitee/sop/taobao/TaobaoSignature.java b/sop-test/src/main/java/com/gitee/sop/taobao/TaobaoSignature.java
new file mode 100644
index 00000000..2f3719be
--- /dev/null
+++ b/sop-test/src/main/java/com/gitee/sop/taobao/TaobaoSignature.java
@@ -0,0 +1,95 @@
+package com.gitee.sop.taobao;
+
+import com.gitee.sop.alipay.StringUtils;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * @author tanghc
+ */
+public class TaobaoSignature {
+
+
+ public static String signTopRequest(Map params, String secret, String signMethod) throws IOException {
+ String content = getSignContent(params);
+ return doSign(content, secret, signMethod);
+ }
+
+ public static String getSignContent(Map params) {
+ // 第一步:检查参数是否已经排序
+ String[] keys = params.keySet().toArray(new String[0]);
+ Arrays.sort(keys);
+
+ // 第二步:把所有参数名和参数值串在一起
+ StringBuilder query = new StringBuilder();
+
+ for (String key : keys) {
+ String value = params.get(key);
+ if (StringUtils.areNotEmpty(key, value)) {
+ query.append(key).append(value);
+ }
+ }
+
+ return query.toString();
+ }
+
+ public static String doSign(String content,String secret, String signMethod) throws IOException {
+ // 第三步:使用MD5/HMAC加密
+ byte[] bytes;
+ if (Constants.SIGN_METHOD_HMAC.equals(signMethod)) {
+ bytes = encryptHMAC(content, secret);
+ } else {
+ bytes = encryptMD5(secret + content + secret);
+ }
+
+ // 第四步:把二进制转化为大写的十六进制(正确签名应该为32大写字符串,此方法需要时使用)
+ return byte2hex(bytes);
+ }
+
+ public static byte[] encryptHMAC(String data, String secret) throws IOException {
+ byte[] bytes = null;
+ try {
+ SecretKey secretKey = new SecretKeySpec(secret.getBytes(Constants.CHARSET_UTF8), "HmacMD5");
+ Mac mac = Mac.getInstance(secretKey.getAlgorithm());
+ mac.init(secretKey);
+ bytes = mac.doFinal(data.getBytes(Constants.CHARSET_UTF8));
+ } catch (GeneralSecurityException gse) {
+ throw new IOException(gse.toString());
+ }
+ return bytes;
+ }
+
+ public static byte[] encryptMD5(String data) throws IOException {
+ return encryptMD5(data.getBytes(Constants.CHARSET_UTF8));
+ }
+
+ public static byte[] encryptMD5(byte[] data) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance(Constants.SIGN_METHOD_MD5);
+ return digest.digest(data);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static String byte2hex(byte[] bytes) {
+ StringBuilder sign = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ String hex = Integer.toHexString(bytes[i] & 0xFF);
+ if (hex.length() == 1) {
+ sign.append("0");
+ }
+ sign.append(hex.toUpperCase());
+ }
+ return sign.toString();
+ }
+}
diff --git a/sop-test/src/test/java/com/gitee/sop/AlipayClientPostTest.java b/sop-test/src/test/java/com/gitee/sop/AlipayClientPostTest.java
new file mode 100644
index 00000000..6c830627
--- /dev/null
+++ b/sop-test/src/test/java/com/gitee/sop/AlipayClientPostTest.java
@@ -0,0 +1,87 @@
+package com.gitee.sop;
+
+import com.alibaba.fastjson.JSON;
+import com.gitee.sop.alipay.AlipaySignature;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 模仿支付宝客户端请求接口
+ */
+public class AlipayClientPostTest extends TestBase {
+
+
+ String url = "http://localhost:8081/api";
+ String appId = "alipay_test";
+ // 支付宝私钥
+ String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=";
+
+ /*
+ 参数 类型 是否必填 最大长度 描述 示例值
+app_id String 是 32 支付宝分配给开发者的应用ID 2014072300007148
+method String 是 128 接口名称 alipay.trade.fastpay.refund.query
+format String 否 40 仅支持JSON JSON
+charset String 是 10 请求使用的编码格式,如utf-8,gbk,gb2312等 utf-8
+sign_type String 是 10 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 RSA2
+sign String 是 344 商户请求参数的签名串,详见签名 详见示例
+timestamp String 是 19 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" 2014-07-24 03:07:50
+version String 是 3 调用的接口版本,固定为:1.0 1.0
+app_auth_token String 否 40 详见应用授权概述
+biz_content String 是 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
+ */
+ @Test
+ public void testPost() throws Exception {
+
+ // 公共请求参数
+ Map params = new HashMap();
+ params.put("app_id", appId);
+ params.put("method", "alipay.story.get");
+ params.put("format", "json");
+ params.put("charset", "utf-8");
+ params.put("sign_type", "RSA2");
+ params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+ params.put("version", "1.0");
+
+ // 业务参数
+ Map bizContent = new HashMap<>();
+ bizContent.put("id", "1");
+ bizContent.put("name", "葫芦娃");
+// bizContent.put("name", "葫芦娃1234567890葫芦娃1234567890"); // 超出长度
+
+ params.put("biz_content", JSON.toJSONString(bizContent));
+
+ System.out.println("----------- 请求信息 -----------");
+ System.out.println("请求参数:" + buildParamQuery(params));
+ System.out.println("商户秘钥:" + privateKey);
+ String content = AlipaySignature.getSignContent(params);
+ System.out.println("待签名内容:" + content);
+ String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8");
+ System.out.println("签名(sign):" + sign);
+
+ params.put("sign", sign);
+
+ System.out.println("----------- 返回结果 -----------");
+ String responseData = post(url, params);// 发送请求
+ System.out.println(responseData);
+ }
+
+ private String buildParamQuery(Map params) {
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry entry : params.entrySet()) {
+ sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
+ }
+ return sb.toString().substring(1);
+ }
+
+}
diff --git a/sop-test/src/test/java/com/gitee/sop/TaobaoClientPostTest.java b/sop-test/src/test/java/com/gitee/sop/TaobaoClientPostTest.java
new file mode 100644
index 00000000..e35315ad
--- /dev/null
+++ b/sop-test/src/test/java/com/gitee/sop/TaobaoClientPostTest.java
@@ -0,0 +1,77 @@
+package com.gitee.sop;
+
+import com.alibaba.fastjson.JSON;
+import com.gitee.sop.taobao.TaobaoSignature;
+import org.junit.Test;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 模仿淘宝客户端请求接口
+ */
+public class TaobaoClientPostTest extends TestBase {
+
+
+ String url = "http://localhost:8081/api";
+ String appId = "taobao_test";
+ // 支付宝私钥
+ String secret = "G9w0BAQEFAAOCAQ8AMIIBCgKCA";
+
+ /*
+参数名称 参数类型 是否必须 参数描述
+method String 是 API接口名称。
+app_key String 是 TOP分配给应用的AppKey。这里要注意正式环境和沙箱环境的AppKey是不同的(包括AppSecret),使用时要注意区分;进入开放平台控制台“应用管理-概览” 和 “应用管理-沙箱环境管理”可分别查看正式环境及沙箱环境的AppKey、AppSecret
+session String 否 用户登录授权成功后,TOP颁发给应用的授权信息,详细介绍请点击这里。当此API文档的标签上注明:“需要授权”,则此参数必传;“不需要授权”,则此参数不需要传;“可选授权”,则此参数为可选。
+timestamp String 是 时间戳,格式为yyyy-MM-dd HH:mm:ss,时区为GMT+8,例如:2016-01-01 12:00:00。淘宝API服务端允许客户端请求最大时间误差为10分钟。
+format String 否 响应格式。默认为xml格式,可选值:xml,json。
+v String 是 API协议版本,可选值:2.0。
+partner_id String 否 合作伙伴身份标识。
+target_app_key String 否 被调用的目标AppKey,仅当被调用的API为第三方ISV提供时有效。
+simplify Boolean 否 是否采用精简JSON返回格式,仅当format=json时有效,默认值为:false。
+sign_method String 是 签名的摘要算法,可选值为:hmac,md5。
+sign String 是 API输入参数签名结果,签名算法参照下面的介绍。
+ */
+ @Test
+ public void testPost() throws Exception {
+
+ // 公共请求参数
+ Map params = new HashMap();
+ params.put("app_key", appId);
+ params.put("method", "alipay.story.get");
+ params.put("format", "json");
+ params.put("sign_method", "md5");
+ params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+ params.put("v", "");
+
+ // 业务参数
+ params.put("id", "1");
+ params.put("name", "葫芦娃");
+// bizContent.put("name", "葫芦娃1234567890葫芦娃1234567890"); // 超出长度
+
+ System.out.println("----------- 请求信息 -----------");
+ System.out.println("请求参数:" + buildParamQuery(params));
+ System.out.println("商户秘钥:" + secret);
+ String content = TaobaoSignature.getSignContent(params);
+ System.out.println("待签名内容:" + content);
+ String sign = TaobaoSignature.doSign(content, secret, "md5");
+ System.out.println("签名(sign):" + sign);
+
+ params.put("sign", sign);
+
+ System.out.println("----------- 返回结果 -----------");
+ String responseData = post(url, params);// 发送请求
+ System.out.println(responseData);
+ }
+
+ private String buildParamQuery(Map params) {
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry entry : params.entrySet()) {
+ sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
+ }
+ return sb.toString().substring(1);
+ }
+
+}
diff --git a/sop-test/src/test/java/com/gitee/sop/TestBase.java b/sop-test/src/test/java/com/gitee/sop/TestBase.java
new file mode 100644
index 00000000..f8606e4d
--- /dev/null
+++ b/sop-test/src/test/java/com/gitee/sop/TestBase.java
@@ -0,0 +1,100 @@
+package com.gitee.sop;
+
+import junit.framework.TestCase;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author tanghc
+ */
+public class TestBase extends TestCase {
+ public void post(String url, String postJson) throws IOException {
+ HttpClient httpClient = HttpClientBuilder.create().build();
+ HttpPost post = new HttpPost(url);
+ // 构造消息头
+
+ post.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
+ // 构建消息实体
+ StringEntity entity = new StringEntity(postJson, Charset.forName("UTF-8"));
+ entity.setContentEncoding("UTF-8");
+ // 发送Json格式的数据请求
+ entity.setContentType("application/json");
+ post.setEntity(entity);
+
+ HttpResponse response = httpClient.execute(post);
+ HttpEntity responseEntity = response.getEntity();
+ String content = IOUtils.toString(responseEntity.getContent(), "UTF-8");
+ System.out.println(content);
+ }
+
+ /**
+ * 发送POST请求
+ * @param url
+ * @return JSON或者字符串
+ * @throws Exception
+ */
+ public static String post(String url, Map params) throws Exception{
+ CloseableHttpClient client = null;
+ CloseableHttpResponse response = null;
+ try{
+ /**
+ * 创建一个httpclient对象
+ */
+ client = HttpClients.createDefault();
+ /**
+ * 创建一个post对象
+ */
+ HttpPost post = new HttpPost(url);
+ List nameValuePairs = params.entrySet().stream().map(entry -> {
+ return new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue()));
+ }).collect(Collectors.toList());
+ /**
+ * 包装成一个Entity对象
+ */
+ UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nameValuePairs, "UTF-8");
+ /**
+ * 设置请求的内容
+ */
+ post.setEntity(entity);
+ /**
+ * 设置请求的报文头部的编码
+ */
+ post.setHeader(new BasicHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"));
+ /**
+ * 执行post请求
+ */
+ response = client.execute(post);
+ /**
+ * 通过EntityUitls获取返回内容
+ */
+ return EntityUtils.toString(response.getEntity(),"UTF-8");
+ }catch (Exception e){
+ e.printStackTrace();
+ }finally {
+ IOUtils.closeQuietly(client);
+ IOUtils.closeQuietly(response);
+ }
+ return null;
+ }
+
+
+}