合并不相关的历史

This commit is contained in:
Krcia 2025-11-04 09:22:20 +08:00
parent 9171a382f8
commit 87b7512d44
207 changed files with 16756 additions and 21 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
/mvnw text eol=lf
*.cmd text eol=crlf

50
.gitignore vendored
View File

@ -1,26 +1,34 @@
# ---> Java
# Compiled class file
*.class
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
# Log file
*.log
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
# BlueJ files
*.ctxt
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
# Mobile Tools for Java (J2ME)
.mtj.tmp/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### VS Code ###
.vscode/

3
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View 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

View File

@ -1,2 +1,3 @@
# elysia-server
AI聊天服务器

2
authorization/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
authorization/.gitignore vendored Normal file
View 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/

View 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
View 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
View 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
View 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>

View File

@ -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);
}
}

View File

@ -0,0 +1,8 @@
package top.krcia.elysiaserver.authorization.annotate;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Metadata {
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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表示拒绝访问
}
});
}
}

View File

@ -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) {
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View 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

View 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

View 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"
}
]
}

View 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}

View 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(
"&nbsp;Status:&nbsp;" + jqXHR.status + "&nbsp;&nbsp;" + jqXHR.statusText
+ "&nbsp;&nbsp;&nbsp;&nbsp;Time:&nbsp;" + totalTime + "&nbsp;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(
"&nbsp;Status:&nbsp;" + jqXHR.status + "&nbsp;&nbsp;"
+ jqXHR.statusText + "&nbsp;&nbsp;&nbsp;&nbsp;Time:&nbsp;" + totalTime
+ "&nbsp;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:";
}

View 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}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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 + '.&nbsp;' + 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 + '.&nbsp;' + 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 + '.&nbsp;' + 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 + '.&nbsp;' + 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 + '.&nbsp;' + spanString + doc[m].desc + '<span></a> </li>';
}
html += '</ul>';
html += '</li>';
}
html += '</ul>';
html += '</li>';
}
}
}
return html;
}

View 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
View File

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
chat/.gitignore vendored Normal file
View 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/

View 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
View 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
View 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
View 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>

View File

@ -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);
}
}

View File

@ -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 {
}

View File

@ -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.");
}
}

View File

@ -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 {
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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表示拒绝访问
}
});
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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) {
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,8 @@
package top.krcia.elysiaserver.chat.controller.base;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/chat")
public class ChatBaseController {
}

View File

@ -0,0 +1,7 @@
package top.krcia.elysiaserver.chat.controller.base;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/robot")
public class RobotBaseController {
}

View File

@ -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> {
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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() {
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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