commit 4d22f42ee9e32e8efb5e1bc5913be27019b237e3 Author: lijiazhuo <13787924+lijiazhuosky@user.noreply.gitee.com> Date: Mon Jan 19 14:45:35 2026 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### 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/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..74f4de4 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.5"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..0d5e649 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..7d59a01 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.2/apache-maven-3.6.2-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..822f699 --- /dev/null +++ b/mvnw @@ -0,0 +1,322 @@ +#!/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 +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# 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 + ))" +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 + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + 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 $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=$(cygpath --path --windows "$javaClass") + fi + 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 + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +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 0000000..84d60ab --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@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 https://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 by 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.5.5/maven-wrapper-0.5.5.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% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%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 0000000..133737d --- /dev/null +++ b/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.1.RELEASE + + + com.example + sso + 0.0.1-SNAPSHOT + jar + sso + Demo project for Spring Boot + + + 1.8 + + + + + com.auth0 + java-jwt + 3.8.1 + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.httpcomponents + httpclient + 4.5.2 + + + com.alibaba + fastjson + 1.2.45 + + + commons-codec + commons-codec + 1.19.0 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + org.projectlombok + lombok + true + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/com/example/sso/SsoApplication.java b/src/main/java/com/example/sso/SsoApplication.java new file mode 100644 index 0000000..6b7596b --- /dev/null +++ b/src/main/java/com/example/sso/SsoApplication.java @@ -0,0 +1,18 @@ +package com.example.sso; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableScheduling +@EnableAsync + +public class SsoApplication { + + public static void main(String[] args) { + SpringApplication.run(SsoApplication.class, args); + } + +} diff --git a/src/main/java/com/example/sso/config/SSOConfig.java b/src/main/java/com/example/sso/config/SSOConfig.java new file mode 100644 index 0000000..010fcd0 --- /dev/null +++ b/src/main/java/com/example/sso/config/SSOConfig.java @@ -0,0 +1,24 @@ +package com.example.sso.config; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotBlank; + +@Configuration +@ConfigurationProperties(prefix = "sso") +@NoArgsConstructor +@AllArgsConstructor +@Validated +@Getter +@Setter +public class SSOConfig { + @NotBlank private String iss; + @NotBlank private String acs; + @NotBlank private String secret; +} diff --git a/src/main/java/com/example/sso/controller/AddController.java b/src/main/java/com/example/sso/controller/AddController.java new file mode 100644 index 0000000..8bcbba1 --- /dev/null +++ b/src/main/java/com/example/sso/controller/AddController.java @@ -0,0 +1,70 @@ +package com.example.sso.controller; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.example.sso.dao.Add; +import com.example.sso.dao.Del; +import com.example.sso.util.*; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + + +@RestController +@Slf4j +public class AddController { + @PostMapping(value = "add",produces = MediaType.TEXT_PLAIN_VALUE) + public String receiveParams( + @RequestParam("msg_signature") String msgSignature, + @RequestParam("timestamp") String timeStamp, + @RequestParam("nonce") String nonce, + @RequestBody String data + ) throws AesException { + + String sToken = "9jtKosB"; + String sCorpID = "wwc276f7a0347c310b"; + String sEncodingAESKey = "p5oRQgLuqAUtO9z8S5ArrgegjVY1kKl7gSMMdmBWk7e"; + + WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID); + String s = wxcpt.DecryptMsg(msgSignature, timeStamp, nonce, data); + log.info("我是消息 " + s); + JSONObject result = QiWeiUtil.result(s); + String CreateTime = result.getString("CreateTime"); + String ChangeType = result.getString("ChangeType"); + String UserID = result.getString("UserID"); + String ExternalUserID = result.getString("ExternalUserID"); + String Source = result.getString("Source"); + String WelcomeCode = result.getString("WelcomeCode"); + if (ChangeType.equals("add_external_contact")) { + Add.add(CreateTime, ChangeType, UserID, ExternalUserID); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("welcome_code",WelcomeCode); + JSONObject text = new JSONObject(); + text.put("content","尊敬的客户,感谢您选择与银建合作。为便于您接入我司内部管理系统,请点击以下链接完成注册,以获取授权访问权限。注册链接:https://www.jiyuankeshang.com/corp_join/68c2a407bf5e56180de1db75?host_corp_id=wwc276f7a0347c310b "); + jsonObject.put("text",text); + String jsonString = jsonObject.toJSONString(); + + String tokencreat = QiWeiUtil.newtoken(); + JSONObject jsonObject2 = JSON.parseObject(tokencreat); + String string = jsonObject2.getString("access_token"); + String welcome = QiWeiUtil.welcome(jsonString, string); + log.info("欢迎 " +welcome ); + }else if (ChangeType.equals("del_external_contact")){ + if (Source != null && Source != "" ) { + Del.del(CreateTime, ChangeType, UserID, ExternalUserID, Source); + }else { + Del.dels(CreateTime, ChangeType, UserID, ExternalUserID); + } + }else if (ChangeType.equals("del_follow_user")){ + Add.del(CreateTime, ChangeType, UserID, ExternalUserID); + } + + return "200"; + } + + +} diff --git a/src/main/java/com/example/sso/controller/CreatAndYaoQingController.java b/src/main/java/com/example/sso/controller/CreatAndYaoQingController.java new file mode 100644 index 0000000..9a03128 --- /dev/null +++ b/src/main/java/com/example/sso/controller/CreatAndYaoQingController.java @@ -0,0 +1,42 @@ +package com.example.sso.controller; + + +import com.example.sso.util.AesException; +import com.example.sso.util.WXBizMsgCrypt; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@RestController +@Slf4j +public class CreatAndYaoQingController { + //创建和邀请 + // @GetMapping("/add") + public String receiveParams( + @RequestParam("msg_signature") String msgSignature, + @RequestParam("timestamp") String timeStamp, + @RequestParam("nonce") String nonce, + @RequestParam("echostr") String echoStr + ) throws AesException { + + String sToken = "9jtKosB"; + String sCorpID = "wwc276f7a0347c310b"; + String sEncodingAESKey = "p5oRQgLuqAUtO9z8S5ArrgegjVY1kKl7gSMMdmBWk7e"; + + WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID); + + String sEchoStr = ""; + try { + sEchoStr = wxcpt.VerifyURL(msgSignature, timeStamp, + nonce, echoStr); + System.out.println("verifyurl echostr: " + sEchoStr); + // 验证URL成功,将sEchoStr返回 + // HttpUtils.SetResponse(sEchoStr); + } catch (Exception e) { + //验证URL失败,错误原因请查看异常 + e.printStackTrace(); + } + + + return sEchoStr; + } +} diff --git a/src/main/java/com/example/sso/controller/TuiSong.java b/src/main/java/com/example/sso/controller/TuiSong.java new file mode 100644 index 0000000..aa4f128 --- /dev/null +++ b/src/main/java/com/example/sso/controller/TuiSong.java @@ -0,0 +1,31 @@ +package com.example.sso.controller; + +import com.alibaba.fastjson.JSONObject; +import com.example.sso.util.V5utils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +public class TuiSong { + @PostMapping("tuisong") + public void tuisong(@RequestBody JSONObject data) { + log.info("数据 " + data.toJSONString()); + JSONObject object = data.getJSONObject("data"); + String mode_type = object.getString("mode_type"); + String account_type = object.getString("account_type)"); + String date = object.getString("date"); + + String beisen_id = object.getString("beisen_id"); + if ((mode_type.equals("自营") ||mode_type.equals("DP") && (account_type.equals("预收款")) &&(date !=null && date !="") ) ) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("username", beisen_id); + String jsonString = jsonObject.toJSONString(); + V5utils.delusername(jsonString); + } + + + } +} diff --git a/src/main/java/com/example/sso/dao/Add.java b/src/main/java/com/example/sso/dao/Add.java new file mode 100644 index 0000000..db4f73a --- /dev/null +++ b/src/main/java/com/example/sso/dao/Add.java @@ -0,0 +1,76 @@ +package com.example.sso.dao; + +import com.alibaba.fastjson.JSONObject; +import com.example.sso.util.V5utils; + +public class Add { + public static void add(String CreateTime,String ChangeType, String UserID,String ExternalUserID) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("app_id", "628eeaace7f28c00089a60cc"); + jsonObject.put("entry_id", "68f88b49bf5e56180dbf35fe"); + jsonObject.put("is_start_trigger", true); + JSONObject data = new JSONObject(); + + + JSONObject data_sources = new JSONObject(); + data_sources.put("value", CreateTime); + data.put("create_time_jy", data_sources ); + + JSONObject match_field = new JSONObject(); + match_field.put("value","添加企业客户"); + data.put("event_jy", match_field ); + + JSONObject serialno = new JSONObject(); + serialno.put("value", UserID); + data.put("userid_jy", serialno ); + + JSONObject serialno1 = new JSONObject(); + serialno1.put("value", UserID); + data.put("corp_user_jy", serialno1 ); + + JSONObject lossSeqNo1 = new JSONObject(); + lossSeqNo1.put("value", ExternalUserID); + data.put("external_userid_jy", lossSeqNo1); + + + jsonObject.put("data", data); + String jsonString = jsonObject.toJSONString(); + V5utils.add(jsonString); + + } + + public static void del(String CreateTime,String ChangeType, String UserID,String ExternalUserID) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("app_id", "628eeaace7f28c00089a60cc"); + jsonObject.put("entry_id", "68f88b49bf5e56180dbf35fe"); + jsonObject.put("is_start_trigger", true); + JSONObject data = new JSONObject(); + + + JSONObject data_sources = new JSONObject(); + data_sources.put("value", CreateTime); + data.put("create_time_jy", data_sources ); + + JSONObject match_field = new JSONObject(); + match_field.put("value","删除跟进成员"); + data.put("event_jy", match_field ); + + JSONObject serialno = new JSONObject(); + serialno.put("value", UserID); + data.put("userid_jy", serialno ); + + JSONObject serialno1 = new JSONObject(); + serialno1.put("value", UserID); + data.put("corp_user_jy", serialno1 ); + + JSONObject lossSeqNo1 = new JSONObject(); + lossSeqNo1.put("value", ExternalUserID); + data.put("external_userid_jy", lossSeqNo1); + + + jsonObject.put("data", data); + String jsonString = jsonObject.toJSONString(); + V5utils.add(jsonString); + + } +} diff --git a/src/main/java/com/example/sso/dao/Del.java b/src/main/java/com/example/sso/dao/Del.java new file mode 100644 index 0000000..3e851a9 --- /dev/null +++ b/src/main/java/com/example/sso/dao/Del.java @@ -0,0 +1,101 @@ +package com.example.sso.dao; + +import com.alibaba.fastjson.JSONObject; +import com.example.sso.util.V5utils; + +public class Del { + public static void del(String CreateTime,String ChangeType, String UserID,String ExternalUserID,String Source) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("app_id", "628eeaace7f28c00089a60cc"); + jsonObject.put("entry_id", "68f88b49bf5e56180dbf35fe"); + jsonObject.put("is_start_trigger", true); + JSONObject data = new JSONObject(); + + + JSONObject data_sources = new JSONObject(); + data_sources.put("value", CreateTime); + data.put("create_time_jy", data_sources ); + + JSONObject match_field = new JSONObject(); + match_field.put("value","删除企业客户"); + data.put("event_jy", match_field ); + + JSONObject serialno = new JSONObject(); + serialno.put("value", UserID); + data.put("userid_jy", serialno ); + + JSONObject serialno1 = new JSONObject(); + serialno1.put("value", UserID); + data.put("corp_user_jy", serialno1 ); + + JSONObject lossSeqNo1 = new JSONObject(); + lossSeqNo1.put("value", ExternalUserID); + data.put("external_userid_jy", lossSeqNo1); + + + if (Source.equals("DELETE_BY_TRANSFER")){ + JSONObject lossSeqNo11 = new JSONObject(); + lossSeqNo11.put("value", "离职继承"); + data.put("event_jy", lossSeqNo11); + }else { + JSONObject lossSeqNo11 = new JSONObject(); + lossSeqNo11.put("value", "异常删除"); + data.put("event_jy", lossSeqNo11); + } + + + + + + + jsonObject.put("data", data); + String jsonString = jsonObject.toJSONString(); + V5utils.add(jsonString); + + } + + + public static void dels(String CreateTime,String ChangeType, String UserID,String ExternalUserID ) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("app_id", "628eeaace7f28c00089a60cc"); + jsonObject.put("entry_id", "68f88b49bf5e56180dbf35fe"); + jsonObject.put("is_start_trigger", true); + JSONObject data = new JSONObject(); + + + JSONObject data_sources = new JSONObject(); + data_sources.put("value", CreateTime); + data.put("create_time_jy", data_sources ); + + JSONObject match_field = new JSONObject(); + match_field.put("value","删除企业客户"); + data.put("event_jy", match_field ); + + JSONObject serialno = new JSONObject(); + serialno.put("value", UserID); + data.put("userid_jy", serialno ); + + JSONObject serialno1 = new JSONObject(); + serialno1.put("value", UserID); + data.put("corp_user_jy", serialno1 ); + + JSONObject lossSeqNo1 = new JSONObject(); + lossSeqNo1.put("value", ExternalUserID); + data.put("external_userid_jy", lossSeqNo1); + + JSONObject lossSeqNo11 = new JSONObject(); + lossSeqNo11.put("value", "异常删除"); + data.put("event_jy", lossSeqNo11); + + + + + + + + jsonObject.put("data", data); + String jsonString = jsonObject.toJSONString(); + V5utils.add(jsonString); + + } +} diff --git a/src/main/java/com/example/sso/service/SSOService.java b/src/main/java/com/example/sso/service/SSOService.java new file mode 100644 index 0000000..70b2470 --- /dev/null +++ b/src/main/java/com/example/sso/service/SSOService.java @@ -0,0 +1,44 @@ +package com.example.sso.service; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.example.sso.config.SSOConfig; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpSession; +import java.util.Calendar; +import java.util.Date; + +@Service +@NoArgsConstructor +@AllArgsConstructor +public class SSOService { + @Getter @Setter @Autowired private SSOConfig ssoConfig; + public String getResponse(String request, String username, HttpSession httpSession) { + Algorithm algorithm = Algorithm.HMAC256((String) httpSession.getAttribute("sso_secret")); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer("com.jiandaoyun") + .build(); + DecodedJWT decoded = verifier.verify(request); + if (!"sso_req".equals(decoded.getClaim("type").asString())) { + return ""; + } + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date()); + calendar.add(Calendar.HOUR_OF_DAY, 1); + return JWT.create() + .withIssuer("com.jiandaoyun") + .withClaim("type", "sso_res") + .withClaim("username", username) + .withAudience("com.jiandaoyun") + .withExpiresAt(calendar.getTime()) + .sign(algorithm); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/sso/test/A.java b/src/main/java/com/example/sso/test/A.java new file mode 100644 index 0000000..792a95d --- /dev/null +++ b/src/main/java/com/example/sso/test/A.java @@ -0,0 +1,64 @@ +package com.example.sso.test; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; + +public class A { + public static void main(String[] args) { + String xmlString = "1761534307"; + + try { + // 创建解析器工厂和解析器 + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + // 将字符串转换为输入源并进行解析 + Document doc = builder.parse(new InputSource(new StringReader(xmlString))); + + // 标准化文档(可选,但能处理一些空白字符问题) + doc.getDocumentElement().normalize(); + + // 获取根元素 + Element root = doc.getDocumentElement(); + + // 提取具体数据 + String toUserName = getTextContentByTagName(root, "ToUserName"); + String fromUserName = getTextContentByTagName(root, "FromUserName"); + String createTime = getTextContentByTagName(root, "CreateTime"); + String msgType = getTextContentByTagName(root, "MsgType"); + String event = getTextContentByTagName(root, "Event"); + String changeType = getTextContentByTagName(root, "ChangeType"); + String userID = getTextContentByTagName(root, "UserID"); + String externalUserID = getTextContentByTagName(root, "ExternalUserID"); + String welcomeCode = getTextContentByTagName(root, "WelcomeCode"); + + // 打印结果 + System.out.println("ToUserName: " + toUserName); + System.out.println("FromUserName: " + fromUserName); + System.out.println("CreateTime: " + createTime); + System.out.println("MsgType: " + msgType); + System.out.println("Event: " + event); + System.out.println("ChangeType: " + changeType); + System.out.println("UserID: " + userID); + System.out.println("ExternalUserID: " + externalUserID); + System.out.println("WelcomeCode: " + welcomeCode); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + // 辅助方法:根据标签名获取元素的文本内容 + private static String getTextContentByTagName(Element element, String tagName) { + NodeList nodeList = element.getElementsByTagName(tagName); + if (nodeList.getLength() > 0) { + return nodeList.item(0).getTextContent(); + } + return null; + } +} diff --git a/src/main/java/com/example/sso/test/B.java b/src/main/java/com/example/sso/test/B.java new file mode 100644 index 0000000..7ce2f08 --- /dev/null +++ b/src/main/java/com/example/sso/test/B.java @@ -0,0 +1,64 @@ +package com.example.sso.test; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; + +public class B { + public static void main(String[] args) { + String xmlString = "1761537016"; + + try { + // 创建解析器工厂和解析器 + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + // 将字符串转换为输入源并进行解析 + Document doc = builder.parse(new InputSource(new StringReader(xmlString))); + + // 标准化文档(可选,但能处理一些空白字符问题) + doc.getDocumentElement().normalize(); + + // 获取根元素 + Element root = doc.getDocumentElement(); + + // 提取具体数据 + String toUserName = getTextContentByTagName(root, "ToUserName"); + String fromUserName = getTextContentByTagName(root, "FromUserName"); + String createTime = getTextContentByTagName(root, "CreateTime"); + String msgType = getTextContentByTagName(root, "MsgType"); + String event = getTextContentByTagName(root, "Event"); + String changeType = getTextContentByTagName(root, "ChangeType"); + String userID = getTextContentByTagName(root, "UserID"); + String externalUserID = getTextContentByTagName(root, "ExternalUserID"); + String welcomeCode = getTextContentByTagName(root, "WelcomeCode"); + + // 打印结果 + System.out.println("ToUserName: " + toUserName); + System.out.println("FromUserName: " + fromUserName); + System.out.println("CreateTime: " + createTime); + System.out.println("MsgType: " + msgType); + System.out.println("Event: " + event); + System.out.println("ChangeType: " + changeType); + System.out.println("UserID: " + userID); + System.out.println("ExternalUserID: " + externalUserID); + System.out.println("WelcomeCode: " + welcomeCode); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + // 辅助方法:根据标签名获取元素的文本内容 + private static String getTextContentByTagName(Element element, String tagName) { + NodeList nodeList = element.getElementsByTagName(tagName); + if (nodeList.getLength() > 0) { + return nodeList.item(0).getTextContent(); + } + return null; + } +} diff --git a/src/main/java/com/example/sso/test/C.java b/src/main/java/com/example/sso/test/C.java new file mode 100644 index 0000000..362b9b2 --- /dev/null +++ b/src/main/java/com/example/sso/test/C.java @@ -0,0 +1,26 @@ +package com.example.sso.test; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class C { + + public static void main(String[] args) { + // 您提供的时间戳(单位:秒) + long timestampInSeconds = 1761537016L; + + // 1. 将秒转换为毫秒,然后创建 Date 对象 + Date date = new Date(timestampInSeconds * 1000L); + + // 2. 创建格式化器,并设置其时间为 UTC + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // 关键步骤:设置时区 + + // 3. 格式化输出 + String utcTime = sdf.format(date); + System.out.println("UTC 时间: " + utcTime); // 输出:2025-10-27 08:30:16 + + + } +} diff --git a/src/main/java/com/example/sso/util/AesException.java b/src/main/java/com/example/sso/util/AesException.java new file mode 100644 index 0000000..2ae6e36 --- /dev/null +++ b/src/main/java/com/example/sso/util/AesException.java @@ -0,0 +1,59 @@ +package com.example.sso.util; + +@SuppressWarnings("serial") +public class AesException extends Exception { + + public final static int OK = 0; + public final static int ValidateSignatureError = -40001; + public final static int ParseXmlError = -40002; + public final static int ComputeSignatureError = -40003; + public final static int IllegalAesKey = -40004; + public final static int ValidateCorpidError = -40005; + public final static int EncryptAESError = -40006; + public final static int DecryptAESError = -40007; + public final static int IllegalBuffer = -40008; + //public final static int EncodeBase64Error = -40009; + //public final static int DecodeBase64Error = -40010; + //public final static int GenReturnXmlError = -40011; + + private int code; + + private static String getMessage(int code) { + switch (code) { + case ValidateSignatureError: + return "签名验证错误"; + case ParseXmlError: + return "xml解析失败"; + case ComputeSignatureError: + return "sha加密生成签名失败"; + case IllegalAesKey: + return "SymmetricKey非法"; + case ValidateCorpidError: + return "corpid校验失败"; + case EncryptAESError: + return "aes加密失败"; + case DecryptAESError: + return "aes解密失败"; + case IllegalBuffer: + return "解密后得到的buffer非法"; +// case EncodeBase64Error: +// return "base64加密错误"; +// case DecodeBase64Error: +// return "base64解密错误"; +// case GenReturnXmlError: +// return "xml生成失败"; + default: + return null; // cannot be + } + } + + public int getCode() { + return code; + } + + AesException(int code) { + super(getMessage(code)); + this.code = code; + } + +} diff --git a/src/main/java/com/example/sso/util/ByteGroup.java b/src/main/java/com/example/sso/util/ByteGroup.java new file mode 100644 index 0000000..f4f3dbf --- /dev/null +++ b/src/main/java/com/example/sso/util/ByteGroup.java @@ -0,0 +1,26 @@ +package com.example.sso.util; + +import java.util.ArrayList; + +class ByteGroup { + ArrayList byteContainer = new ArrayList(); + + public byte[] toBytes() { + byte[] bytes = new byte[byteContainer.size()]; + for (int i = 0; i < byteContainer.size(); i++) { + bytes[i] = byteContainer.get(i); + } + return bytes; + } + + public ByteGroup addBytes(byte[] bytes) { + for (byte b : bytes) { + byteContainer.add(b); + } + return this; + } + + public int size() { + return byteContainer.size(); + } +} diff --git a/src/main/java/com/example/sso/util/PKCS7Encoder.java b/src/main/java/com/example/sso/util/PKCS7Encoder.java new file mode 100644 index 0000000..2bb447d --- /dev/null +++ b/src/main/java/com/example/sso/util/PKCS7Encoder.java @@ -0,0 +1,67 @@ +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.example.sso.util; + +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * 提供基于PKCS7算法的加解密接口. + */ +class PKCS7Encoder { + static Charset CHARSET = Charset.forName("utf-8"); + static int BLOCK_SIZE = 32; + + /** + * 获得对明文进行补位填充的字节. + * + * @param count 需要进行填充补位操作的明文字节个数 + * @return 补齐用的字节数组 + */ + static byte[] encode(int count) { + // 计算需要填充的位数 + int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); + if (amountToPad == 0) { + amountToPad = BLOCK_SIZE; + } + // 获得补位所用的字符 + char padChr = chr(amountToPad); + String tmp = new String(); + for (int index = 0; index < amountToPad; index++) { + tmp += padChr; + } + return tmp.getBytes(CHARSET); + } + + /** + * 删除解密后明文的补位字符 + * + * @param decrypted 解密后的明文 + * @return 删除补位字符后的明文 + */ + static byte[] decode(byte[] decrypted) { + int pad = (int) decrypted[decrypted.length - 1]; + if (pad < 1 || pad > 32) { + pad = 0; + } + return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); + } + + /** + * 将数字转化成ASCII码对应的字符,用于对明文进行补码 + * + * @param a 需要转化的数字 + * @return 转化得到的字符 + */ + static char chr(int a) { + byte target = (byte) (a & 0xFF); + return (char) target; + } + +} diff --git a/src/main/java/com/example/sso/util/QiWeiUtil.java b/src/main/java/com/example/sso/util/QiWeiUtil.java new file mode 100644 index 0000000..300dc8e --- /dev/null +++ b/src/main/java/com/example/sso/util/QiWeiUtil.java @@ -0,0 +1,207 @@ +package com.example.sso.util; + +import com.alibaba.fastjson.JSONObject; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; + +public class QiWeiUtil { + public static JSONObject result(String xmlString) { + // String xmlString = "1761534307"; + + JSONObject jsonObject = null; + try { + // 创建解析器工厂和解析器 + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + // 将字符串转换为输入源并进行解析 + Document doc = builder.parse(new InputSource(new StringReader(xmlString))); + + // 标准化文档(可选,但能处理一些空白字符问题) + doc.getDocumentElement().normalize(); + + // 获取根元素 + Element root = doc.getDocumentElement(); + + // 提取具体数据 + String toUserName = getTextContentByTagName(root, "ToUserName"); + String fromUserName = getTextContentByTagName(root, "FromUserName"); + String createTime = getTextContentByTagName(root, "CreateTime"); + String msgType = getTextContentByTagName(root, "MsgType"); + String event = getTextContentByTagName(root, "Event"); + String changeType = getTextContentByTagName(root, "ChangeType"); + String userID = getTextContentByTagName(root, "UserID"); + String externalUserID = getTextContentByTagName(root, "ExternalUserID"); + String welcomeCode = getTextContentByTagName(root, "WelcomeCode"); + String source = getTextContentByTagName(root, "Source"); + + // 打印结果 + System.out.println("ToUserName: " + toUserName); + System.out.println("FromUserName: " + fromUserName); + System.out.println("CreateTime: " + createTime); + System.out.println("MsgType: " + msgType); + System.out.println("Event: " + event); + System.out.println("ChangeType: " + changeType); + System.out.println("UserID: " + userID); + System.out.println("ExternalUserID: " + externalUserID); + System.out.println("WelcomeCode: " + welcomeCode); + System.out.println("Source: " + source); + + jsonObject = new JSONObject(); + String time = TimeUtil.time(createTime); + + jsonObject.put("CreateTime", time); + jsonObject.put("ChangeType", changeType); + jsonObject.put("UserID", userID); + jsonObject.put("ExternalUserID", externalUserID); + jsonObject.put("Source", source); + jsonObject.put("WelcomeCode", welcomeCode); + + } catch (Exception e) { + e.printStackTrace(); + } + return jsonObject; + } + + // 辅助方法:根据标签名获取元素的文本内容 + private static String getTextContentByTagName(Element element, String tagName) { + NodeList nodeList = element.getElementsByTagName(tagName); + if (nodeList.getLength() > 0) { + return nodeList.item(0).getTextContent(); + } + return null; + } + + + public static String token(){ + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // 创建 POST 请求对象 + HttpGet httpPost = new HttpGet("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=wwc276f7a0347c310b&corpsecret=m-h_vn1sLr4PSw87SS-l-gRZN3uuHcYdCrFz0gDh-Ds"); + + String responseBody = null; + try { + + + + + // 执行请求,获取响应对象 + CloseableHttpResponse response = httpClient.execute(httpPost); + + try { + // 从响应对象中获取响应实体 + HttpEntity responseEntity = response.getEntity(); + + // 处理响应数据 + responseBody = EntityUtils.toString(responseEntity); + System.out.println(responseBody); + } finally { + // 关闭响应对象 + response.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭 HttpClient + httpClient.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return responseBody; + } + + + public static String welcome(String jsonBody,String token){ + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // 创建 POST 请求对象 + HttpPost httpPost = new HttpPost("https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token=" + token); + + String responseBody = null; + try { + + + + StringEntity entity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON); + httpPost.setEntity(entity); + + // 执行请求,获取响应对象 + CloseableHttpResponse response = httpClient.execute(httpPost); + + try { + // 从响应对象中获取响应实体 + HttpEntity responseEntity = response.getEntity(); + + // 处理响应数据 + responseBody = EntityUtils.toString(responseEntity); + System.out.println(responseBody); + } finally { + // 关闭响应对象 + response.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭 HttpClient + httpClient.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return responseBody; + } + + public static String newtoken() { + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // 创建 POST 请求对象 + HttpGet httpGet = new HttpGet("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=wwc276f7a0347c310b&corpsecret=8jSj7EF1J6P1zzDeqzMxc-1c4EV3tTGyB5yljB2RWtk"); + + String responseBody = null; + try { + + + // 执行请求,获取响应对象 + CloseableHttpResponse response = httpClient.execute(httpGet); + + try { + // 从响应对象中获取响应实体 + HttpEntity responseEntity = response.getEntity(); + + // 处理响应数据 + responseBody = EntityUtils.toString(responseEntity); + System.out.println(responseBody); + } finally { + // 关闭响应对象 + response.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭 HttpClient + httpClient.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return responseBody; + } +} diff --git a/src/main/java/com/example/sso/util/SHA1.java b/src/main/java/com/example/sso/util/SHA1.java new file mode 100644 index 0000000..b17d985 --- /dev/null +++ b/src/main/java/com/example/sso/util/SHA1.java @@ -0,0 +1,61 @@ +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.example.sso.util; + +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * SHA1 class + * + * 计算消息签名接口. + */ +class SHA1 { + + /** + * 用SHA1算法生成安全签名 + * @param token 票据 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @param encrypt 密文 + * @return 安全签名 + * @throws AesException + */ + public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException + { + try { + String[] array = new String[] { token, timestamp, nonce, encrypt }; + StringBuffer sb = new StringBuffer(); + // 字符串排序 + Arrays.sort(array); + for (int i = 0; i < 4; i++) { + sb.append(array[i]); + } + String str = sb.toString(); + // SHA1签名生成 + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(str.getBytes()); + byte[] digest = md.digest(); + + StringBuffer hexstr = new StringBuffer(); + String shaHex = ""; + for (int i = 0; i < digest.length; i++) { + shaHex = Integer.toHexString(digest[i] & 0xFF); + if (shaHex.length() < 2) { + hexstr.append(0); + } + hexstr.append(shaHex); + } + return hexstr.toString(); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ComputeSignatureError); + } + } +} diff --git a/src/main/java/com/example/sso/util/TimeUtil.java b/src/main/java/com/example/sso/util/TimeUtil.java new file mode 100644 index 0000000..2e30cf9 --- /dev/null +++ b/src/main/java/com/example/sso/util/TimeUtil.java @@ -0,0 +1,44 @@ +package com.example.sso.util; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class TimeUtil { + public static String time(String time) { + long timestampInSeconds = Long.parseLong(time); + // 您提供的时间戳(单位:秒) + + + // 1. 将秒转换为毫秒,然后创建 Date 对象 + Date date = new Date(timestampInSeconds * 1000L); + + // 2. 创建格式化器,并设置其时间为 UTC + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // 关键步骤:设置时区 + + // 3. 格式化输出 + String utcTime = sdf.format(date); + System.out.println("UTC 时间: " + utcTime); // 输出:2025-10-27 08:30:16 + return utcTime; + } + + + public static void main(String[] args) { + long timestampInSeconds = Long.parseLong("1761880638"); + // 您提供的时间戳(单位:秒) + + + // 1. 将秒转换为毫秒,然后创建 Date 对象 + Date date = new Date(timestampInSeconds * 1000L); + + // 2. 创建格式化器,并设置其时间为 UTC + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // 关键步骤:设置时区 + + // 3. 格式化输出 + String utcTime = sdf.format(date); + System.out.println("UTC 时间: " + utcTime); // 输出:2025-10-27 08:30:16 + + } +} diff --git a/src/main/java/com/example/sso/util/V5utils.java b/src/main/java/com/example/sso/util/V5utils.java new file mode 100644 index 0000000..6465ccb --- /dev/null +++ b/src/main/java/com/example/sso/util/V5utils.java @@ -0,0 +1,233 @@ +package com.example.sso.util; + +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +public class V5utils { + /* + 查询多条数据 + */ + public static String list(String jsonBody){ + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // 创建 POST 请求对象 + HttpPost httpPost = new HttpPost("https://www.jiyuankeshang.com/api/v5/app/entry/data/list"); + + String responseBody = null; + try { + // 设置请求头 + httpPost.setHeader("Content-Type", "application/json"); + httpPost.setHeader("Authorization", "Bearer " + "BkIyzlh1onqnqu9cQ3ralDQBjECn97ex"); + + + StringEntity entity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON); + httpPost.setEntity(entity); + + // 执行请求,获取响应对象 + CloseableHttpResponse response = httpClient.execute(httpPost); + + try { + // 从响应对象中获取响应实体 + HttpEntity responseEntity = response.getEntity(); + + // 处理响应数据 + responseBody = EntityUtils.toString(responseEntity); + System.out.println(responseBody); + } finally { + // 关闭响应对象 + response.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭 HttpClient + httpClient.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return responseBody; + } + + public static String delusername(String jsonBody){ + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // 创建 POST 请求对象 + HttpPost httpPost = new HttpPost("https://www.jiyuankeshang.com/api/v5/corp/user/delete"); + + String responseBody = null; + try { + // 设置请求头 + httpPost.setHeader("Content-Type", "application/json"); + httpPost.setHeader("Authorization", "Bearer " + "BkIyzlh1onqnqu9cQ3ralDQBjECn97ex"); + + + StringEntity entity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON); + httpPost.setEntity(entity); + + // 执行请求,获取响应对象 + CloseableHttpResponse response = httpClient.execute(httpPost); + + try { + // 从响应对象中获取响应实体 + HttpEntity responseEntity = response.getEntity(); + + // 处理响应数据 + responseBody = EntityUtils.toString(responseEntity); + System.out.println(responseBody); + } finally { + // 关闭响应对象 + response.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭 HttpClient + httpClient.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return responseBody; + } + + + public static String delete(String jsonBody){ + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // 创建 POST 请求对象 + HttpPost httpPost = new HttpPost("https://www.jiyuankeshang.com/api/v5/app/entry/data/delete"); + + String responseBody = null; + try { + // 设置请求头 + httpPost.setHeader("Content-Type", "application/json"); + httpPost.setHeader("Authorization", "Bearer " + "BkIyzlh1onqnqu9cQ3ralDQBjECn97ex"); + + + StringEntity entity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON); + httpPost.setEntity(entity); + + // 执行请求,获取响应对象 + CloseableHttpResponse response = httpClient.execute(httpPost); + + try { + // 从响应对象中获取响应实体 + HttpEntity responseEntity = response.getEntity(); + + // 处理响应数据 + responseBody = EntityUtils.toString(responseEntity); + System.out.println(responseBody); + } finally { + // 关闭响应对象 + response.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭 HttpClient + httpClient.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return responseBody; + } + + //新增 + public static String add(String jsonBody){ + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // 创建 POST 请求对象 + HttpPost httpPost = new HttpPost("https://www.jiyuankeshang.com/api/v5/app/entry/data/create"); + + String responseBody = null; + try { + // 设置请求头 + httpPost.setHeader("Content-Type", "application/json"); + httpPost.setHeader("Authorization", "Bearer " + "BkIyzlh1onqnqu9cQ3ralDQBjECn97ex"); + + + StringEntity entity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON); + httpPost.setEntity(entity); + + // 执行请求,获取响应对象 + CloseableHttpResponse response = httpClient.execute(httpPost); + + try { + // 从响应对象中获取响应实体 + HttpEntity responseEntity = response.getEntity(); + + // 处理响应数据 + responseBody = EntityUtils.toString(responseEntity); + System.out.println(responseBody); + } finally { + // 关闭响应对象 + response.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭 HttpClient + httpClient.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return responseBody; + } + + + public static String updata(String jsonBody){ + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // 创建 POST 请求对象 + HttpPost httpPost = new HttpPost("https://www.jiyuankeshang.com/api/v5/app/entry/data/update"); + + String responseBody = null; + try { + // 设置请求头 + httpPost.setHeader("Content-Type", "application/json"); + httpPost.setHeader("Authorization", "Bearer " + "BkIyzlh1onqnqu9cQ3ralDQBjECn97ex"); + + + StringEntity entity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON); + httpPost.setEntity(entity); + + // 执行请求,获取响应对象 + CloseableHttpResponse response = httpClient.execute(httpPost); + + try { + // 从响应对象中获取响应实体 + HttpEntity responseEntity = response.getEntity(); + + // 处理响应数据 + responseBody = EntityUtils.toString(responseEntity); + System.out.println(responseBody); + } finally { + // 关闭响应对象 + response.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭 HttpClient + httpClient.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return responseBody; + } +} diff --git a/src/main/java/com/example/sso/util/WXBizMsgCrypt.java b/src/main/java/com/example/sso/util/WXBizMsgCrypt.java new file mode 100644 index 0000000..fc976ee --- /dev/null +++ b/src/main/java/com/example/sso/util/WXBizMsgCrypt.java @@ -0,0 +1,291 @@ +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +/** + * 针对org.apache.commons.codec.binary.Base64, + * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) + * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi + */ +package com.example.sso.util; + +import org.apache.commons.codec.binary.Base64; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Random; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + + + +/** + * 提供接收和推送给企业微信消息的加解密接口(UTF8编码的字符串). + *
    + *
  1. 第三方回复加密消息给企业微信
  2. + *
  3. 第三方收到企业微信发送的消息,验证消息的安全性,并对消息进行解密。
  4. + *
+ * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 + *
    + *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: + * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
  2. + *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. + *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. + *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
  8. + *
+ */ +public class WXBizMsgCrypt { + static Charset CHARSET = Charset.forName("utf-8"); + Base64 base64 = new Base64(); + byte[] aesKey; + String token; + String receiveid; + + /** + * 构造函数 + * @param token 企业微信后台,开发者设置的token + * @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey + * @param receiveid, 不同场景含义不同,详见文档 + * + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public WXBizMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException { + if (encodingAesKey.length() != 43) { + throw new AesException(AesException.IllegalAesKey); + } + + this.token = token; + this.receiveid = receiveid; + aesKey = Base64.decodeBase64(encodingAesKey + "="); + } + + // 生成4个字节的网络字节序 + byte[] getNetworkBytesOrder(int sourceNumber) { + byte[] orderBytes = new byte[4]; + orderBytes[3] = (byte) (sourceNumber & 0xFF); + orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF); + orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF); + orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF); + return orderBytes; + } + + // 还原4个字节的网络字节序 + int recoverNetworkBytesOrder(byte[] orderBytes) { + int sourceNumber = 0; + for (int i = 0; i < 4; i++) { + sourceNumber <<= 8; + sourceNumber |= orderBytes[i] & 0xff; + } + return sourceNumber; + } + + // 随机生成16位字符串 + String getRandomStr() { + String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 16; i++) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + return sb.toString(); + } + + /** + * 对明文进行加密. + * + * @param text 需要加密的明文 + * @return 加密后base64编码的字符串 + * @throws AesException aes加密失败 + */ + String encrypt(String randomStr, String text) throws AesException { + ByteGroup byteCollector = new ByteGroup(); + byte[] randomStrBytes = randomStr.getBytes(CHARSET); + byte[] textBytes = text.getBytes(CHARSET); + byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length); + byte[] receiveidBytes = receiveid.getBytes(CHARSET); + + // randomStr + networkBytesOrder + text + receiveid + byteCollector.addBytes(randomStrBytes); + byteCollector.addBytes(networkBytesOrder); + byteCollector.addBytes(textBytes); + byteCollector.addBytes(receiveidBytes); + + // ... + pad: 使用自定义的填充方式对明文进行补位填充 + byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); + byteCollector.addBytes(padBytes); + + // 获得最终的字节流, 未加密 + byte[] unencrypted = byteCollector.toBytes(); + + try { + // 设置加密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); + + // 加密 + byte[] encrypted = cipher.doFinal(unencrypted); + + // 使用BASE64对加密后的字符串进行编码 + String base64Encrypted = base64.encodeToString(encrypted); + + return base64Encrypted; + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.EncryptAESError); + } + } + + /** + * 对密文进行解密. + * + * @param text 需要解密的密文 + * @return 解密得到的明文 + * @throws AesException aes解密失败 + */ + String decrypt(String text) throws AesException { + byte[] original; + try { + // 设置解密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); + cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); + + // 使用BASE64对密文进行解码 + byte[] encrypted = Base64.decodeBase64(text); + + // 解密 + original = cipher.doFinal(encrypted); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.DecryptAESError); + } + + String xmlContent, from_receiveid; + try { + // 去除补位字符 + byte[] bytes = PKCS7Encoder.decode(original); + + // 分离16位随机字符串,网络字节序和receiveid + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); + + int xmlLength = recoverNetworkBytesOrder(networkOrder); + + xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); + from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), + CHARSET); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.IllegalBuffer); + } + + // receiveid不相同的情况 + if (!from_receiveid.equals(receiveid)) { + throw new AesException(AesException.ValidateCorpidError); + } + return xmlContent; + + } + + /** + * 将企业微信回复用户的消息加密打包. + *
    + *
  1. 对要发送的消息进行AES-CBC加密
  2. + *
  3. 生成安全签名
  4. + *
  5. 将消息密文和安全签名打包成xml格式
  6. + *
+ * + * @param replyMsg 企业微信待回复用户的消息,xml格式的字符串 + * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp + * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce + * + * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException { + // 加密 + String encrypt = encrypt(getRandomStr(), replyMsg); + + // 生成安全签名 + if (timeStamp == "") { + timeStamp = Long.toString(System.currentTimeMillis()); + } + + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt); + + // System.out.println("发送给平台的签名是: " + signature[1].toString()); + // 生成发送的xml + String result = XMLParse.generate(encrypt, signature, timeStamp, nonce); + return result; + } + + /** + * 检验消息的真实性,并且获取解密后的明文. + *
    + *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. + *
  3. 若验证通过,则提取xml中的加密消息
  4. + *
  5. 对消息进行解密
  6. + *
+ * + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param postData 密文,对应POST请求的数据 + * + * @return 解密后的原文 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData) + throws AesException { + + // 密钥,公众账号的app secret + // 提取密文 + Object[] encrypt = XMLParse.extract(postData); + + // 验证安全签名 + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString()); + + // 和URL中的签名比较是否相等 + // System.out.println("第三方收到URL中的签名:" + msg_sign); + // System.out.println("第三方校验签名:" + signature); + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + // 解密 + String result = decrypt(encrypt[1].toString()); + return result; + } + + /** + * 验证URL + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param echoStr 随机串,对应URL参数的echostr + * + * @return 解密之后的echostr + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr) + throws AesException { + String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr); + + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + String result = decrypt(echoStr); + return result; + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/sso/util/XMLParse.java b/src/main/java/com/example/sso/util/XMLParse.java new file mode 100644 index 0000000..9770302 --- /dev/null +++ b/src/main/java/com/example/sso/util/XMLParse.java @@ -0,0 +1,104 @@ +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.example.sso.util; + +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +/** + * XMLParse class + * + * 提供提取消息格式中的密文及生成回复消息格式的接口. + */ +class XMLParse { + + /** + * 提取出xml数据包中的加密消息 + * @param xmltext 待提取的xml字符串 + * @return 提取出的加密消息字符串 + * @throws AesException + */ + public static Object[] extract(String xmltext) throws AesException { + Object[] result = new Object[3]; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + + String FEATURE = null; + // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented + // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl + FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; + dbf.setFeature(FEATURE, true); + + // If you can't completely disable DTDs, then at least do the following: + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities + // JDK7+ - http://xml.org/sax/features/external-general-entities + FEATURE = "http://xml.org/sax/features/external-general-entities"; + dbf.setFeature(FEATURE, false); + + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities + // JDK7+ - http://xml.org/sax/features/external-parameter-entities + FEATURE = "http://xml.org/sax/features/external-parameter-entities"; + dbf.setFeature(FEATURE, false); + + // Disable external DTDs as well + FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; + dbf.setFeature(FEATURE, false); + + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + + // And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then + // ensure the entity settings are disabled (as shown above) and beware that SSRF attacks + // (http://cwe.mitre.org/data/definitions/918.html) and denial + // of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk." + + // remaining parser logic + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(xmltext); + InputSource is = new InputSource(sr); + Document document = db.parse(is); + + Element root = document.getDocumentElement(); + NodeList nodelist1 = root.getElementsByTagName("Encrypt"); + result[0] = 0; + result[1] = nodelist1.item(0).getTextContent(); + return result; + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ParseXmlError); + } + } + + /** + * 生成xml消息 + * @param encrypt 加密后的消息密文 + * @param signature 安全签名 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @return 生成的xml字符串 + */ + public static String generate(String encrypt, String signature, String timestamp, String nonce) { + + String format = "\n" + "\n" + + "\n" + + "%3$s\n" + "\n" + ""; + return String.format(format, encrypt, signature, timestamp, nonce); + + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..1ce4ec6 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,13 @@ +sso: + iss: https://www.jiandaoyun.com/sso/custom/5e6456c078aba300063b2fff/iss + acs: https://www.jiandaoyun.com/sso/custom/5e6456c078aba300063b2fff/acs + secret: 11 +server: + port: 3015 +jdy: + appkey: BkIyzlh1onqnqu9cQ3ralDQBjECn97ex + appid: 61bae73aebe2f500080d567b + formid: 61bae7bc5aa1e60008dbd378 # + formid_account: 11 # +#测试环境 + diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..c57a06f --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,39 @@ + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + ${LOG_PATH}/${LOG_FILE}.log + + + + + ${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.log + + ${MAX_HISTORY} + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + true + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/example/sso/SsoApplicationTests.java b/src/test/java/com/example/sso/SsoApplicationTests.java new file mode 100644 index 0000000..1a3000a --- /dev/null +++ b/src/test/java/com/example/sso/SsoApplicationTests.java @@ -0,0 +1,27 @@ +package com.example.sso; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + + +@SpringBootTest +class SsoApplicationTests { + + @Test + void context() { + String json = "[{\"name\":\"1111\",\"code\":\"123\"},{\"name\":\"1111\",\"code\":\"123\"},{\"name\":\"1234\",\"code\":\"111\"}]"; + List list = JSONObject.parseArray(json); + HashSet hs = new HashSet(list); + String jsonSet = JSON.toJSONString(hs); + JSONArray newjsonarray= new JSONArray(Collections.singletonList(jsonSet)); + System.out.println(newjsonarray); + } + +}