合并不相关的历史
This commit is contained in:
parent
9171a382f8
commit
87b7512d44
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/mvnw text eol=lf
|
||||||
|
*.cmd text eol=crlf
|
||||||
|
|
||||||
50
.gitignore
vendored
50
.gitignore
vendored
@ -1,26 +1,34 @@
|
|||||||
# ---> Java
|
HELP.md
|
||||||
# Compiled class file
|
target/
|
||||||
*.class
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
# Log file
|
### STS ###
|
||||||
*.log
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
# BlueJ files
|
### IntelliJ IDEA ###
|
||||||
*.ctxt
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
### NetBeans ###
|
||||||
.mtj.tmp/
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
# Package Files #
|
### VS Code ###
|
||||||
*.jar
|
.vscode/
|
||||||
*.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*
|
|
||||||
replay_pid*
|
|
||||||
|
|
||||||
|
|||||||
3
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
3
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
distributionType=only-script
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
|
||||||
|
|
||||||
2
authorization/.gitattributes
vendored
Normal file
2
authorization/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/mvnw text eol=lf
|
||||||
|
*.cmd text eol=crlf
|
||||||
33
authorization/.gitignore
vendored
Normal file
33
authorization/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### 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/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
2
authorization/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
2
authorization/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
distributionType=only-script
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
|
||||||
295
authorization/mvnw
vendored
Normal file
295
authorization/mvnw
vendored
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
#!/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.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Apache Maven Wrapper startup batch script, version 3.3.3
|
||||||
|
#
|
||||||
|
# Optional ENV vars
|
||||||
|
# -----------------
|
||||||
|
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||||
|
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
set -euf
|
||||||
|
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||||
|
|
||||||
|
# OS specific support.
|
||||||
|
native_path() { printf %s\\n "$1"; }
|
||||||
|
case "$(uname)" in
|
||||||
|
CYGWIN* | MINGW*)
|
||||||
|
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||||
|
native_path() { cygpath --path --windows "$1"; }
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# set JAVACMD and JAVACCMD
|
||||||
|
set_java_home() {
|
||||||
|
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||||
|
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"
|
||||||
|
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||||
|
|
||||||
|
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||||
|
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||||
|
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v java
|
||||||
|
)" || :
|
||||||
|
JAVACCMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v javac
|
||||||
|
)" || :
|
||||||
|
|
||||||
|
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||||
|
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# hash string like Java String::hashCode
|
||||||
|
hash_string() {
|
||||||
|
str="${1:-}" h=0
|
||||||
|
while [ -n "$str" ]; do
|
||||||
|
char="${str%"${str#?}"}"
|
||||||
|
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||||
|
str="${str#?}"
|
||||||
|
done
|
||||||
|
printf %x\\n $h
|
||||||
|
}
|
||||||
|
|
||||||
|
verbose() { :; }
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf %s\\n "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trim() {
|
||||||
|
# MWRAPPER-139:
|
||||||
|
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||||
|
# Needed for removing poorly interpreted newline sequences when running in more
|
||||||
|
# exotic environments such as mingw bash on Windows.
|
||||||
|
printf "%s" "${1}" | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptDir="$(dirname "$0")"
|
||||||
|
scriptName="$(basename "$0")"
|
||||||
|
|
||||||
|
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
while IFS="=" read -r key value; do
|
||||||
|
case "${key-}" in
|
||||||
|
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||||
|
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||||
|
esac
|
||||||
|
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
|
||||||
|
case "${distributionUrl##*/}" in
|
||||||
|
maven-mvnd-*bin.*)
|
||||||
|
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||||
|
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||||
|
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||||
|
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||||
|
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||||
|
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||||
|
*)
|
||||||
|
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||||
|
distributionPlatform=linux-amd64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||||
|
;;
|
||||||
|
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||||
|
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||||
|
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||||
|
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||||
|
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||||
|
|
||||||
|
exec_maven() {
|
||||||
|
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||||
|
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -d "$MAVEN_HOME" ]; then
|
||||||
|
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
exec_maven "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${distributionUrl-}" in
|
||||||
|
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||||
|
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||||
|
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||||
|
trap clean HUP INT TERM EXIT
|
||||||
|
else
|
||||||
|
die "cannot create temp dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
verbose "Downloading from: $distributionUrl"
|
||||||
|
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
# select .zip or .tar.gz
|
||||||
|
if ! command -v unzip >/dev/null; then
|
||||||
|
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# verbose opt
|
||||||
|
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||||
|
|
||||||
|
# normalize http auth
|
||||||
|
case "${MVNW_PASSWORD:+has-password}" in
|
||||||
|
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||||
|
verbose "Found wget ... using wget"
|
||||||
|
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||||
|
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||||
|
verbose "Found curl ... using curl"
|
||||||
|
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||||
|
elif set_java_home; then
|
||||||
|
verbose "Falling back to use Java to download"
|
||||||
|
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||||
|
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
cat >"$javaSource" <<-END
|
||||||
|
public class Downloader extends java.net.Authenticator
|
||||||
|
{
|
||||||
|
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||||
|
{
|
||||||
|
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||||
|
}
|
||||||
|
public static void main( String[] args ) throws Exception
|
||||||
|
{
|
||||||
|
setDefault( new Downloader() );
|
||||||
|
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END
|
||||||
|
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||||
|
verbose " - Compiling Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||||
|
verbose " - Running Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
if [ -n "${distributionSha256Sum-}" ]; then
|
||||||
|
distributionSha256Result=false
|
||||||
|
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||||
|
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||||
|
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
elif command -v sha256sum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
elif command -v shasum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||||
|
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ $distributionSha256Result = false ]; then
|
||||||
|
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||||
|
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
if command -v unzip >/dev/null; then
|
||||||
|
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||||
|
else
|
||||||
|
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
actualDistributionDir=""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
|
||||||
|
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$distributionUrlNameMain"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
# enable globbing to iterate over items
|
||||||
|
set +f
|
||||||
|
for dir in "$TMP_DOWNLOAD_DIR"/*; do
|
||||||
|
if [ -d "$dir" ]; then
|
||||||
|
if [ -f "$dir/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$(basename "$dir")"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
set -f
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
verbose "Contents of $TMP_DOWNLOAD_DIR:"
|
||||||
|
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
|
||||||
|
die "Could not find Maven distribution directory in extracted archive"
|
||||||
|
fi
|
||||||
|
|
||||||
|
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
|
||||||
|
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||||
|
|
||||||
|
clean || :
|
||||||
|
exec_maven "$@"
|
||||||
189
authorization/mvnw.cmd
vendored
Normal file
189
authorization/mvnw.cmd
vendored
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
<# : batch portion
|
||||||
|
@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 Apache Maven Wrapper startup batch script, version 3.3.3
|
||||||
|
@REM
|
||||||
|
@REM Optional ENV vars
|
||||||
|
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||||
|
@SET __MVNW_CMD__=
|
||||||
|
@SET __MVNW_ERROR__=
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||||
|
@SET PSModulePath=
|
||||||
|
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||||
|
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||||
|
)
|
||||||
|
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=
|
||||||
|
@SET __MVNW_ARG0_NAME__=
|
||||||
|
@SET MVNW_USERNAME=
|
||||||
|
@SET MVNW_PASSWORD=
|
||||||
|
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
|
||||||
|
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||||
|
@GOTO :EOF
|
||||||
|
: end batch / begin powershell #>
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
if ($env:MVNW_VERBOSE -eq "true") {
|
||||||
|
$VerbosePreference = "Continue"
|
||||||
|
}
|
||||||
|
|
||||||
|
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||||
|
if (!$distributionUrl) {
|
||||||
|
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||||
|
"maven-mvnd-*" {
|
||||||
|
$USE_MVND = $true
|
||||||
|
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||||
|
$MVN_CMD = "mvnd.cmd"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
$USE_MVND = $false
|
||||||
|
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
if ($env:MVNW_REPOURL) {
|
||||||
|
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||||
|
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
|
||||||
|
}
|
||||||
|
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||||
|
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||||
|
|
||||||
|
$MAVEN_M2_PATH = "$HOME/.m2"
|
||||||
|
if ($env:MAVEN_USER_HOME) {
|
||||||
|
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
|
||||||
|
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_WRAPPER_DISTS = $null
|
||||||
|
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
|
||||||
|
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
|
||||||
|
} else {
|
||||||
|
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
|
||||||
|
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||||
|
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||||
|
|
||||||
|
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||||
|
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
exit $?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||||
|
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||||
|
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||||
|
trap {
|
||||||
|
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
Write-Verbose "Downloading from: $distributionUrl"
|
||||||
|
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
$webclient = New-Object System.Net.WebClient
|
||||||
|
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||||
|
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||||
|
}
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||||
|
if ($distributionSha256Sum) {
|
||||||
|
if ($USE_MVND) {
|
||||||
|
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||||
|
}
|
||||||
|
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||||
|
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||||
|
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
$actualDistributionDir = ""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
|
||||||
|
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
|
||||||
|
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
|
||||||
|
$actualDistributionDir = $distributionUrlNameMain
|
||||||
|
}
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
|
||||||
|
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
|
||||||
|
if (Test-Path -Path $testPath -PathType Leaf) {
|
||||||
|
$actualDistributionDir = $_.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Write-Error "Could not find Maven distribution directory in extracted archive"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||||
|
try {
|
||||||
|
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||||
|
} catch {
|
||||||
|
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||||
|
Write-Error "fail to move MAVEN_HOME"
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
102
authorization/pom.xml
Normal file
102
authorization/pom.xml
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>top.krcia</groupId>
|
||||||
|
<artifactId>elysia-server</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<groupId>top.krcia</groupId>
|
||||||
|
<artifactId>authorization</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>authorization</name>
|
||||||
|
<description>authorization</description>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<url/>
|
||||||
|
<licenses>
|
||||||
|
<license/>
|
||||||
|
</licenses>
|
||||||
|
<developers>
|
||||||
|
<developer/>
|
||||||
|
</developers>
|
||||||
|
<scm>
|
||||||
|
<connection/>
|
||||||
|
<developerConnection/>
|
||||||
|
<tag/>
|
||||||
|
<url/>
|
||||||
|
</scm>
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>${hutool.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>top.krcia</groupId>
|
||||||
|
<artifactId>utils</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>top.krcia</groupId>
|
||||||
|
<artifactId>result</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-core</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-extension</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@MapperScan(basePackages = {"top.krcia.elysiaserver.authorization.dao"})
|
||||||
|
public class AuthorizationApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AuthorizationApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.annotate;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Metadata {
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.aspect;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||||
|
import top.krcia.elysiaserver.authorization.annotate.Metadata;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class MetadataAspect implements ResponseBodyAdvice<Object> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Value("${elysia-authorization.metadata-save.agent-url}")
|
||||||
|
private String agentUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
// 对所有Controller返回值都生效
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
|
||||||
|
Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
||||||
|
org.springframework.http.server.ServerHttpRequest request,
|
||||||
|
org.springframework.http.server.ServerHttpResponse response) {
|
||||||
|
|
||||||
|
if (body == null) return null;
|
||||||
|
|
||||||
|
processObject(body);
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processObject(Object obj) {
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
// 如果是你的包装类 R
|
||||||
|
if (obj instanceof R<?>) {
|
||||||
|
R<?> r = (R<?>) obj;
|
||||||
|
if (r.getData() != null) processObject(r.getData());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof Collection<?>) {
|
||||||
|
for (Object item : (Collection<?>) obj) {
|
||||||
|
processObject(item);
|
||||||
|
}
|
||||||
|
} else if (!isPrimitiveOrWrapper(obj.getClass())) {
|
||||||
|
Field[] fields = obj.getClass().getDeclaredFields();
|
||||||
|
for (Field field : fields) {
|
||||||
|
if (field.isAnnotationPresent(Metadata.class)) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
try {
|
||||||
|
Object value = field.get(obj);
|
||||||
|
if (value instanceof String && value != null && !((String) value).startsWith(agentUrl)) {
|
||||||
|
field.set(obj, agentUrl + value);
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isPrimitiveOrWrapper(Class<?> clazz) {
|
||||||
|
return clazz.isPrimitive()
|
||||||
|
|| clazz.equals(String.class)
|
||||||
|
|| clazz.equals(Integer.class)
|
||||||
|
|| clazz.equals(Long.class)
|
||||||
|
|| clazz.equals(Boolean.class)
|
||||||
|
|| clazz.equals(Double.class)
|
||||||
|
|| clazz.equals(Float.class)
|
||||||
|
|| clazz.equals(Short.class)
|
||||||
|
|| clazz.equals(Byte.class)
|
||||||
|
|| clazz.equals(Character.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.config;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import top.krcia.elysiaserver.utils.Mail;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class MailConfig {
|
||||||
|
@Value("${elysia-authorization.mail.host}")
|
||||||
|
private String host;
|
||||||
|
@Value("${elysia-authorization.mail.port}")
|
||||||
|
private int port;
|
||||||
|
@Value("${elysia-authorization.mail.username}")
|
||||||
|
private String username;
|
||||||
|
@Value("${elysia-authorization.mail.auth-code}")
|
||||||
|
private String authCode;
|
||||||
|
@Value("${elysia-authorization.mail.enable-ssl}")
|
||||||
|
private boolean enableSsl;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
private void init() {
|
||||||
|
Mail.init(host,port,username,authCode,enableSsl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||||
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||||
|
import top.krcia.elysiaserver.utils.Kedis;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RedisTemplateConfig {
|
||||||
|
|
||||||
|
// 专用于 Redis 的 ObjectMapper
|
||||||
|
@Bean("redisObjectMapper")
|
||||||
|
public ObjectMapper redisObjectMapper() {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.registerModule(new JavaTimeModule());
|
||||||
|
// Redis 序列化可以保留时间戳(根据需求调整)
|
||||||
|
objectMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
return objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisTemplate<String, Object> redisTemplate(
|
||||||
|
RedisConnectionFactory redisConnectionFactory,
|
||||||
|
@Qualifier("redisObjectMapper") ObjectMapper redisObjectMapper) {
|
||||||
|
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||||
|
template.setConnectionFactory(redisConnectionFactory);
|
||||||
|
template.setKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setValueSerializer(redisSerializer());
|
||||||
|
template.setHashValueSerializer(redisSerializer());
|
||||||
|
template.afterPropertiesSet();
|
||||||
|
Kedis.init(template);
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||||
|
return builder.build(); // 自动应用 application.yml 的配置
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisSerializer<Object> redisSerializer() {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||||
|
// 必须设置,否则无法将JSON转化为对象,会转化成Map类型
|
||||||
|
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||||
|
|
||||||
|
// 自定义ObjectMapper的时间处理模块
|
||||||
|
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||||
|
|
||||||
|
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||||
|
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||||
|
|
||||||
|
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||||
|
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||||
|
|
||||||
|
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||||
|
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||||
|
|
||||||
|
objectMapper.registerModule(javaTimeModule);
|
||||||
|
|
||||||
|
// 禁用将日期序列化为时间戳的行为
|
||||||
|
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
|
||||||
|
//创建JSON序列化器
|
||||||
|
return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
import org.springframework.web.servlet.resource.ResourceResolver;
|
||||||
|
import org.springframework.web.servlet.resource.ResourceResolverChain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
//重写WebMvcConfigurer实现全局跨域配置
|
||||||
|
@Configuration
|
||||||
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Value("${elysia-authorization.enable-cors}")
|
||||||
|
private boolean enableCors;
|
||||||
|
@Value("${elysia-authorization.enable-api-doc}")
|
||||||
|
private boolean enableApiDoc;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
if (enableCors) {
|
||||||
|
registry.addMapping("/api/**")
|
||||||
|
.allowCredentials(true)
|
||||||
|
.allowedOriginPatterns("*") // 使用 allowedOriginPatterns 代替 allowedOrigins
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.exposedHeaders("*");
|
||||||
|
registry.addMapping("/oss/**")
|
||||||
|
.allowCredentials(true)
|
||||||
|
.allowedOriginPatterns("*") // 使用 allowedOriginPatterns 代替 allowedOrigins
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.exposedHeaders("*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
if (enableApiDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 屏蔽static下的某个文件夹,例如屏蔽static/private/
|
||||||
|
registry.addResourceHandler("/doc/**")
|
||||||
|
.addResourceLocations("classpath:/static/doc/")
|
||||||
|
.resourceChain(true)
|
||||||
|
.addResolver(new ResourceResolver() {
|
||||||
|
@Override
|
||||||
|
public Resource resolveResource(HttpServletRequest request, String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||||
|
return new ClassPathResource("static/prevent/denied.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolveUrlPath(String resourcePath, List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||||
|
return null; // 返回null表示拒绝访问
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.controller;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.servlet.HandlerMapping;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
@RequestMapping("/oss/")
|
||||||
|
@RestController
|
||||||
|
public class OSSController {
|
||||||
|
|
||||||
|
@Value("${elysia-authorization.metadata-save.path}")
|
||||||
|
private String metadataSavePath;
|
||||||
|
|
||||||
|
@GetMapping("/**")
|
||||||
|
public void oss(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
|
response.setContentType("image/png");
|
||||||
|
final String path =
|
||||||
|
request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
|
||||||
|
final String bestMatchingPattern =
|
||||||
|
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
|
||||||
|
File file = new File(Path.of(metadataSavePath, new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, path)).toString());
|
||||||
|
try (FileInputStream in = new FileInputStream(file);
|
||||||
|
ServletOutputStream out = response.getOutputStream()) {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int len;
|
||||||
|
while ((len = in.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.controller.auth;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import top.krcia.elysiaserver.authorization.controller.auth.base.BaseController;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.AuthDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.ForgotPasswordDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.ModifyPasswordDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.RegisterDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.vo.SysUserVO;
|
||||||
|
import top.krcia.elysiaserver.authorization.service.AuthService;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
import top.krcia.elysiaserver.result.RS;
|
||||||
|
import top.krcia.elysiaserver.utils.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鉴权
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class AuthController extends BaseController {
|
||||||
|
@Resource
|
||||||
|
private AuthService authService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取令牌
|
||||||
|
*
|
||||||
|
* @param captcha 验证码值
|
||||||
|
* @param captchaCode 验证码代码
|
||||||
|
* @param auth 登录信息
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/login")
|
||||||
|
public R<String> login(@RequestHeader("captcha") String captcha, @RequestHeader("captcha-code") String captchaCode, @RequestBody AuthDto auth) {
|
||||||
|
//判定是否使用验证码
|
||||||
|
if (profile != Profile.DEVELOPMENT) {
|
||||||
|
String redisCaptcha = Kedis.get("CAPTCHA:".concat(captchaCode), String.class);
|
||||||
|
Kedis.delete("CAPTCHA:".concat(captchaCode).concat(captchaCode));
|
||||||
|
if (redisCaptcha == null) {
|
||||||
|
return R.print(RS.CAPTCHA_NOT_EXIST);
|
||||||
|
}
|
||||||
|
if (!redisCaptcha.equalsIgnoreCase(captcha)) {
|
||||||
|
return R.print(RS.CAPTCHA_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authService.login(auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忘记密码
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/forgot-password-mail")
|
||||||
|
public R forgotPassword(ForgotPasswordDto forgotPasswordDto) {
|
||||||
|
return authService.forgotPassword(forgotPasswordDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码
|
||||||
|
*
|
||||||
|
* @param captcha 验证码值
|
||||||
|
* @param captchaCode 验证码代码
|
||||||
|
* @param modifyPasswordDto
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PutMapping("modify-password")
|
||||||
|
public R modifyPassword(@RequestHeader("Authorization")String authorization,@RequestHeader("captcha") String captcha, @RequestHeader("captcha-code") String captchaCode, @RequestBody ModifyPasswordDto modifyPasswordDto) {
|
||||||
|
//判定是否使用验证码
|
||||||
|
if (profile != Profile.DEVELOPMENT) {
|
||||||
|
String redisCaptcha = Kedis.get("CAPTCHA:".concat(captchaCode), String.class);
|
||||||
|
Kedis.delete("CAPTCHA:".concat(captchaCode).concat(captchaCode));
|
||||||
|
if (redisCaptcha == null) {
|
||||||
|
return R.print(RS.CAPTCHA_NOT_EXIST);
|
||||||
|
}
|
||||||
|
if (!redisCaptcha.equalsIgnoreCase(captcha)) {
|
||||||
|
return R.print(RS.CAPTCHA_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authService.modifyPassword(authorization,modifyPasswordDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册新用户
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/register")
|
||||||
|
public R Register(RegisterDto registerDto) {
|
||||||
|
return authService.register(registerDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/profile")
|
||||||
|
public R<SysUserVO> profile(@RequestHeader("Authorization") String authorization) {
|
||||||
|
return authService.profile(authorization);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.controller.auth;
|
||||||
|
|
||||||
|
import cn.hutool.captcha.CaptchaUtil;
|
||||||
|
import cn.hutool.captcha.LineCaptcha;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import top.krcia.elysiaserver.authorization.controller.auth.base.BaseController;
|
||||||
|
import top.krcia.elysiaserver.authorization.service.AuthService;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
import top.krcia.elysiaserver.utils.Kedis;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class CaptchaController extends BaseController {
|
||||||
|
@Resource
|
||||||
|
private AuthService authService;
|
||||||
|
/**
|
||||||
|
* 获取图形验证码
|
||||||
|
* @param response
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@GetMapping("/captcha")
|
||||||
|
public void getCaptcha(HttpServletResponse response) throws IOException {
|
||||||
|
// 创建验证码:宽100,高40,字符4位,干扰线20条
|
||||||
|
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(130, 40, 4, 40);
|
||||||
|
// 生成唯一key
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
String code = lineCaptcha.getCode();
|
||||||
|
|
||||||
|
// 存入Redis,过期时间5分钟
|
||||||
|
Kedis.set("CAPTCHA:" + uuid, code, 5);
|
||||||
|
// 响应头中返回uuid,前端携带uuid和用户输入的验证码验证
|
||||||
|
response.setHeader("Captcha-UUID", uuid);
|
||||||
|
|
||||||
|
// 输出图片
|
||||||
|
response.setContentType("image/png");
|
||||||
|
lineCaptcha.write(response.getOutputStream());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 发送注册邮箱验证码
|
||||||
|
*
|
||||||
|
* @param mail 邮箱|1503175889@qq.com
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/register-mail")
|
||||||
|
public R sendRegisterMail(String mail) {
|
||||||
|
return authService.sendRegisterMail(mail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送忘记密码邮箱验证码
|
||||||
|
*
|
||||||
|
* @param mail 邮箱|1503175889@qq.com
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/forgot-password-mail")
|
||||||
|
public R sendForgotPasswordMail(String mail) {
|
||||||
|
return authService.sendForgotPasswordMail(mail);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.controller.auth.base;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import top.krcia.elysiaserver.utils.Profile;
|
||||||
|
|
||||||
|
@RequestMapping("/authorization")
|
||||||
|
public class BaseController {
|
||||||
|
@Value("${spring.profiles.active}")
|
||||||
|
private String active;
|
||||||
|
protected Profile profile;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
private void init() {
|
||||||
|
profile = Profile.get(active);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Update;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.pojo.RobotFollow;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (RobotFollow)表数据库访问层
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-12 13:58:35
|
||||||
|
*/
|
||||||
|
public interface RobotFollowDao extends BaseMapper<RobotFollow> {
|
||||||
|
@Update("""
|
||||||
|
CREATE TABLE robot_follow_${userId} LIKE robot_follow
|
||||||
|
""")
|
||||||
|
int createFollowTable(Long userId);
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.pojo.SysUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SysUser)表数据库访问层
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-09 23:01:29
|
||||||
|
*/
|
||||||
|
public interface SysUserDao extends BaseMapper<SysUser> {
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM sys_user WHERE user_name=#{username}
|
||||||
|
""")
|
||||||
|
SysUser getUserByUsername(@Param("username") String username);
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM sys_user WHERE email=#{email}
|
||||||
|
""")
|
||||||
|
SysUser getUserByEmail(@Param("email") String email);
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM sys_user WHERE email=#{email} OR user_name=#{username}
|
||||||
|
""")
|
||||||
|
SysUser findUser(@Param("email") String email,@Param("username") String username);
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM sys_user WHERE email=#{account} OR user_name=#{account}
|
||||||
|
""")
|
||||||
|
SysUser findUserByAccount(@Param("account") String account);
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.entity.dto;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class AuthDto {
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
* @mock admin
|
||||||
|
*/
|
||||||
|
|
||||||
|
private String user;
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
* @mock vir_sh@2025
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ForgotPasswordDto {
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
/**
|
||||||
|
* 邮箱验证码
|
||||||
|
*/
|
||||||
|
private String emailCode;
|
||||||
|
/**
|
||||||
|
* 新密码
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.entity.dto;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ModifyPasswordDto {
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
* @mock vir_sh@2025
|
||||||
|
*/
|
||||||
|
private String oldPassword;
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
* @mock vir_sh@2025
|
||||||
|
*/
|
||||||
|
private String newPassword;
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.entity.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SysUser)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-09 23:01:29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RegisterDto {
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
private MultipartFile avatar;
|
||||||
|
/**
|
||||||
|
* 姓名
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String userName;
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
/**
|
||||||
|
* DeepseekApi
|
||||||
|
*/
|
||||||
|
private String deepseekKey;
|
||||||
|
/**
|
||||||
|
* 邮箱验证码
|
||||||
|
*/
|
||||||
|
private String emailCode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.entity.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (RobotFollow)表实体类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-12 13:58:35
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class RobotFollow extends Model<RobotFollow> {
|
||||||
|
//机器人列表
|
||||||
|
private Long robotId;
|
||||||
|
|
||||||
|
|
||||||
|
public Long getRobotId() {
|
||||||
|
return robotId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRobotId(Long robotId) {
|
||||||
|
this.robotId = robotId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RobotFollow(Long robotId) {
|
||||||
|
this.robotId = robotId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.entity.pojo;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SysUser)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-09 23:01:29
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@Data
|
||||||
|
public class SysUser extends Model<SysUser> {
|
||||||
|
//用户ID
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long userId;
|
||||||
|
//头像
|
||||||
|
private String avatar;
|
||||||
|
//姓名
|
||||||
|
private String name;
|
||||||
|
//昵称
|
||||||
|
private String nickname;
|
||||||
|
//用户名
|
||||||
|
private String userName;
|
||||||
|
//密码
|
||||||
|
private String password;
|
||||||
|
//状态:0启用,1禁用,-1删除
|
||||||
|
private Integer status;
|
||||||
|
//状态时间
|
||||||
|
private Date statusTime;
|
||||||
|
//状态时间
|
||||||
|
private String email;
|
||||||
|
//创建时间
|
||||||
|
private Date createTime;
|
||||||
|
//DeepseekApi
|
||||||
|
private String deepseekKey;
|
||||||
|
//DeepseekApi
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主键值
|
||||||
|
*
|
||||||
|
* @return 主键值
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Serializable pkVal() {
|
||||||
|
return this.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser(String avatar, String name, String nickname, String userName, String password,String email,String deepseekKey) {
|
||||||
|
this.avatar = avatar;
|
||||||
|
this.name = name;
|
||||||
|
this.nickname = nickname;
|
||||||
|
this.userName = userName;
|
||||||
|
this.password = password;
|
||||||
|
this.deepseekKey = deepseekKey;
|
||||||
|
this.status = 0;
|
||||||
|
this.statusTime = new Date();
|
||||||
|
this.email = email;
|
||||||
|
this.createTime = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.entity.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Balance {
|
||||||
|
/**
|
||||||
|
* 是否可用
|
||||||
|
*/
|
||||||
|
private boolean isAvailable;
|
||||||
|
/**
|
||||||
|
* 总余额(人名币)
|
||||||
|
*/
|
||||||
|
private String balance;
|
||||||
|
|
||||||
|
public Balance(boolean isAvailable, String balance) {
|
||||||
|
this.isAvailable = isAvailable;
|
||||||
|
this.balance = balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Balance() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.entity.vo;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import top.krcia.elysiaserver.authorization.annotate.Metadata;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SysUser)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-09 23:01:29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SysUserVO{
|
||||||
|
/**
|
||||||
|
* 姓名
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
@Metadata
|
||||||
|
private String avatar;
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String userName;
|
||||||
|
/**
|
||||||
|
* 状态:0启用,1禁用,-1删除
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
/**
|
||||||
|
* 状态时间
|
||||||
|
*/
|
||||||
|
private Date statusTime;
|
||||||
|
/**
|
||||||
|
* 邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.service;
|
||||||
|
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.AuthDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.ForgotPasswordDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.ModifyPasswordDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.RegisterDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.vo.SysUserVO;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
|
||||||
|
public interface AuthService {
|
||||||
|
R<String> login(AuthDto auth);
|
||||||
|
|
||||||
|
R sendRegisterMail(String mail);
|
||||||
|
|
||||||
|
R sendForgotPasswordMail(String mail);
|
||||||
|
|
||||||
|
R forgotPassword(ForgotPasswordDto forgotPasswordDto);
|
||||||
|
R modifyPassword(String authorization,ModifyPasswordDto modifyPasswordDto);
|
||||||
|
|
||||||
|
R register(RegisterDto registerDto);
|
||||||
|
|
||||||
|
R<SysUserVO> profile(String authorization);
|
||||||
|
}
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
package top.krcia.elysiaserver.authorization.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import top.krcia.elysiaserver.authorization.dao.RobotFollowDao;
|
||||||
|
import top.krcia.elysiaserver.authorization.dao.SysUserDao;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.AuthDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.ForgotPasswordDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.ModifyPasswordDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.dto.RegisterDto;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.pojo.SysUser;
|
||||||
|
import top.krcia.elysiaserver.authorization.entity.vo.SysUserVO;
|
||||||
|
import top.krcia.elysiaserver.authorization.service.AuthService;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
import top.krcia.elysiaserver.result.RS;
|
||||||
|
import top.krcia.elysiaserver.utils.*;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class AuthServiceImpl implements AuthService {
|
||||||
|
@Resource
|
||||||
|
private SysUserDao sysUserDao;
|
||||||
|
@Resource
|
||||||
|
private RobotFollowDao robotFollowDao;
|
||||||
|
|
||||||
|
@Value("${elysia-authorization.metadata-save.path}")
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<String> login(AuthDto auth) {
|
||||||
|
SysUser user = sysUserDao.findUserByAccount(auth.getUser());
|
||||||
|
if (user == null) {
|
||||||
|
return R.print(RS.USER_NOT_FIND);
|
||||||
|
}
|
||||||
|
if (!PasswordUtil.checkPassword(auth.getPassword(), user.getPassword())) {
|
||||||
|
return R.print(RS.PASSWORD_ERROR);
|
||||||
|
}
|
||||||
|
String token = JWTGenerator.generateJWTToken(user.getUserId());
|
||||||
|
UpdateWrapper wrapper = new UpdateWrapper();
|
||||||
|
wrapper.set("token", token);
|
||||||
|
wrapper.eq("user_id", user.getUserId());
|
||||||
|
sysUserDao.update(wrapper);
|
||||||
|
return R.success(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R sendRegisterMail(String mail) {
|
||||||
|
if (Kedis.hasKey(C.REDIS_MAIL_CODE.concat(":").concat(mail))) {
|
||||||
|
return R.printFormat(RS.ERROR, "请在5分钟后再次获取");
|
||||||
|
}
|
||||||
|
SysUser user = sysUserDao.getUserByEmail(mail);
|
||||||
|
if (user != null) {
|
||||||
|
return R.print(RS.EMAIL_REGISTER, maskString(user.getUserName()));
|
||||||
|
}
|
||||||
|
String code = VerifyCode.generate(5);
|
||||||
|
Kedis.set(C.REDIS_MAIL_CODE.concat(":").concat(mail), code, 5);
|
||||||
|
Mail.sendHtmlMail(mail, C.MAIL_PROJECT_NAME, C.MAIL_TITLE, "register.html", code);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R sendForgotPasswordMail(String mail) {
|
||||||
|
if (Kedis.hasKey(C.REDIS_MAIL_CODE.concat(":").concat(mail))) {
|
||||||
|
return R.printFormat(RS.ERROR, "请在5分钟后再次获取");
|
||||||
|
}
|
||||||
|
SysUser user = sysUserDao.getUserByEmail(mail);
|
||||||
|
if (user == null) {
|
||||||
|
return R.print(RS.USER_NOT_FIND);
|
||||||
|
}
|
||||||
|
String code = VerifyCode.generate(5);
|
||||||
|
Kedis.set(C.REDIS_MAIL_CODE.concat(":").concat(mail), code, 5);
|
||||||
|
Mail.sendHtmlMail(mail, C.MAIL_PROJECT_NAME, C.MAIL_TITLE, "forgot-password.html", code);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R forgotPassword(ForgotPasswordDto forgotPasswordDto) {
|
||||||
|
String value = Kedis.get(C.REDIS_MAIL_CODE.concat(":").concat(forgotPasswordDto.getEmail()), String.class);
|
||||||
|
if (!StringUtils.hasText(value) || !forgotPasswordDto.getEmailCode().toUpperCase().equals(value.toUpperCase())) {
|
||||||
|
return R.print(RS.CAPTCHA_ERROR);
|
||||||
|
}
|
||||||
|
SysUser user = sysUserDao.getUserByEmail(forgotPasswordDto.getEmail());
|
||||||
|
if (user == null) {
|
||||||
|
|
||||||
|
return R.print(RS.USER_NOT_FIND);
|
||||||
|
}
|
||||||
|
user.setPassword(PasswordUtil.hashPassword(forgotPasswordDto.getPassword()));
|
||||||
|
int i = sysUserDao.updateById(user);
|
||||||
|
return i > 0 ? R.success() : R.print(RS.MODIFY_FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R modifyPassword(String authorization, ModifyPasswordDto modifyPasswordDto) {
|
||||||
|
Long userId = JWTGenerator.getUserId(authorization);
|
||||||
|
SysUser sysUser = new SysUser(userId);
|
||||||
|
sysUser = sysUserDao.selectById(sysUser);
|
||||||
|
if (!PasswordUtil.checkPassword(modifyPasswordDto.getOldPassword(), sysUser.getPassword())) {
|
||||||
|
return R.print(RS.OLD_PASSWORD_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PasswordUtil.checkPassword(modifyPasswordDto.getNewPassword(), sysUser.getPassword())) {
|
||||||
|
return R.print(RS.OLD_PASSWORD_EQ_NEW_PASSWORD);
|
||||||
|
}
|
||||||
|
sysUser.setPassword(PasswordUtil.hashPassword(modifyPasswordDto.getNewPassword()));
|
||||||
|
int i = sysUserDao.updateById(sysUser);
|
||||||
|
return i > 0 ? R.success() : R.print(RS.MODIFY_FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R register(RegisterDto registerDto) {
|
||||||
|
String value = Kedis.get(C.REDIS_MAIL_CODE.concat(":").concat(registerDto.getEmail()), String.class);
|
||||||
|
if (!StringUtils.hasText(value) || !registerDto.getEmailCode().toUpperCase().equals(value.toUpperCase())) {
|
||||||
|
return R.print(RS.CAPTCHA_ERROR);
|
||||||
|
}
|
||||||
|
SysUser user = sysUserDao.findUser(registerDto.getEmail(), registerDto.getUserName());
|
||||||
|
if (user != null) {
|
||||||
|
|
||||||
|
return R.print(RS.REGISTER_INFO_EXIST);
|
||||||
|
}
|
||||||
|
String aId = U.get();
|
||||||
|
String data = FileUtil.upLoad(registerDto.getAvatar(), aId, Path.of(path, "avatar").toString());
|
||||||
|
if (data.equals("ERROR")) {
|
||||||
|
return R.print(RS.FILE_UPLOAD_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
String password = PasswordUtil.hashPassword(registerDto.getPassword());
|
||||||
|
SysUser sysUser = new SysUser("/avatar/" + data, registerDto.getName(), registerDto.getNickname(), registerDto.getUserName(), password, registerDto.getEmail(), registerDto.getDeepseekKey());
|
||||||
|
int i = sysUserDao.insert(sysUser);
|
||||||
|
if (i > 0) {
|
||||||
|
robotFollowDao.createFollowTable(sysUser.getUserId());
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
return R.printFormat(RS.SERVER_ERROR, "数据新增失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<SysUserVO> profile(String authorization) {
|
||||||
|
Long userId = JWTGenerator.getUserId(authorization);
|
||||||
|
if (userId==null){
|
||||||
|
return R.print(RS.USER_NOT_LOGIN);
|
||||||
|
}
|
||||||
|
SysUser user = new SysUser(userId);
|
||||||
|
user = sysUserDao.selectById(user);
|
||||||
|
if (user != null & authorization.equals(user.getToken())) {
|
||||||
|
SysUserVO sysUserVO = new SysUserVO();
|
||||||
|
BeanUtils.copyProperties(user, sysUserVO);
|
||||||
|
return R.success(sysUserVO);
|
||||||
|
}
|
||||||
|
return R.print(RS.USER_NOT_LOGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String maskString(String input) {
|
||||||
|
if (input == null || input.length() <= 6) {
|
||||||
|
// 如果字符串长度小于等于11,直接返回原字符串或全部替换
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalLength = input.length();
|
||||||
|
String firstPart = input.substring(0, 3);
|
||||||
|
String lastPart = input.substring(totalLength - 2);
|
||||||
|
int middleLength = totalLength - 5;
|
||||||
|
|
||||||
|
// 生成中间部分的星号
|
||||||
|
String middleStars = "*".repeat(middleLength);
|
||||||
|
|
||||||
|
return firstPart + middleStars + lastPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
25
authorization/src/main/resources/application-development.yml
Normal file
25
authorization/src/main/resources/application-development.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
spring:
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: krcia.top
|
||||||
|
port: 6379
|
||||||
|
password: xiong990416.
|
||||||
|
database: 3
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://krcia.top:3306/elysia-db?serverTimezone=Asia/Shanghai
|
||||||
|
username: root
|
||||||
|
password: xiong990416.
|
||||||
|
elysia-authorization:
|
||||||
|
version: v0.1-0909-dev
|
||||||
|
enable-cors: true
|
||||||
|
enable-api-doc: true
|
||||||
|
mail:
|
||||||
|
host: smtp.qq.com
|
||||||
|
port: 465
|
||||||
|
username: krcia@qq.com
|
||||||
|
auth-code: ghylxjvfnnjqdfhe
|
||||||
|
enable-ssl: true
|
||||||
|
metadata-save:
|
||||||
|
path: ./metadata/
|
||||||
|
agent-url: http://192.168.1.2:4562/oss
|
||||||
11
authorization/src/main/resources/application.yml
Normal file
11
authorization/src/main/resources/application.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
server:
|
||||||
|
port: 4562
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: elysia-authorization
|
||||||
|
profiles:
|
||||||
|
active: development
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-request-size: 300MB
|
||||||
|
max-file-size: 180MB
|
||||||
20
authorization/src/main/resources/smart-doc.json
Normal file
20
authorization/src/main/resources/smart-doc.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"outPath": "./src/main/resources/static/doc",
|
||||||
|
"serverUrl": "/api",
|
||||||
|
"serverEnv": "http://{{server}}",
|
||||||
|
"allInOne": true,
|
||||||
|
"coverOld": true,
|
||||||
|
"createDebugPage": true,
|
||||||
|
"showAuthor": "true",
|
||||||
|
"allInOneDocFileName": "index.html",
|
||||||
|
"requestHeaders": [
|
||||||
|
{
|
||||||
|
"name": "Authorization",
|
||||||
|
"type": "string",
|
||||||
|
"value": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJpZCI6MTAwMDAwfQ.yBaS2nSUJoD2kbhj3lXnHvkmc-qwJF_5djhQf_2A069j9HzYVmUObXcDwaAhHQf-",
|
||||||
|
"desc": "鉴权信息",
|
||||||
|
"required": true,
|
||||||
|
"excludePathPatterns": "/authorization/login,/authorization/captcha,/authorization/register"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
authorization/src/main/resources/static/doc/AllInOne.css
Normal file
13
authorization/src/main/resources/static/doc/AllInOne.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}script{display:none!important}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}a{background:transparent}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}html,body{font-size:100%}body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}a:hover{cursor:pointer}img,object,embed{max-width:100%;height:auto}object,embed{height:100%}img{-ms-interpolation-mode:bicubic}.left{float:left!important}.right{float:right!important}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}.text-justify{text-align:justify!important}.hide{display:none}img,object,svg{display:inline-block;vertical-align:middle}textarea{height:auto;min-height:50px}select{width:100%}.center{margin-left:auto;margin-right:auto}.spread{width:100%}p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6}.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}a{color:#364149;text-decoration:underline;line-height:inherit}a:hover,a:focus{color:#364149}a img{border:0}p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}p aside{font-size:.875em;line-height:1.35;font-style:italic}h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}h1{font-size:2.125em}h2{font-size:1.6875em}h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}h4,h5{font-size:1.125em}h6{font-size:1em}hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}em,i{font-style:italic;line-height:inherit}strong,b{font-weight:bold;line-height:inherit}small{font-size:60%;line-height:inherit}code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
|
||||||
|
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}ul,ol{margin-left:1.5em}ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}ul.square{list-style-type:square}ul.circle{list-style-type:circle}ul.disc{list-style-type:disc}ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}dl dt{margin-bottom:.3125em;font-weight:bold}dl dd{margin-bottom:1.25em}abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}abbr{text-transform:none}blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}blockquote cite:before{content:"\2014 \0020"}blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}h1{font-size:2.75em}h2{font-size:2.3125em}h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}h4{font-size:1.4375em}}table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}table thead,table tfoot{background:#f7f8f7;font-weight:bold}table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}.clearfix:after,.float-group:after{clear:both}*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word}*:not(pre)>code.nobreak{word-wrap:normal}*:not(pre)>code.nowrap{white-space:nowrap}pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}em em{font-style:normal}strong strong{font-weight:400}.keyseq{color:rgba(51,51,51,.8)}kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}.keyseq kbd:first-child{margin-left:0}.keyseq kbd:last-child{margin-right:0}.menuseq,.menuref{color:#000}.menuseq b:not(.caret),.menuref{font-weight:inherit}.menuseq{word-spacing:-.02em}.menuseq b.caret{font-size:1.25em;line-height:.8}.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}b.button:before{content:"[";padding:0 3px 0 2px}b.button:after{content:"]";padding:0 2px 0 3px}p a>code:hover{color:rgba(0,0,0,.9)}#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}#header:after,#content:after,#footnotes:after,#footer:after{clear:both}#content{margin-top:1.25em}#content:before{content:none}#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8}#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px}#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}#header .details span:first-child{margin-left:-.125em}#header .details span.email a{color:rgba(0,0,0,.85)}#header .details br{display:none}
|
||||||
|
#header .details br+span:before{content:"\00a0\2013\00a0"}#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}#header .details br+span#revremark:before{content:"\00a0|\00a0"}#header #revnumber{text-transform:capitalize}#header #revnumber:after{content:"\00a0"}#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}#toc{border-bottom:1px solid #efefed;padding-bottom:.5em}#toc>ul{margin-left:.125em;padding-left:1.25em}#toc ul.sectlevel0>li>a{font-style:italic}#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}#toc li{line-height:1.3334;margin-top:.3334em;padding-bottom:4px;padding-top:4px}#toc a{text-decoration:none}#toc a:active{text-decoration:underline}#toctitle{color:#7a2518;font-size:1.2em}@media only screen and (min-width:768px){#toctitle{font-size:1.375em}body.toc2{padding-left:15em;padding-right:0}#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;#padding:1.25em 1em;height:100%;overflow:auto}#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}#toc.toc2>ul{font-size:.9em;margin-bottom:0}#toc.toc2 ul ul{margin-left:0;padding-left:1em}#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}body.toc2.toc-right{padding-left:0;padding-right:15em}body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}#toc.toc2{width:20em}#toc.toc2 #toctitle{border-bottom:1px solid rgba(0,0,0,.07);padding-top:20px;padding-bottom:15px}#toc.toc2 #toctitle span{padding-left:1.25em;padding-bottom:15px}#toc.toc2>ul{font-size:.95em}#toc.toc2 ul ul{padding-left:1.25em}body.toc2.toc-right{padding-left:0;padding-right:20em}}#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}#content #toc>:first-child{margin-top:0}#content #toc>:last-child{margin-bottom:0}#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}#footer-text{color:rgba(255,255,255,.8);line-height:1.44}.sect1{padding-bottom:.625em}@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}}.sect1+.sect1{border-top:1px solid #efefed}#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0}.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)}table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit}.admonitionblock>table{border-collapse:separate;border:0;background:0;width:100%}.admonitionblock>table td.icon{text-align:center;width:80px}
|
||||||
|
.admonitionblock>table td.icon img{max-width:initial}.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)}.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}.exampleblock>.content>:first-child{margin-top:0}.exampleblock>.content>:last-child{margin-bottom:0}.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}.sidebarblock>:first-child{margin-top:0}.sidebarblock>:last-child{margin-bottom:0}.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em}.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal}@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}.listingblock pre.highlightjs{padding:0}.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}.listingblock pre.prettyprint{border-width:0}.listingblock>.content{position:relative}.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}.listingblock:hover code[data-lang]:before{display:block}.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:0}table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45}table.pyhltable td.code{padding-left:.75em;padding-right:0}pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}pre.pygments .lineno{display:inline-block;margin-right:.25em}table.pyhltable .linenodiv{background:none!important;padding-right:0!important}.quoteblock{margin:0 1em 1.25em 1.5em;display:table}.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}.quoteblock blockquote{margin:0;padding:0;border:0}.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right}.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}.quoteblock .quoteblock blockquote:before{display:none}.verseblock{margin:0 1em 1.25em 1em}.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
|
||||||
|
.verseblock pre strong{font-weight:400}.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}.quoteblock .attribution br,.verseblock .attribution br{display:none}.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}.quoteblock.abstract{margin:0 0 1.25em 0;display:block}.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}table.tableblock{max-width:100%;border-collapse:separate}table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0}table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0}table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0}table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0}table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px 0}table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0 0}table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0}table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0}table.frame-all{border-width:1px}table.frame-sides{border-width:0 1px}table.frame-topbot{border-width:1px 0}th.halign-left,td.halign-left{text-align:left}th.halign-right,td.halign-right{text-align:right}th.halign-center,td.halign-center{text-align:center}th.valign-top,td.valign-top{vertical-align:top}th.valign-bottom,td.valign-bottom{vertical-align:bottom}th.valign-middle,td.valign-middle{vertical-align:middle}table thead th,table tfoot th{font-weight:bold}tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}p.tableblock>code:only-child{background:0;padding:0}p.tableblock{font-size:1em}td>div.verse{white-space:pre}ol{margin-left:1.75em}ul li ol{margin-left:1.5em}dl dd{margin-left:1.125em}dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}ul.unstyled,ol.unstyled{margin-left:0}ul.checklist{margin-left:.625em}ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em}ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block}ul.inline>li>*{display:block}.unstyled dl dt{font-weight:400;font-style:normal}ol.arabic{list-style-type:decimal}ol.decimal{list-style-type:decimal-leading-zero}ol.loweralpha{list-style-type:lower-alpha}ol.upperalpha{list-style-type:upper-alpha}ol.lowerroman{list-style-type:lower-roman}ol.upperroman{list-style-type:upper-roman}ol.lowergreek{list-style-type:lower-greek}.hdlist>table,.colist>table{border:0;background:0}.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:0}td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}td.hdlist1{font-weight:bold;padding-bottom:1.25em}.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}.colist>table tr>td:first-of-type{padding:.4em .75em 0 .75em;line-height:1;vertical-align:top}.colist>table tr>td:first-of-type img{max-width:initial}.colist>table tr>td:last-of-type{padding:.25em 0}.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}.imageblock.left,.imageblock[style*="float:left"]{margin:.25em .625em 1.25em 0}.imageblock.right,.imageblock[style*="float:right"]{margin:.25em 0 1.25em .625em}.imageblock>.title{margin-bottom:0}.imageblock.thumb,.imageblock.th{border-width:6px}.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}.image.left{margin-right:.625em}.image.right{margin-left:.625em}a.image{text-decoration:none;display:inline-block}a.image object{pointer-events:none}sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}sup.footnote a,sup.footnoteref a{text-decoration:none}
|
||||||
|
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0}#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;text-indent:-1.05em;margin-bottom:.2em}#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none}#footnotes .footnote:last-of-type{margin-bottom:0}#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}.gist .file-data>table td.line-data{width:99%}div.unbreakable{page-break-inside:avoid}.big{font-size:larger}.small{font-size:smaller}.underline{text-decoration:underline}.overline{text-decoration:overline}.line-through{text-decoration:line-through}.aqua{color:#00bfbf}.aqua-background{background-color:#00fafa}.black{color:#000}.black-background{background-color:#000}.blue{color:#0000bf}.blue-background{background-color:#0000fa}.fuchsia{color:#bf00bf}.fuchsia-background{background-color:#fa00fa}.gray{color:#606060}.gray-background{background-color:#7d7d7d}.green{color:#006000}.green-background{background-color:#007d00}.lime{color:#00bf00}.lime-background{background-color:#00fa00}.maroon{color:#600000}.maroon-background{background-color:#7d0000}.navy{color:#000060}.navy-background{background-color:#00007d}.olive{color:#606000}.olive-background{background-color:#7d7d00}.purple{color:#600060}.purple-background{background-color:#7d007d}.red{color:#bf0000}.red-background{background-color:#fa0000}.silver{color:#909090}.silver-background{background-color:#bcbcbc}.teal{color:#006060}.teal-background{background-color:#007d7d}.white{color:#bfbfbf}.white-background{background-color:#fafafa}.yellow{color:#bfbf00}.yellow-background{background-color:#fafa00}span.icon>.fa{cursor:default}a span.icon>.fa{cursor:inherit}.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c}.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900}.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}.conum[data-value] *{color:#fff!important}.conum[data-value]+b{display:none}.conum[data-value]:after{content:attr(data-value)}pre .conum[data-value]{position:relative;top:-.125em}b.conum *{color:inherit!important}.conum:not([data-value]):empty{display:none}dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}h1,h2,p,td.content,span.alt{letter-spacing:-.01em}p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}p,blockquote,dt,td.content,span.alt{font-size:1.0625rem}p{margin-bottom:1.25rem}.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}.print-only{display:none!important}@media print{@page{margin:1.25cm .75cm}*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}a{color:inherit!important;text-decoration:underline!important}a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}abbr[title]:after{content:" (" attr(title) ")"}pre,blockquote,tr,img,object,svg{page-break-inside:avoid}thead{display:table-header-group}svg{max-width:100%}p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}#toc,.sidebarblock,.exampleblock>.content{background:none!important}#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important}.sect1{padding-bottom:0!important}.sect1+.sect1{border:0!important}#header>h1:first-child{margin-top:1.25rem}
|
||||||
|
body.book #header{text-align:center}body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0}body.book #header .details{border:0!important;display:block;padding:0!important}body.book #header .details span:first-child{margin-left:0!important}body.book #header .details br{display:block}body.book #header .details br+span:before{content:none!important}body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}.listingblock code[data-lang]:before{display:block}#footer{background:none!important;padding:0 .9375em}#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em}.hide-on-print{display:none!important}.print-only{display:block!important}.hide-for-print{display:none!important}.show-for-print{display:inherit!important}}#content .page-footer{height:100px;border-top:1px solid #ccc;overflow:hidden;padding:10px 0;font-size:14px;color:gray}#content .footer-modification{float:right}#content .footer-modification a{text-decoration:none}.sectlevel2{display:none}.submenu{background:#e7e7e6}.submenu li{border:0}.submenu a{color:#555}.checkbox{position:relative;height:30px}.checkbox input[type='checkbox']{left:0;top:0;width:20px;height:20px;opacity:0;border-radius:4px}.checkbox label{position:absolute;left:30px;top:0;height:20px;line-height:20px}.checkbox label:before{content:'';position:absolute;left:-30px;top:2px;width:20px;height:20px;border:1px solid #ddd;border-radius:4px;transition:all .3s ease;-webkit-transition:all .3s ease;-moz-transition:all .3s ease}.checkbox label:after{content:'';position:absolute;left:-22px;top:3px;width:6px;height:12px;border:0;border-right:1px solid #fff;border-bottom:1px solid #fff;border-radius:2px;transform:rotate(45deg);-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);transition:all .3s ease;-webkit-transition:all .3s ease;-moz-transition:all .3s ease}.checkbox input[type='checkbox']:checked+label:before{background:#4cd764;border-color:#4cd764}.checkbox input[type='checkbox']:checked+label:after{background:#4cd764}.send-button{color:#fff;background-color:#5cb85c;display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px;outline-color:transparent}textarea{width:100%;background-color:#f7f7f8;border:1px solid #f7f7f8;border-radius:4px;font-size:1em;padding:1em;font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;outline-color:#dedede}input{border:0;background-color:transparent;outline-color:transparent;outline-style:dotted;max-width:100%}#book-search-input{padding:13px;background:0;transition:top .5s ease;border-bottom:1px solid rgba(0,0,0,.07);border-top:1px solid rgba(0,0,0,.07);margin-top:-1px}#book-search-input input,#book-search-input input:focus,#book-search-input input:hover{width:100%;background:0;border:1px solid transparent;box-shadow:none;outline:0;line-height:22px;padding:7px 7px;color:inherit}[contenteditable="plaintext-only"]:focus{border:0;outline:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}script{display:none!important}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}a{background:transparent}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
|
||||||
|
button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}html,body{font-size:100%}body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}a:hover{cursor:pointer}img,object,embed{max-width:100%;height:auto}object,embed{height:100%}img{-ms-interpolation-mode:bicubic}.left{float:left!important}.right{float:right!important}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}.text-justify{text-align:justify!important}.hide{display:none}img,object,svg{display:inline-block;vertical-align:middle}textarea{height:auto;min-height:50px}select{width:100%}.center{margin-left:auto;margin-right:auto}.spread{width:100%}p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6}.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}a{color:#364149;text-decoration:underline;line-height:inherit}a:hover,a:focus{color:#364149}a img{border:0}p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}p aside{font-size:.875em;line-height:1.35;font-style:italic}h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}h1{font-size:2.125em}h2{font-size:1.6875em}h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}h4,h5{font-size:1.125em}h6{font-size:1em}hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}em,i{font-style:italic;line-height:inherit}strong,b{font-weight:bold;line-height:inherit}small{font-size:60%;line-height:inherit}code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}ul,ol{margin-left:1.5em}ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}ul.square{list-style-type:square}ul.circle{list-style-type:circle}ul.disc{list-style-type:disc}ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}dl dt{margin-bottom:.3125em;font-weight:bold}dl dd{margin-bottom:1.25em}abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}abbr{text-transform:none}blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}blockquote cite:before{content:"\2014 \0020"}blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}h1{font-size:2.75em}h2{font-size:2.3125em}h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}h4{font-size:1.4375em}}table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}table thead,table tfoot{background:#f7f8f7;font-weight:bold}table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
|
||||||
|
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}.clearfix:after,.float-group:after{clear:both}*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word}*:not(pre)>code.nobreak{word-wrap:normal}*:not(pre)>code.nowrap{white-space:nowrap}pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}em em{font-style:normal}strong strong{font-weight:400}.keyseq{color:rgba(51,51,51,.8)}kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}.keyseq kbd:first-child{margin-left:0}.keyseq kbd:last-child{margin-right:0}.menuseq,.menuref{color:#000}.menuseq b:not(.caret),.menuref{font-weight:inherit}.menuseq{word-spacing:-.02em}.menuseq b.caret{font-size:1.25em;line-height:.8}.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}b.button:before{content:"[";padding:0 3px 0 2px}b.button:after{content:"]";padding:0 2px 0 3px}p a>code:hover{color:rgba(0,0,0,.9)}#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}#header:after,#content:after,#footnotes:after,#footer:after{clear:both}#content{margin-top:1.25em}#content:before{content:none}#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8}#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px}#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}#header .details span:first-child{margin-left:-.125em}#header .details span.email a{color:rgba(0,0,0,.85)}#header .details br{display:none}#header .details br+span:before{content:"\00a0\2013\00a0"}#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}#header .details br+span#revremark:before{content:"\00a0|\00a0"}#header #revnumber{text-transform:capitalize}#header #revnumber:after{content:"\00a0"}#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}#toc{border-bottom:1px solid #efefed;padding-bottom:.5em}#toc>ul{margin-left:.125em;padding-left:1.25em}#toc ul.sectlevel0>li>a{font-style:italic}#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}#toc li{line-height:1.3334;margin-top:.3334em;padding-bottom:4px;padding-top:4px}#toc a{text-decoration:none}#toc a:active{text-decoration:underline}#toctitle{color:#7a2518;font-size:1.2em}@media only screen and (min-width:768px){#toctitle{font-size:1.375em}body.toc2{padding-left:15em;padding-right:0}#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;#padding:1.25em 1em;height:100%;overflow:auto}#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
|
||||||
|
#toc.toc2>ul{font-size:.9em;margin-bottom:0}#toc.toc2 ul ul{margin-left:0;padding-left:1em}#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}body.toc2.toc-right{padding-left:0;padding-right:15em}body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}#toc.toc2{width:20em}#toc.toc2 #toctitle{font-size:1.375em;border-bottom:1px solid rgba(0,0,0,.07);padding-top:20px;padding-bottom:15px}#toc.toc2 #toctitle span{padding-left:1.25em;padding-bottom:15px}#toc.toc2>ul{font-size:.95em}#toc.toc2 ul ul{padding-left:1.25em}body.toc2.toc-right{padding-left:0;padding-right:20em}}#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}#content #toc>:first-child{margin-top:0}#content #toc>:last-child{margin-bottom:0}#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}#footer-text{color:rgba(255,255,255,.8);line-height:1.44}.sect1{padding-bottom:.625em}@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}}.sect1+.sect1{border-top:1px solid #efefed}#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0}.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)}table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit}.admonitionblock>table{border-collapse:separate;border:0;background:0;width:100%}.admonitionblock>table td.icon{text-align:center;width:80px}.admonitionblock>table td.icon img{max-width:initial}.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)}.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}.exampleblock>.content>:first-child{margin-top:0}.exampleblock>.content>:last-child{margin-bottom:0}.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}.sidebarblock>:first-child{margin-top:0}.sidebarblock>:last-child{margin-bottom:0}.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
|
||||||
|
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em}.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal}@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}.listingblock pre.highlightjs{padding:0}.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}.listingblock pre.prettyprint{border-width:0}.listingblock>.content{position:relative}.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}.listingblock:hover code[data-lang]:before{display:block}.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:0}table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45}table.pyhltable td.code{padding-left:.75em;padding-right:0}pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}pre.pygments .lineno{display:inline-block;margin-right:.25em}table.pyhltable .linenodiv{background:none!important;padding-right:0!important}.quoteblock{margin:0 1em 1.25em 1.5em;display:table}.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}.quoteblock blockquote{margin:0;padding:0;border:0}.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right}.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}.quoteblock .quoteblock blockquote:before{display:none}.verseblock{margin:0 1em 1.25em 1em}.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}.verseblock pre strong{font-weight:400}.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}.quoteblock .attribution br,.verseblock .attribution br{display:none}.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}.quoteblock.abstract{margin:0 0 1.25em 0;display:block}.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}table.tableblock{max-width:100%;border-collapse:separate}table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0}table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0}table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0}table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0}table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px 0}table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0 0}table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0}table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0}
|
||||||
|
table.frame-all{border-width:1px}table.frame-sides{border-width:0 1px}table.frame-topbot{border-width:1px 0}th.halign-left,td.halign-left{text-align:left}th.halign-right,td.halign-right{text-align:right}th.halign-center,td.halign-center{text-align:center}th.valign-top,td.valign-top{vertical-align:top}th.valign-bottom,td.valign-bottom{vertical-align:bottom}th.valign-middle,td.valign-middle{vertical-align:middle}table thead th,table tfoot th{font-weight:bold}tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}p.tableblock>code:only-child{background:0;padding:0}p.tableblock{font-size:1em}td>div.verse{white-space:pre}ol{margin-left:1.75em}ul li ol{margin-left:1.5em}dl dd{margin-left:1.125em}dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}ul.unstyled,ol.unstyled{margin-left:0}ul.checklist{margin-left:.625em}ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em}ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block}ul.inline>li>*{display:block}.unstyled dl dt{font-weight:400;font-style:normal}ol.arabic{list-style-type:decimal}ol.decimal{list-style-type:decimal-leading-zero}ol.loweralpha{list-style-type:lower-alpha}ol.upperalpha{list-style-type:upper-alpha}ol.lowerroman{list-style-type:lower-roman}ol.upperroman{list-style-type:upper-roman}ol.lowergreek{list-style-type:lower-greek}.hdlist>table,.colist>table{border:0;background:0}.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:0}td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}td.hdlist1{font-weight:bold;padding-bottom:1.25em}.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}.colist>table tr>td:first-of-type{padding:.4em .75em 0 .75em;line-height:1;vertical-align:top}.colist>table tr>td:first-of-type img{max-width:initial}.colist>table tr>td:last-of-type{padding:.25em 0}.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}.imageblock.left,.imageblock[style*="float:left"]{margin:.25em .625em 1.25em 0}.imageblock.right,.imageblock[style*="float:right"]{margin:.25em 0 1.25em .625em}.imageblock>.title{margin-bottom:0}.imageblock.thumb,.imageblock.th{border-width:6px}.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}.image.left{margin-right:.625em}.image.right{margin-left:.625em}a.image{text-decoration:none;display:inline-block}a.image object{pointer-events:none}sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}sup.footnote a,sup.footnoteref a{text-decoration:none}sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0}#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;text-indent:-1.05em;margin-bottom:.2em}#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none}#footnotes .footnote:last-of-type{margin-bottom:0}#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}.gist .file-data>table td.line-data{width:99%}div.unbreakable{page-break-inside:avoid}.big{font-size:larger}.small{font-size:smaller}.underline{text-decoration:underline}.overline{text-decoration:overline}.line-through{text-decoration:line-through}.aqua{color:#00bfbf}.aqua-background{background-color:#00fafa}.black{color:#000}.black-background{background-color:#000}.blue{color:#0000bf}.blue-background{background-color:#0000fa}.fuchsia{color:#bf00bf}.fuchsia-background{background-color:#fa00fa}.gray{color:#606060}.gray-background{background-color:#7d7d7d}.green{color:#006000}.green-background{background-color:#007d00}.lime{color:#00bf00}.lime-background{background-color:#00fa00}.maroon{color:#600000}.maroon-background{background-color:#7d0000}.navy{color:#000060}.navy-background{background-color:#00007d}.olive{color:#606000}.olive-background{background-color:#7d7d00}.purple{color:#600060}.purple-background{background-color:#7d007d}.red{color:#bf0000}
|
||||||
|
.red-background{background-color:#fa0000}.silver{color:#909090}.silver-background{background-color:#bcbcbc}.teal{color:#006060}.teal-background{background-color:#007d7d}.white{color:#bfbfbf}.white-background{background-color:#fafafa}.yellow{color:#bfbf00}.yellow-background{background-color:#fafa00}span.icon>.fa{cursor:default}a span.icon>.fa{cursor:inherit}.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c}.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900}.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}.conum[data-value] *{color:#fff!important}.conum[data-value]+b{display:none}.conum[data-value]:after{content:attr(data-value)}pre .conum[data-value]{position:relative;top:-.125em}b.conum *{color:inherit!important}.conum:not([data-value]):empty{display:none}dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}h1,h2,p,td.content,span.alt{letter-spacing:-.01em}p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}p,blockquote,dt,td.content,span.alt{font-size:1.0625rem}p{margin-bottom:1.25rem}.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}.print-only{display:none!important}@media print{@page{margin:1.25cm .75cm}*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}a{color:inherit!important;text-decoration:underline!important}a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}abbr[title]:after{content:" (" attr(title) ")"}pre,blockquote,tr,img,object,svg{page-break-inside:avoid}thead{display:table-header-group}svg{max-width:100%}p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}#toc,.sidebarblock,.exampleblock>.content{background:none!important}#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important}.sect1{padding-bottom:0!important}.sect1+.sect1{border:0!important}#header>h1:first-child{margin-top:1.25rem}body.book #header{text-align:center}body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0}body.book #header .details{border:0!important;display:block;padding:0!important}body.book #header .details span:first-child{margin-left:0!important}body.book #header .details br{display:block}body.book #header .details br+span:before{content:none!important}body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}.listingblock code[data-lang]:before{display:block}#footer{background:none!important;padding:0 .9375em}#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em}.hide-on-print{display:none!important}.print-only{display:block!important}.hide-for-print{display:none!important}.show-for-print{display:inherit!important}}#content .page-footer{height:100px;border-top:1px solid #ccc;overflow:hidden;padding:10px 0;font-size:14px;color:gray}#content .footer-modification{float:right}#content .footer-modification a{text-decoration:none}.sectlevel2{display:none}.submenu{background:#e7e7e6}.submenu li{border:0}.submenu a{color:#555}.copyright{text-align:right;padding-top:1.25em}#toTop{display:none;position:fixed;bottom:10px;right:0;width:44px;height:44px;border-radius:50%;background-color:#ced4ce;cursor:pointer;text-align:center}#upArrow{position:absolute;left:24%;right:0;bottom:19%;transition:.3s ease-in-out;display:block}#upText{position:absolute;left:0;right:0;bottom:0;font-size:16px;font-weight: 600;line-height:45px;display:none;transition:.3s ease-in-out;-webkit-box-align:center}
|
||||||
407
authorization/src/main/resources/static/doc/debug.js
Normal file
407
authorization/src/main/resources/static/doc/debug.js
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
$(function () {
|
||||||
|
const Accordion = function (el, multiple) {
|
||||||
|
this.el = el || {};
|
||||||
|
this.multiple = multiple || false;
|
||||||
|
const links = this.el.find('.dd');
|
||||||
|
links.on('click', {el: this.el, multiple: this.multiple}, this.dropdown);
|
||||||
|
};
|
||||||
|
Accordion.prototype.dropdown = function (e) {
|
||||||
|
const $el = e.data.el;
|
||||||
|
const $this = $(this), $next = $this.next();
|
||||||
|
$next.slideToggle();
|
||||||
|
$this.parent().toggleClass('open');
|
||||||
|
if (!e.data.multiple) {
|
||||||
|
$el.find('.submenu').not($next).slideUp("20").parent().removeClass(
|
||||||
|
'open');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
new Accordion($('#accordion'), false);
|
||||||
|
hljs.highlightAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("[contenteditable=plaintext-only]").on('blur', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const $this = $(this);
|
||||||
|
const data = $this.text();
|
||||||
|
let content;
|
||||||
|
if (undefined === e.originalEvent.clipboardData) {
|
||||||
|
content = data;
|
||||||
|
} else {
|
||||||
|
content = e.originalEvent.clipboardData.getData('text/plain')
|
||||||
|
}
|
||||||
|
content = JSON.stringify(JSON.parse(content), null, 4);
|
||||||
|
const highlightedCode = hljs.highlight('json', content).value;
|
||||||
|
$this.html(highlightedCode);
|
||||||
|
})
|
||||||
|
$("button").on("click", function () {
|
||||||
|
const $this = $(this);
|
||||||
|
const id = $this.data("id");
|
||||||
|
console.log("method-id=>" + id);
|
||||||
|
|
||||||
|
let body = $("#" + id + "-body").text();
|
||||||
|
|
||||||
|
// header
|
||||||
|
const $headerElement = $("#" + id + "-header");
|
||||||
|
const headersData = getInputData($headerElement);
|
||||||
|
|
||||||
|
// body param
|
||||||
|
const $paramElement = $("#" + id + "-param");
|
||||||
|
let bodyParamData = getInputData($paramElement)
|
||||||
|
|
||||||
|
// path param
|
||||||
|
const $pathElement = $("#" + id + "-path-params")
|
||||||
|
const pathParamData = getInputData($pathElement)
|
||||||
|
|
||||||
|
// query param
|
||||||
|
const $queryElement = $("#" + id + "-query-params")
|
||||||
|
let $urlDataElement = $("#" + id + "-url");
|
||||||
|
const url = $urlDataElement.data("url");
|
||||||
|
const isDownload = $urlDataElement.data("download");
|
||||||
|
const page = $urlDataElement.data("page");
|
||||||
|
const method = $("#" + id + "-method").data("method");
|
||||||
|
const contentType = $("#" + id + "-content-type").data("content-type");
|
||||||
|
console.log("request-headers=>" + JSON.stringify(headersData))
|
||||||
|
console.log("path-params=>" + JSON.stringify(pathParamData))
|
||||||
|
|
||||||
|
console.log("body-params=>" + JSON.stringify(bodyParamData))
|
||||||
|
console.log("json-body=>" + body);
|
||||||
|
let finalUrl = "";
|
||||||
|
let queryParamData;
|
||||||
|
if (!isEmpty(page)) {
|
||||||
|
queryParamData = getInputData($queryElement)
|
||||||
|
finalUrl = castToGetUri(page, pathParamData, queryParamData)
|
||||||
|
window.open(finalUrl, "_blank");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isDownload) {
|
||||||
|
queryParamData = getInputData($queryElement);
|
||||||
|
download(url, headersData, pathParamData, queryParamData, bodyParamData,
|
||||||
|
method, contentType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ajaxOptions = {};
|
||||||
|
|
||||||
|
if ("multipart/form-data" === contentType) {
|
||||||
|
finalUrl = castToGetUri(url, pathParamData);
|
||||||
|
queryParamData = getInputData($queryElement, true, true)
|
||||||
|
body = queryParamData;
|
||||||
|
ajaxOptions.processData = false;
|
||||||
|
ajaxOptions.contentType = false;
|
||||||
|
} else if ("POST" === method && contentType !== "multipart/form-data"
|
||||||
|
&& contentType !== "application/json") {
|
||||||
|
finalUrl = castToGetUri(url, pathParamData);
|
||||||
|
queryParamData = getInputData($queryElement, true)
|
||||||
|
body = queryParamData;
|
||||||
|
} else {
|
||||||
|
queryParamData = getInputData($queryElement)
|
||||||
|
finalUrl = castToGetUri(url, pathParamData, queryParamData)
|
||||||
|
ajaxOptions.contentType = contentType;
|
||||||
|
}
|
||||||
|
console.log("query-params=>" + JSON.stringify(queryParamData));
|
||||||
|
console.log("url=>" + finalUrl)
|
||||||
|
ajaxOptions.headers = headersData
|
||||||
|
ajaxOptions.url = finalUrl
|
||||||
|
ajaxOptions.type = method
|
||||||
|
ajaxOptions.data = body;
|
||||||
|
|
||||||
|
const $responseEle = $("#" + id + "-response").find("pre code");
|
||||||
|
const ajaxTime = new Date().getTime();
|
||||||
|
$.ajax(ajaxOptions).done(function (result, textStatus, jqXHR) {
|
||||||
|
const totalTime = new Date().getTime() - ajaxTime;
|
||||||
|
$this.css("background", "#5cb85c");
|
||||||
|
$("#" + id + "-resp-status").html(
|
||||||
|
" Status: " + jqXHR.status + " " + jqXHR.statusText
|
||||||
|
+ " Time: " + totalTime + " ms");
|
||||||
|
const highlightedCode = hljs.highlight('json',
|
||||||
|
JSON.stringify(result, null, 4)).value;
|
||||||
|
$responseEle.html(highlightedCode);
|
||||||
|
}).fail(function (jqXHR) {
|
||||||
|
const totalTime = new Date().getTime() - ajaxTime;
|
||||||
|
$this.css("background", "#D44B47");
|
||||||
|
if (jqXHR.status === 0 && jqXHR.readyState === 0) {
|
||||||
|
$("#" + id + "-resp-status").html(
|
||||||
|
"Connection refused, please check the server.");
|
||||||
|
} else {
|
||||||
|
$("#" + id + "-resp-status").html(
|
||||||
|
" Status: " + jqXHR.status + " "
|
||||||
|
+ jqXHR.statusText + " Time: " + totalTime
|
||||||
|
+ " ms");
|
||||||
|
}
|
||||||
|
if (undefined !== jqXHR.responseJSON) {
|
||||||
|
const highlightedCode = hljs.highlight('json',
|
||||||
|
JSON.stringify(jqXHR.responseJSON, null, 4)).value;
|
||||||
|
$responseEle.html(highlightedCode);
|
||||||
|
}
|
||||||
|
}).always(function () {
|
||||||
|
|
||||||
|
});
|
||||||
|
const curlCmd = toCurl(ajaxOptions);
|
||||||
|
const highlightedCode = hljs.highlight('bash', curlCmd).value;
|
||||||
|
$("#" + id + "-curl").find("pre code").html(highlightedCode);
|
||||||
|
})
|
||||||
|
$(".check-all").on("click", function () {
|
||||||
|
const checkboxName = $(this).prop("name");
|
||||||
|
const checked = $(this).is(':checked');
|
||||||
|
if (!checked) {
|
||||||
|
$(this).removeAttr("checked");
|
||||||
|
} else {
|
||||||
|
$(this).prop("checked", true);
|
||||||
|
}
|
||||||
|
$('input[name="' + checkboxName + '"]').each(function () {
|
||||||
|
if (!checked) {
|
||||||
|
$(this).prop("checked", false);
|
||||||
|
} else {
|
||||||
|
$(this).prop("checked", true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function castToGetUri(url, pathParams, params) {
|
||||||
|
if (undefined !== url) {
|
||||||
|
let urls = url.split(";")
|
||||||
|
url = urls[0];
|
||||||
|
}
|
||||||
|
if (pathParams instanceof Object && !(pathParams instanceof Array)) {
|
||||||
|
url = url.format(pathParams)
|
||||||
|
}
|
||||||
|
if (params instanceof Object && !(params instanceof Array)) {
|
||||||
|
const pm = params || {};
|
||||||
|
const arr = [];
|
||||||
|
arr.push(url);
|
||||||
|
let j = 0;
|
||||||
|
for (const i in pm) {
|
||||||
|
if (j === 0) {
|
||||||
|
arr.push("?");
|
||||||
|
arr.push(i + "=" + pm[i]);
|
||||||
|
} else {
|
||||||
|
arr.push("&" + i + "=" + pm[i]);
|
||||||
|
}
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
return arr.join("");
|
||||||
|
} else {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputData(element, isPost, returnFormDate) {
|
||||||
|
const formData = new FormData();
|
||||||
|
$(element).find("tr").each(function (i) {
|
||||||
|
const checked = $(this).find('td:eq(0)').children(".checkbox").children(
|
||||||
|
"input").is(':checked');
|
||||||
|
if (checked) {
|
||||||
|
const input = $(this).find('td:eq(2) input');
|
||||||
|
let attr = $(input).attr("type");
|
||||||
|
let multiple = $(input).attr("multiple");
|
||||||
|
console.log("input type:" + attr)
|
||||||
|
const name = $(input).attr("name");
|
||||||
|
if (attr === "file") {
|
||||||
|
let files = $(input)[0].files;
|
||||||
|
if (multiple) {
|
||||||
|
$.each(files, function (i, file) {
|
||||||
|
formData.append(name, file);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
formData.append(name, files[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const val = $(input).val();
|
||||||
|
if (isValidUrl(val)) {
|
||||||
|
formData.append(name, encodeURI(val));
|
||||||
|
} else {
|
||||||
|
// support chinese
|
||||||
|
if (hasChinese(val) && !isPost) {
|
||||||
|
formData.append(name, encodeURIComponent(val));
|
||||||
|
} else {
|
||||||
|
formData.append(name, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (returnFormDate) {
|
||||||
|
return formData;
|
||||||
|
}
|
||||||
|
const headersData = {};
|
||||||
|
formData.forEach((value, key) => headersData[key] = value);
|
||||||
|
return headersData;
|
||||||
|
}
|
||||||
|
|
||||||
|
String.prototype.format = function (args) {
|
||||||
|
let reg;
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
let result = this;
|
||||||
|
if (arguments.length === 1 && typeof (args) == "object") {
|
||||||
|
for (const key in args) {
|
||||||
|
reg = new RegExp("({" + key + "})", "g");
|
||||||
|
result = result.replace(reg, args[key]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < arguments.length; i++) {
|
||||||
|
if (arguments[i] === undefined) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
reg = new RegExp("({[" + i + "]})", "g");
|
||||||
|
result = result.replace(reg, arguments[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function download(url, headersData, pathParamData, queryParamData,
|
||||||
|
bodyParamData, method, contentType) {
|
||||||
|
url = castToGetUri(url, pathParamData, queryParamData)
|
||||||
|
const xmlRequest = new XMLHttpRequest();
|
||||||
|
xmlRequest.open(method, url, true);
|
||||||
|
xmlRequest.setRequestHeader("Content-type", contentType);
|
||||||
|
for (let key in headersData) {
|
||||||
|
xmlRequest.setRequestHeader(key, headersData[key])
|
||||||
|
}
|
||||||
|
xmlRequest.responseType = "blob";
|
||||||
|
xmlRequest.onload = function () {
|
||||||
|
if (this.status === 200) {
|
||||||
|
let fileName = "";
|
||||||
|
const disposition = xmlRequest.getResponseHeader('Content-Disposition');
|
||||||
|
if (disposition && disposition.indexOf('attachment') !== -1) {
|
||||||
|
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
|
||||||
|
const matches = filenameRegex.exec(disposition);
|
||||||
|
if (matches != null && matches[1]) {
|
||||||
|
fileName = decodeURI(matches[1].replace(/['"]/g, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("download filename:" + fileName);
|
||||||
|
const blob = this.response;
|
||||||
|
if (navigator.msSaveBlob) {
|
||||||
|
// IE10 can't do a[download], only Blobs:
|
||||||
|
window.navigator.msSaveBlob(blob, fileName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (window.URL) { // simple fast and modern way using Blob and URL:
|
||||||
|
const a = document.createElement("a");
|
||||||
|
document.body.appendChild(a);
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName;
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
console.log(fileName);
|
||||||
|
} else {
|
||||||
|
console.log("download failed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
xmlRequest.send(bodyParamData);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to send data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toCurl(request) {
|
||||||
|
if (typeof request !== 'object') {
|
||||||
|
throw "Request is not an object";
|
||||||
|
}
|
||||||
|
// default is a GET request
|
||||||
|
const cmd = ["curl", "-X", request.type || "GET"];
|
||||||
|
|
||||||
|
if (request.url.indexOf('https') === 0) {
|
||||||
|
cmd.push("-k");
|
||||||
|
}
|
||||||
|
|
||||||
|
// append Content-Type
|
||||||
|
if (request.contentType) {
|
||||||
|
cmd.push("-H");
|
||||||
|
cmd.push("'Content-Type:");
|
||||||
|
cmd.push(request.contentType + "'");
|
||||||
|
}
|
||||||
|
// append request headers
|
||||||
|
let headerValue;
|
||||||
|
if (typeof request.headers == 'object') {
|
||||||
|
for (let key in request.headers) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(request.headers, key)) {
|
||||||
|
cmd.push("-H");
|
||||||
|
headerValue = request.headers[key];
|
||||||
|
if (headerValue.value === '') {
|
||||||
|
cmd.push("'" + key + "'");
|
||||||
|
} else {
|
||||||
|
cmd.push("'" + key + ':' + headerValue + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// display the response headers
|
||||||
|
cmd.push("-i");
|
||||||
|
// append request url
|
||||||
|
let url = request.url;
|
||||||
|
if (!url.startsWith("http")) {
|
||||||
|
const protocol = window.document.location.protocol;
|
||||||
|
const domain = window.document.location.hostname;
|
||||||
|
const port = window.document.location.port;
|
||||||
|
url = protocol + "//" + domain + ":" + port + url;
|
||||||
|
}
|
||||||
|
cmd.push(url);
|
||||||
|
// append data
|
||||||
|
|
||||||
|
if (typeof request.data == 'object') {
|
||||||
|
let index = 0;
|
||||||
|
const bodyData = [];
|
||||||
|
bodyData.push("\"")
|
||||||
|
for (let key in request.data) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(request.data, key)) {
|
||||||
|
if (index === 0) {
|
||||||
|
bodyData.push(key);
|
||||||
|
bodyData.push("=");
|
||||||
|
bodyData.push(request.data[key]);
|
||||||
|
} else {
|
||||||
|
bodyData.push("&")
|
||||||
|
bodyData.push(key);
|
||||||
|
bodyData.push("=");
|
||||||
|
bodyData.push(request.data[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
bodyData.push("\"");
|
||||||
|
let bodyStr = ""
|
||||||
|
bodyData.forEach(function (item) {
|
||||||
|
bodyStr += item;
|
||||||
|
});
|
||||||
|
cmd.push("--data");
|
||||||
|
cmd.push(bodyStr);
|
||||||
|
} else if (request.data && request.data.length > 0) {
|
||||||
|
// append json data
|
||||||
|
cmd.push("--data");
|
||||||
|
cmd.push("'" + request.data + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
let curlCmd = "";
|
||||||
|
cmd.forEach(function (item) {
|
||||||
|
curlCmd += item + " ";
|
||||||
|
});
|
||||||
|
console.log(curlCmd);
|
||||||
|
return curlCmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmpty(obj) {
|
||||||
|
return obj === undefined || obj === null || String(obj).trim() === '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasChinese(_string) {
|
||||||
|
const chineseReg = /[\u4e00-\u9fa5]/g
|
||||||
|
return chineseReg.test(_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidUrl(_string) {
|
||||||
|
let urlString;
|
||||||
|
try {
|
||||||
|
urlString = new URL(_string);
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return urlString.protocol === "http:" || urlString.protocol === "https:";
|
||||||
|
}
|
||||||
6
authorization/src/main/resources/static/doc/font.css
Normal file
6
authorization/src/main/resources/static/doc/font.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@font-face{font-family:'Droid Sans Mono';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/droidsansmono/v19/6NUO8FuJNQ2MbkrZ5-J8lKFrp7pRef2r.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Kaw1J5X9T9RW6j9bNfFImZzC7TMQ.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Kaw1J5X9T9RW6j9bNfFImbjC7TMQ.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Kaw1J5X9T9RW6j9bNfFImZjC7TMQ.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Kaw1J5X9T9RW6j9bNfFImaTC7TMQ.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Kaw1J5X9T9RW6j9bNfFImZTC7TMQ.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Kaw1J5X9T9RW6j9bNfFImZDC7TMQ.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Kaw1J5X9T9RW6j9bNfFImajC7.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Vaw1J5X9T9RW6j9bNfFIu0RWufuVMCoY.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Vaw1J5X9T9RW6j9bNfFIu0RWud-VMCoY.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Vaw1J5X9T9RW6j9bNfFIu0RWuf-VMCoY.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Vaw1J5X9T9RW6j9bNfFIu0RWucOVMCoY.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Vaw1J5X9T9RW6j9bNfFIu0RWufOVMCoY.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Vaw1J5X9T9RW6j9bNfFIu0RWufeVMCoY.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Noto Serif';font-style:italic;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Vaw1J5X9T9RW6j9bNfFIu0RWuc-VM.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Iaw1J5X9T9RW6j9bNfFoWaCi_.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}
|
||||||
|
@font-face{font-family:'Noto Serif';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Iaw1J5X9T9RW6j9bNfFMWaCi_.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Iaw1J5X9T9RW6j9bNfFsWaCi_.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Iaw1J5X9T9RW6j9bNfFQWaCi_.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Iaw1J5X9T9RW6j9bNfFgWaCi_.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Iaw1J5X9T9RW6j9bNfFkWaCi_.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Iaw1J5X9T9RW6j9bNfFcWaA.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Law1J5X9T9RW6j9bNdOwzfRqecf1I.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Law1J5X9T9RW6j9bNdOwzfROecf1I.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Law1J5X9T9RW6j9bNdOwzfRuecf1I.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Law1J5X9T9RW6j9bNdOwzfRSecf1I.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Law1J5X9T9RW6j9bNdOwzfRiecf1I.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Law1J5X9T9RW6j9bNdOwzfRmecf1I.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Noto Serif';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/notoserif/v20/ga6Law1J5X9T9RW6j9bNdOwzfReecQ.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWV0ewJER.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWVQewJER.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWVwewJER.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWVMewJER.woff2) format('woff2');unicode-range:U+0370-03FF}
|
||||||
|
@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWVIewJER.woff2) format('woff2');unicode-range:U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWV8ewJER.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWV4ewJER.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk5hkWVAewA.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWV0ewJER.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWVQewJER.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWVwewJER.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWVMewJER.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWVIewJER.woff2) format('woff2');unicode-range:U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWV8ewJER.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWV4ewJER.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0Rk8ZkWVAewA.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjWV0ewJER.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjWVQewJER.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
|
||||||
|
@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjWVwewJER.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjWVMewJER.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjWVIewJER.woff2) format('woff2');unicode-range:U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F}@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjWV8ewJER.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjWV4ewJER.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Open Sans';font-style:italic;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memQYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWq8tWZ0Pw86hd0RkxhjWVAewA.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4taVIGxA.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4kaVIGxA.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4saVIGxA.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4jaVIGxA.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4iaVIGxA.woff2) format('woff2');unicode-range:U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4vaVIGxA.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4uaVIGxA.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsiH0B4gaVI.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||||||
|
@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4taVIGxA.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4kaVIGxA.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4saVIGxA.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4jaVIGxA.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4iaVIGxA.woff2) format('woff2');unicode-range:U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4vaVIGxA.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4uaVIGxA.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVI.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1x4taVIGxA.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1x4kaVIGxA.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1x4saVIGxA.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1x4jaVIGxA.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1x4iaVIGxA.woff2) format('woff2');unicode-range:U+0590-05FF,U+200C-2010,U+20AA,U+25CC,U+FB1D-FB4F}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1x4vaVIGxA.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1x4uaVIGxA.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
||||||
|
@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;font-stretch:normal;src:url(https://fonts.gstatic.com/s/opensans/v28/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsgH1x4gaVI.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||||||
1213
authorization/src/main/resources/static/doc/highlight.min.js
vendored
Normal file
1213
authorization/src/main/resources/static/doc/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
50
authorization/src/main/resources/static/doc/index.html
Normal file
50
authorization/src/main/resources/static/doc/index.html
Normal file
File diff suppressed because one or more lines are too long
2
authorization/src/main/resources/static/doc/jquery.min.js
vendored
Normal file
2
authorization/src/main/resources/static/doc/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
229
authorization/src/main/resources/static/doc/search.js
Normal file
229
authorization/src/main/resources/static/doc/search.js
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
let api = [];
|
||||||
|
const apiDocListSize = 1
|
||||||
|
api.push({
|
||||||
|
name: 'default',
|
||||||
|
order: '1',
|
||||||
|
list: []
|
||||||
|
})
|
||||||
|
api[0].list.push({
|
||||||
|
alias: 'AuthController',
|
||||||
|
order: '2',
|
||||||
|
link: '鉴权',
|
||||||
|
desc: '鉴权',
|
||||||
|
list: []
|
||||||
|
})
|
||||||
|
api[0].list[0].list.push({
|
||||||
|
order: '1',
|
||||||
|
deprecated: 'false',
|
||||||
|
url: '/api/authorization/login',
|
||||||
|
methodId: 'c202a4fb7c3b7bc0b78f2f223b07f8b1',
|
||||||
|
desc: '获取令牌',
|
||||||
|
});
|
||||||
|
api[0].list[0].list.push({
|
||||||
|
order: '2',
|
||||||
|
deprecated: 'false',
|
||||||
|
url: '/api/authorization/forgot-password-mail',
|
||||||
|
methodId: '2397c6149aab44dc4cf09dbe29b71251',
|
||||||
|
desc: '忘记密码',
|
||||||
|
});
|
||||||
|
api[0].list[0].list.push({
|
||||||
|
order: '3',
|
||||||
|
deprecated: 'false',
|
||||||
|
url: '/api/authorization/modify-password',
|
||||||
|
methodId: '222f70ecbada3978f3db71ab29f6cf69',
|
||||||
|
desc: '修改密码',
|
||||||
|
});
|
||||||
|
api[0].list[0].list.push({
|
||||||
|
order: '4',
|
||||||
|
deprecated: 'false',
|
||||||
|
url: '/api/authorization/register',
|
||||||
|
methodId: '2fc76face5f8b411ad1de6a2dc0a9443',
|
||||||
|
desc: '注册新用户',
|
||||||
|
});
|
||||||
|
api[0].list[0].list.push({
|
||||||
|
order: '5',
|
||||||
|
deprecated: 'false',
|
||||||
|
url: '/api/authorization/profile',
|
||||||
|
methodId: '7a266d43f7d3b225a499a25a2dd37fb9',
|
||||||
|
desc: '用户信息',
|
||||||
|
});
|
||||||
|
api[0].list.push({
|
||||||
|
alias: 'CaptchaController',
|
||||||
|
order: '3',
|
||||||
|
link: '验证码',
|
||||||
|
desc: '验证码',
|
||||||
|
list: []
|
||||||
|
})
|
||||||
|
api[0].list[1].list.push({
|
||||||
|
order: '1',
|
||||||
|
deprecated: 'false',
|
||||||
|
url: '/api/authorization/captcha',
|
||||||
|
methodId: '045430c99582b55701d7b38f96a2912f',
|
||||||
|
desc: '获取图形验证码',
|
||||||
|
});
|
||||||
|
api[0].list[1].list.push({
|
||||||
|
order: '2',
|
||||||
|
deprecated: 'false',
|
||||||
|
url: '/api/authorization/register-mail',
|
||||||
|
methodId: '0850b45553d65be82ccf362a95cee708',
|
||||||
|
desc: '发送注册邮箱验证码',
|
||||||
|
});
|
||||||
|
api[0].list[1].list.push({
|
||||||
|
order: '3',
|
||||||
|
deprecated: 'false',
|
||||||
|
url: '/api/authorization/forgot-password-mail',
|
||||||
|
methodId: '62119a6b40f3944f55486ef825ec2488',
|
||||||
|
desc: '发送忘记密码邮箱验证码',
|
||||||
|
});
|
||||||
|
document.onkeydown = keyDownSearch;
|
||||||
|
function keyDownSearch(e) {
|
||||||
|
const theEvent = e;
|
||||||
|
const code = theEvent.keyCode || theEvent.which || theEvent.charCode;
|
||||||
|
if (code === 13) {
|
||||||
|
const search = document.getElementById('search');
|
||||||
|
const searchValue = search.value.toLocaleLowerCase();
|
||||||
|
|
||||||
|
let searchGroup = [];
|
||||||
|
for (let i = 0; i < api.length; i++) {
|
||||||
|
|
||||||
|
let apiGroup = api[i];
|
||||||
|
|
||||||
|
let searchArr = [];
|
||||||
|
for (let i = 0; i < apiGroup.list.length; i++) {
|
||||||
|
let apiData = apiGroup.list[i];
|
||||||
|
const desc = apiData.desc;
|
||||||
|
if (desc.toLocaleLowerCase().indexOf(searchValue) > -1) {
|
||||||
|
searchArr.push({
|
||||||
|
order: apiData.order,
|
||||||
|
desc: apiData.desc,
|
||||||
|
link: apiData.link,
|
||||||
|
alias: apiData.alias,
|
||||||
|
list: apiData.list
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let methodList = apiData.list || [];
|
||||||
|
let methodListTemp = [];
|
||||||
|
for (let j = 0; j < methodList.length; j++) {
|
||||||
|
const methodData = methodList[j];
|
||||||
|
const methodDesc = methodData.desc;
|
||||||
|
if (methodDesc.toLocaleLowerCase().indexOf(searchValue) > -1) {
|
||||||
|
methodListTemp.push(methodData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (methodListTemp.length > 0) {
|
||||||
|
const data = {
|
||||||
|
order: apiData.order,
|
||||||
|
desc: apiData.desc,
|
||||||
|
link: apiData.link,
|
||||||
|
alias: apiData.alias,
|
||||||
|
list: methodListTemp
|
||||||
|
};
|
||||||
|
searchArr.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (apiGroup.name.toLocaleLowerCase().indexOf(searchValue) > -1) {
|
||||||
|
searchGroup.push({
|
||||||
|
name: apiGroup.name,
|
||||||
|
order: apiGroup.order,
|
||||||
|
list: searchArr
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (searchArr.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
searchGroup.push({
|
||||||
|
name: apiGroup.name,
|
||||||
|
order: apiGroup.order,
|
||||||
|
list: searchArr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let html;
|
||||||
|
if (searchValue === '') {
|
||||||
|
const liClass = "";
|
||||||
|
const display = "display: none";
|
||||||
|
html = buildAccordion(api,liClass,display);
|
||||||
|
document.getElementById('accordion').innerHTML = html;
|
||||||
|
} else {
|
||||||
|
const liClass = "open";
|
||||||
|
const display = "display: block";
|
||||||
|
html = buildAccordion(searchGroup,liClass,display);
|
||||||
|
document.getElementById('accordion').innerHTML = html;
|
||||||
|
}
|
||||||
|
const Accordion = function (el, multiple) {
|
||||||
|
this.el = el || {};
|
||||||
|
this.multiple = multiple || false;
|
||||||
|
const links = this.el.find('.dd');
|
||||||
|
links.on('click', {el: this.el, multiple: this.multiple}, this.dropdown);
|
||||||
|
};
|
||||||
|
Accordion.prototype.dropdown = function (e) {
|
||||||
|
const $el = e.data.el;
|
||||||
|
let $this = $(this), $next = $this.next();
|
||||||
|
$next.slideToggle();
|
||||||
|
$this.parent().toggleClass('open');
|
||||||
|
if (!e.data.multiple) {
|
||||||
|
$el.find('.submenu').not($next).slideUp("20").parent().removeClass('open');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
new Accordion($('#accordion'), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAccordion(apiGroups, liClass, display) {
|
||||||
|
let html = "";
|
||||||
|
if (apiGroups.length > 0) {
|
||||||
|
if (apiDocListSize === 1) {
|
||||||
|
let apiData = apiGroups[0].list;
|
||||||
|
let order = apiGroups[0].order;
|
||||||
|
for (let j = 0; j < apiData.length; j++) {
|
||||||
|
html += '<li class="'+liClass+'">';
|
||||||
|
html += '<a class="dd" href="#' + apiData[j].alias + '">' + apiData[j].order + '. ' + apiData[j].desc + '</a>';
|
||||||
|
html += '<ul class="sectlevel2" style="'+display+'">';
|
||||||
|
let doc = apiData[j].list;
|
||||||
|
for (let m = 0; m < doc.length; m++) {
|
||||||
|
let spanString;
|
||||||
|
if (doc[m].deprecated === 'true') {
|
||||||
|
spanString='<span class="line-through">';
|
||||||
|
} else {
|
||||||
|
spanString='<span>';
|
||||||
|
}
|
||||||
|
html += '<li><a href="#' + doc[m].methodId + '">' + apiData[j].order + '.' + doc[m].order + '. ' + spanString + doc[m].desc + '<span></a> </li>';
|
||||||
|
}
|
||||||
|
html += '</ul>';
|
||||||
|
html += '</li>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < apiGroups.length; i++) {
|
||||||
|
let apiGroup = apiGroups[i];
|
||||||
|
html += '<li class="'+liClass+'">';
|
||||||
|
html += '<a class="dd" href="#_'+apiGroup.order+'_' + apiGroup.name + '">' + apiGroup.order + '. ' + apiGroup.name + '</a>';
|
||||||
|
html += '<ul class="sectlevel1">';
|
||||||
|
|
||||||
|
let apiData = apiGroup.list;
|
||||||
|
for (let j = 0; j < apiData.length; j++) {
|
||||||
|
html += '<li class="'+liClass+'">';
|
||||||
|
html += '<a class="dd" href="#' + apiData[j].alias + '">' +apiGroup.order+'.'+ apiData[j].order + '. ' + apiData[j].desc + '</a>';
|
||||||
|
html += '<ul class="sectlevel2" style="'+display+'">';
|
||||||
|
let doc = apiData[j].list;
|
||||||
|
for (let m = 0; m < doc.length; m++) {
|
||||||
|
let spanString;
|
||||||
|
if (doc[m].deprecated === 'true') {
|
||||||
|
spanString='<span class="line-through">';
|
||||||
|
} else {
|
||||||
|
spanString='<span>';
|
||||||
|
}
|
||||||
|
html += '<li><a href="#' + doc[m].methodId + '">'+apiGroup.order+'.' + apiData[j].order + '.' + doc[m].order + '. ' + spanString + doc[m].desc + '<span></a> </li>';
|
||||||
|
}
|
||||||
|
html += '</ul>';
|
||||||
|
html += '</li>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</ul>';
|
||||||
|
html += '</li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
94
authorization/src/main/resources/static/prevent/denied.html
Normal file
94
authorization/src/main/resources/static/prevent/denied.html
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>🚫禁止访问🚫</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
background: #858585;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 10px rgba(183, 162, 162, 0.2);
|
||||||
|
animation: fadeIn 1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #ff4d4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 10px 0 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 80px;
|
||||||
|
color: #ff4d4d;
|
||||||
|
animation: bounce 1s infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
from {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="icon">🚫</div>
|
||||||
|
<h2>禁止访问</h2>
|
||||||
|
<p>当前页面已被禁用,请联系管理员</p>
|
||||||
|
<a href="/" class="btn">返回首页</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2
chat/.gitattributes
vendored
Normal file
2
chat/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/mvnw text eol=lf
|
||||||
|
*.cmd text eol=crlf
|
||||||
33
chat/.gitignore
vendored
Normal file
33
chat/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### 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/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
2
chat/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
2
chat/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
distributionType=only-script
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
|
||||||
295
chat/mvnw
vendored
Normal file
295
chat/mvnw
vendored
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
#!/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.
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Apache Maven Wrapper startup batch script, version 3.3.3
|
||||||
|
#
|
||||||
|
# Optional ENV vars
|
||||||
|
# -----------------
|
||||||
|
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||||
|
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
set -euf
|
||||||
|
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||||
|
|
||||||
|
# OS specific support.
|
||||||
|
native_path() { printf %s\\n "$1"; }
|
||||||
|
case "$(uname)" in
|
||||||
|
CYGWIN* | MINGW*)
|
||||||
|
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||||
|
native_path() { cygpath --path --windows "$1"; }
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# set JAVACMD and JAVACCMD
|
||||||
|
set_java_home() {
|
||||||
|
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||||
|
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"
|
||||||
|
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||||
|
|
||||||
|
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||||
|
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||||
|
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v java
|
||||||
|
)" || :
|
||||||
|
JAVACCMD="$(
|
||||||
|
'set' +e
|
||||||
|
'unset' -f command 2>/dev/null
|
||||||
|
'command' -v javac
|
||||||
|
)" || :
|
||||||
|
|
||||||
|
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||||
|
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# hash string like Java String::hashCode
|
||||||
|
hash_string() {
|
||||||
|
str="${1:-}" h=0
|
||||||
|
while [ -n "$str" ]; do
|
||||||
|
char="${str%"${str#?}"}"
|
||||||
|
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||||
|
str="${str#?}"
|
||||||
|
done
|
||||||
|
printf %x\\n $h
|
||||||
|
}
|
||||||
|
|
||||||
|
verbose() { :; }
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf %s\\n "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trim() {
|
||||||
|
# MWRAPPER-139:
|
||||||
|
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||||
|
# Needed for removing poorly interpreted newline sequences when running in more
|
||||||
|
# exotic environments such as mingw bash on Windows.
|
||||||
|
printf "%s" "${1}" | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptDir="$(dirname "$0")"
|
||||||
|
scriptName="$(basename "$0")"
|
||||||
|
|
||||||
|
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
while IFS="=" read -r key value; do
|
||||||
|
case "${key-}" in
|
||||||
|
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||||
|
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||||
|
esac
|
||||||
|
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
|
||||||
|
case "${distributionUrl##*/}" in
|
||||||
|
maven-mvnd-*bin.*)
|
||||||
|
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||||
|
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||||
|
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||||
|
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||||
|
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||||
|
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||||
|
*)
|
||||||
|
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||||
|
distributionPlatform=linux-amd64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||||
|
;;
|
||||||
|
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||||
|
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||||
|
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||||
|
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||||
|
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||||
|
|
||||||
|
exec_maven() {
|
||||||
|
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||||
|
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -d "$MAVEN_HOME" ]; then
|
||||||
|
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
exec_maven "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${distributionUrl-}" in
|
||||||
|
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||||
|
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||||
|
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||||
|
trap clean HUP INT TERM EXIT
|
||||||
|
else
|
||||||
|
die "cannot create temp dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
verbose "Downloading from: $distributionUrl"
|
||||||
|
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
# select .zip or .tar.gz
|
||||||
|
if ! command -v unzip >/dev/null; then
|
||||||
|
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||||
|
distributionUrlName="${distributionUrl##*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# verbose opt
|
||||||
|
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||||
|
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||||
|
|
||||||
|
# normalize http auth
|
||||||
|
case "${MVNW_PASSWORD:+has-password}" in
|
||||||
|
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||||
|
verbose "Found wget ... using wget"
|
||||||
|
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||||
|
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||||
|
verbose "Found curl ... using curl"
|
||||||
|
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||||
|
elif set_java_home; then
|
||||||
|
verbose "Falling back to use Java to download"
|
||||||
|
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||||
|
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
cat >"$javaSource" <<-END
|
||||||
|
public class Downloader extends java.net.Authenticator
|
||||||
|
{
|
||||||
|
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||||
|
{
|
||||||
|
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||||
|
}
|
||||||
|
public static void main( String[] args ) throws Exception
|
||||||
|
{
|
||||||
|
setDefault( new Downloader() );
|
||||||
|
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END
|
||||||
|
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||||
|
verbose " - Compiling Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||||
|
verbose " - Running Downloader.java ..."
|
||||||
|
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
if [ -n "${distributionSha256Sum-}" ]; then
|
||||||
|
distributionSha256Result=false
|
||||||
|
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||||
|
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||||
|
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
elif command -v sha256sum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
elif command -v shasum >/dev/null; then
|
||||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||||
|
distributionSha256Result=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||||
|
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ $distributionSha256Result = false ]; then
|
||||||
|
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||||
|
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
if command -v unzip >/dev/null; then
|
||||||
|
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||||
|
else
|
||||||
|
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
actualDistributionDir=""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
|
||||||
|
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$distributionUrlNameMain"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
# enable globbing to iterate over items
|
||||||
|
set +f
|
||||||
|
for dir in "$TMP_DOWNLOAD_DIR"/*; do
|
||||||
|
if [ -d "$dir" ]; then
|
||||||
|
if [ -f "$dir/bin/$MVN_CMD" ]; then
|
||||||
|
actualDistributionDir="$(basename "$dir")"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
set -f
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$actualDistributionDir" ]; then
|
||||||
|
verbose "Contents of $TMP_DOWNLOAD_DIR:"
|
||||||
|
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
|
||||||
|
die "Could not find Maven distribution directory in extracted archive"
|
||||||
|
fi
|
||||||
|
|
||||||
|
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
|
||||||
|
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||||
|
|
||||||
|
clean || :
|
||||||
|
exec_maven "$@"
|
||||||
189
chat/mvnw.cmd
vendored
Normal file
189
chat/mvnw.cmd
vendored
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
<# : batch portion
|
||||||
|
@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 Apache Maven Wrapper startup batch script, version 3.3.3
|
||||||
|
@REM
|
||||||
|
@REM Optional ENV vars
|
||||||
|
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||||
|
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||||
|
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||||
|
@REM ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||||
|
@SET __MVNW_CMD__=
|
||||||
|
@SET __MVNW_ERROR__=
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||||
|
@SET PSModulePath=
|
||||||
|
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||||
|
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||||
|
)
|
||||||
|
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||||
|
@SET __MVNW_PSMODULEP_SAVE=
|
||||||
|
@SET __MVNW_ARG0_NAME__=
|
||||||
|
@SET MVNW_USERNAME=
|
||||||
|
@SET MVNW_PASSWORD=
|
||||||
|
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
|
||||||
|
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||||
|
@GOTO :EOF
|
||||||
|
: end batch / begin powershell #>
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
if ($env:MVNW_VERBOSE -eq "true") {
|
||||||
|
$VerbosePreference = "Continue"
|
||||||
|
}
|
||||||
|
|
||||||
|
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||||
|
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||||
|
if (!$distributionUrl) {
|
||||||
|
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||||
|
"maven-mvnd-*" {
|
||||||
|
$USE_MVND = $true
|
||||||
|
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||||
|
$MVN_CMD = "mvnd.cmd"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
$USE_MVND = $false
|
||||||
|
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||||
|
if ($env:MVNW_REPOURL) {
|
||||||
|
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||||
|
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
|
||||||
|
}
|
||||||
|
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||||
|
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||||
|
|
||||||
|
$MAVEN_M2_PATH = "$HOME/.m2"
|
||||||
|
if ($env:MAVEN_USER_HOME) {
|
||||||
|
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
|
||||||
|
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_WRAPPER_DISTS = $null
|
||||||
|
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
|
||||||
|
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
|
||||||
|
} else {
|
||||||
|
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
|
||||||
|
}
|
||||||
|
|
||||||
|
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
|
||||||
|
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||||
|
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||||
|
|
||||||
|
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||||
|
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
|
exit $?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||||
|
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
# prepare tmp dir
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||||
|
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||||
|
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||||
|
trap {
|
||||||
|
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||||
|
|
||||||
|
# Download and Install Apache Maven
|
||||||
|
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||||
|
Write-Verbose "Downloading from: $distributionUrl"
|
||||||
|
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||||
|
|
||||||
|
$webclient = New-Object System.Net.WebClient
|
||||||
|
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||||
|
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||||
|
}
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||||
|
|
||||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||||
|
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||||
|
if ($distributionSha256Sum) {
|
||||||
|
if ($USE_MVND) {
|
||||||
|
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||||
|
}
|
||||||
|
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||||
|
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||||
|
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# unzip and move
|
||||||
|
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||||
|
|
||||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||||
|
$actualDistributionDir = ""
|
||||||
|
|
||||||
|
# First try the expected directory name (for regular distributions)
|
||||||
|
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
|
||||||
|
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
|
||||||
|
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
|
||||||
|
$actualDistributionDir = $distributionUrlNameMain
|
||||||
|
}
|
||||||
|
|
||||||
|
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
|
||||||
|
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
|
||||||
|
if (Test-Path -Path $testPath -PathType Leaf) {
|
||||||
|
$actualDistributionDir = $_.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$actualDistributionDir) {
|
||||||
|
Write-Error "Could not find Maven distribution directory in extracted archive"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||||
|
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||||
|
try {
|
||||||
|
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||||
|
} catch {
|
||||||
|
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||||
|
Write-Error "fail to move MAVEN_HOME"
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||||
90
chat/pom.xml
Normal file
90
chat/pom.xml
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>top.krcia</groupId>
|
||||||
|
<artifactId>elysia-server</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<groupId>top.krcia</groupId>
|
||||||
|
<artifactId>chat</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>chat</name>
|
||||||
|
<description>chat</description>
|
||||||
|
<url/>
|
||||||
|
<licenses>
|
||||||
|
<license/>
|
||||||
|
</licenses>
|
||||||
|
<developers>
|
||||||
|
<developer/>
|
||||||
|
</developers>
|
||||||
|
<scm>
|
||||||
|
<connection/>
|
||||||
|
<developerConnection/>
|
||||||
|
<tag/>
|
||||||
|
<url/>
|
||||||
|
</scm>
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-core</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-extension</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>top.krcia</groupId>
|
||||||
|
<artifactId>utils</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>top.krcia</groupId>
|
||||||
|
<artifactId>result</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package top.krcia.elysiaserver.chat;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@MapperScan(basePackages = {"top.krcia.elysiaserver.chat.dao"})
|
||||||
|
public class ChatApplication{
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ChatApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.annotate;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Metadata {
|
||||||
|
}
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.aspect;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||||
|
import top.krcia.elysiaserver.chat.annotate.Metadata;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class MetadataAspect implements ResponseBodyAdvice<Object> {
|
||||||
|
|
||||||
|
@Value("${elysia-authorization.metadata-save.agent-url}")
|
||||||
|
private String agentUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
// 对所有Controller返回值都生效
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
|
||||||
|
Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
||||||
|
org.springframework.http.server.ServerHttpRequest request,
|
||||||
|
org.springframework.http.server.ServerHttpResponse response) {
|
||||||
|
|
||||||
|
if (body == null) return null;
|
||||||
|
|
||||||
|
processObject(body);
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processObject(Object obj) {
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
Class<?> clazz = obj.getClass();
|
||||||
|
|
||||||
|
// 处理 R<T>,递归 data
|
||||||
|
if (obj instanceof R<?>) {
|
||||||
|
Object data = ((R<?>) obj).getData();
|
||||||
|
processObject(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理集合
|
||||||
|
if (obj instanceof Collection<?>) {
|
||||||
|
for (Object item : (Collection<?>) obj) {
|
||||||
|
processObject(item);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 Map,只递归 value
|
||||||
|
if (obj instanceof Map<?, ?> map) {
|
||||||
|
for (Object value : map.values()) {
|
||||||
|
processObject(value);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础类型 / 包装类 / String / 枚举:不递归
|
||||||
|
if (isPrimitiveOrWrapper(clazz) || clazz.isEnum()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过 JDK 内置类,不反射它的字段
|
||||||
|
if (isJdkClass(clazz)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反射处理 POJO
|
||||||
|
Field[] fields = clazz.getDeclaredFields();
|
||||||
|
for (Field field : fields) {
|
||||||
|
try {
|
||||||
|
field.setAccessible(true);
|
||||||
|
Object value = field.get(obj);
|
||||||
|
|
||||||
|
// 只处理 @Metadata 的 String 字段
|
||||||
|
if (field.isAnnotationPresent(Metadata.class)
|
||||||
|
&& value instanceof String str
|
||||||
|
&& !str.isEmpty()
|
||||||
|
&& !str.startsWith(agentUrl)) {
|
||||||
|
field.set(obj, agentUrl + str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理子字段
|
||||||
|
processObject(value);
|
||||||
|
} catch (IllegalAccessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPrimitiveOrWrapper(Class<?> clazz) {
|
||||||
|
return clazz.isPrimitive()
|
||||||
|
|| clazz.equals(String.class)
|
||||||
|
|| clazz.equals(Integer.class)
|
||||||
|
|| clazz.equals(Long.class)
|
||||||
|
|| clazz.equals(Boolean.class)
|
||||||
|
|| clazz.equals(Double.class)
|
||||||
|
|| clazz.equals(Float.class)
|
||||||
|
|| clazz.equals(Short.class)
|
||||||
|
|| clazz.equals(Byte.class)
|
||||||
|
|| clazz.equals(Character.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isJdkClass(Class<?> clazz) {
|
||||||
|
String name = clazz.getName();
|
||||||
|
return name.startsWith("java.")
|
||||||
|
|| name.startsWith("javax.")
|
||||||
|
|| name.startsWith("jakarta.")
|
||||||
|
|| name.startsWith("sun.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync(proxyTargetClass = true)
|
||||||
|
@EnableScheduling
|
||||||
|
|
||||||
|
public class AsyncConfig {
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.config;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.DbType;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||||
|
import org.apache.ibatis.plugin.Interceptor;
|
||||||
|
import org.apache.ibatis.session.SqlSessionFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableScheduling
|
||||||
|
public class MyBatisPlusConfig {
|
||||||
|
@Bean
|
||||||
|
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||||
|
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||||
|
// 新版 MP 分页插件
|
||||||
|
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
|
||||||
|
return interceptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||||
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||||
|
import top.krcia.elysiaserver.utils.Kedis;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RedisTemplateConfig {
|
||||||
|
|
||||||
|
// 专用于 Redis 的 ObjectMapper
|
||||||
|
@Bean("redisObjectMapper")
|
||||||
|
public ObjectMapper redisObjectMapper() {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.registerModule(new JavaTimeModule());
|
||||||
|
// Redis 序列化可以保留时间戳(根据需求调整)
|
||||||
|
objectMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
return objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisTemplate<String, Object> redisTemplate(
|
||||||
|
RedisConnectionFactory redisConnectionFactory,
|
||||||
|
@Qualifier("redisObjectMapper") ObjectMapper redisObjectMapper) {
|
||||||
|
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||||
|
template.setConnectionFactory(redisConnectionFactory);
|
||||||
|
template.setKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setValueSerializer(redisSerializer());
|
||||||
|
template.setHashValueSerializer(redisSerializer());
|
||||||
|
template.afterPropertiesSet();
|
||||||
|
Kedis.init(template);
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||||
|
return builder.build(); // 自动应用 application.yml 的配置
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisSerializer<Object> redisSerializer() {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||||
|
// 必须设置,否则无法将JSON转化为对象,会转化成Map类型
|
||||||
|
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||||
|
|
||||||
|
// 自定义ObjectMapper的时间处理模块
|
||||||
|
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||||
|
|
||||||
|
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||||
|
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||||
|
|
||||||
|
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||||
|
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||||
|
|
||||||
|
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||||
|
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||||
|
|
||||||
|
objectMapper.registerModule(javaTimeModule);
|
||||||
|
|
||||||
|
// 禁用将日期序列化为时间戳的行为
|
||||||
|
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
|
||||||
|
//创建JSON序列化器
|
||||||
|
return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
import org.springframework.web.servlet.resource.ResourceResolver;
|
||||||
|
import org.springframework.web.servlet.resource.ResourceResolverChain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
//重写WebMvcConfigurer实现全局跨域配置
|
||||||
|
@Configuration
|
||||||
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Value("${elysia-authorization.enable-cors}")
|
||||||
|
private boolean enableCors;
|
||||||
|
@Value("${elysia-authorization.enable-api-doc}")
|
||||||
|
private boolean enableApiDoc;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
if (enableCors) {
|
||||||
|
registry.addMapping("/api/**")
|
||||||
|
.allowCredentials(true)
|
||||||
|
.allowedOriginPatterns("*") // 使用 allowedOriginPatterns 代替 allowedOrigins
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.exposedHeaders("*");
|
||||||
|
registry.addMapping("/oss/**")
|
||||||
|
.allowCredentials(true)
|
||||||
|
.allowedOriginPatterns("*") // 使用 allowedOriginPatterns 代替 allowedOrigins
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.exposedHeaders("*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
if (enableApiDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 屏蔽static下的某个文件夹,例如屏蔽static/private/
|
||||||
|
registry.addResourceHandler("/doc/**")
|
||||||
|
.addResourceLocations("classpath:/static/doc/")
|
||||||
|
.resourceChain(true)
|
||||||
|
.addResolver(new ResourceResolver() {
|
||||||
|
@Override
|
||||||
|
public Resource resolveResource(HttpServletRequest request, String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||||
|
return new ClassPathResource("static/prevent/denied.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolveUrlPath(String resourcePath, List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||||
|
return null; // 返回null表示拒绝访问
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.config.socket;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.server.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.socket.WebSocketHandler;
|
||||||
|
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||||
|
import top.krcia.elysiaserver.chat.dao.SysUserDao;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.SysUser;
|
||||||
|
import top.krcia.elysiaserver.utils.JWTGenerator;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class ChatSocketAuthInterceptor implements HandshakeInterceptor {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysUserDao sysUserDao;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
|
||||||
|
WebSocketHandler wsHandler, Map<String, Object> attributes) {
|
||||||
|
|
||||||
|
URI uri = request.getURI();
|
||||||
|
String query = uri.getQuery();
|
||||||
|
String token = getQueryParam(query, "authorization");
|
||||||
|
Long userId = JWTGenerator.getUserId(token);
|
||||||
|
SysUser sysUser = sysUserDao.selectById(userId);
|
||||||
|
if (sysUser == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
attributes.put("userId", userId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
|
||||||
|
// 握手后逻辑,通常不需要处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单解析 query 参数
|
||||||
|
private String getQueryParam(String query, String key) {
|
||||||
|
if (query == null) return null;
|
||||||
|
String[] pairs = query.split("&");
|
||||||
|
for (String pair : pairs) {
|
||||||
|
String[] kv = pair.split("=");
|
||||||
|
if (kv.length == 2 && kv[0].equals(key)) return kv[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.config.socket;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||||
|
import top.krcia.elysiaserver.chat.task.ChatSocketHandler;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSocket
|
||||||
|
@Slf4j
|
||||||
|
public class WebSocketConfig implements WebSocketConfigurer {
|
||||||
|
@Value("${server.port}")
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ChatSocketHandler chatSocketHandler;
|
||||||
|
@Resource
|
||||||
|
private ChatSocketAuthInterceptor chatSocketAuthInterceptor;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||||
|
// 注册原有的认证端点
|
||||||
|
String chatUrl = String.format("/socket/chat");
|
||||||
|
registry.addHandler(chatSocketHandler, chatUrl)
|
||||||
|
.addInterceptors(chatSocketAuthInterceptor)
|
||||||
|
.setAllowedOriginPatterns("*");
|
||||||
|
|
||||||
|
// 打印启动信息
|
||||||
|
System.out.println("""
|
||||||
|
__ __ ___ ____ _____ ___ __ __ _ ___ ______\s
|
||||||
|
| |__| | / _]| \\ / ___/ / \\ / ]| |/ ] / _]| |
|
||||||
|
| | | | / [_ | o )( \\_ | | / / | ' / / [_ | |
|
||||||
|
| | | || _]| | \\__ || O | / / | \\ | _]|_| |_|
|
||||||
|
| ` ' || [_ | O | / \\ || |/ \\_ | \\| [_ | | \s
|
||||||
|
\\ / | || | \\ || |\\ || . || | | | \s
|
||||||
|
\\_/\\_/ |_____||_____| \\___| \\___/ \\____||__|\\_||_____| |__| \s
|
||||||
|
""");
|
||||||
|
|
||||||
|
log.info(String.format("WebSocket started on port %d (ws)", port));
|
||||||
|
log.info(String.format("WebSocket Endpoints: [%s]", chatUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.controller;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import top.krcia.elysiaserver.chat.controller.base.ChatBaseController;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.CreateRobotDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.HistoryListParam;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.PParamDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.ChatRoomVo;
|
||||||
|
import top.krcia.elysiaserver.chat.service.ChatService;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天管理
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class ChatController extends ChatBaseController {
|
||||||
|
@Resource
|
||||||
|
private ChatService chatService;
|
||||||
|
/**
|
||||||
|
* 创建聊天
|
||||||
|
*
|
||||||
|
* @param authorization
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/create")
|
||||||
|
public R<String> create(@RequestHeader("Authorization") String authorization, @RequestBody CreateRobotDto robotDto) {
|
||||||
|
return chatService.create(authorization, robotDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除聊天
|
||||||
|
*
|
||||||
|
* @param roomId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/del/{roomId}")
|
||||||
|
public R delete(@RequestHeader("Authorization") String authorization, @PathVariable String roomId) {
|
||||||
|
return chatService.delete(authorization, roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空聊天
|
||||||
|
*
|
||||||
|
* @param authorization
|
||||||
|
* @param roomId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/clear/{roomId}")
|
||||||
|
public R clearRecord(@RequestHeader("Authorization") String authorization, @PathVariable String roomId) {
|
||||||
|
return chatService.clearRecord(authorization, roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有聊天室
|
||||||
|
*
|
||||||
|
* @param authorization
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public R<List<ChatRoomVo>> list(@RequestHeader("Authorization") String authorization) {
|
||||||
|
return chatService.list(authorization);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息
|
||||||
|
*
|
||||||
|
* @param authorization
|
||||||
|
* @param roomId
|
||||||
|
* @param message
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/send/{roomId}")
|
||||||
|
public R sendMessage(@RequestHeader("Authorization") String authorization, @PathVariable String roomId, String message,String messageId) {
|
||||||
|
return chatService.sendMessage(authorization, roomId, message,messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天消息
|
||||||
|
*
|
||||||
|
* @param authorization
|
||||||
|
* @param roomId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/history/{roomId}")
|
||||||
|
public R chatRoomRecord(@RequestHeader("Authorization") String authorization, @PathVariable String roomId, PParamDto param) {
|
||||||
|
return chatService.chatRoomRecord(authorization, roomId, param);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 聊天消息(缓存)
|
||||||
|
*
|
||||||
|
* @param authorization
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/history-list")
|
||||||
|
public R chatRoomRecord(@RequestHeader("Authorization") String authorization, @RequestBody List<HistoryListParam> historyList) {
|
||||||
|
return chatService.chatRoomRecord(authorization,historyList);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.controller;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.servlet.HandlerMapping;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
@RequestMapping("/oss/")
|
||||||
|
@RestController
|
||||||
|
public class OSSController {
|
||||||
|
|
||||||
|
@Value("${elysia-authorization.metadata-save.path}")
|
||||||
|
private String metadataSavePath;
|
||||||
|
|
||||||
|
@GetMapping("/**")
|
||||||
|
public void oss(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
|
response.setContentType("image/png");
|
||||||
|
final String path =
|
||||||
|
request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
|
||||||
|
final String bestMatchingPattern =
|
||||||
|
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
|
||||||
|
File file = new File(Path.of(metadataSavePath, new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, path)).toString());
|
||||||
|
try (FileInputStream in = new FileInputStream(file);
|
||||||
|
ServletOutputStream out = response.getOutputStream()) {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int len;
|
||||||
|
while ((len = in.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.controller;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import top.krcia.elysiaserver.chat.controller.base.RobotBaseController;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.RobotDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.RobotFilterDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.ExcludeVo;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.RobotEditorVo;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.RobotFollowVo;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.RobotVo;
|
||||||
|
import top.krcia.elysiaserver.chat.service.RobotService;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
import top.krcia.elysiaserver.utils.P;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器人管理
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class RobotController extends RobotBaseController {
|
||||||
|
@Resource
|
||||||
|
private RobotService robotService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建机器人
|
||||||
|
*
|
||||||
|
* @param robotDto
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/create")
|
||||||
|
public R createRobot(@RequestHeader("Authorization") String authorization, RobotDto robotDto) {
|
||||||
|
return robotService.create(authorization, robotDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器人列表
|
||||||
|
*
|
||||||
|
* @param robotFilterDto
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public R<P<RobotVo>> robotList(@RequestHeader("Authorization") String authorization, RobotFilterDto robotFilterDto) {
|
||||||
|
return robotService.rebootList(authorization, robotFilterDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑机器人
|
||||||
|
*
|
||||||
|
* @param robotDto
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/editor/{robotId}")
|
||||||
|
public R editorRobot(@RequestHeader("Authorization") String authorization, @PathVariable Long robotId,@RequestBody RobotEditorVo robotDto) {
|
||||||
|
return robotService.editor(authorization, robotId, robotDto);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 删除机器人
|
||||||
|
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/delete/{robotId}")
|
||||||
|
public R deleteRobot(@RequestHeader("Authorization") String authorization, @PathVariable Long robotId) {
|
||||||
|
return robotService.delete(authorization, robotId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注列表
|
||||||
|
*
|
||||||
|
* @param authorization
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("follow-list")
|
||||||
|
public R<ExcludeVo<Long,RobotFollowVo>> followList(@RequestHeader("Authorization") String authorization, @RequestParam(value = "cacheId", required = false) List<Long> cacheId) {
|
||||||
|
return robotService.followList(authorization,cacheId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关注机器人
|
||||||
|
*
|
||||||
|
* @param authorization
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("follow/{robotId}")
|
||||||
|
public R follow(@RequestHeader("Authorization") String authorization, @PathVariable Long robotId) {
|
||||||
|
return robotService.followOption(authorization, robotId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消关注机器人
|
||||||
|
*
|
||||||
|
* @param authorization
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("unfollow/{robotId}")
|
||||||
|
public R followList(@RequestHeader("Authorization") String authorization, @PathVariable Long robotId) {
|
||||||
|
return robotService.followOption(authorization, robotId, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.controller;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import top.krcia.elysiaserver.chat.controller.base.RobotBaseController;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.RobotTemplateVo;
|
||||||
|
import top.krcia.elysiaserver.chat.service.TemplateService;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复模板
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class TemplateController extends RobotBaseController {
|
||||||
|
@Resource
|
||||||
|
private TemplateService templateService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有模板
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/template")
|
||||||
|
public R<List<RobotTemplateVo>> allList(){
|
||||||
|
return templateService.allTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.controller.base;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
|
@RequestMapping("/chat")
|
||||||
|
public class ChatBaseController {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.controller.base;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
|
@RequestMapping("/robot")
|
||||||
|
public class RobotBaseController {
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.ChatRoom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (ChatRoom)表数据库访问层
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-11 11:14:00
|
||||||
|
*/
|
||||||
|
public interface ChatRoomDao extends BaseMapper<ChatRoom> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
import top.krcia.elysiaserver.chat.dao.provider.ChatRoomRecordProvider;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.ChatRoomRecord;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (ChatRoomRecord)表数据库访问层
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-11 11:14:00
|
||||||
|
*/
|
||||||
|
public interface ChatRoomRecordDao extends BaseMapper<ChatRoomRecord> {
|
||||||
|
|
||||||
|
@Update("""
|
||||||
|
CREATE TABLE chat_room_record_${roomId} LIKE chat_room_record
|
||||||
|
""")
|
||||||
|
int createRecord(String roomId);
|
||||||
|
@Update("""
|
||||||
|
DROP TABLE chat_room_record_${roomId}
|
||||||
|
""")
|
||||||
|
int deleteRecord(String roomId);
|
||||||
|
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM chat_room_record_${roomId} ORDER BY chat_date DESC limit 1
|
||||||
|
""")
|
||||||
|
ChatRoomRecord getlastRecord(String roomId);
|
||||||
|
@Insert("""
|
||||||
|
INSERT INTO chat_room_record_${roomId}(message_id, chat_date,`from`,message) VALUES(#{messageId},#{chatDate},#{from},#{message})
|
||||||
|
""")
|
||||||
|
int addMessage(String messageId, String roomId, int from, String message, Date chatDate);
|
||||||
|
@Insert("""
|
||||||
|
DELETE FROM chat_room_record_${roomId}
|
||||||
|
""")
|
||||||
|
int clearMessage(String roomId);
|
||||||
|
@SelectProvider(type = ChatRoomRecordProvider.class, method = "selectPage")
|
||||||
|
IPage<ChatRoomRecord> selectPage(IPage<ChatRoomRecord> page,
|
||||||
|
@Param(Constants.WRAPPER) QueryWrapper<?> ew,
|
||||||
|
@Param("roomId") String roomId);
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM chat_room_record_${roomId} WHERE chat_date>(SELECT chat_date FROM chat_room_record_${roomId} WHERE message_id = #{messageId}) ORDER BY chat_date DESC
|
||||||
|
""")
|
||||||
|
List<ChatRoomRecord> selectCache(@Param("roomId") String roomId,
|
||||||
|
@Param("messageId") String messageId);
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.SelectProvider;
|
||||||
|
import top.krcia.elysiaserver.chat.dao.provider.RobotProvider;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.Robot;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.RobotListVo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Robot)表数据库访问层
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-10 21:56:08
|
||||||
|
*/
|
||||||
|
public interface RobotDao extends BaseMapper<Robot> {
|
||||||
|
@SelectProvider(type = RobotProvider.class, method = "selectRobotWithFollow")
|
||||||
|
IPage<RobotListVo> selectRobotWithFollow(IPage<?> page, @Param(Constants.WRAPPER) QueryWrapper<?> ew,@Param("userId") Long userId);
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Delete;
|
||||||
|
import org.apache.ibatis.annotations.Insert;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.RobotFollow;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.Robot;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (RobotFollow)表数据库访问层
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-12 13:58:35
|
||||||
|
*/
|
||||||
|
public interface RobotFollowDao extends BaseMapper<RobotFollow> {
|
||||||
|
@Select({
|
||||||
|
"<script>",
|
||||||
|
"SELECT r.* ",
|
||||||
|
"FROM robot_follow_${user_id} AS rf ",
|
||||||
|
"LEFT JOIN `robot` AS r ON rf.`robot_id` = r.`robot_id` ",
|
||||||
|
"<where>",
|
||||||
|
" r.status = 0", // 添加status条件
|
||||||
|
" <if test='cache_id != null and cache_id.size > 0'>",
|
||||||
|
" AND rf.robot_id NOT IN ",
|
||||||
|
" <foreach collection='cache_id' item='cid' open='(' separator=',' close=')'>",
|
||||||
|
" #{cid}",
|
||||||
|
" </foreach>",
|
||||||
|
" </if>",
|
||||||
|
"</where>",
|
||||||
|
"ORDER BY rf.time DESC",
|
||||||
|
"</script>"
|
||||||
|
})
|
||||||
|
List<Robot> followList(@Param("user_id") Long userId,
|
||||||
|
@Param("cache_id") List<Long> cacheId);
|
||||||
|
|
||||||
|
@Insert("""
|
||||||
|
INSERT INTO robot_follow_${user_id}(robot_id) VALUES(#{robot_id})
|
||||||
|
""")
|
||||||
|
int follow(@Param("user_id") Long userId, @Param("robot_id") Long robotId);
|
||||||
|
|
||||||
|
@Delete("""
|
||||||
|
DELETE FROM robot_follow_${user_id} WHERE robot_id =#{robot_id}
|
||||||
|
""")
|
||||||
|
int unfollow(@Param("user_id") Long userId, @Param("robot_id") Long robotId);
|
||||||
|
|
||||||
|
@Select("""
|
||||||
|
SELECT COUNT(1) FROM robot_follow_${user_id} WHERE robot_id =#{robot_id}
|
||||||
|
""")
|
||||||
|
Long selectCount(@Param("user_id") Long userId, @Param("robot_id") Long robotId);
|
||||||
|
@Select({
|
||||||
|
"<script>",
|
||||||
|
"SELECT t.id ",
|
||||||
|
"FROM (",
|
||||||
|
" <foreach collection='robotIds' item='id' separator='union all'>",
|
||||||
|
" SELECT #{id} AS id, 0 AS status", // 假设status在子查询中可用
|
||||||
|
" </foreach>",
|
||||||
|
") t ",
|
||||||
|
"WHERE NOT EXISTS (",
|
||||||
|
" SELECT 1 FROM robot_follow_${userId} rf WHERE rf.robot_id = t.id",
|
||||||
|
") ",
|
||||||
|
"OR t.status = 1",
|
||||||
|
"</script>"
|
||||||
|
})
|
||||||
|
List<Long> unfollowIdList(@Param("userId") Long userId, @Param("robotIds") List<Long> robotIds);
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.dao;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.RobotTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (RobotTemplate)表数据库访问层
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-28 10:02:31
|
||||||
|
*/
|
||||||
|
public interface RobotTemplateDao extends BaseMapper<RobotTemplate> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量新增数据(MyBatis原生foreach方法)
|
||||||
|
*
|
||||||
|
* @param entities List<RobotTemplate> 实例对象列表
|
||||||
|
* @return 影响行数
|
||||||
|
*/
|
||||||
|
int insertBatch(@Param("entities") List<RobotTemplate> entities);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量新增或按主键更新数据(MyBatis原生foreach方法)
|
||||||
|
*
|
||||||
|
* @param entities List<RobotTemplate> 实例对象列表
|
||||||
|
* @return 影响行数
|
||||||
|
* @throws org.springframework.jdbc.BadSqlGrammarException 入参是空List的时候会抛SQL语句错误的异常,请自行校验入参
|
||||||
|
*/
|
||||||
|
int insertOrUpdateBatch(@Param("entities") List<RobotTemplate> entities);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.SysUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SysUser)表数据库访问层
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-09 23:01:29
|
||||||
|
*/
|
||||||
|
public interface SysUserDao extends BaseMapper<SysUser> {
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM sys_user WHERE user_name=#{username}
|
||||||
|
""")
|
||||||
|
SysUser getUserByUsername(@Param("username") String username);
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM sys_user WHERE email=#{email}
|
||||||
|
""")
|
||||||
|
SysUser getUserByEmail(@Param("email") String email);
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM sys_user WHERE email=#{email} OR user_name=#{username}
|
||||||
|
""")
|
||||||
|
SysUser findUser(@Param("email") String email,@Param("username") String username);
|
||||||
|
@Select("""
|
||||||
|
SELECT * FROM sys_user WHERE email=#{account} OR user_name=#{account}
|
||||||
|
""")
|
||||||
|
SysUser findUserByAccount(@Param("account") String account);
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.dao.provider;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||||
|
import org.apache.ibatis.builder.annotation.ProviderMethodResolver;
|
||||||
|
import org.apache.ibatis.jdbc.SQL;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ChatRoomRecordProvider implements ProviderMethodResolver {
|
||||||
|
|
||||||
|
public String selectPage(Map<String, Object> params) {
|
||||||
|
String roomId = (String) params.get("roomId");
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
sql.append("SELECT * FROM chat_room_record_"+roomId);
|
||||||
|
// 获取QueryWrapper
|
||||||
|
Object ewObj = params.get("ew");
|
||||||
|
if (ewObj instanceof QueryWrapper<?> wrapper) {
|
||||||
|
String customSqlSegment = wrapper.getCustomSqlSegment();
|
||||||
|
if (customSqlSegment != null && !customSqlSegment.isEmpty()) {
|
||||||
|
sql.append(" ").append(customSqlSegment).append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sql.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.dao.provider;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class RobotProvider {
|
||||||
|
public String selectRobotWithFollow(Map<String, Object> params) {
|
||||||
|
Long userId = (Long) params.get("userId");
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
|
||||||
|
sql.append("""
|
||||||
|
SELECT r.*,
|
||||||
|
CASE
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM robot_follow_${userId} AS rf
|
||||||
|
WHERE rf.robot_id = r.robot_id
|
||||||
|
) THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END AS is_follow,
|
||||||
|
rt.template_name AS template,
|
||||||
|
sy.nickname AS creator_username
|
||||||
|
FROM robot AS r
|
||||||
|
LEFT JOIN robot_template AS rt
|
||||||
|
ON r.template_id=rt.template_id
|
||||||
|
LEFT JOIN sys_user AS sy
|
||||||
|
ON r.creator_id = sy.user_id
|
||||||
|
""".replace("${userId}", userId.toString()));
|
||||||
|
Object ewObj = params.get("ew");
|
||||||
|
if (ewObj instanceof QueryWrapper<?> wrapper) {
|
||||||
|
String customSqlSegment = wrapper.getCustomSqlSegment();
|
||||||
|
if (customSqlSegment != null && !customSqlSegment.isEmpty()) {
|
||||||
|
sql.append(" ").append(customSqlSegment).append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CreateRobotDto {
|
||||||
|
/**
|
||||||
|
* 房间使用引擎
|
||||||
|
*/
|
||||||
|
private String engine;
|
||||||
|
/**
|
||||||
|
* 房间名称
|
||||||
|
*/
|
||||||
|
private String roomName;
|
||||||
|
/**
|
||||||
|
* 机器人ID
|
||||||
|
*/
|
||||||
|
private Long robotId;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class HistoryListParam {
|
||||||
|
/**
|
||||||
|
* 聊天室ID
|
||||||
|
*/
|
||||||
|
private String roomId;
|
||||||
|
/**
|
||||||
|
* 消息ID
|
||||||
|
*/
|
||||||
|
private String messageId;
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PParamDto {
|
||||||
|
/**
|
||||||
|
* 页面大小
|
||||||
|
* @mock 10
|
||||||
|
*/
|
||||||
|
private int size;
|
||||||
|
/**
|
||||||
|
* 页码
|
||||||
|
* @mock 1
|
||||||
|
*/
|
||||||
|
private int current;
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Robot)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-10 21:56:09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RobotDto {
|
||||||
|
/**
|
||||||
|
* 机器人头像
|
||||||
|
*/
|
||||||
|
private MultipartFile avatar;
|
||||||
|
/**
|
||||||
|
* 机器人名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String describe;
|
||||||
|
/**
|
||||||
|
* 系统提示词
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
/**
|
||||||
|
* 是否私有
|
||||||
|
*/
|
||||||
|
private boolean isPrivate;
|
||||||
|
/**
|
||||||
|
* 回复模板
|
||||||
|
*/
|
||||||
|
private String templateId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import top.krcia.elysiaserver.chat.annotate.Metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Robot)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-10 21:56:09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RobotFilterDto extends PParamDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否私有,0公开,1私有
|
||||||
|
*/
|
||||||
|
private boolean isPrivate;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.pojo;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (ChatRoom)表实体类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-11 11:14:00
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@Data
|
||||||
|
public class ChatRoom extends Model<ChatRoom> {
|
||||||
|
//房间ID
|
||||||
|
@TableId
|
||||||
|
private String roomId;
|
||||||
|
//房间名称
|
||||||
|
private String roomName;
|
||||||
|
//用户ID
|
||||||
|
private Long sysUserId;
|
||||||
|
//机器人ID
|
||||||
|
private Long robotId;
|
||||||
|
//聊天室头像
|
||||||
|
private String roomImage;
|
||||||
|
//最后一次聊天时间
|
||||||
|
private Date lastChatTime;
|
||||||
|
//聊天引擎
|
||||||
|
private String engine;
|
||||||
|
|
||||||
|
public ChatRoom(String roomId,String roomName, Long sysUserId, Long robotId, String roomImage,String engine) {
|
||||||
|
this.roomId = roomId;
|
||||||
|
this.roomName = roomName;
|
||||||
|
this.sysUserId = sysUserId;
|
||||||
|
this.robotId = robotId;
|
||||||
|
this.roomImage = roomImage;
|
||||||
|
this.engine = engine;
|
||||||
|
this.lastChatTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatRoom(String roomId) {
|
||||||
|
this.roomId = roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatRoom() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主键值
|
||||||
|
*
|
||||||
|
* @return 主键值
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Serializable pkVal() {
|
||||||
|
return this.roomId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.pojo;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (ChatRoomRecord)表实体类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-11 11:14:00
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@Data
|
||||||
|
public class ChatRoomRecord extends Model<ChatRoomRecord> {
|
||||||
|
@TableId
|
||||||
|
//消息ID
|
||||||
|
private String messageId;
|
||||||
|
//聊天时间
|
||||||
|
private Date chatDate;
|
||||||
|
//消息所属者(0:机器人|1用户)
|
||||||
|
private Integer from;
|
||||||
|
//消息内容
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.pojo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DeepSeekParam {
|
||||||
|
private String model;
|
||||||
|
private List<MessagesDTO> messages;
|
||||||
|
private Boolean stream = false;
|
||||||
|
private double temperature = 1.8;
|
||||||
|
|
||||||
|
public DeepSeekParam(String model,String robotTemplate,double temperature) {
|
||||||
|
messages = new ArrayList<>();
|
||||||
|
MessagesDTO messagesDTO = new MessagesDTO();
|
||||||
|
messagesDTO.setRole("system");
|
||||||
|
messagesDTO.setContent(robotTemplate);
|
||||||
|
messages.add(messagesDTO);
|
||||||
|
this.model = model;
|
||||||
|
this.temperature = temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSystemPrompt(String systemPrompt) {
|
||||||
|
MessagesDTO messagesDTO = new MessagesDTO();
|
||||||
|
messagesDTO.setRole("system");
|
||||||
|
messagesDTO.setContent(systemPrompt);
|
||||||
|
messages.add(messagesDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(List<ChatRoomRecord> message, String role) {
|
||||||
|
List<MessagesDTO> messagesDTOS = new ArrayList<>();
|
||||||
|
for (ChatRoomRecord crr : message) {
|
||||||
|
MessagesDTO messagesDTO = new MessagesDTO(crr.getFrom() == 0 ? "assistant" : "user", (crr.getFrom() != 0?role+":":"")+crr.getMessage());
|
||||||
|
messagesDTOS.add(messagesDTO);
|
||||||
|
}
|
||||||
|
messages.addAll(messagesDTOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class MessagesDTO {
|
||||||
|
private String role;
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
public MessagesDTO(String role, String content) {
|
||||||
|
this.role = role;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessagesDTO() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.pojo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ReplyQueue {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Long userId;
|
||||||
|
/**
|
||||||
|
* 房间ID
|
||||||
|
*/
|
||||||
|
private String roomId;
|
||||||
|
/**
|
||||||
|
* 聊天内容
|
||||||
|
*/
|
||||||
|
private List<ChatRoomRecord> message;
|
||||||
|
/**
|
||||||
|
* APIkey
|
||||||
|
*/
|
||||||
|
private String key;
|
||||||
|
/**
|
||||||
|
* 模型
|
||||||
|
*/
|
||||||
|
private String model;
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
/**
|
||||||
|
* 系统提示词
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
/**
|
||||||
|
* 回复模板
|
||||||
|
*/
|
||||||
|
private String robotTemplate;
|
||||||
|
/**
|
||||||
|
* 温度
|
||||||
|
*/
|
||||||
|
private double temperature;
|
||||||
|
|
||||||
|
public ReplyQueue(Long userId,String roomId, List<ChatRoomRecord> message, String key, String model, String name, String avatar, String systemPrompt, String robotTemplate,double temperature) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.roomId = roomId;
|
||||||
|
this.message = message;
|
||||||
|
this.key = key;
|
||||||
|
this.model = model;
|
||||||
|
this.name = name;
|
||||||
|
this.avatar = avatar;
|
||||||
|
this.systemPrompt = systemPrompt;
|
||||||
|
this.robotTemplate = robotTemplate;
|
||||||
|
this.temperature = temperature;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Robot)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-10 21:56:09
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@Data
|
||||||
|
public class Robot extends Model<Robot> {
|
||||||
|
//机器人ID
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long robotId;
|
||||||
|
//机器人头像
|
||||||
|
private String avatar;
|
||||||
|
//机器人名称
|
||||||
|
private String name;
|
||||||
|
//描述
|
||||||
|
@TableField("`describe`")
|
||||||
|
private String describe;
|
||||||
|
//系统提示词
|
||||||
|
private String systemPrompt;
|
||||||
|
//创建者
|
||||||
|
private Long creatorId;
|
||||||
|
//是否私有,0公开,1私有
|
||||||
|
private Integer isPrivate;
|
||||||
|
//回复模板
|
||||||
|
private String templateId;
|
||||||
|
//创建时间
|
||||||
|
private Date createDate;
|
||||||
|
|
||||||
|
public Robot( String avatar, String name, String describe, String systemPrompt, Long creatorId,String templateId, Integer isPrivate) {
|
||||||
|
this.avatar = avatar;
|
||||||
|
this.name = name;
|
||||||
|
this.describe = describe;
|
||||||
|
this.systemPrompt = systemPrompt;
|
||||||
|
this.creatorId = creatorId;
|
||||||
|
this.isPrivate = isPrivate;
|
||||||
|
this.templateId = templateId;
|
||||||
|
this.createDate = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Robot() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Robot(Long robotId) {
|
||||||
|
this.robotId = robotId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主键值
|
||||||
|
*
|
||||||
|
* @return 主键值
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Serializable pkVal() {
|
||||||
|
return this.robotId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (RobotFollow)表实体类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-12 13:58:35
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class RobotFollow extends Model<RobotFollow> {
|
||||||
|
//用户列表
|
||||||
|
private Long userId;
|
||||||
|
//机器人列表
|
||||||
|
private Long robotId;
|
||||||
|
|
||||||
|
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getRobotId() {
|
||||||
|
return robotId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRobotId(Long robotId) {
|
||||||
|
this.robotId = robotId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RobotFollow(Long userId, Long robotId) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.robotId = robotId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.pojo;
|
||||||
|
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (RobotTemplate)表实体类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-28 10:02:31
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@Data
|
||||||
|
public class RobotTemplate extends Model<RobotTemplate> {
|
||||||
|
//模板ID
|
||||||
|
@TableId
|
||||||
|
private String templateId;
|
||||||
|
//模板名称
|
||||||
|
private String templateName;
|
||||||
|
//模板内容
|
||||||
|
private String content;
|
||||||
|
//创建时间
|
||||||
|
private Date createTime;
|
||||||
|
//创建者ID
|
||||||
|
private Long creatorId;
|
||||||
|
//示例
|
||||||
|
private String example;
|
||||||
|
//温度
|
||||||
|
private Double temperature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主键值
|
||||||
|
*
|
||||||
|
* @return 主键值
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Serializable pkVal() {
|
||||||
|
return this.templateId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RobotTemplate() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public RobotTemplate(String templateId, String templateName, String content, Long creatorId,String example) {
|
||||||
|
this.templateId = templateId;
|
||||||
|
this.templateName = templateName;
|
||||||
|
this.content = content;
|
||||||
|
this.creatorId = creatorId;
|
||||||
|
this.example = example;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RobotTemplate(String templateId) {
|
||||||
|
this.templateId = templateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.pojo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SysUser)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-09 23:01:29
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@Data
|
||||||
|
public class SysUser extends Model<SysUser> {
|
||||||
|
//用户ID
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long userId;
|
||||||
|
//头像
|
||||||
|
private String avatar;
|
||||||
|
//姓名
|
||||||
|
private String name;
|
||||||
|
//昵称
|
||||||
|
private String nickname;
|
||||||
|
//用户名
|
||||||
|
private String userName;
|
||||||
|
//密码
|
||||||
|
private String password;
|
||||||
|
//状态:0启用,1禁用,-1删除
|
||||||
|
private Integer status;
|
||||||
|
//状态时间
|
||||||
|
private Date statusTime;
|
||||||
|
//状态时间
|
||||||
|
private String email;
|
||||||
|
//创建时间
|
||||||
|
private Date createTime;
|
||||||
|
//DeepseekApi
|
||||||
|
private String deepseekKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主键值
|
||||||
|
*
|
||||||
|
* @return 主键值
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Serializable pkVal() {
|
||||||
|
return this.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser(String avatar, String name, String nickname, String userName, String password, String email) {
|
||||||
|
this.avatar = avatar;
|
||||||
|
this.name = name;
|
||||||
|
this.nickname = nickname;
|
||||||
|
this.userName = userName;
|
||||||
|
this.password = password;
|
||||||
|
this.status = 0;
|
||||||
|
this.statusTime = new Date();
|
||||||
|
this.email = email;
|
||||||
|
this.createTime = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (ChatRoomRecord)表实体类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-11 11:14:00
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@Data
|
||||||
|
public class ChatRecordVo extends Model<ChatRecordVo> {
|
||||||
|
//消息ID
|
||||||
|
private String messageId;
|
||||||
|
//聊天时间
|
||||||
|
private Date chatDate;
|
||||||
|
//消息所属者(0:机器人|1用户)
|
||||||
|
private Integer from;
|
||||||
|
//消息内容
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ChatRecordVo(String messageId,Date chatDate, Integer from, String message) {
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.chatDate = chatDate;
|
||||||
|
this.from = from;
|
||||||
|
this.message = message;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
import top.krcia.elysiaserver.chat.annotate.Metadata;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (ChatRoom)表实体类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-11 11:14:00
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ChatRoomVo{
|
||||||
|
/**
|
||||||
|
* 房间ID
|
||||||
|
*/
|
||||||
|
private String roomId;
|
||||||
|
/**
|
||||||
|
* 房间名称
|
||||||
|
*/
|
||||||
|
private String roomName;
|
||||||
|
/**
|
||||||
|
* 聊天室名称
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
/**
|
||||||
|
* 聊天室头像
|
||||||
|
*/
|
||||||
|
@Metadata
|
||||||
|
private String roomImage;
|
||||||
|
/**
|
||||||
|
* 最后一次聊天时间
|
||||||
|
*/
|
||||||
|
private Date lastChatTime;
|
||||||
|
/**
|
||||||
|
* 最后一次聊天消息
|
||||||
|
*/
|
||||||
|
private String lastChatMessage;
|
||||||
|
/**
|
||||||
|
*房间使用引擎
|
||||||
|
*/
|
||||||
|
private String engine;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ExcludeVo<I,T> {
|
||||||
|
private List<I> excludeIdList;
|
||||||
|
private List<T> data;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.ChatRoomRecord;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class HistoryVo extends HashMap<String, List<ChatRoomRecord>> {
|
||||||
|
public void add(String roomId, List<ChatRoomRecord> chatRoomRecord) {
|
||||||
|
super.put(roomId, chatRoomRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import top.krcia.elysiaserver.chat.annotate.Metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Robot)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-10 21:56:09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RobotEditorVo {
|
||||||
|
/**
|
||||||
|
* 系统提示词
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
/**
|
||||||
|
* 是否私有
|
||||||
|
*/
|
||||||
|
private boolean isPrivate;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import top.krcia.elysiaserver.chat.annotate.Metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Robot)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-10 21:56:09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RobotFollowVo {
|
||||||
|
/**
|
||||||
|
* 机器人ID
|
||||||
|
*/
|
||||||
|
private Long robotId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器人头像
|
||||||
|
*/
|
||||||
|
@Metadata
|
||||||
|
private String avatar;
|
||||||
|
/**
|
||||||
|
* 机器人名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简介
|
||||||
|
*/
|
||||||
|
private String describe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简介
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
/**
|
||||||
|
* 是否私有,0公开,1私有
|
||||||
|
*/
|
||||||
|
private boolean isPrivate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为创建者
|
||||||
|
*/
|
||||||
|
private boolean isCreator;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Robot)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-10 21:56:09
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@Data
|
||||||
|
public class RobotListVo extends Model<RobotListVo> {
|
||||||
|
//机器人ID
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long robotId;
|
||||||
|
//机器人头像
|
||||||
|
private String avatar;
|
||||||
|
//机器人名称
|
||||||
|
private String name;
|
||||||
|
//描述
|
||||||
|
@TableField("`describe`")
|
||||||
|
private String describe;
|
||||||
|
//系统提示词
|
||||||
|
private String systemPrompt;
|
||||||
|
//创建者
|
||||||
|
private Long creatorId;
|
||||||
|
//是否私有,0公开,1私有
|
||||||
|
private Integer isPrivate;
|
||||||
|
//创建者名称
|
||||||
|
private String creatorUsername;
|
||||||
|
//回复模板
|
||||||
|
private String template;
|
||||||
|
//创建时间
|
||||||
|
private Date createDate;
|
||||||
|
//是否关注
|
||||||
|
private boolean isFollow;
|
||||||
|
|
||||||
|
public RobotListVo(Long robotId) {
|
||||||
|
this.robotId = robotId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主键值
|
||||||
|
*
|
||||||
|
* @return 主键值
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Serializable pkVal() {
|
||||||
|
return this.robotId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (RobotTemplate)表实体类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-28 10:02:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RobotTemplateVo{
|
||||||
|
/**
|
||||||
|
* 模板ID
|
||||||
|
*/
|
||||||
|
private String templateId;
|
||||||
|
/**
|
||||||
|
* 模板名称
|
||||||
|
*/
|
||||||
|
private String templateName;
|
||||||
|
/**
|
||||||
|
* 示例
|
||||||
|
*/
|
||||||
|
private String example;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
import top.krcia.elysiaserver.chat.annotate.Metadata;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Robot)表实体类
|
||||||
|
*
|
||||||
|
* @author makejava
|
||||||
|
* @since 2025-09-10 21:56:09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RobotVo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器人ID
|
||||||
|
*/
|
||||||
|
|
||||||
|
private Long robotId;
|
||||||
|
/**
|
||||||
|
* 机器人头像
|
||||||
|
*/
|
||||||
|
@Metadata
|
||||||
|
private String avatar;
|
||||||
|
/**
|
||||||
|
* 机器人名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String describe;
|
||||||
|
/**
|
||||||
|
* 系统提示词
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
/**
|
||||||
|
* 是否已关注
|
||||||
|
*/
|
||||||
|
private boolean follow;
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
private Long creatorId;
|
||||||
|
/**
|
||||||
|
* 创建者名称
|
||||||
|
*/
|
||||||
|
private String creatorUsername;
|
||||||
|
/**
|
||||||
|
* 回复模板
|
||||||
|
*/
|
||||||
|
private String template;
|
||||||
|
/**
|
||||||
|
* 是否私有,0公开,1私有
|
||||||
|
*/
|
||||||
|
private boolean isPrivate;
|
||||||
|
|
||||||
|
public void setIsPrivate(Integer isPrivate) {
|
||||||
|
this.isPrivate = isPrivate == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.entity.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (ChatRoom)表实体类
|
||||||
|
*
|
||||||
|
* @author admin
|
||||||
|
* @since 2025-09-11 11:14:00
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WsChatVo {
|
||||||
|
//房间ID
|
||||||
|
private String roomId;
|
||||||
|
//名称
|
||||||
|
private String name;
|
||||||
|
//头像
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
private String messageId;
|
||||||
|
//语言
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
|
||||||
|
public WsChatVo(String roomId, String name, String avatar,String messageId, String content) {
|
||||||
|
this.roomId = roomId;
|
||||||
|
this.name = name;
|
||||||
|
this.avatar = avatar;
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WsChatVo(String roomId) {
|
||||||
|
this.roomId = roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WsChatVo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.service;
|
||||||
|
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.CreateRobotDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.HistoryListParam;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.PParamDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.ChatRoomVo;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ChatService {
|
||||||
|
public R create(String authorization, CreateRobotDto robotDto);
|
||||||
|
public R delete(String authorization,String roomId);
|
||||||
|
public R sendMessage(String authorization,String roomId,String message,String messageId);
|
||||||
|
public R chatRoomRecord(String authorization, String roomId, PParamDto param);
|
||||||
|
public R chatRoomRecord(String authorization, List<HistoryListParam> historyList);
|
||||||
|
public R clearRecord(String authorization, String roomId);
|
||||||
|
public R<List<ChatRoomVo>> list(String authorization);
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.service;
|
||||||
|
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.RobotDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.RobotFilterDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.ExcludeVo;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.RobotEditorVo;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.RobotFollowVo;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.RobotVo;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
import top.krcia.elysiaserver.utils.P;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface RobotService {
|
||||||
|
R create(String authorization, RobotDto robotDto);
|
||||||
|
R editor(String authorization,Long robotId, RobotEditorVo robotDto);
|
||||||
|
R delete(String authorization,Long robotId);
|
||||||
|
R<P<RobotVo>> rebootList(String authorization,RobotFilterDto robotFilterDto);
|
||||||
|
R<ExcludeVo<Long,RobotFollowVo>> followList(String authorization, List<Long> cacheId);
|
||||||
|
R<List<RobotFollowVo>> followOption(String authorization,Long robotId,boolean follow);
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.service;
|
||||||
|
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.RobotTemplateVo;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface TemplateService {
|
||||||
|
R<List<RobotTemplateVo>> allTemplate();
|
||||||
|
}
|
||||||
@ -0,0 +1,179 @@
|
|||||||
|
package top.krcia.elysiaserver.chat.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import top.krcia.elysiaserver.chat.dao.*;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.CreateRobotDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.HistoryListParam;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.dto.PParamDto;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.pojo.*;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.ChatRecordVo;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.ChatRoomVo;
|
||||||
|
import top.krcia.elysiaserver.chat.entity.vo.HistoryVo;
|
||||||
|
import top.krcia.elysiaserver.chat.service.ChatService;
|
||||||
|
import top.krcia.elysiaserver.chat.task.RobotReplyTask;
|
||||||
|
import top.krcia.elysiaserver.result.R;
|
||||||
|
import top.krcia.elysiaserver.result.RS;
|
||||||
|
import top.krcia.elysiaserver.utils.JWTGenerator;
|
||||||
|
import top.krcia.elysiaserver.utils.P;
|
||||||
|
import top.krcia.elysiaserver.utils.U;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ChatServiceImpl implements ChatService {
|
||||||
|
|
||||||
|
@Value("${elysia-authorization.metadata-save.agent-url}")
|
||||||
|
private String agentUrl;
|
||||||
|
@Resource
|
||||||
|
private ChatRoomDao chatRoomDao;
|
||||||
|
@Resource
|
||||||
|
private ChatRoomRecordDao chatRoomRecordDao;
|
||||||
|
@Resource
|
||||||
|
private RobotDao robotDao;
|
||||||
|
@Resource
|
||||||
|
private SysUserDao sysUserDao;
|
||||||
|
@Resource
|
||||||
|
private RobotTemplateDao robotTemplateDao;
|
||||||
|
@Resource
|
||||||
|
private RobotReplyTask robotReplyTask;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R create(String authorization, CreateRobotDto robotDto) {
|
||||||
|
Long userId = JWTGenerator.getUserId(authorization);
|
||||||
|
String uid = U.get();
|
||||||
|
Robot robot = new Robot(robotDto.getRobotId());
|
||||||
|
robot = robotDao.selectById(robot);
|
||||||
|
ChatRoom chatRoom = new ChatRoom(uid, robotDto.getRoomName(), userId, robotDto.getRobotId(), robot.getAvatar(), robotDto.getEngine());
|
||||||
|
chatRoomDao.insert(chatRoom);
|
||||||
|
chatRoomRecordDao.createRecord(uid);
|
||||||
|
return R.success(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R delete(String authorization, String roomId) {
|
||||||
|
Long userId = JWTGenerator.getUserId(authorization);
|
||||||
|
ChatRoom chatRoom = new ChatRoom(roomId);
|
||||||
|
chatRoom = chatRoomDao.selectById(chatRoom);
|
||||||
|
if (chatRoom.getSysUserId().longValue() != userId.longValue()) {
|
||||||
|
return R.print(RS.NOT_FIND_JURISDICTION_DO);
|
||||||
|
}
|
||||||
|
chatRoomDao.deleteById(chatRoom);
|
||||||
|
chatRoomRecordDao.deleteRecord(chatRoom.getRoomId());
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R sendMessage(String authorization, String roomId, String message,String messageId) {
|
||||||
|
Long userId = JWTGenerator.getUserId(authorization);
|
||||||
|
ChatRoom chatRoom = new ChatRoom(roomId);
|
||||||
|
chatRoom = chatRoomDao.selectById(chatRoom);
|
||||||
|
if (chatRoom.getSysUserId().longValue() != userId.longValue()) {
|
||||||
|
return R.print(RS.NOT_FIND_JURISDICTION_DO);
|
||||||
|
}
|
||||||
|
Robot robot = new Robot(chatRoom.getRobotId());
|
||||||
|
robot = robotDao.selectById(robot);
|
||||||
|
chatRoomRecordDao.addMessage(messageId, chatRoom.getRoomId(), 1, message, new Date());
|
||||||
|
IPage<ChatRoomRecord> iPage = new Page();
|
||||||
|
iPage.setCurrent(1);
|
||||||
|
iPage.setSize(100);
|
||||||
|
QueryWrapper<ChatRoomRecord> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.orderByAsc("chat_date");
|
||||||
|
iPage = chatRoomRecordDao.selectPage(iPage, queryWrapper, roomId);
|
||||||
|
SysUser sysUser = new SysUser(userId);
|
||||||
|
sysUser = sysUserDao.selectById(sysUser);
|
||||||
|
RobotTemplate robotTemplate = robotTemplateDao.selectById(new RobotTemplate(robot.getTemplateId()));
|
||||||
|
ReplyQueue replyQueue = new ReplyQueue(sysUser.getUserId(), roomId, iPage.getRecords(), sysUser.getDeepseekKey(), chatRoom.getEngine(), chatRoom.getRoomName(), agentUrl + chatRoom.getRoomImage(), robot.getSystemPrompt(), robotTemplate.getContent(), robotTemplate.getTemperature());
|
||||||
|
robotReplyTask.addTask(replyQueue);
|
||||||
|
chatRoom.setLastChatTime(new Date());
|
||||||
|
chatRoomDao.updateById(chatRoom);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R chatRoomRecord(String authorization, String roomId, PParamDto param) {
|
||||||
|
Long userId = JWTGenerator.getUserId(authorization);
|
||||||
|
ChatRoom chatRoom = new ChatRoom(roomId);
|
||||||
|
chatRoom = chatRoomDao.selectById(chatRoom);
|
||||||
|
if (chatRoom.getSysUserId().longValue() != userId.longValue()) {
|
||||||
|
return R.print(RS.NOT_FIND_JURISDICTION_DO);
|
||||||
|
}
|
||||||
|
IPage<ChatRoomRecord> iPage = new Page();
|
||||||
|
iPage.setCurrent(param.getCurrent());
|
||||||
|
iPage.setSize(param.getSize());
|
||||||
|
QueryWrapper<ChatRoomRecord> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.orderByDesc("chat_date");
|
||||||
|
iPage = chatRoomRecordDao.selectPage(iPage, queryWrapper, roomId);
|
||||||
|
P<ChatRecordVo> result = new P<>(iPage.getCurrent(), iPage.getSize(), iPage.getTotal());
|
||||||
|
List<ChatRecordVo> l = new ArrayList<>();
|
||||||
|
for (ChatRoomRecord crr : iPage.getRecords()) {
|
||||||
|
l.add(new ChatRecordVo(crr.getMessageId(), crr.getChatDate(), crr.getFrom(), crr.getMessage()));
|
||||||
|
}
|
||||||
|
result.setResult(l);
|
||||||
|
return R.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R chatRoomRecord(String authorization, List<HistoryListParam> historyList) {
|
||||||
|
Long userId = JWTGenerator.getUserId(authorization);
|
||||||
|
HistoryVo historyVo = new HistoryVo();
|
||||||
|
for (HistoryListParam hlp : historyList){
|
||||||
|
ChatRoom chatRoom = new ChatRoom(hlp.getRoomId());
|
||||||
|
chatRoom = chatRoomDao.selectById(chatRoom);
|
||||||
|
if (chatRoom.getSysUserId().longValue() != userId.longValue()) {
|
||||||
|
return R.print(RS.NOT_FIND_JURISDICTION_DO);
|
||||||
|
}
|
||||||
|
List<ChatRoomRecord> data = chatRoomRecordDao.selectCache(hlp.getRoomId(), hlp.getMessageId());
|
||||||
|
historyVo.add(hlp.getRoomId(),data);
|
||||||
|
}
|
||||||
|
return R.success(historyVo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R clearRecord(String authorization, String roomId) {
|
||||||
|
Long userId = JWTGenerator.getUserId(authorization);
|
||||||
|
ChatRoom chatRoom = new ChatRoom(roomId);
|
||||||
|
chatRoom = chatRoomDao.selectById(chatRoom);
|
||||||
|
if (chatRoom.getSysUserId().longValue() != userId.longValue()) {
|
||||||
|
return R.print(RS.NOT_FIND_JURISDICTION_DO);
|
||||||
|
}
|
||||||
|
chatRoomRecordDao.clearMessage(roomId);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<List<ChatRoomVo>> list(String authorization) {
|
||||||
|
Long userId = JWTGenerator.getUserId(authorization);
|
||||||
|
QueryWrapper<ChatRoom> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("sys_user_id", userId);
|
||||||
|
queryWrapper.orderByDesc("last_chat_time");
|
||||||
|
List<ChatRoom> chatRoomList = chatRoomDao.selectList(queryWrapper);
|
||||||
|
List<ChatRoomVo> list = new ArrayList<>();
|
||||||
|
for (ChatRoom chatRoom : chatRoomList) {
|
||||||
|
ChatRoomVo crv = new ChatRoomVo();
|
||||||
|
BeanUtils.copyProperties(chatRoom, crv);
|
||||||
|
ChatRoomRecord chatRoomRecord = chatRoomRecordDao.getlastRecord(chatRoom.getRoomId());
|
||||||
|
if (chatRoomRecord != null) {
|
||||||
|
if (chatRoomRecord.getFrom().intValue() == 0) {
|
||||||
|
crv.setLastChatMessage(chatRoomRecord.getMessage());
|
||||||
|
} else {
|
||||||
|
crv.setLastChatMessage(chatRoomRecord.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
list.add(crv);
|
||||||
|
}
|
||||||
|
return R.success(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user