@ -0,0 +1,33 @@ |
||||
HELP.md |
||||
target/ |
||||
!.mvn/wrapper/maven-wrapper.jar |
||||
!**/src/main/** |
||||
!**/src/test/** |
||||
|
||||
### STS ### |
||||
.apt_generated |
||||
.classpath |
||||
.factorypath |
||||
.project |
||||
.settings |
||||
.springBeans |
||||
.sts4-cache |
||||
|
||||
### IntelliJ IDEA ### |
||||
.idea |
||||
*.iws |
||||
*.iml |
||||
*.ipr |
||||
|
||||
### NetBeans ### |
||||
/nbproject/private/ |
||||
/nbbuild/ |
||||
/dist/ |
||||
/nbdist/ |
||||
/.nb-gradle/ |
||||
build/ |
||||
|
||||
### VS Code ### |
||||
.vscode/ |
||||
|
||||
*.DS_Store |
||||
@ -0,0 +1,118 @@ |
||||
/* |
||||
* Copyright 2007-present the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import java.net.*; |
||||
import java.io.*; |
||||
import java.nio.channels.*; |
||||
import java.util.Properties; |
||||
|
||||
public class MavenWrapperDownloader { |
||||
|
||||
private static final String WRAPPER_VERSION = "0.5.6"; |
||||
/** |
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. |
||||
*/ |
||||
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" |
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; |
||||
|
||||
/** |
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to |
||||
* use instead of the default one. |
||||
*/ |
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH = |
||||
".mvn/wrapper/maven-wrapper.properties"; |
||||
|
||||
/** |
||||
* Path where the maven-wrapper.jar will be saved to. |
||||
*/ |
||||
private static final String MAVEN_WRAPPER_JAR_PATH = |
||||
".mvn/wrapper/maven-wrapper.jar"; |
||||
|
||||
/** |
||||
* Name of the property which should be used to override the default download url for the wrapper. |
||||
*/ |
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; |
||||
|
||||
public static void main(String args[]) { |
||||
System.out.println("- Downloader started"); |
||||
File baseDirectory = new File(args[0]); |
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); |
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); |
||||
String url = DEFAULT_DOWNLOAD_URL; |
||||
if (mavenWrapperPropertyFile.exists()) { |
||||
FileInputStream mavenWrapperPropertyFileInputStream = null; |
||||
try { |
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); |
||||
Properties mavenWrapperProperties = new Properties(); |
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); |
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); |
||||
} catch (IOException e) { |
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); |
||||
} finally { |
||||
try { |
||||
if (mavenWrapperPropertyFileInputStream != null) { |
||||
mavenWrapperPropertyFileInputStream.close(); |
||||
} |
||||
} catch (IOException e) { |
||||
// Ignore ...
|
||||
} |
||||
} |
||||
} |
||||
System.out.println("- Downloading from: " + url); |
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); |
||||
if (!outputFile.getParentFile().exists()) { |
||||
if (!outputFile.getParentFile().mkdirs()) { |
||||
System.out.println( |
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); |
||||
} |
||||
} |
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); |
||||
try { |
||||
downloadFileFromURL(url, outputFile); |
||||
System.out.println("Done"); |
||||
System.exit(0); |
||||
} catch (Throwable e) { |
||||
System.out.println("- Error downloading"); |
||||
e.printStackTrace(); |
||||
System.exit(1); |
||||
} |
||||
} |
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception { |
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { |
||||
String username = System.getenv("MVNW_USERNAME"); |
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); |
||||
Authenticator.setDefault(new Authenticator() { |
||||
@Override |
||||
protected PasswordAuthentication getPasswordAuthentication() { |
||||
return new PasswordAuthentication(username, password); |
||||
} |
||||
}); |
||||
} |
||||
URL website = new URL(urlString); |
||||
ReadableByteChannel rbc; |
||||
rbc = Channels.newChannel(website.openStream()); |
||||
FileOutputStream fos = new FileOutputStream(destination); |
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); |
||||
fos.close(); |
||||
rbc.close(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,2 @@ |
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip |
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar |
||||
@ -0,0 +1,310 @@ |
||||
#!/bin/sh |
||||
# ---------------------------------------------------------------------------- |
||||
# Licensed to the Apache Software Foundation (ASF) under one |
||||
# or more contributor license agreements. See the NOTICE file |
||||
# distributed with this work for additional information |
||||
# regarding copyright ownership. The ASF licenses this file |
||||
# to you under the Apache License, Version 2.0 (the |
||||
# "License"); you may not use this file except in compliance |
||||
# with the License. You may obtain a copy of the License at |
||||
# |
||||
# https://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, |
||||
# software distributed under the License is distributed on an |
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||
# KIND, either express or implied. See the License for the |
||||
# specific language governing permissions and limitations |
||||
# under the License. |
||||
# ---------------------------------------------------------------------------- |
||||
|
||||
# ---------------------------------------------------------------------------- |
||||
# Maven Start Up Batch script |
||||
# |
||||
# Required ENV vars: |
||||
# ------------------ |
||||
# JAVA_HOME - location of a JDK home dir |
||||
# |
||||
# Optional ENV vars |
||||
# ----------------- |
||||
# M2_HOME - location of maven2's installed home dir |
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven |
||||
# e.g. to debug Maven itself, use |
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 |
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files |
||||
# ---------------------------------------------------------------------------- |
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then |
||||
|
||||
if [ -f /etc/mavenrc ] ; then |
||||
. /etc/mavenrc |
||||
fi |
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then |
||||
. "$HOME/.mavenrc" |
||||
fi |
||||
|
||||
fi |
||||
|
||||
# OS specific support. $var _must_ be set to either true or false. |
||||
cygwin=false; |
||||
darwin=false; |
||||
mingw=false |
||||
case "`uname`" in |
||||
CYGWIN*) cygwin=true ;; |
||||
MINGW*) mingw=true;; |
||||
Darwin*) darwin=true |
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home |
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html |
||||
if [ -z "$JAVA_HOME" ]; then |
||||
if [ -x "/usr/libexec/java_home" ]; then |
||||
export JAVA_HOME="`/usr/libexec/java_home`" |
||||
else |
||||
export JAVA_HOME="/Library/Java/Home" |
||||
fi |
||||
fi |
||||
;; |
||||
esac |
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then |
||||
if [ -r /etc/gentoo-release ] ; then |
||||
JAVA_HOME=`java-config --jre-home` |
||||
fi |
||||
fi |
||||
|
||||
if [ -z "$M2_HOME" ] ; then |
||||
## resolve links - $0 may be a link to maven's home |
||||
PRG="$0" |
||||
|
||||
# need this for relative symlinks |
||||
while [ -h "$PRG" ] ; do |
||||
ls=`ls -ld "$PRG"` |
||||
link=`expr "$ls" : '.*-> \(.*\)$'` |
||||
if expr "$link" : '/.*' > /dev/null; then |
||||
PRG="$link" |
||||
else |
||||
PRG="`dirname "$PRG"`/$link" |
||||
fi |
||||
done |
||||
|
||||
saveddir=`pwd` |
||||
|
||||
M2_HOME=`dirname "$PRG"`/.. |
||||
|
||||
# make it fully qualified |
||||
M2_HOME=`cd "$M2_HOME" && pwd` |
||||
|
||||
cd "$saveddir" |
||||
# echo Using m2 at $M2_HOME |
||||
fi |
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched |
||||
if $cygwin ; then |
||||
[ -n "$M2_HOME" ] && |
||||
M2_HOME=`cygpath --unix "$M2_HOME"` |
||||
[ -n "$JAVA_HOME" ] && |
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"` |
||||
[ -n "$CLASSPATH" ] && |
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"` |
||||
fi |
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched |
||||
if $mingw ; then |
||||
[ -n "$M2_HOME" ] && |
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`" |
||||
[ -n "$JAVA_HOME" ] && |
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" |
||||
fi |
||||
|
||||
if [ -z "$JAVA_HOME" ]; then |
||||
javaExecutable="`which javac`" |
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then |
||||
# readlink(1) is not available as standard on Solaris 10. |
||||
readLink=`which readlink` |
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then |
||||
if $darwin ; then |
||||
javaHome="`dirname \"$javaExecutable\"`" |
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" |
||||
else |
||||
javaExecutable="`readlink -f \"$javaExecutable\"`" |
||||
fi |
||||
javaHome="`dirname \"$javaExecutable\"`" |
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'` |
||||
JAVA_HOME="$javaHome" |
||||
export JAVA_HOME |
||||
fi |
||||
fi |
||||
fi |
||||
|
||||
if [ -z "$JAVACMD" ] ; then |
||||
if [ -n "$JAVA_HOME" ] ; then |
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
||||
# IBM's JDK on AIX uses strange locations for the executables |
||||
JAVACMD="$JAVA_HOME/jre/sh/java" |
||||
else |
||||
JAVACMD="$JAVA_HOME/bin/java" |
||||
fi |
||||
else |
||||
JAVACMD="`which java`" |
||||
fi |
||||
fi |
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then |
||||
echo "Error: JAVA_HOME is not defined correctly." >&2 |
||||
echo " We cannot execute $JAVACMD" >&2 |
||||
exit 1 |
||||
fi |
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then |
||||
echo "Warning: JAVA_HOME environment variable is not set." |
||||
fi |
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher |
||||
|
||||
# traverses directory structure from process work directory to filesystem root |
||||
# first directory with .mvn subdirectory is considered project base directory |
||||
find_maven_basedir() { |
||||
|
||||
if [ -z "$1" ] |
||||
then |
||||
echo "Path not specified to find_maven_basedir" |
||||
return 1 |
||||
fi |
||||
|
||||
basedir="$1" |
||||
wdir="$1" |
||||
while [ "$wdir" != '/' ] ; do |
||||
if [ -d "$wdir"/.mvn ] ; then |
||||
basedir=$wdir |
||||
break |
||||
fi |
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc) |
||||
if [ -d "${wdir}" ]; then |
||||
wdir=`cd "$wdir/.."; pwd` |
||||
fi |
||||
# end of workaround |
||||
done |
||||
echo "${basedir}" |
||||
} |
||||
|
||||
# concatenates all lines of a file |
||||
concat_lines() { |
||||
if [ -f "$1" ]; then |
||||
echo "$(tr -s '\n' ' ' < "$1")" |
||||
fi |
||||
} |
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"` |
||||
if [ -z "$BASE_DIR" ]; then |
||||
exit 1; |
||||
fi |
||||
|
||||
########################################################################################## |
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central |
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data. |
||||
########################################################################################## |
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Found .mvn/wrapper/maven-wrapper.jar" |
||||
fi |
||||
else |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." |
||||
fi |
||||
if [ -n "$MVNW_REPOURL" ]; then |
||||
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" |
||||
else |
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" |
||||
fi |
||||
while IFS="=" read key value; do |
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;; |
||||
esac |
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Downloading from: $jarUrl" |
||||
fi |
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" |
||||
if $cygwin; then |
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` |
||||
fi |
||||
|
||||
if command -v wget > /dev/null; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Found wget ... using wget" |
||||
fi |
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then |
||||
wget "$jarUrl" -O "$wrapperJarPath" |
||||
else |
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" |
||||
fi |
||||
elif command -v curl > /dev/null; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Found curl ... using curl" |
||||
fi |
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then |
||||
curl -o "$wrapperJarPath" "$jarUrl" -f |
||||
else |
||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f |
||||
fi |
||||
|
||||
else |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo "Falling back to using Java to download" |
||||
fi |
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" |
||||
# For Cygwin, switch paths to Windows format before running javac |
||||
if $cygwin; then |
||||
javaClass=`cygpath --path --windows "$javaClass"` |
||||
fi |
||||
if [ -e "$javaClass" ]; then |
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo " - Compiling MavenWrapperDownloader.java ..." |
||||
fi |
||||
# Compiling the Java class |
||||
("$JAVA_HOME/bin/javac" "$javaClass") |
||||
fi |
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then |
||||
# Running the downloader |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo " - Running MavenWrapperDownloader.java ..." |
||||
fi |
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") |
||||
fi |
||||
fi |
||||
fi |
||||
fi |
||||
########################################################################################## |
||||
# End of extension |
||||
########################################################################################## |
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} |
||||
if [ "$MVNW_VERBOSE" = true ]; then |
||||
echo $MAVEN_PROJECTBASEDIR |
||||
fi |
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" |
||||
|
||||
# For Cygwin, switch paths to Windows format before running java |
||||
if $cygwin; then |
||||
[ -n "$M2_HOME" ] && |
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"` |
||||
[ -n "$JAVA_HOME" ] && |
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` |
||||
[ -n "$CLASSPATH" ] && |
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"` |
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] && |
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` |
||||
fi |
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will |
||||
# work with both Windows and non-Windows executions. |
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" |
||||
export MAVEN_CMD_LINE_ARGS |
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain |
||||
|
||||
exec "$JAVACMD" \ |
||||
$MAVEN_OPTS \ |
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ |
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ |
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" |
||||
@ -0,0 +1,182 @@ |
||||
@REM ---------------------------------------------------------------------------- |
||||
@REM Licensed to the Apache Software Foundation (ASF) under one |
||||
@REM or more contributor license agreements. See the NOTICE file |
||||
@REM distributed with this work for additional information |
||||
@REM regarding copyright ownership. The ASF licenses this file |
||||
@REM to you under the Apache License, Version 2.0 (the |
||||
@REM "License"); you may not use this file except in compliance |
||||
@REM with the License. You may obtain a copy of the License at |
||||
@REM |
||||
@REM https://www.apache.org/licenses/LICENSE-2.0 |
||||
@REM |
||||
@REM Unless required by applicable law or agreed to in writing, |
||||
@REM software distributed under the License is distributed on an |
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||
@REM KIND, either express or implied. See the License for the |
||||
@REM specific language governing permissions and limitations |
||||
@REM under the License. |
||||
@REM ---------------------------------------------------------------------------- |
||||
|
||||
@REM ---------------------------------------------------------------------------- |
||||
@REM Maven Start Up Batch script |
||||
@REM |
||||
@REM Required ENV vars: |
||||
@REM JAVA_HOME - location of a JDK home dir |
||||
@REM |
||||
@REM Optional ENV vars |
||||
@REM M2_HOME - location of maven2's installed home dir |
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands |
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending |
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven |
||||
@REM e.g. to debug Maven itself, use |
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 |
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files |
||||
@REM ---------------------------------------------------------------------------- |
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' |
||||
@echo off |
||||
@REM set title of command window |
||||
title %0 |
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' |
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% |
||||
|
||||
@REM set %HOME% to equivalent of $HOME |
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") |
||||
|
||||
@REM Execute a user defined script before this one |
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre |
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending |
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" |
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" |
||||
:skipRcPre |
||||
|
||||
@setlocal |
||||
|
||||
set ERROR_CODE=0 |
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal |
||||
@setlocal |
||||
|
||||
@REM ==== START VALIDATION ==== |
||||
if not "%JAVA_HOME%" == "" goto OkJHome |
||||
|
||||
echo. |
||||
echo Error: JAVA_HOME not found in your environment. >&2 |
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2 |
||||
echo location of your Java installation. >&2 |
||||
echo. |
||||
goto error |
||||
|
||||
:OkJHome |
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init |
||||
|
||||
echo. |
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2 |
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2 |
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2 |
||||
echo location of your Java installation. >&2 |
||||
echo. |
||||
goto error |
||||
|
||||
@REM ==== END VALIDATION ==== |
||||
|
||||
:init |
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". |
||||
@REM Fallback to current working directory if not found. |
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% |
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir |
||||
|
||||
set EXEC_DIR=%CD% |
||||
set WDIR=%EXEC_DIR% |
||||
:findBaseDir |
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound |
||||
cd .. |
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound |
||||
set WDIR=%CD% |
||||
goto findBaseDir |
||||
|
||||
:baseDirFound |
||||
set MAVEN_PROJECTBASEDIR=%WDIR% |
||||
cd "%EXEC_DIR%" |
||||
goto endDetectBaseDir |
||||
|
||||
:baseDirNotFound |
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR% |
||||
cd "%EXEC_DIR%" |
||||
|
||||
:endDetectBaseDir |
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig |
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion |
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a |
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% |
||||
|
||||
:endReadAdditionalConfig |
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" |
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" |
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain |
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" |
||||
|
||||
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( |
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B |
||||
) |
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central |
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data. |
||||
if exist %WRAPPER_JAR% ( |
||||
if "%MVNW_VERBOSE%" == "true" ( |
||||
echo Found %WRAPPER_JAR% |
||||
) |
||||
) else ( |
||||
if not "%MVNW_REPOURL%" == "" ( |
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" |
||||
) |
||||
if "%MVNW_VERBOSE%" == "true" ( |
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ... |
||||
echo Downloading from: %DOWNLOAD_URL% |
||||
) |
||||
|
||||
powershell -Command "&{"^ |
||||
"$webclient = new-object System.Net.WebClient;"^ |
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ |
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ |
||||
"}"^ |
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ |
||||
"}" |
||||
if "%MVNW_VERBOSE%" == "true" ( |
||||
echo Finished downloading %WRAPPER_JAR% |
||||
) |
||||
) |
||||
@REM End of extension |
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will |
||||
@REM work with both Windows and non-Windows executions. |
||||
set MAVEN_CMD_LINE_ARGS=%* |
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* |
||||
if ERRORLEVEL 1 goto error |
||||
goto end |
||||
|
||||
:error |
||||
set ERROR_CODE=1 |
||||
|
||||
:end |
||||
@endlocal & set ERROR_CODE=%ERROR_CODE% |
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost |
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending |
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" |
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" |
||||
:skipRcPost |
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' |
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause |
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% |
||||
|
||||
exit /B %ERROR_CODE% |
||||
@ -0,0 +1,167 @@ |
||||
<?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>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-parent</artifactId> |
||||
<version>2.6.1</version> |
||||
<relativePath/> <!-- lookup parent from repository --> |
||||
</parent> |
||||
<groupId>edu.ncst</groupId> |
||||
<artifactId>award</artifactId> |
||||
<version>0.0.1-SNAPSHOT</version> |
||||
<name>award</name> |
||||
<description>award project for Spring Boot</description> |
||||
<properties> |
||||
<java.version>1.8</java.version> |
||||
</properties> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-web</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.mybatis.spring.boot</groupId> |
||||
<artifactId>mybatis-spring-boot-starter</artifactId> |
||||
<version>2.2.0</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-devtools</artifactId> |
||||
<scope>runtime</scope> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>mysql</groupId> |
||||
<artifactId>mysql-connector-java</artifactId> |
||||
<scope>runtime</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.projectlombok</groupId> |
||||
<artifactId>lombok</artifactId> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-test</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>com.alibaba</groupId> |
||||
<artifactId>fastjson</artifactId> |
||||
<version>1.2.35</version> |
||||
</dependency> |
||||
<!-- MyBatis --> |
||||
<dependency> |
||||
<groupId>org.mybatis.spring.boot</groupId> |
||||
<artifactId>mybatis-spring-boot-starter</artifactId> |
||||
<version>2.1.3</version> |
||||
</dependency> |
||||
<!-- MyBatis Plus --> |
||||
<dependency> |
||||
<groupId>com.baomidou</groupId> |
||||
<artifactId>mybatis-plus-boot-starter</artifactId> |
||||
<version>3.3.2</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>com.baomidou</groupId> |
||||
<artifactId>mybatis-plus-generator</artifactId> |
||||
<version>3.3.2</version> |
||||
</dependency> |
||||
<!-- 数据校验 --> |
||||
<dependency> |
||||
<groupId>javax.validation</groupId> |
||||
<artifactId>validation-api</artifactId> |
||||
<version>2.0.1.Final</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.hibernate</groupId> |
||||
<artifactId>hibernate-validator</artifactId> |
||||
<version>6.0.13.Final</version> |
||||
</dependency> |
||||
<!-- JWT --> |
||||
<dependency> |
||||
<groupId>io.jsonwebtoken</groupId> |
||||
<artifactId>jjwt</artifactId> |
||||
<version>0.9.0</version> |
||||
</dependency> |
||||
<!-- Redis --> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-data-redis</artifactId> |
||||
</dependency> |
||||
<!-- AOP --> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-aop</artifactId> |
||||
</dependency> |
||||
<!-- DevTools --> |
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>springloaded</artifactId> |
||||
<version>1.2.8.RELEASE</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-devtools</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-security</artifactId> |
||||
</dependency> |
||||
<!-- swagger --> |
||||
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> |
||||
<!-- <dependency>--> |
||||
<!-- <groupId>io.springfox</groupId>--> |
||||
<!-- <artifactId>springfox-swagger2</artifactId>--> |
||||
<!-- <version>2.9.2</version>--> |
||||
<!-- </dependency>--> |
||||
<!-- <!– https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui –>--> |
||||
<!-- <dependency>--> |
||||
<!-- <groupId>io.springfox</groupId>--> |
||||
<!-- <artifactId>springfox-swagger-ui</artifactId>--> |
||||
<!-- <version>2.9.2</version>--> |
||||
<!-- </dependency>--> |
||||
|
||||
<!-- <dependency>--> |
||||
<!-- <groupId>io.springfox</groupId>--> |
||||
<!-- <artifactId>springfox-boot-starter</artifactId>--> |
||||
<!-- <version>3.0.0</version>--> |
||||
<!-- </dependency>--> |
||||
<dependency> |
||||
<groupId>com.spring4all</groupId> |
||||
<artifactId>swagger-spring-boot-starter</artifactId> |
||||
<version>1.9.1.RELEASE</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>com.google.guava</groupId> |
||||
<artifactId>guava</artifactId> |
||||
<version>25.1-jre</version> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-configuration-processor</artifactId> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
</dependencies> |
||||
|
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
<configuration> |
||||
<excludes> |
||||
<exclude> |
||||
<groupId>org.projectlombok</groupId> |
||||
<artifactId>lombok</artifactId> |
||||
</exclude> |
||||
</excludes> |
||||
</configuration> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
|
||||
</project> |
||||
@ -0,0 +1,36 @@ |
||||
package edu.ncst.award; |
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; |
||||
import org.mybatis.spring.annotation.MapperScan; |
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
import com.spring4all.swagger.EnableSwagger2Doc; |
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; |
||||
import org.springframework.cache.annotation.EnableCaching; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy; |
||||
import org.springframework.scheduling.annotation.EnableScheduling; |
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; |
||||
import org.springframework.transaction.annotation.EnableTransactionManagement; |
||||
|
||||
|
||||
@EnableSwagger2Doc |
||||
@SpringBootApplication |
||||
@MapperScan(basePackages = {"edu.ncst.award.mapper"}) |
||||
@EnableTransactionManagement |
||||
@EnableCaching |
||||
@EnableAspectJAutoProxy |
||||
public class AwardApplication { |
||||
/** |
||||
* 分页插件 |
||||
*/ |
||||
@Bean |
||||
public PaginationInterceptor paginationInterceptor() { |
||||
return new PaginationInterceptor(); |
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
SpringApplication.run(AwardApplication.class, args); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,34 @@ |
||||
package edu.ncst.award.config; |
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.access.AccessDeniedException; |
||||
import org.springframework.security.web.access.AccessDeniedHandler; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.IOException; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
@Service |
||||
public class CustomAccessDeniedHandler implements AccessDeniedHandler { |
||||
|
||||
@Autowired |
||||
private ObjectMapper objectMapper; |
||||
|
||||
@Override |
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { |
||||
response.setContentType("application/json;charset=UTF-8"); |
||||
Map map = new HashMap(); |
||||
map.put("code",403); |
||||
map.put("msg", "无访问权限"); |
||||
map.put("data",""); |
||||
response.setContentType("application/json"); |
||||
response.setStatus(HttpServletResponse.SC_OK); |
||||
response.getWriter().write(objectMapper.writeValueAsString(map)); |
||||
} |
||||
} |
||||
@ -0,0 +1,47 @@ |
||||
|
||||
package edu.ncst.award.config; |
||||
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature; |
||||
import com.alibaba.fastjson.support.config.FastJsonConfig; |
||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
|
||||
import java.nio.charset.Charset; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
@Configuration |
||||
public class JsonConfig { |
||||
|
||||
|
||||
@Bean |
||||
public HttpMessageConverter configureMessageConverters() { |
||||
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); |
||||
FastJsonConfig config = new FastJsonConfig(); |
||||
config.setSerializerFeatures( |
||||
// 保留map空的字段
|
||||
SerializerFeature.WriteMapNullValue, |
||||
// 将String类型的null转成""
|
||||
SerializerFeature.WriteNullStringAsEmpty, |
||||
// 将Number类型的null转成0
|
||||
SerializerFeature.WriteNullNumberAsZero, |
||||
// 将List类型的null转成[]
|
||||
SerializerFeature.WriteNullListAsEmpty, |
||||
// 将Boolean类型的null转成false
|
||||
SerializerFeature.WriteNullBooleanAsFalse, |
||||
// 避免循环引用
|
||||
SerializerFeature.DisableCircularReferenceDetect); |
||||
|
||||
converter.setFastJsonConfig(config); |
||||
converter.setDefaultCharset(Charset.forName("UTF-8")); |
||||
List<MediaType> mediaTypeList = new ArrayList<>(); |
||||
// 解决中文乱码问题,相当于在Controller上的@RequestMapping中加了个属性produces = "application/json"
|
||||
mediaTypeList.add(MediaType.APPLICATION_JSON); |
||||
converter.setSupportedMediaTypes(mediaTypeList); |
||||
return converter; |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,204 @@ |
||||
package edu.ncst.award.config; |
||||
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import edu.ncst.award.config.security.DataBaseUrlVoter; |
||||
import edu.ncst.award.config.security.filter.AuthCheckFilter; |
||||
import edu.ncst.award.config.security.filter.LoginFilter; |
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.service.ILoginLogService; |
||||
import edu.ncst.award.service.impl.system.UserServiceImpl; |
||||
import edu.ncst.award.utils.ResultUtils; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.data.redis.core.RedisTemplate; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.security.access.AccessDecisionManager; |
||||
import org.springframework.security.access.AccessDecisionVoter; |
||||
import org.springframework.security.access.vote.AuthenticatedVoter; |
||||
import org.springframework.security.access.vote.RoleVoter; |
||||
import org.springframework.security.access.vote.UnanimousBased; |
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; |
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; |
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity; |
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
||||
import org.springframework.security.config.http.SessionCreationPolicy; |
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
||||
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
import org.springframework.security.web.access.expression.WebExpressionVoter; |
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; |
||||
|
||||
import java.io.PrintWriter; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 安全配置 |
||||
*/ |
||||
@Configuration |
||||
@EnableWebSecurity |
||||
@EnableGlobalMethodSecurity(securedEnabled=true) |
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter { |
||||
|
||||
@Autowired |
||||
private UserServiceImpl userService; |
||||
@Autowired |
||||
private ILoginLogService loginLogService; |
||||
@Autowired |
||||
private DataBaseUrlVoter dataBaseUrlVoter; |
||||
@Autowired |
||||
private RedisTemplate<String, String> redisCacheTemplate; |
||||
/** |
||||
* 认证失败处理类 |
||||
*/ |
||||
@Autowired |
||||
CustomAccessDeniedHandler accessDeniedHandler; |
||||
|
||||
//org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126)
|
||||
/** |
||||
* 解决 无法直接注入 AuthenticationManager |
||||
* |
||||
* @return |
||||
* @throws Exception |
||||
*/ |
||||
@Bean |
||||
@Override |
||||
public AuthenticationManager authenticationManagerBean() throws Exception |
||||
{ |
||||
return super.authenticationManagerBean(); |
||||
} |
||||
|
||||
/** |
||||
* 权限白名单 |
||||
* 注意:该名单下匹配的 URL 不会经过鉴权,因此也无法获取当前用户的信息 |
||||
*/ |
||||
private final String[] WHITE_LIST = { |
||||
"/v2/api-docs", |
||||
"/configuration/ui", |
||||
"/swagger-resources/**", |
||||
"/configuration/**", |
||||
"/swagger-ui.html", |
||||
"/webjars/**", |
||||
"/api/captcha.jpg", |
||||
"/websocket/**", |
||||
"/api/image/get/**", |
||||
"/common/download/**", |
||||
"/test/**", |
||||
"/*.html", |
||||
"/**/*.html", |
||||
"/**/*.css", |
||||
"/**/*.js", |
||||
"/error*" |
||||
}; |
||||
|
||||
/** |
||||
* 密码加密器 |
||||
*/ |
||||
@Bean |
||||
public PasswordEncoder passwordEncoderBean() { |
||||
return new BCryptPasswordEncoder(); |
||||
} |
||||
|
||||
/** |
||||
* 权限投票访问决策管理器 |
||||
*/ |
||||
@Bean |
||||
public AccessDecisionManager accessDecisionManager() { |
||||
List<AccessDecisionVoter<? extends Object>> decisionVoters |
||||
= Arrays.asList( |
||||
new WebExpressionVoter(), |
||||
new RoleVoter(),//主要用来判断当前请求是否具备该接口所需要的角色
|
||||
dataBaseUrlVoter, |
||||
new AuthenticatedVoter()); |
||||
return new UnanimousBased(decisionVoters);//要求所有 AccessDecisionVoter 均返回肯定的结果时,才代表授予权限。
|
||||
} |
||||
|
||||
@Override |
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception { |
||||
auth.userDetailsService(userService); |
||||
} |
||||
|
||||
/** |
||||
* anyRequest | 匹配所有请求路径 |
||||
* access | SpringEl表达式结果为true时可以访问 |
||||
* anonymous | 匿名可以访问 |
||||
* denyAll | 用户不能访问 |
||||
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) |
||||
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 |
||||
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 |
||||
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 |
||||
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 |
||||
* hasRole | 如果有参数,参数表示角色,则其角色可以访问 |
||||
* permitAll | 用户可以任意访问 |
||||
* rememberMe | 允许通过remember-me登录的用户访问 |
||||
* authenticated | 用户登录后可访问 |
||||
*/ |
||||
//权限验证 基于资源配置
|
||||
// HttpSecurity 及WebSecurity 作用是不一样的:
|
||||
// WebSecurity 主要针对的全局的忽略规则,
|
||||
// HttpSecurity主要是权限控制规则。
|
||||
@Override |
||||
protected void configure(HttpSecurity http) throws Exception { |
||||
|
||||
//允许跨域
|
||||
http.cors().and().anonymous().and() |
||||
// CRSF禁用,因为不使用session
|
||||
.csrf().disable() |
||||
.formLogin().disable().httpBasic().disable() |
||||
.authorizeRequests() |
||||
.accessDecisionManager(accessDecisionManager())//访问决策管理器
|
||||
.antMatchers("/api/login").permitAll() |
||||
.antMatchers("/system/init").permitAll() |
||||
// 除上面外的所有请求全部需要鉴权认证
|
||||
.anyRequest().authenticated() |
||||
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler).and() |
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); |
||||
|
||||
http.addFilterAfter(new AuthCheckFilter(redisCacheTemplate), UsernamePasswordAuthenticationFilter.class) |
||||
.addFilterAt( |
||||
new LoginFilter(authenticationManager(), loginLogService, redisCacheTemplate), UsernamePasswordAuthenticationFilter.class |
||||
) |
||||
.exceptionHandling()//登录认证失败
|
||||
.authenticationEntryPoint(((request, response, authException) -> { |
||||
response.setCharacterEncoding("utf-8"); |
||||
response.setContentType("application/json;charset=utf-8"); |
||||
|
||||
PrintWriter out = response.getWriter(); |
||||
out.write(new ObjectMapper().writeValueAsString(ResultUtils.error(CodeMsg.USER_NEED_LOGIN))); |
||||
out.flush(); |
||||
out.close(); |
||||
})); |
||||
|
||||
// 退出登录
|
||||
http.logout() |
||||
.logoutUrl("/logout") |
||||
.deleteCookies("JSESSIONID") |
||||
.logoutSuccessHandler(((request, response, authentication) -> { |
||||
PrintWriter out = response.getWriter(); |
||||
response.setContentType("application/json"); |
||||
out.write(new ObjectMapper().writeValueAsString(ResultUtils.success("注销成功"))); |
||||
out.flush(); |
||||
out.close(); |
||||
})); |
||||
} |
||||
|
||||
|
||||
// @Override
|
||||
// protected void configure(HttpSecurity httpSecurity) throws Exception{
|
||||
// //调试阶段
|
||||
// httpSecurity.csrf().disable().authorizeRequests();
|
||||
// httpSecurity.authorizeRequests().anyRequest()
|
||||
// .permitAll().and().logout().permitAll();
|
||||
// }
|
||||
|
||||
//用于配置全局的某些通用事物,例如静态资源等
|
||||
@Override |
||||
public void configure(WebSecurity web) throws Exception { |
||||
web.ignoring().antMatchers(WHITE_LIST); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,109 @@ |
||||
package edu.ncst.award.config.security; |
||||
|
||||
import edu.ncst.award.model.system.Role; |
||||
import edu.ncst.award.service.IResourceService; |
||||
import edu.ncst.award.utils.URLUtils; |
||||
import edu.ncst.award.vo.result.UserInfoVO; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.access.AccessDecisionVoter; |
||||
import org.springframework.security.access.ConfigAttribute; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.web.FilterInvocation; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.util.AntPathMatcher; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* URL 鉴权投票器 |
||||
*/ |
||||
@Component |
||||
public class DataBaseUrlVoter implements AccessDecisionVoter<FilterInvocation> { |
||||
|
||||
@Autowired |
||||
private IResourceService resourceService; |
||||
|
||||
@Override |
||||
public boolean supports(ConfigAttribute attribute) { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean supports(Class<?> clazz) { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) { |
||||
if (authentication == null) { |
||||
return ACCESS_DENIED; // 反对票
|
||||
} |
||||
|
||||
String url = fi.getRequestUrl(); // 当前请求的URL
|
||||
String urlWithoutQueryString = URLUtils.removeQueryString(url); |
||||
|
||||
// Current User Info
|
||||
Object details = authentication.getDetails(); |
||||
|
||||
if (details instanceof UserInfoVO) { |
||||
UserInfoVO userInfoVO = (UserInfoVO) details; |
||||
AntPathMatcher antPathMatcher = new AntPathMatcher(); |
||||
|
||||
//获取不限用户角色的资源
|
||||
List<String> permitAllResourceURLs = resourceService.getPermitAllResourceURLs(); |
||||
// Permit All Resource
|
||||
for (String permitUrl : permitAllResourceURLs) { |
||||
boolean match = antPathMatcher.match(permitUrl, urlWithoutQueryString); |
||||
if (match) { |
||||
return ACCESS_GRANTED; // 赞同票
|
||||
} |
||||
} |
||||
|
||||
// 判断用户角色信息
|
||||
// 反对票:1、没有角色信息 2、角色未绑定该资源
|
||||
// 赞成票:用户是超级管理员
|
||||
|
||||
List<Role> roles = userInfoVO.getRoles(); |
||||
if (roles == null || roles.isEmpty()) { |
||||
return ACCESS_DENIED; // 反对票
|
||||
} |
||||
|
||||
List<String> roleNames = roles.stream().map(Role::getName).collect(Collectors.toList()); |
||||
|
||||
// Super Admin User Always Pass
|
||||
if (roleNames.contains("SUPER_ADMIN")) { |
||||
return ACCESS_GRANTED; // 赞同票
|
||||
} |
||||
|
||||
List<String> rolesResourceList = resourceService.getRolesResourceList(roleNames);//获取目标角色绑定的资源
|
||||
|
||||
|
||||
|
||||
|
||||
// Role Resource
|
||||
if (rolesResourceList == null) { |
||||
return ACCESS_DENIED; // 反对票
|
||||
} |
||||
|
||||
for (String allowUrl : rolesResourceList) { |
||||
boolean match = antPathMatcher.match(allowUrl, urlWithoutQueryString); |
||||
if (match) { |
||||
return ACCESS_GRANTED; // 赞同票
|
||||
} |
||||
} |
||||
} |
||||
|
||||
return ACCESS_DENIED; |
||||
} |
||||
|
||||
Collection<? extends GrantedAuthority> extractAuthorities( |
||||
Authentication authentication) { |
||||
return authentication.getAuthorities(); |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,125 @@ |
||||
package edu.ncst.award.config.security.filter; |
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import edu.ncst.award.config.security.token.JwtAuthenticationToken; |
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.model.system.Role; |
||||
import edu.ncst.award.utils.ResponseUtils; |
||||
import edu.ncst.award.utils.ResultUtils; |
||||
import edu.ncst.award.vo.ResultVO; |
||||
import edu.ncst.award.vo.result.UserInfoVO; |
||||
import io.jsonwebtoken.Claims; |
||||
import io.jsonwebtoken.ExpiredJwtException; |
||||
import io.jsonwebtoken.Jws; |
||||
import io.jsonwebtoken.Jwts; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.data.redis.core.RedisTemplate; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
|
||||
import javax.servlet.FilterChain; |
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.Cookie; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 用户凭证检查过滤器 |
||||
* 并不是所有的container都像我们期望的只过滤一次,servlet版本不同,表现也不同 |
||||
* OncePerRequestFilter可以保证一次请求只通过一次过滤器。推荐自定义的授权过滤器继承OncePerRequestFilter |
||||
*/ |
||||
@Slf4j |
||||
public class AuthCheckFilter extends OncePerRequestFilter { |
||||
private final RedisTemplate<String, String> stringRedisTemplate; |
||||
|
||||
public AuthCheckFilter(RedisTemplate<String, String> stringRedisTemplate) { |
||||
super(); |
||||
this.stringRedisTemplate = stringRedisTemplate; |
||||
} |
||||
@Override |
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { |
||||
String jwtToken = null; |
||||
|
||||
// 优先从 Cookie 中获取 jwtTOKEN
|
||||
Cookie[] cookies = request.getCookies(); |
||||
|
||||
log.info("开始鉴权====cookies====" +cookies); |
||||
boolean cookieHasToken = false; |
||||
if (cookies != null) { |
||||
for (Cookie cookie : cookies) { |
||||
if ("SYS_TOKEN".equals(cookie.getName())) { |
||||
jwtToken = cookie.getValue(); |
||||
cookieHasToken = true; |
||||
log.info("获取到token====cookies===="); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 如果 Cookie 中不存在 TOKEN,则尝试从 Header 中获取 TOKEN
|
||||
if (!cookieHasToken) { |
||||
String authorization = request.getHeader("Authorization"); |
||||
log.info("开始鉴权====header Authorization====" +authorization); |
||||
if (authorization != null && authorization.startsWith("Bearer ")) { |
||||
jwtToken = authorization.replace("Bearer ", ""); |
||||
log.info("获取到token====header===="); |
||||
} else { |
||||
log.info("header中无token,进入过滤器链"); |
||||
filterChain.doFilter(request, response); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
if (jwtToken == null || "".equals(jwtToken)) { |
||||
log.error("鉴权TOKEN:未获取,用户需要登录"); |
||||
ResultVO<Object> resultVO = ResultUtils.error(CodeMsg.USER_NEED_LOGIN); |
||||
ResponseUtils.output(response, resultVO); |
||||
} |
||||
else { |
||||
log.info("鉴权TOKEN:" +"Bearer " + jwtToken); |
||||
} |
||||
|
||||
try { |
||||
// 解析TOKEN
|
||||
Jws<Claims> jws = Jwts.parser().setSigningKey("ncst20220218") |
||||
.parseClaimsJws(jwtToken); |
||||
|
||||
Claims claims = jws.getBody(); |
||||
String userName = claims.getSubject(); |
||||
//可以通过控制redis数据从服务端强制下线用户
|
||||
if(!stringRedisTemplate.hasKey("jwt_token:"+userName)){ |
||||
throw new ExpiredJwtException(null,jws.getBody(),"登录失效"); |
||||
} |
||||
|
||||
// 获取用户信息
|
||||
String userInfoJSON = (String) claims.get("userInfo"); |
||||
ObjectMapper objectMapper = new ObjectMapper(); |
||||
UserInfoVO userInfoVO = objectMapper.readValue(userInfoJSON, UserInfoVO.class); |
||||
|
||||
// 获取角色列表
|
||||
List<Role> roles = userInfoVO.getRoles(); |
||||
List<GrantedAuthority> authorities = new ArrayList<>(); |
||||
|
||||
if (roles != null && !roles.isEmpty()) { |
||||
for (Role role : roles) { |
||||
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName())); |
||||
} |
||||
} |
||||
|
||||
// 将 TOKEN 存到 CONTEXT 当中
|
||||
JwtAuthenticationToken token = new JwtAuthenticationToken(userName, authorities, userInfoVO); |
||||
SecurityContextHolder.getContext().setAuthentication(token); |
||||
} catch (ExpiredJwtException e) { |
||||
ResultVO<Object> resultVO = ResultUtils.error(CodeMsg.USER_TOKEN_EXPIRED);// 用户凭证过期
|
||||
ResponseUtils.output(response, resultVO); |
||||
} finally { |
||||
filterChain.doFilter(request, response); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,255 @@ |
||||
package edu.ncst.award.config.security.filter; |
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import edu.ncst.award.config.security.token.MobileCaptchaAuthenticationToken; |
||||
import edu.ncst.award.exception.BusinessException; |
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.model.system.Role; |
||||
import edu.ncst.award.model.system.User; |
||||
import edu.ncst.award.service.ILoginLogService; |
||||
import edu.ncst.award.utils.CookieUtils; |
||||
import edu.ncst.award.utils.ResultUtils; |
||||
import edu.ncst.award.vo.ResultVO; |
||||
import edu.ncst.award.vo.param.UserLoginDTO; |
||||
import edu.ncst.award.vo.result.UserInfoVO; |
||||
import io.jsonwebtoken.Jwts; |
||||
import io.jsonwebtoken.SignatureAlgorithm; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.data.redis.core.RedisTemplate; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.security.authentication.*; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
|
||||
import javax.servlet.FilterChain; |
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.Cookie; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.IOException; |
||||
import java.io.PrintWriter; |
||||
import java.util.Collection; |
||||
import java.util.Date; |
||||
import java.util.List; |
||||
import java.util.UUID; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* 统一登录入口 |
||||
*/ |
||||
@Slf4j |
||||
public class LoginFilter extends AbstractAuthenticationProcessingFilter {//OncePerRequestFilter
|
||||
|
||||
private static final String loginUrl = "/api/login"; |
||||
|
||||
private final ILoginLogService loginLogService; |
||||
|
||||
private final RedisTemplate<String, String> stringRedisTemplate; |
||||
|
||||
private String tempToken=""; |
||||
private final int MAX_ATTEMPT = 20; |
||||
|
||||
public LoginFilter(AuthenticationManager authenticationManager, ILoginLogService loginLogService, RedisTemplate<String, String> stringRedisTemplate) { |
||||
super(new AntPathRequestMatcher(loginUrl, HttpMethod.POST.name())); |
||||
setAuthenticationManager(authenticationManager); |
||||
this.loginLogService = loginLogService; |
||||
this.stringRedisTemplate = stringRedisTemplate; |
||||
} |
||||
|
||||
/** |
||||
* 尝试登录 |
||||
* 根据登录表单的类型(type)将登录凭证分发给对应的 AuthenticationProvider |
||||
* 当前filter的认证逻辑 |
||||
*/ |
||||
@Override |
||||
public Authentication attemptAuthentication( |
||||
HttpServletRequest request, HttpServletResponse response |
||||
) throws AuthenticationException, IOException, ServletException { |
||||
UserLoginDTO paramUserLoginVO = new ObjectMapper().readValue(request.getInputStream(), UserLoginDTO.class); |
||||
AuthenticationManager authenticationManager = getAuthenticationManager(); |
||||
|
||||
request.setAttribute("userLoginVO", paramUserLoginVO); |
||||
// 登录类型
|
||||
String loginType = paramUserLoginVO.getType(); |
||||
log.info("尝试登录===="+ paramUserLoginVO); |
||||
|
||||
if ("ACCOUNT".equalsIgnoreCase(loginType)) { |
||||
// 校验 TOKEN
|
||||
Cookie tempTokenCookie = CookieUtils.getCookie(request, "TEMP_TOKEN");//cookie中的TEMP_TOKEN携带验证码的sessionid,去redis获取有效期内的验证码
|
||||
|
||||
|
||||
if (tempTokenCookie == null || "".equals(tempTokenCookie.getValue())) { |
||||
throw new InternalAuthenticationServiceException("无效的请求"); |
||||
} |
||||
|
||||
tempToken = tempTokenCookie.getValue(); |
||||
Boolean tokenIsValid = stringRedisTemplate.hasKey("tempToken:" + tempToken); |
||||
|
||||
if (tokenIsValid == null || !tokenIsValid) { |
||||
throw new InternalAuthenticationServiceException("无效的请求"); |
||||
} |
||||
// 校验验证码
|
||||
String rightCaptcha = stringRedisTemplate.opsForValue().get("captcha:" + tempToken); |
||||
String loginCaptcha = paramUserLoginVO.getLoginCaptcha(); |
||||
|
||||
if (loginCaptcha == null || "".equals(loginCaptcha)) { |
||||
throw new BusinessException(CodeMsg.CAPTCHA_IS_EMPTY); |
||||
} else if (rightCaptcha == null || "".equals(rightCaptcha)) { |
||||
throw new BusinessException(CodeMsg.CAPTCHA_IS_EXPIRED); |
||||
} else if (!loginCaptcha.equalsIgnoreCase(rightCaptcha)) { |
||||
throw new BusinessException(CodeMsg.CAPTCHA_IS_NOT_RIGHT); |
||||
} |
||||
// 用户名密码登录,用户名可以是用户账号名、手机号或电子邮箱
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(paramUserLoginVO.getUsername(), paramUserLoginVO.getPassword()); |
||||
request.setAttribute("token", token); |
||||
|
||||
return authenticationManager.authenticate(token); |
||||
} else if ("MOBILE".equalsIgnoreCase(loginType)) { |
||||
// 手机号验证码登录
|
||||
MobileCaptchaAuthenticationToken token = new MobileCaptchaAuthenticationToken(paramUserLoginVO.getPhoneNumber(), paramUserLoginVO.getMobileCaptcha()); |
||||
request.setAttribute("token", token); |
||||
|
||||
return authenticationManager.authenticate(token); |
||||
} else { |
||||
throw new AuthenticationServiceException("登录方式错误"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 登录成功 |
||||
* 无论以何种方式登录,登录成功后都向客户端返回 JWT,前端将该 JWT 临时存储到 localStorage 当中,以便后续的请求在请求头中携带 JWT。 |
||||
*/ |
||||
@Override |
||||
protected void successfulAuthentication( |
||||
HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult |
||||
) throws IOException { |
||||
Object principal = authResult.getPrincipal(); |
||||
UserInfoVO userInfoVO = new UserInfoVO(); |
||||
|
||||
if (authResult instanceof UsernamePasswordAuthenticationToken) { |
||||
userInfoVO.setLoginType("ACCOUNT"); |
||||
} else if (authResult instanceof MobileCaptchaAuthenticationToken) { |
||||
userInfoVO.setLoginType("MOBILE"); |
||||
} |
||||
|
||||
if (principal instanceof User) { |
||||
User user = (User) principal; |
||||
BeanUtils.copyProperties(user, userInfoVO); |
||||
} |
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper(); |
||||
String userInfoJSON = objectMapper.writeValueAsString(userInfoVO); |
||||
|
||||
UUID uuid = UUID.randomUUID(); |
||||
String jwtTokenId = uuid.toString(); |
||||
// 生成 jwt
|
||||
String jwt = Jwts.builder() |
||||
.claim("userInfo", userInfoJSON) |
||||
.setSubject(authResult.getName()) |
||||
.setExpiration(new Date(System.currentTimeMillis() + 10 * 24 * 60 * 60 * 1000)) // 10天有效
|
||||
.signWith(SignatureAlgorithm.HS512, "ncst20220218") |
||||
.setId(jwtTokenId) |
||||
.compact(); |
||||
|
||||
stringRedisTemplate.opsForValue().set("jwt_token:"+authResult.getName(),jwt,10, TimeUnit.DAYS); |
||||
// stringRedisTemplate.delete("user_attempts:"+request.getRemoteAddr());
|
||||
// 记录登录日志
|
||||
loginLogService.addLoginLog(userInfoVO.getId(), true); |
||||
|
||||
// 返回结果
|
||||
ResultVO<String> result = ResultUtils.success("登录成功", jwt); |
||||
response.setContentType("application/json;charset=utf-8"); |
||||
Cookie cookieToken = new Cookie("SYS_TOKEN", jwt); |
||||
cookieToken.setHttpOnly(true); |
||||
response.addCookie(cookieToken); |
||||
|
||||
log.info("登录成功"); |
||||
PrintWriter out = response.getWriter(); |
||||
out.print(new ObjectMapper().writeValueAsString(result)); |
||||
out.flush(); |
||||
out.close(); |
||||
} |
||||
|
||||
// 将权限列表转换成逗号分隔的字符串
|
||||
private String convertRoleToString(List<Role> roleList) { |
||||
StringBuilder roleStrings = new StringBuilder(); |
||||
|
||||
if (roleList == null) { |
||||
return ""; |
||||
} |
||||
|
||||
for (Role role : roleList) { |
||||
roleStrings.append(role.getName()).append(","); |
||||
} |
||||
|
||||
if (roleStrings.length() > 0) { |
||||
roleStrings.deleteCharAt(roleStrings.length() - 1); |
||||
} |
||||
|
||||
return roleStrings.toString(); |
||||
} |
||||
|
||||
// 将权限列表转换成逗号分隔的字符串
|
||||
private String convertAuthorityToString(Collection<? extends GrantedAuthority> authorities) { |
||||
StringBuilder authorityString = new StringBuilder(); |
||||
|
||||
for (GrantedAuthority authority : authorities) { |
||||
authorityString.append(authority.getAuthority()).append(","); |
||||
} |
||||
|
||||
if (authorityString.length() > 0) { |
||||
authorityString.deleteCharAt(authorityString.length() - 1); |
||||
} |
||||
|
||||
return authorityString.toString(); |
||||
} |
||||
|
||||
/** |
||||
* 登录失败 |
||||
* 向前端返回登录失败的错误信息 |
||||
*/ |
||||
@Override |
||||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { |
||||
ResultVO<?> resultVO = new ResultVO<>(); |
||||
log.debug("登录失败:"+failed); |
||||
if (failed instanceof LockedException) { |
||||
resultVO = ResultUtils.error(CodeMsg.USER_IS_LOCKED); |
||||
} else if (failed instanceof CredentialsExpiredException) { |
||||
resultVO = ResultUtils.error(CodeMsg.USER_CREDENTIALS_EXPIRED); |
||||
} else if (failed instanceof AccountExpiredException) { |
||||
resultVO = ResultUtils.error(CodeMsg.USER_ACCOUNT_EXPIRED); |
||||
} else if (failed instanceof DisabledException) { |
||||
resultVO = ResultUtils.error(CodeMsg.USER_IS_DISABLED); |
||||
} else if (failed instanceof AuthenticationException) { |
||||
if (failed instanceof BusinessException) { |
||||
stringRedisTemplate.delete("captcha:" + tempToken); |
||||
BusinessException businessException = (BusinessException) failed; |
||||
resultVO = ResultUtils.error(businessException.getError()); |
||||
|
||||
}else{ |
||||
Throwable cause = failed.getCause(); |
||||
if (cause instanceof BusinessException) { |
||||
BusinessException businessException = (BusinessException) cause; |
||||
resultVO = ResultUtils.error(businessException.getError()); |
||||
} else { |
||||
resultVO = ResultUtils.error(CodeMsg.USER_BAD_CREDENTIALS); |
||||
} |
||||
} |
||||
} else { |
||||
resultVO = ResultUtils.error(CodeMsg.LOGIN_FAIL); |
||||
} |
||||
|
||||
// 拦截后直接将错误信息反馈给前端,不经过全局异常处理器(GlobalExceptionHandler)处理
|
||||
response.setCharacterEncoding("utf-8"); |
||||
response.setContentType("application/json;charset=utf-8"); |
||||
PrintWriter out = response.getWriter(); |
||||
out.write(new ObjectMapper().writeValueAsString(resultVO)); |
||||
out.flush(); |
||||
out.close(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,39 @@ |
||||
package edu.ncst.award.config.security.token; |
||||
|
||||
import edu.ncst.award.vo.result.UserInfoVO; |
||||
import org.springframework.security.authentication.AbstractAuthenticationToken; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* JWT认证凭证(Security Context) |
||||
*/ |
||||
public class JwtAuthenticationToken extends AbstractAuthenticationToken { |
||||
|
||||
private final Object principal; |
||||
|
||||
private final Object details; |
||||
|
||||
public JwtAuthenticationToken(Object principal, List<GrantedAuthority> authorities, UserInfoVO userInfoVO) { |
||||
super(authorities); |
||||
this.principal = principal; |
||||
this.details = userInfoVO; |
||||
this.setAuthenticated(true); |
||||
} |
||||
|
||||
@Override |
||||
public Object getCredentials() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public Object getPrincipal() { |
||||
return this.principal; |
||||
} |
||||
|
||||
@Override |
||||
public Object getDetails() { |
||||
return this.details; |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@ |
||||
package edu.ncst.award.config.security.token; |
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* 手机验证码方式登录凭证 |
||||
*/ |
||||
public class MobileCaptchaAuthenticationToken extends AbstractAuthenticationToken { |
||||
|
||||
// 手机号码
|
||||
private String phoneNumber; |
||||
|
||||
// 验证码
|
||||
private String captchaCode; |
||||
|
||||
public MobileCaptchaAuthenticationToken(String phoneNumber, String captchaCode) { |
||||
super(null); |
||||
this.phoneNumber = phoneNumber; |
||||
this.captchaCode = captchaCode; |
||||
} |
||||
|
||||
public MobileCaptchaAuthenticationToken(Collection<? extends GrantedAuthority> authorities) { |
||||
super(authorities); |
||||
} |
||||
|
||||
@Override |
||||
public Object getCredentials() { |
||||
return this.phoneNumber; |
||||
} |
||||
|
||||
@Override |
||||
public Object getPrincipal() { |
||||
return this.captchaCode; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,40 @@ |
||||
package edu.ncst.award.controller; |
||||
|
||||
|
||||
import edu.ncst.award.utils.ResultUtils; |
||||
import edu.ncst.award.vo.ResultVO; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import javax.annotation.security.PermitAll; |
||||
import javax.servlet.http.Cookie; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
@Api(tags = "AuthController") |
||||
@RestController |
||||
@RequestMapping("/api/auth") |
||||
public class AuthController { |
||||
|
||||
|
||||
@ApiOperation("注销登录") |
||||
@GetMapping("/logout") |
||||
public ResultVO<?> logout(HttpServletRequest request, HttpServletResponse response) { |
||||
Cookie[] cookies = request.getCookies(); |
||||
|
||||
if (cookies != null) { |
||||
for (Cookie cookie : cookies) { |
||||
cookie.setValue(""); |
||||
cookie.setPath("/api"); |
||||
cookie.setMaxAge(0); |
||||
response.addCookie(cookie); |
||||
} |
||||
} |
||||
|
||||
return ResultUtils.success("注销成功", null); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@ |
||||
package edu.ncst.award.controller; |
||||
|
||||
import edu.ncst.award.utils.CaptchaUtil; |
||||
import edu.ncst.award.utils.CookieUtils; |
||||
import io.swagger.annotations.Api; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.data.redis.core.StringRedisTemplate; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
||||
import javax.imageio.ImageIO; |
||||
import javax.servlet.ServletOutputStream; |
||||
import javax.servlet.http.Cookie; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.awt.image.BufferedImage; |
||||
import java.io.IOException; |
||||
import java.util.UUID; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
@Api(description = "验证码") |
||||
@Controller |
||||
@RequestMapping("/api") |
||||
public class CaptchaController { |
||||
|
||||
@Autowired |
||||
private StringRedisTemplate stringRedisTemplate; |
||||
|
||||
@GetMapping("/captcha.jpg") |
||||
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException { |
||||
Cookie tempTokenCookie = CookieUtils.getCookie(request, "TEMP_TOKEN"); |
||||
String tempToken = null; |
||||
Boolean tokenIsValid = false; |
||||
|
||||
// 校验 TOKEN 是否存在
|
||||
if (tempTokenCookie != null && tempTokenCookie.getValue() != null && !"".equals(tempTokenCookie.getValue())) { |
||||
tempToken = tempTokenCookie.getValue(); |
||||
} |
||||
|
||||
// 校验 TOKEN 是否有效
|
||||
if (tempToken != null) { |
||||
tokenIsValid = stringRedisTemplate.hasKey("tempToken:" + tempToken); |
||||
} |
||||
|
||||
// 如果没有 TOKEN 或者 TOKEN 失效都会生成一个新的TOKEN
|
||||
if (tokenIsValid == null || !tokenIsValid) { |
||||
UUID uuid = UUID.randomUUID(); |
||||
tempToken = uuid.toString(); |
||||
|
||||
// 将临时 TOKEN 存入Redis,设置24小时的过期时间
|
||||
stringRedisTemplate.opsForValue().set("tempToken:" + tempToken, "1", 24, TimeUnit.HOURS); |
||||
|
||||
// 将临时TOKEN存入Cookie同步设置24小时过期时间
|
||||
Cookie newCookie = new Cookie("TEMP_TOKEN", tempToken); |
||||
newCookie.setMaxAge(24 * 59 * 60); |
||||
newCookie.setHttpOnly(true); |
||||
|
||||
response.addCookie(newCookie); |
||||
} |
||||
|
||||
// 生成验证码
|
||||
response.setHeader("Cache-Control", "no-store, no-cache"); |
||||
response.setContentType("image/jpeg"); |
||||
|
||||
BufferedImage image = CaptchaUtil.createImage(); |
||||
String randomText = CaptchaUtil.drawRandomText(image); |
||||
|
||||
// 将验证码存入 Redis,设置两分钟的过期时间
|
||||
stringRedisTemplate.opsForValue().set("captcha:" + tempToken, randomText, 2, TimeUnit.MINUTES); |
||||
ServletOutputStream out = response.getOutputStream(); |
||||
|
||||
ImageIO.write(image, "jpeg", out); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,123 @@ |
||||
package edu.ncst.award.controller; |
||||
|
||||
import edu.ncst.award.model.system.Menu; |
||||
import edu.ncst.award.service.IMenuService; |
||||
import edu.ncst.award.utils.ResponseUtils; |
||||
import edu.ncst.award.utils.ResultUtils; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.Query; |
||||
import edu.ncst.award.vo.ResultVO; |
||||
import edu.ncst.award.vo.param.ResetMenuDTO; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import java.util.List; |
||||
|
||||
; |
||||
|
||||
/** |
||||
* 菜单 控制器 |
||||
*/ |
||||
@Api(tags = "MenuController") |
||||
@Slf4j |
||||
@RestController |
||||
@RequestMapping("/api/menu") |
||||
public class MenuController { |
||||
|
||||
private final IMenuService menuService; |
||||
|
||||
public MenuController(IMenuService menuService) { |
||||
this.menuService = menuService; |
||||
} |
||||
|
||||
@ApiOperation("获取菜单列表") |
||||
@GetMapping("/list") |
||||
public ResultVO<ListResultVO<Menu>> getMenuList(Query query) { |
||||
ListResultVO<Menu> menuList = menuService.getMenuList(query); |
||||
|
||||
return ResultUtils.success("ok", menuList); |
||||
} |
||||
|
||||
@ApiOperation("获取菜单树") |
||||
@GetMapping("/tree") |
||||
public ResultVO<List<Menu>> getMenuTree() { |
||||
List<Menu> menuTree = menuService.getMenuTree(); |
||||
return ResultUtils.success("ok", menuTree); |
||||
} |
||||
|
||||
@ApiOperation("获取指定角色能够访问的菜单ID列表") |
||||
@GetMapping("/ids/role") |
||||
public ResultVO<List<Integer>> getRoleMenuIds(Integer roleId) { |
||||
List<Integer> menuIds = menuService.getRoleMenuIds(roleId,true); |
||||
return ResultUtils.success("ok", menuIds); |
||||
} |
||||
|
||||
@ApiOperation("获取菜单详情") |
||||
@PostMapping("/detail") |
||||
public ResultVO<Menu> getMenuDetail(@RequestParam Integer id) { |
||||
Menu menuDetail = menuService.getMenuDetail(id); |
||||
|
||||
return ResultUtils.success("ok", menuDetail); |
||||
} |
||||
|
||||
@ApiOperation("添加菜单") |
||||
@PostMapping("/add") |
||||
public ResultVO<Menu> addMenu(@RequestBody Menu menu) { |
||||
log.info("将要添加的菜单:" + menu); |
||||
Menu result = menuService.addMenu(menu); |
||||
|
||||
return ResultUtils.success("ok", result); |
||||
} |
||||
|
||||
@ApiOperation("修改菜单") |
||||
@PostMapping("/modify") |
||||
public ResultVO<Menu> modifyMenu(@RequestBody Menu menu) { |
||||
Menu result = menuService.modifyMenu(menu); |
||||
|
||||
return ResultUtils.success("ok", result); |
||||
} |
||||
|
||||
@ApiOperation("删除菜单") |
||||
@PostMapping("/delete") |
||||
public ResultVO<Menu> deleteMenu(@RequestParam Integer id) { |
||||
menuService.deleteMenu(id); |
||||
|
||||
return ResultUtils.success("ok", null); |
||||
} |
||||
|
||||
|
||||
@ApiOperation("获取指定用户可访问的菜单列表") |
||||
@GetMapping("/user/{userId}") |
||||
public ResultVO<ListResultVO<Menu>> getUserMenuList(@PathVariable Integer userId) { |
||||
return ResultUtils.success("ok", null); |
||||
} |
||||
|
||||
@ApiOperation("获取当前用户可访问的菜单列表") |
||||
@GetMapping("/user/current") |
||||
public ResultVO<List<Menu>> getCurrentUserMenuTree() { |
||||
List<Menu> currentUserMenuTree = menuService.getCurrentUserMenuTree(); |
||||
|
||||
return ResultUtils.success("ok", currentUserMenuTree); |
||||
} |
||||
|
||||
@ApiOperation("获取当前用户可访问的菜单列表") |
||||
@GetMapping("/user/authority") |
||||
public ResultVO<List<String>> getCurrentUserMenuAuthority() { |
||||
List<String> currentUserMenuTree = menuService.getCurrentUserMenuAuthority(); |
||||
|
||||
return ResultUtils.success("ok", currentUserMenuTree); |
||||
} |
||||
|
||||
|
||||
@ApiOperation("重置菜单元信息") |
||||
@PostMapping("/reset") |
||||
public ResponseEntity<?> resetMenuMeta(@RequestBody ResetMenuDTO resetMenuDTO) { |
||||
menuService.resetMenuMeta(resetMenuDTO); |
||||
|
||||
return ResponseUtils.success("ok", null); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,146 @@ |
||||
package edu.ncst.award.controller; |
||||
|
||||
|
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.model.system.MajorCategory; |
||||
import edu.ncst.award.model.system.Resource; |
||||
import edu.ncst.award.service.IUserService; |
||||
import edu.ncst.award.utils.ResponseUtils; |
||||
import edu.ncst.award.utils.ResultUtils; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.ResultVO; |
||||
import edu.ncst.award.vo.param.query.UserQuery; |
||||
import edu.ncst.award.vo.result.UserInfoVO; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import java.util.ArrayList; |
||||
|
||||
@RestController |
||||
@RequestMapping("/test") |
||||
@Api(tags = "TestController") |
||||
@Slf4j |
||||
public class TestController { |
||||
|
||||
@Autowired |
||||
private IUserService userService; |
||||
|
||||
@ApiOperation("get传参,简单类型,requestParam注解") |
||||
@GetMapping("/user/detail") |
||||
@ResponseBody |
||||
public ResponseEntity getUserInfo(@RequestParam(name = "phone", required = false) String param, @RequestParam String name){ |
||||
if(param==null){ |
||||
return ResponseUtils.error(CodeMsg.PARAM_ERROR); |
||||
} |
||||
UserQuery queryVO = new UserQuery(); |
||||
queryVO.setPhone(param); |
||||
UserInfoVO user = userService.getUserInfo(queryVO); |
||||
return ResponseUtils.success(user); |
||||
} |
||||
|
||||
@ApiOperation("post传参,简单类型,requestParam注解") |
||||
@PostMapping("/user/detail00") |
||||
@ResponseBody |
||||
public ResponseEntity getUserInfo00(@RequestParam(name = "param", required = false) String param,@RequestParam String name){ |
||||
if(param==null){ |
||||
return ResponseUtils.error(CodeMsg.PARAM_ERROR); |
||||
} |
||||
UserQuery queryVO = new UserQuery(); |
||||
queryVO.setPhone(param); |
||||
UserInfoVO user = userService.getUserInfo(queryVO); |
||||
return ResponseUtils.success(user); |
||||
} |
||||
@ApiOperation("get方法传参,类接收,无注解") |
||||
@GetMapping("/user/detail1") |
||||
@ResponseBody |
||||
public UserInfoVO getUserInfo1(UserQuery param){ |
||||
if(param.getPhone()==null){ |
||||
String s = param.getPhone().toString(); |
||||
} |
||||
UserInfoVO user = userService.getUserInfo(param); |
||||
user.setFullName("getUserInfo1"); |
||||
return user; |
||||
} |
||||
|
||||
@ApiOperation("post方法传参,类接收,无注解") |
||||
@PostMapping("/user/detail11") |
||||
@ResponseBody |
||||
public UserInfoVO getUserInfo11(UserQuery param){ |
||||
if(param.getPhone()==null){ |
||||
String s = param.getPhone().toString(); |
||||
} |
||||
UserInfoVO user = userService.getUserInfo(param); |
||||
user.setFullName("getUserInfo1"); |
||||
return user; |
||||
} |
||||
|
||||
@ApiOperation("get方法传参,类接收,requestparam注解") |
||||
@GetMapping("/user/detail2") |
||||
@ResponseBody |
||||
public UserInfoVO getUserInfo2(@RequestParam UserQuery param){ |
||||
if(param.getPhone()==null){ |
||||
String s = param.getPhone().toString(); |
||||
} |
||||
UserInfoVO user = userService.getUserInfo(param); |
||||
user.setFullName("getUserInfo2"); |
||||
return user; |
||||
} |
||||
|
||||
@ApiOperation("post方法传参,类接收,requestparam注解") |
||||
@PostMapping("/user/detail22") |
||||
@ResponseBody |
||||
public UserInfoVO getUserInfo22(@RequestParam UserQuery param){ |
||||
if(param.getPhone()==null){ |
||||
String s = param.getPhone().toString(); |
||||
} |
||||
UserInfoVO user = userService.getUserInfo(param); |
||||
user.setFullName("getUserInfo22"); |
||||
return user; |
||||
} |
||||
|
||||
@ApiOperation("get方法传参,类接收,requestbody注解") |
||||
@GetMapping("/user/detail3") |
||||
@ResponseBody |
||||
public UserInfoVO getUserInfo3(@RequestBody UserQuery param){ |
||||
if(param.getPhone()==null){ |
||||
String s = param.getPhone().toString(); |
||||
} |
||||
UserInfoVO user = userService.getUserInfo(param); |
||||
user.setFullName("getUserInfo3"); |
||||
return user; |
||||
} |
||||
|
||||
@ApiOperation("post方法传参,类接收,requestbody注解") |
||||
@PostMapping("/user/detail4") |
||||
@ResponseBody |
||||
public UserInfoVO getUserInfo4(@RequestBody UserQuery param){ |
||||
if(param.getPhone()==null){ |
||||
String s = param.getPhone().toString(); |
||||
} |
||||
UserInfoVO user = userService.getUserInfo(param); |
||||
user.setFullName("getUserInfo3"); |
||||
return user; |
||||
} |
||||
|
||||
@ApiOperation("post方法传参,类接收,requestbody注解") |
||||
@PostMapping("/user/detail5") |
||||
@ResponseBody |
||||
public ListResultVO<MajorCategory> getUserInfo5(@RequestBody MajorCategory param){ |
||||
ListResultVO<MajorCategory> listResultVO = new ListResultVO<>(); |
||||
ArrayList l = new ArrayList(); |
||||
l.add(param); |
||||
listResultVO.setList(l); |
||||
return listResultVO; |
||||
} |
||||
|
||||
@ApiOperation("post方法传参,类接收,requestbody注解") |
||||
@PostMapping("/user/detailResource") |
||||
@ResponseBody |
||||
public ResultVO<Resource> getUserInfoResource(@RequestBody Resource param){ |
||||
return ResultUtils.success(param); |
||||
} |
||||
} |
||||
@ -0,0 +1,56 @@ |
||||
package edu.ncst.award.controller; |
||||
|
||||
|
||||
import edu.ncst.award.model.system.User; |
||||
import edu.ncst.award.service.IUserService; |
||||
import edu.ncst.award.utils.ResponseUtils; |
||||
import edu.ncst.award.utils.ResultUtils; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.ResultVO; |
||||
import edu.ncst.award.vo.param.CurrentUserDTO; |
||||
import edu.ncst.award.vo.param.query.UserQuery; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.validation.BindingResult; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import java.util.List; |
||||
|
||||
; |
||||
|
||||
/** |
||||
* 用户 控制器 |
||||
*/ |
||||
@Api(description = "用户管理", tags = "UserController") |
||||
@Slf4j |
||||
@RestController |
||||
@RequestMapping("/api/user") |
||||
public class UserController { |
||||
|
||||
private final IUserService userService; |
||||
|
||||
public UserController(IUserService userService) { |
||||
this.userService = userService; |
||||
} |
||||
|
||||
@ApiOperation("获取用户列表") |
||||
@GetMapping("/list") |
||||
public ResultVO<ListResultVO<User>> getUserList(UserQuery query) { |
||||
ListResultVO<User> userList = userService.getUserList(query); |
||||
|
||||
return ResultUtils.success("ok", userList); |
||||
} |
||||
|
||||
|
||||
@ApiOperation("获取当前用户信息") |
||||
@GetMapping("/current") |
||||
public ResponseEntity<?> getCurrentUser() { |
||||
CurrentUserDTO currentUserDTO = userService.getCurrentUser(); |
||||
|
||||
return ResponseUtils.success(currentUserDTO); |
||||
} |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,41 @@ |
||||
package edu.ncst.award.exception; |
||||
|
||||
import org.springframework.security.core.AuthenticationException; |
||||
|
||||
public class BusinessException extends AuthenticationException { |
||||
|
||||
private CodeMsg codeMsg; |
||||
|
||||
public BusinessException(String msg){ |
||||
super(msg); |
||||
codeMsg = CodeMsg.BUSINESS_ERROR.fillArgs(msg); |
||||
} |
||||
|
||||
public BusinessException(CodeMsg msg){ |
||||
super(msg.getMsg()); |
||||
if(msg==null){ |
||||
msg=CodeMsg.BUSINESS_ERROR.fillArgs("未知错误"); |
||||
} |
||||
|
||||
this.codeMsg = msg; |
||||
} |
||||
public CodeMsg getError() { |
||||
if(codeMsg==null){ |
||||
codeMsg=CodeMsg.BUSINESS_ERROR.fillArgs("未知错误"); |
||||
} |
||||
return codeMsg; |
||||
} |
||||
|
||||
@Override |
||||
public String getMessage() { |
||||
if(codeMsg==null){ |
||||
String msg = super.getMessage(); |
||||
if(msg==null){ |
||||
return CodeMsg.BUSINESS_ERROR.fillArgs("未知错误").getMsg(); |
||||
} else{ |
||||
return msg; |
||||
} |
||||
} |
||||
return codeMsg.getMsg(); |
||||
} |
||||
} |
||||
@ -0,0 +1,63 @@ |
||||
package edu.ncst.award.exception; |
||||
|
||||
import lombok.Data; |
||||
|
||||
@Data |
||||
public class CodeMsg { |
||||
|
||||
public static final CodeMsg SUCCESS = new CodeMsg(0, "成功"); |
||||
public static final CodeMsg SUCCESS_MSG = new CodeMsg(0, "%s"); |
||||
public final static CodeMsg SERVER_ERROR = new CodeMsg(500, "服务器错误:%s"); |
||||
public static final CodeMsg PARAM_ERROR = new CodeMsg(501, "参数错误:%s"); |
||||
public static final CodeMsg GLOBAL_ERROR = new CodeMsg(502, "全局错误:%s"); |
||||
public static final CodeMsg BUSINESS_ERROR = new CodeMsg(503, "业务错误:%s");; |
||||
|
||||
public final static CodeMsg USER_NEED_LOGIN = new CodeMsg(10000, "尚未登录,请登录。"); |
||||
public final static CodeMsg USERNAME_REQUIRE_NOT_NULL = new CodeMsg(10001, "用户名不能为空"); |
||||
public final static CodeMsg PASSWORD_REQUIRE_NOT_NULL = new CodeMsg(10002, "用户密码不能为空"); |
||||
public final static CodeMsg USER_NOT_FOUND = new CodeMsg(10003, "该用户不存在。"); |
||||
public final static CodeMsg USERNAME_OR_PASSWORD_ERROR = new CodeMsg(10004, "用户名或密码错误"); |
||||
public final static CodeMsg USER_IS_LOCKED = new CodeMsg(10005, "当前IP已被锁定,请1小时后再试。"); |
||||
public final static CodeMsg USER_CREDENTIALS_EXPIRED = new CodeMsg(10006, "用户登录凭证过期,请重新登录。"); |
||||
public final static CodeMsg USER_ACCOUNT_EXPIRED = new CodeMsg(10007, "用户账号过期,请联系管理员。"); |
||||
public final static CodeMsg USER_IS_DISABLED = new CodeMsg(10008, "该用户已被禁用,请联系管理员。"); |
||||
public final static CodeMsg USER_BAD_CREDENTIALS = new CodeMsg(10009, "无效的登录凭证,请尝试重新登录"); |
||||
public final static CodeMsg USER_TOKEN_EXPIRED = new CodeMsg(10010, "用户凭证已过期,请联系管理员。"); |
||||
public final static CodeMsg NOT_GET_CURRENT_USER_INFO = new CodeMsg(10011, "无法获取当前用户信息。"); |
||||
public final static CodeMsg CAN_NOT_DATA_ACCESS = new CodeMsg(10012, "当前用户没有访问相关数据的权限。"); |
||||
public final static CodeMsg LOGIN_FAIL = new CodeMsg(10015, "登录失败,未知原因。"); |
||||
public final static CodeMsg CAPTCHA_IS_NOT_RIGHT = new CodeMsg(10018, "验证码错误。"); |
||||
public final static CodeMsg CAPTCHA_IS_EMPTY = new CodeMsg(10019, "验证码不能为空。"); |
||||
public final static CodeMsg CAPTCHA_IS_EXPIRED = new CodeMsg(10020, "验证码已过期。"); |
||||
public static final CodeMsg TEMP_TOKEN_NOT_FOUND = new CodeMsg(10021,"无法获取临时授权"); |
||||
public static final CodeMsg TEMP_TOKEN_EXPIRED = new CodeMsg(10022,"临时授权已过期"); |
||||
|
||||
/** |
||||
* 基本操作 101XX |
||||
*/ |
||||
public final static CodeMsg FETCH_ERROR = new CodeMsg(10100, "获取数据失败"); |
||||
public final static CodeMsg ADD_ERROR = new CodeMsg(10101, "添加失败%s"); |
||||
public final static CodeMsg MODIFY_ERROR = new CodeMsg(10102, "修改失败"); |
||||
public final static CodeMsg DELETE_ERROR = new CodeMsg(10103, "删除失败"); |
||||
public final static CodeMsg DELETE_ERROR_RELATION_DATA = new CodeMsg(101031, "存在关联业务数据,删除或禁用失败"); |
||||
public final static CodeMsg REPEAT_RECORD = new CodeMsg(10104, "字段与已有记录重复"); |
||||
public final static CodeMsg ENABLE_OR_DISABLE_ERROR = new CodeMsg(10105, "启用或禁用出错。"); |
||||
public final static CodeMsg CAN_FOUND_DICT = new CodeMsg(10106, "找不到字典。"); |
||||
public final static CodeMsg FORM_ERROR = new CodeMsg(10107, "表单参数错误"); |
||||
public static final CodeMsg MORE_THAN_ONE_ITEM = new CodeMsg(10108, "多余一条数据被查出"); |
||||
public static final CodeMsg ALREADY_EXIST_ITEM = new CodeMsg(10109, "数据重复"); |
||||
|
||||
|
||||
|
||||
private int code; |
||||
private String msg; |
||||
|
||||
public CodeMsg(int i, String s) { |
||||
this.code=i; |
||||
this.msg=s; |
||||
} |
||||
public CodeMsg fillArgs(Object... args) { |
||||
String message = String.format(msg, args); |
||||
return new CodeMsg(code, message); |
||||
} |
||||
} |
||||
@ -0,0 +1,54 @@ |
||||
package edu.ncst.award.exception.handler; |
||||
|
||||
import edu.ncst.award.exception.BusinessException; |
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.utils.ResponseUtils; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ExceptionHandler; |
||||
import org.springframework.web.bind.annotation.ResponseBody; |
||||
import org.springframework.web.bind.annotation.RestControllerAdvice; |
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j; |
||||
|
||||
/** |
||||
* 自定义异常处理器 |
||||
* |
||||
* @author ieflex |
||||
*/ |
||||
@RestControllerAdvice |
||||
@Slf4j |
||||
public class InterfaceExceptionHandler { |
||||
|
||||
/** |
||||
* 接口 业务异常 |
||||
*/ |
||||
@ResponseBody |
||||
@ExceptionHandler(BusinessException.class) |
||||
public ResponseEntity businessInterfaceException(BusinessException e) { |
||||
log.error(e.getMessage(), e); |
||||
return ResponseUtils.error(CodeMsg.BUSINESS_ERROR.fillArgs(e.getError())); |
||||
} |
||||
|
||||
/** |
||||
* 拦截所有运行时的全局异常 |
||||
*/ |
||||
@ExceptionHandler(RuntimeException.class) |
||||
@ResponseBody |
||||
public ResponseEntity runtimeException(RuntimeException e) { |
||||
log.error(e.getMessage(), e); |
||||
// 返回 JOSN
|
||||
return ResponseUtils.error(CodeMsg.GLOBAL_ERROR.fillArgs(e.toString())); |
||||
} |
||||
|
||||
/** |
||||
* 系统异常捕获处理 |
||||
*/ |
||||
@ExceptionHandler(Exception.class) |
||||
@ResponseBody |
||||
public ResponseEntity exception(Exception e) { |
||||
log.error(e.getMessage(), e); |
||||
return ResponseUtils.error(CodeMsg.SERVER_ERROR.fillArgs(e.toString())); |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,36 @@ |
||||
package edu.ncst.award.mapper.system; |
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
||||
import edu.ncst.award.model.system.Menu; |
||||
import org.apache.ibatis.annotations.Param; |
||||
import org.springframework.stereotype.Repository; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 菜单 Mapper 接口 |
||||
*/ |
||||
@Repository |
||||
public interface MenuMapper extends BaseMapper<Menu> { |
||||
|
||||
int batchInsert(List<Menu> menuList); |
||||
|
||||
int batchInsertOrUpdate(List<Menu> menuList); |
||||
|
||||
Page<Menu> getMenuList(Page<Menu> pageRequest, @Param("parentId") Integer parentId); |
||||
|
||||
List<Menu> getMenuTree(@Param("parentId") Integer parentId); |
||||
|
||||
// 清空菜单项
|
||||
int removeAllMenuItem(); |
||||
|
||||
// 获取指定角色能够访问的菜单ID
|
||||
List<Integer> getRoleMenuIds(Integer roleId); |
||||
|
||||
List<String> getCurrentUserMenuAuthority(List<String> roleNames); |
||||
|
||||
List<Integer> getParentMenuid(List<Integer> menuIds); |
||||
|
||||
List<Integer> getRoleMenuIdsWithParent(Integer roleId); |
||||
} |
||||
@ -0,0 +1,39 @@ |
||||
package edu.ncst.award.mapper.system; |
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
import edu.ncst.award.model.system.Resource; |
||||
import org.springframework.stereotype.Repository; |
||||
import org.springframework.web.bind.annotation.RequestParam; |
||||
|
||||
import java.util.List; |
||||
|
||||
@Repository |
||||
public interface ResourceMapper extends BaseMapper<Resource> { |
||||
|
||||
List<String> getRolesResourceUrls(List<String> roleNames); |
||||
|
||||
int batchReplaceOrInsert(List<Resource> resources); |
||||
|
||||
int batchInsert(List<Resource> resources); |
||||
|
||||
int replaceOrInsert(@RequestParam("resource") Resource resource); |
||||
|
||||
Integer getIdByName(String name); |
||||
|
||||
int insertNotExisted(@RequestParam("resource") Resource resource); |
||||
|
||||
List<Integer> getRoleResourceIds(Integer roleId); |
||||
|
||||
List<Resource> getResourceTree(); |
||||
|
||||
List<Resource> getPermitAllResourceList(); |
||||
|
||||
List<Resource> getUserScopeResourceList(); |
||||
|
||||
List<String> getPermitAllResourceURLs(); |
||||
|
||||
List<String> getUserScopeResourceURLs(); |
||||
|
||||
// 所有用户都可以访问的查询接口
|
||||
List<Integer> getAllResourceIds(); |
||||
} |
||||
@ -0,0 +1,51 @@ |
||||
package edu.ncst.award.mapper.system; |
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
import com.baomidou.mybatisplus.core.toolkit.Constants; |
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
||||
import edu.ncst.award.model.system.User; |
||||
import org.apache.ibatis.annotations.Param; |
||||
import org.apache.ibatis.annotations.Update; |
||||
import org.springframework.stereotype.Repository; |
||||
|
||||
import java.util.Date; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 用户 Mapper 接口 |
||||
*/ |
||||
@Repository |
||||
|
||||
public interface UserMapper extends BaseMapper<User> { |
||||
|
||||
Page<Integer> getUserPage( |
||||
@Param("pageRequest") Page<User> pageRequest, |
||||
@Param(Constants.WRAPPER) QueryWrapper queryWrapper |
||||
); |
||||
|
||||
List<User> getUserList( @Param(Constants.WRAPPER) QueryWrapper queryWrapper); |
||||
|
||||
User getUserDetail(Integer userId); |
||||
|
||||
User getUserByAccount(String account); |
||||
|
||||
User getUserByOpenId(String openId); |
||||
|
||||
@Update("UPDATE sys_user SET enable = #{ enable } WHERE id = #{ userId }") |
||||
int setUserEnable(Integer userId, Boolean enable); |
||||
|
||||
// 更新用户的最后登录记录
|
||||
int updateUserLastLoginRecord( |
||||
@Param("userId") Integer userId, |
||||
@Param("lastLoginIp") String lastLoginIp, |
||||
@Param("lastLoginTime") Date lastLoginTime |
||||
); |
||||
|
||||
// 绑定用户微信号
|
||||
int bindUserOpenId(Integer userId, String openId); |
||||
|
||||
int getUserByUnitID(Integer unitID); |
||||
|
||||
void batchUpdatePassword(List<User> newuser); |
||||
} |
||||
@ -0,0 +1,40 @@ |
||||
package edu.ncst.award.model; |
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill; |
||||
import com.baomidou.mybatisplus.annotation.TableField; |
||||
import com.baomidou.mybatisplus.annotation.TableLogic; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.Date; |
||||
|
||||
@Data |
||||
public abstract class BaseEntity implements Serializable { |
||||
|
||||
@ApiModelProperty(value = "创建时间") |
||||
@TableField(value = "create_time", fill = FieldFill.INSERT) |
||||
private Date createTime; |
||||
|
||||
@ApiModelProperty(value = "创建人") |
||||
@TableField(value = "create_user_id", fill = FieldFill.INSERT) |
||||
private Integer createUserId; |
||||
|
||||
@ApiModelProperty(value = "最后更新时间") |
||||
@TableField(value = "last_modify_time", fill = FieldFill.INSERT_UPDATE) |
||||
private Date lastModifyTime; |
||||
|
||||
@ApiModelProperty(value = "最后更新者") |
||||
@TableField(value = "last_modify_user_id", fill = FieldFill.INSERT_UPDATE) |
||||
private Integer lastModifyUserId; |
||||
|
||||
@ApiModelProperty(value = "删除时间") |
||||
@TableField("delete_time") |
||||
@TableLogic |
||||
private Date deleteTime; |
||||
|
||||
@ApiModelProperty(value = "删除者") |
||||
@TableField("delete_user_id") |
||||
private Integer deleteUserId; |
||||
|
||||
} |
||||
@ -0,0 +1,50 @@ |
||||
package edu.ncst.award.model.log; |
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType; |
||||
import com.baomidou.mybatisplus.annotation.TableField; |
||||
import com.baomidou.mybatisplus.annotation.TableId; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import edu.ncst.award.model.system.User; |
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.Date; |
||||
|
||||
/** |
||||
* 登录日志 |
||||
*/ |
||||
@ApiModel(value="LoginLog", description="登录日志") |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = false) |
||||
@TableName("log_login_log") |
||||
public class LoginLog implements Serializable { |
||||
|
||||
private static final long serialVersionUID=1L; |
||||
|
||||
@ApiModelProperty(value = "ID") |
||||
@TableId(value = "id", type = IdType.AUTO) |
||||
private Integer id; |
||||
|
||||
@ApiModelProperty(value = "登录方式") |
||||
private String type; |
||||
|
||||
@ApiModelProperty(value = "用户ID") |
||||
private Integer userId; |
||||
|
||||
@ApiModelProperty(value = "登錄用户") |
||||
@TableField(exist = false) |
||||
private User user; |
||||
|
||||
@ApiModelProperty(value = "登录凭证", notes = "用户名、手机号、电子邮箱或微信登录凭证code") |
||||
private String principal; |
||||
|
||||
@ApiModelProperty(value = "IP地址") |
||||
private String ipAddress; |
||||
|
||||
@ApiModelProperty(value = "登录时间") |
||||
private Date loginTime; |
||||
|
||||
} |
||||
@ -0,0 +1,59 @@ |
||||
package edu.ncst.award.model.system; |
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType; |
||||
import com.baomidou.mybatisplus.annotation.TableField; |
||||
import com.baomidou.mybatisplus.annotation.TableId; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import edu.ncst.award.model.BaseEntity; |
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 专业分类 |
||||
*/ |
||||
@ApiModel(value="MajorCategory", description="专业分类") |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = true) |
||||
@TableName(value = "major_category", autoResultMap = true) |
||||
public class MajorCategory extends BaseEntity implements Serializable { |
||||
|
||||
private static final long serialVersionUID = -1167360738461144523L; |
||||
|
||||
@ApiModelProperty(value = "分类ID") |
||||
@TableId(value = "id", type = IdType.AUTO) |
||||
private Integer id; |
||||
|
||||
@ApiModelProperty(value = "分类编码") |
||||
private String code; |
||||
|
||||
@ApiModelProperty(value = "分类名称") |
||||
private String name; |
||||
|
||||
@ApiModelProperty(value = "是否启用") |
||||
private Boolean enable; |
||||
|
||||
@ApiModelProperty(value = "分类级别") |
||||
private Integer level; |
||||
|
||||
@ApiModelProperty(value = "上级分类ID") |
||||
private Integer parentId; |
||||
|
||||
@ApiModelProperty(value = "备注") |
||||
private String remark; |
||||
|
||||
@ApiModelProperty(value = "下级分类列表") |
||||
@TableField(exist = false) |
||||
private List<MajorCategory> children; |
||||
|
||||
public MajorCategory(Integer id) { |
||||
this.id = id; |
||||
} |
||||
|
||||
public MajorCategory() { |
||||
} |
||||
} |
||||
@ -0,0 +1,87 @@ |
||||
package edu.ncst.award.model.system; |
||||
|
||||
import com.baomidou.mybatisplus.annotation.*; |
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.Date; |
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
|
||||
/** |
||||
* 菜单 |
||||
*/ |
||||
@ApiModel(value="Menu", description="菜单") |
||||
@Data |
||||
@TableName("sys_menu") |
||||
public class Menu implements Serializable { |
||||
|
||||
private static final long serialVersionUID=1L; |
||||
|
||||
@ApiModelProperty(value = "菜单ID") |
||||
@TableId(value = "id", type = IdType.AUTO) |
||||
private Integer id; |
||||
|
||||
@ApiModelProperty(value = "菜单名称") |
||||
private String name; |
||||
|
||||
@ApiModelProperty(value = "菜单标题") |
||||
private String title; |
||||
|
||||
@ApiModelProperty(value = "菜单图标") |
||||
private String icon; |
||||
|
||||
@ApiModelProperty(value = "菜单链接") |
||||
private String url; |
||||
|
||||
// @ApiModelProperty(value = "路由名称")
|
||||
// private String routeName;
|
||||
//
|
||||
// @ApiModelProperty(value = "路由路径")
|
||||
// private String routePath;
|
||||
//
|
||||
// @ApiModelProperty(value = "路由组件")
|
||||
// private String routeComponentName;
|
||||
//
|
||||
// @ApiModelProperty(value = "是否开启路由缓存")
|
||||
// private Boolean routeCache;
|
||||
|
||||
// @ApiModelProperty(value = "关联权限ID")
|
||||
// private Integer authorityId;
|
||||
|
||||
@ApiModelProperty(value = "上级菜单") |
||||
private Integer parentId; |
||||
|
||||
@ApiModelProperty(value = "下级菜单") |
||||
@TableField(exist = false) |
||||
private List<Menu> children; |
||||
|
||||
@ApiModelProperty(value = "是否显示在菜单上") |
||||
private Boolean showInMenu = true; |
||||
|
||||
@ApiModelProperty(value = "显示排序") |
||||
private Integer sort = 0; |
||||
|
||||
@ApiModelProperty(value = "备注") |
||||
private String remark; |
||||
|
||||
@ApiModelProperty(value = "删除时间") |
||||
@TableLogic |
||||
private Date deleteTime; |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (o == null || getClass() != o.getClass()) return false; |
||||
Menu menu = (Menu) o; |
||||
return Objects.equals(name, menu.name); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(name); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,58 @@ |
||||
package edu.ncst.award.model.system; |
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType; |
||||
import com.baomidou.mybatisplus.annotation.TableField; |
||||
import com.baomidou.mybatisplus.annotation.TableId; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.List; |
||||
|
||||
@ApiModel(value="Resource", description="资源") |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = false) |
||||
@TableName("sys_resource") |
||||
public class Resource implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 1L; |
||||
|
||||
@ApiModelProperty(value = "资源ID") |
||||
@TableId(value = "id", type = IdType.AUTO) |
||||
private Integer id; |
||||
|
||||
@ApiModelProperty(value = "显示文本") |
||||
private String label; |
||||
|
||||
@ApiModelProperty(value = "资源名称(方法签名)") |
||||
private String name; |
||||
|
||||
@ApiModelProperty(value = "请求方法类型") |
||||
private String method; |
||||
|
||||
@ApiModelProperty(value = "链接") |
||||
private String url; |
||||
|
||||
@ApiModelProperty(value = "允许所有访问", notes = "不需要用户登录") |
||||
private Boolean permitAll = false; |
||||
|
||||
@ApiModelProperty(value = "用户作用域") |
||||
private Boolean userScope = false; |
||||
|
||||
@ApiModelProperty(value = "上级菜单ID") |
||||
private Integer parentId; |
||||
|
||||
@ApiModelProperty(value = "上级菜单ID") |
||||
@TableField(exist = false) |
||||
private List<Resource> children; |
||||
|
||||
@ApiModelProperty(value = "排序") |
||||
private Integer sort; |
||||
|
||||
@ApiModelProperty(value = "是否對所有角色開放") |
||||
private Boolean isForAll = true; |
||||
|
||||
} |
||||
@ -0,0 +1,76 @@ |
||||
package edu.ncst.award.model.system; |
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType; |
||||
import com.baomidou.mybatisplus.annotation.TableId; |
||||
import com.baomidou.mybatisplus.annotation.TableLogic; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
import edu.ncst.award.model.BaseEntity; |
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
|
||||
import java.util.Date; |
||||
import java.util.Objects; |
||||
|
||||
/** |
||||
* 角色 |
||||
*/ |
||||
@ApiModel(value="Role", description="角色") |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = false) |
||||
@TableName("sys_role") |
||||
public class Role extends BaseEntity { |
||||
|
||||
private static final long serialVersionUID=1L; |
||||
|
||||
@ApiModelProperty(value = "角色ID") |
||||
@TableId(value = "id", type = IdType.AUTO) |
||||
private Integer id; |
||||
|
||||
@ApiModelProperty(value = "角色名称") |
||||
private String name; |
||||
|
||||
@ApiModelProperty(value = "显示文本") |
||||
private String label; |
||||
|
||||
@ApiModelProperty(value = "是否启用") |
||||
private Boolean enable; |
||||
|
||||
@ApiModelProperty(value = "删除时间") |
||||
@TableLogic |
||||
@JsonIgnore |
||||
private Date deleteTime; |
||||
|
||||
public Role(Integer id) { |
||||
this.id = id; |
||||
} |
||||
|
||||
public Role() { |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
|
||||
if (o == null || getClass() != o.getClass()) return false; |
||||
|
||||
Role role = (Role) o; |
||||
|
||||
return Objects.equals(name, role.name); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(name); |
||||
} |
||||
|
||||
public static Role forName(String name) { |
||||
Role role = new Role(); |
||||
role.setName(name); |
||||
return role; |
||||
} |
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,84 @@ |
||||
package edu.ncst.award.model.system; |
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField; |
||||
import com.baomidou.mybatisplus.annotation.TableName; |
||||
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
import edu.ncst.award.model.BaseEntity; |
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
@ApiModel(value="User", description="用户") |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = false) |
||||
@TableName("sys_user") |
||||
public class User extends BaseEntity implements Serializable, UserDetails { |
||||
@ApiModelProperty(value = "用户id") |
||||
private Integer id; |
||||
@ApiModelProperty(value = "用户密码") |
||||
private String password; |
||||
@ApiModelProperty(value = "用户名") |
||||
private String username; |
||||
@ApiModelProperty(value = "用户真实名") |
||||
private String fullName; |
||||
@ApiModelProperty(value = "地址信息") |
||||
private String address; |
||||
@ApiModelProperty(value = "手机号") |
||||
private String phone; |
||||
@ApiModelProperty(value = "所属单位") |
||||
private Integer unitId; |
||||
@ApiModelProperty(value = "角色列表") |
||||
@TableField(exist = false) |
||||
private List<Role> roles; |
||||
@ApiModelProperty(value = "是否启用 1:启用 0:禁用") |
||||
private Boolean enable; |
||||
|
||||
|
||||
|
||||
|
||||
@Override |
||||
public Collection<? extends GrantedAuthority> getAuthorities() { |
||||
List<GrantedAuthority> grantedAuthorities = new ArrayList<>(); |
||||
|
||||
if (this.roles != null && this.roles.size() > 0) { |
||||
List<GrantedAuthority> roleAuthority = roles.stream() |
||||
.map((role) -> new SimpleGrantedAuthority("ROLE_" + role.getName())) |
||||
.collect(Collectors.toList()); |
||||
grantedAuthorities.addAll(roleAuthority); |
||||
} |
||||
|
||||
return grantedAuthorities; |
||||
} |
||||
|
||||
@JsonIgnore |
||||
public boolean isAccountNonExpired() { |
||||
return true; |
||||
}//用户是否未过期
|
||||
|
||||
@JsonIgnore |
||||
@Override |
||||
public boolean isAccountNonLocked() { |
||||
return true; |
||||
}//用户是否未锁定
|
||||
|
||||
@JsonIgnore |
||||
@Override |
||||
public boolean isCredentialsNonExpired() { |
||||
return true; |
||||
}//凭证是否未过期
|
||||
|
||||
@Override |
||||
public boolean isEnabled() { |
||||
return this.enable; |
||||
}//是否启用
|
||||
} |
||||
@ -0,0 +1,19 @@ |
||||
package edu.ncst.award.service; |
||||
|
||||
import edu.ncst.award.model.log.LoginLog; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.param.query.LoginLogQuery; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
/** |
||||
* 登录日志 服务类 |
||||
*/ |
||||
public interface ILoginLogService { |
||||
|
||||
// 查询登录日志列表
|
||||
ListResultVO<LoginLog> getLoginLogList(LoginLogQuery query); |
||||
|
||||
// 添加登录日志
|
||||
void addLoginLog(Integer userId, Boolean success); |
||||
|
||||
} |
||||
@ -0,0 +1,61 @@ |
||||
package edu.ncst.award.service; |
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService; |
||||
import edu.ncst.award.model.system.Menu; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.Query; |
||||
import edu.ncst.award.vo.param.ResetMenuDTO; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 菜单 服务类 |
||||
*/ |
||||
public interface IMenuService extends IService<Menu> { |
||||
|
||||
// 获取菜单列表
|
||||
ListResultVO<Menu> getMenuList(Query query); |
||||
|
||||
// 获取菜单详情
|
||||
Menu getMenuDetail(Integer id); |
||||
|
||||
// 添加菜单
|
||||
Menu addMenu(Menu menu); |
||||
|
||||
// 修改菜单
|
||||
Menu modifyMenu(Menu menu); |
||||
|
||||
// 删除菜单
|
||||
boolean deleteMenu(Integer id); |
||||
|
||||
|
||||
/** |
||||
* 导入菜单元信息 |
||||
* @param resetMenuDTO |
||||
*/ |
||||
void resetMenuMeta(ResetMenuDTO resetMenuDTO); |
||||
|
||||
/** |
||||
* 获取菜单树 |
||||
* @return 菜单树 |
||||
*/ |
||||
List<Menu> getMenuTree(); |
||||
|
||||
/** |
||||
* 获取当前用户菜单树 |
||||
* @return 菜单树 |
||||
*/ |
||||
List<Menu> getCurrentUserMenuTree(); |
||||
|
||||
/** |
||||
* 获取当前用户菜单权限列表 |
||||
* @return 菜单树 |
||||
*/ |
||||
List<String> getCurrentUserMenuAuthority(); |
||||
|
||||
/** |
||||
* 获取指定角色能够访问的菜单ID |
||||
* @param roleId |
||||
*/ |
||||
List<Integer> getRoleMenuIds(Integer roleId, boolean withParent); |
||||
} |
||||
@ -0,0 +1,25 @@ |
||||
package edu.ncst.award.service; |
||||
|
||||
import edu.ncst.award.model.system.Resource; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.Query; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.List; |
||||
|
||||
public interface IResourceService { |
||||
void reimportInterfaceMeta(); |
||||
|
||||
ListResultVO<Resource> getResourceList(Query query); |
||||
|
||||
List<Resource> getResourceTree(); |
||||
|
||||
List<Integer> getRoleResourceIds(Integer roleId); |
||||
|
||||
List<String> getRolesResourceList(List<String> roleNames); |
||||
|
||||
List<Resource> getPermitAllResourceList(); |
||||
|
||||
List<String> getPermitAllResourceURLs(); |
||||
|
||||
} |
||||
@ -0,0 +1,5 @@ |
||||
package edu.ncst.award.service; |
||||
|
||||
public interface ISystemService { |
||||
|
||||
} |
||||
@ -0,0 +1,19 @@ |
||||
package edu.ncst.award.service; |
||||
|
||||
import edu.ncst.award.model.system.User; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.param.CurrentUserDTO; |
||||
import edu.ncst.award.vo.param.query.UserQuery; |
||||
import edu.ncst.award.vo.result.UserInfoVO; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
|
||||
public interface IUserService { |
||||
UserInfoVO getUserInfo(UserQuery query); |
||||
|
||||
ListResultVO<User> getUserList(UserQuery query); |
||||
|
||||
CurrentUserDTO getCurrentUser(); |
||||
|
||||
} |
||||
@ -0,0 +1,34 @@ |
||||
package edu.ncst.award.service.impl.log; |
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
||||
|
||||
import edu.ncst.award.model.log.LoginLog; |
||||
import edu.ncst.award.service.ILoginLogService; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.param.query.LoginLogQuery; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.util.Date; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* 登录日志 服务实现类 |
||||
*/ |
||||
@Service |
||||
public class LoginLogServiceImpl implements ILoginLogService { |
||||
|
||||
|
||||
@Override |
||||
public ListResultVO<LoginLog> getLoginLogList(LoginLogQuery query) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public void addLoginLog(Integer userId, Boolean success) { |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,320 @@ |
||||
package edu.ncst.award.service.impl.system; |
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
||||
import edu.ncst.award.exception.BusinessException; |
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.mapper.system.MenuMapper; |
||||
import edu.ncst.award.model.system.Menu; |
||||
import edu.ncst.award.model.system.Role; |
||||
import edu.ncst.award.service.IMenuService; |
||||
import edu.ncst.award.utils.SessionUtils; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.Query; |
||||
import edu.ncst.award.vo.param.ResetMenuDTO; |
||||
import edu.ncst.award.vo.result.UserInfoVO; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.apache.ibatis.session.SqlSession; |
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
import java.util.*; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* 菜单 服务实现类 |
||||
*/ |
||||
@Service |
||||
@Slf4j |
||||
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IMenuService { |
||||
|
||||
private final MenuMapper menuMapper; |
||||
|
||||
@Autowired |
||||
private SqlSession sqlSession; |
||||
|
||||
public MenuServiceImpl(MenuMapper menuMapper) { |
||||
this.menuMapper = menuMapper; |
||||
} |
||||
|
||||
// 获取菜单列表
|
||||
@Override |
||||
public ListResultVO<Menu> getMenuList(Query query) { |
||||
QueryWrapper<Menu> queryWrapper = new QueryWrapper<>(); |
||||
Page<Menu> pageRequest = query.toPageRequest(); |
||||
Page<Menu> page = menuMapper.getMenuList(pageRequest, null); |
||||
|
||||
// 封装返回结果
|
||||
ListResultVO<Menu> listResultVO = new ListResultVO<>(); |
||||
listResultVO.setList(page.getRecords()); |
||||
listResultVO.setTotal(page.getTotal()); |
||||
listResultVO.setCurrent(page.getCurrent()); |
||||
|
||||
return listResultVO; |
||||
} |
||||
|
||||
// 获取菜单详情
|
||||
@Override |
||||
public Menu getMenuDetail(Integer id) { |
||||
Menu menu = menuMapper.selectById(id); |
||||
return menu; |
||||
} |
||||
|
||||
// 添加菜单
|
||||
@Transactional |
||||
@Override |
||||
public Menu addMenu(Menu menu) { |
||||
int insert = menuMapper.insert(menu); |
||||
Menu result = getMenuDetail(menu.getId()); |
||||
if (insert == 1) { |
||||
return result; |
||||
} else { |
||||
throw new BusinessException(CodeMsg.ADD_ERROR); |
||||
} |
||||
} |
||||
|
||||
// 修改菜单
|
||||
@Transactional |
||||
@Override |
||||
public Menu modifyMenu(Menu menu) { |
||||
int update = menuMapper.updateById(menu); |
||||
Menu result = getMenuDetail(menu.getId()); |
||||
|
||||
if (update == 1) { |
||||
return result; |
||||
} else { |
||||
throw new BusinessException(CodeMsg.MODIFY_ERROR); |
||||
} |
||||
} |
||||
|
||||
// 删除菜单
|
||||
@Transactional |
||||
@Override |
||||
public boolean deleteMenu(Integer id) { |
||||
int delete = menuMapper.deleteById(id); |
||||
|
||||
if (delete == 1) { |
||||
return true; |
||||
} else { |
||||
throw new BusinessException(CodeMsg.DELETE_ERROR); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 导入菜单 |
||||
* 说明:该操作会将原有的菜单元信息全部清空,然后再导入新的菜单元信息 |
||||
* @param resetMenuDTO 重置菜单表单 |
||||
*/ |
||||
@Transactional |
||||
@Override |
||||
public void resetMenuMeta(ResetMenuDTO resetMenuDTO) { |
||||
List<Menu> oldTree = getMenuTree(); |
||||
List<Menu> newTree = resetMenuDTO.getMenus(); |
||||
Map<String, Menu> waitDeleteMap = new HashMap<>(); |
||||
|
||||
findWaitDeleteMenu(oldTree, newTree, waitDeleteMap); |
||||
diffMenuMeta(oldTree, newTree, waitDeleteMap, null); |
||||
|
||||
// 批量删除不存在的菜单
|
||||
Collection<Menu> deleteMenus = waitDeleteMap.values(); |
||||
List<Integer> menuIds = deleteMenus.stream().map(Menu::getId).collect(Collectors.toList()); |
||||
|
||||
if (!menuIds.isEmpty()) { |
||||
menuMapper.deleteBatchIds(menuIds); |
||||
} |
||||
} |
||||
|
||||
private void findWaitDeleteMenu(List<Menu> oldTree, List<Menu> newTree, Map<String, Menu> waitDeleteMap) { |
||||
HashMap<String, Menu> newMenuMap = new HashMap<>(); |
||||
|
||||
for (Menu menu : newTree) { |
||||
newMenuMap.put(menu.getName(), menu); |
||||
} |
||||
|
||||
// 旧菜单列表存在 但 新菜单列表不存在 加入到待删除列表(旧有新无)
|
||||
for (Menu oldMenu : oldTree) { |
||||
String key = oldMenu.getName(); |
||||
|
||||
if (!newMenuMap.containsKey(key)) { |
||||
waitDeleteMap.put(key, oldMenu); |
||||
} else { |
||||
Menu newMenu = newMenuMap.get(key); |
||||
List<Menu> children = newMenu.getChildren(); |
||||
|
||||
if (children != null && children.size() > 0) { |
||||
findWaitDeleteMenu(oldMenu.getChildren(), children, waitDeleteMap); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void diffMenuMeta(List<Menu> oldTree, List<Menu> newTree, Map<String, Menu> waitDeleteMap, Integer parentId) { |
||||
if (oldTree == null || newTree == null) return; |
||||
|
||||
HashMap<String, Menu> oldMenuMap = new HashMap<>(); |
||||
|
||||
for (Menu menu : oldTree) { |
||||
oldMenuMap.put(menu.getName(), menu); |
||||
} |
||||
|
||||
// 遍历新菜单列表
|
||||
for (Menu newMenu : newTree) { |
||||
List<Menu> children = newMenu.getChildren(); |
||||
List<Menu> oldChildren = new ArrayList<>(); |
||||
String key = newMenu.getName(); |
||||
log.info(newMenu.toString()); |
||||
log.info(key); |
||||
Integer menuId; |
||||
|
||||
if (newMenu.getName() == null || "".equals(newMenu.getName())) { |
||||
continue; |
||||
} |
||||
|
||||
if (oldMenuMap.containsKey(key)) { |
||||
// 旧菜单列表存在 且 新菜单列表存在 执行更新操作(旧有新有)
|
||||
Menu oldMenu = oldMenuMap.get(key); |
||||
oldChildren = oldMenu.getChildren(); |
||||
menuId = oldMenu.getId(); |
||||
|
||||
// 更新旧菜单数据
|
||||
BeanUtils.copyProperties(newMenu, oldMenu,"id", "children"); |
||||
oldMenu.setParentId(parentId); |
||||
|
||||
// 属性完全相同(除ID,新菜单没有ID可以进行比较)时没有必要进行更新操作
|
||||
if (!isMenuEquals(oldMenu, newMenu)) { |
||||
menuMapper.updateById(oldMenu); |
||||
} |
||||
} else if (waitDeleteMap.containsKey(key)) { |
||||
Menu oldMenu = waitDeleteMap.remove(key); |
||||
oldChildren = oldMenu.getChildren(); |
||||
menuId = oldMenu.getId(); |
||||
|
||||
// 更新旧菜单数据
|
||||
BeanUtils.copyProperties(newMenu, oldMenu, "id", "children"); |
||||
oldMenu.setParentId(parentId); |
||||
|
||||
if (!isMenuEquals(oldMenu, newMenu)) { |
||||
menuMapper.updateById(oldMenu); |
||||
} |
||||
} else { |
||||
log.info(newMenu.toString()); |
||||
// 旧菜单列表不存在 但 新菜单列表存在 执行插入操作(旧无新有)
|
||||
newMenu.setParentId(parentId); |
||||
menuMapper.insert(newMenu); |
||||
menuId = newMenu.getId(); |
||||
} |
||||
|
||||
if (children != null && children.size() > 0) { |
||||
diffMenuMeta(oldChildren, children, waitDeleteMap, menuId); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 判断两个菜单中的字段是否相等 |
||||
*/ |
||||
private boolean isMenuEquals(Menu oldMenu, Menu newMenu) { |
||||
if (oldMenu == null && newMenu == null) { |
||||
return true; |
||||
} else if (oldMenu == null || newMenu == null) { |
||||
return false; |
||||
} |
||||
|
||||
return Objects.equals(oldMenu.getName(), newMenu.getName()) && |
||||
Objects.equals(oldMenu.getTitle(), newMenu.getTitle()) && |
||||
Objects.equals(oldMenu.getIcon(), newMenu.getIcon()) && |
||||
Objects.equals(oldMenu.getUrl(), newMenu.getUrl()) && |
||||
Objects.equals(oldMenu.getShowInMenu(), newMenu.getShowInMenu()) && |
||||
Objects.equals(oldMenu.getParentId(), newMenu.getParentId()) && |
||||
Objects.equals(oldMenu.getSort(), newMenu.getSort()) && |
||||
Objects.equals(oldMenu.getRemark(), newMenu.getRemark()); |
||||
} |
||||
|
||||
/** |
||||
* 获取菜单树 |
||||
* @return 菜单树 |
||||
*/ |
||||
@Override |
||||
public List<Menu> getMenuTree() { |
||||
return menuMapper.getMenuTree(null); |
||||
} |
||||
|
||||
/** |
||||
* 获取当前菜单树 |
||||
* @return 菜单树 |
||||
*/ |
||||
@Override |
||||
public List<Menu> getCurrentUserMenuTree() { |
||||
UserInfoVO userInfoVO = SessionUtils.getCurrentUser(); |
||||
List<Role> roles = userInfoVO.getRoles(); |
||||
Set<Integer> allowMenuIdSet = new HashSet<>(); |
||||
|
||||
if (roles != null) { |
||||
for (Role role : roles) { |
||||
List<Integer> roleMenuIds = getRoleMenuIds(role.getId(),true); |
||||
allowMenuIdSet.addAll(roleMenuIds); |
||||
} |
||||
} |
||||
|
||||
List<Menu> menuTree = getMenuTree(); |
||||
|
||||
return filterMenuList(menuTree, allowMenuIdSet); |
||||
} |
||||
|
||||
/** |
||||
* 过滤掉不允许访问的菜单 |
||||
*/ |
||||
private List<Menu> filterMenuList(List<Menu> menuTree, Set<Integer> allowMenuIdSet) { |
||||
List<Menu> allowMenuTree = new ArrayList<>(); |
||||
|
||||
if (menuTree == null || menuTree.size() == 0) { |
||||
return menuTree; |
||||
} |
||||
|
||||
for (Menu menu : menuTree) { |
||||
if (allowMenuIdSet.contains(menu.getId())) { |
||||
|
||||
if (menu.getChildren() != null && menu.getChildren().size() > 0) { |
||||
menu.setChildren(filterMenuList(menu.getChildren(), allowMenuIdSet)); |
||||
} |
||||
|
||||
allowMenuTree.add(menu); |
||||
} |
||||
} |
||||
|
||||
return allowMenuTree; |
||||
} |
||||
|
||||
/** |
||||
* 获取指定角色能够访问的菜单ID |
||||
*/ |
||||
@Override |
||||
public List<Integer> getRoleMenuIds(Integer roleId , boolean withParent) { |
||||
if(withParent){ |
||||
return menuMapper.getRoleMenuIdsWithParent(roleId); |
||||
}else{ |
||||
return menuMapper.getRoleMenuIds(roleId); |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 获取当前用户 |
||||
* @return |
||||
*/ |
||||
@Override |
||||
public List<String> getCurrentUserMenuAuthority() { |
||||
UserInfoVO userInfoVO = SessionUtils.getCurrentUser(); |
||||
List<Role> roles = userInfoVO.getRoles(); |
||||
|
||||
if (roles != null && !roles.isEmpty()) { |
||||
List<String> roleNames = roles.stream().map(Role::getName).collect(Collectors.toList()); |
||||
return menuMapper.getCurrentUserMenuAuthority(roleNames); |
||||
} else { |
||||
return new ArrayList<>(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,387 @@ |
||||
package edu.ncst.award.service.impl.system; |
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
||||
import edu.ncst.award.exception.BusinessException; |
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.mapper.system.ResourceMapper; |
||||
import edu.ncst.award.model.system.Resource; |
||||
import edu.ncst.award.service.IResourceService; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.Query; |
||||
import io.swagger.annotations.Api; |
||||
import io.swagger.annotations.ApiOperation; |
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.cache.annotation.CacheEvict; |
||||
import org.springframework.cache.annotation.Cacheable; |
||||
import org.springframework.cache.annotation.Caching; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
import javax.annotation.security.PermitAll; |
||||
import java.io.File; |
||||
import java.io.FileFilter; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Modifier; |
||||
import java.net.URI; |
||||
import java.net.URISyntaxException; |
||||
import java.util.*; |
||||
|
||||
@Service |
||||
public class ResourceServiceImpl implements IResourceService { |
||||
|
||||
@Autowired |
||||
private ResourceMapper resourceMapper; |
||||
|
||||
// 欲扫描的包路径
|
||||
private static final String SCAN_PACKAGE = "edu.ncst.equmanager.controller"; |
||||
|
||||
/** |
||||
* 重新导入接口元信息 |
||||
*/ |
||||
@Caching( |
||||
evict = { |
||||
@CacheEvict(cacheNames = "permitAllResourceList"), |
||||
@CacheEvict(cacheNames = "userScopeResourceList"), |
||||
@CacheEvict(cacheNames = "permitAllResourceURLs"), |
||||
@CacheEvict(cacheNames = "userScopeResourceURLs") |
||||
} |
||||
) |
||||
@Transactional |
||||
@Override |
||||
public void reimportInterfaceMeta() { |
||||
List<Resource> newInterfaceMetas = getSystemInterfaceMetas(); |
||||
Set<String> newInterfaceNameSet = new HashSet<>(); |
||||
|
||||
QueryWrapper<Resource> queryWrapper = new QueryWrapper<>(); |
||||
List<Resource> oldInterfaceMetas = resourceMapper.selectList(queryWrapper); |
||||
HashMap<String, Resource> oldInterfaceNameMap = new HashMap<>(); |
||||
List<Resource> insertResourceList = new ArrayList<>(); |
||||
|
||||
// 待删除接口列表
|
||||
List<Integer> deleteInterfaceIds = new ArrayList<>(); |
||||
|
||||
for (Resource newInterfaceMeta : newInterfaceMetas) { |
||||
newInterfaceNameSet.add(newInterfaceMeta.getName()); |
||||
} |
||||
|
||||
for (Resource oldInterfaceMeta : oldInterfaceMetas) { |
||||
oldInterfaceNameMap.put(oldInterfaceMeta.getName(), oldInterfaceMeta); |
||||
|
||||
if (!newInterfaceNameSet.contains(oldInterfaceMeta.getName())) { |
||||
// 旧有新无,进行删除操作
|
||||
deleteInterfaceIds.add(oldInterfaceMeta.getId()); |
||||
} |
||||
} |
||||
|
||||
// 检查数据库中的接口是否还存在于当前系统当中
|
||||
for (Resource newInterface : newInterfaceMetas) { |
||||
String key = newInterface.getName(); |
||||
|
||||
if (oldInterfaceNameMap.containsKey(key)) { |
||||
// 新有旧有,进行 UPDATE
|
||||
Resource oldResource = oldInterfaceNameMap.get(key); |
||||
|
||||
if (isResourceChange(oldResource, newInterface)) { |
||||
BeanUtils.copyProperties(newInterface, oldResource, "id"); |
||||
resourceMapper.updateById(oldResource); |
||||
} |
||||
} else { |
||||
// 新有旧无,进行 INSERT, 如果上级资源ID为空,说明是资源分组,资源分组已经在getSystemInterfaceMetas方法中插入到数据库
|
||||
if (newInterface.getParentId() != null) { |
||||
insertResourceList.add(newInterface); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 批量删除
|
||||
if (!deleteInterfaceIds.isEmpty()) { |
||||
resourceMapper.deleteBatchIds(deleteInterfaceIds); |
||||
} |
||||
|
||||
// 批量插入
|
||||
if (!insertResourceList.isEmpty()) { |
||||
resourceMapper.batchInsert(insertResourceList); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取系统当前所有接口的元信息 |
||||
*/ |
||||
public List<Resource> getSystemInterfaceMetas() { |
||||
List<Class<?>> classes = findClassLocal(); |
||||
List<Resource> resources = new ArrayList<>(); |
||||
|
||||
for (Class<?> aClass : classes) { |
||||
RequestMapping baseRequestMapping = aClass.getAnnotation(RequestMapping.class); |
||||
Api apiAnnotation = aClass.getAnnotation(Api.class); |
||||
|
||||
String baseUrl = ""; // Base URL
|
||||
String groupName = ""; // 分组标识名
|
||||
Resource parentResource = null; // 上级资源
|
||||
|
||||
if (baseRequestMapping != null) { |
||||
String[] urls = baseRequestMapping.value(); |
||||
if (urls.length > 0) { |
||||
baseUrl = urls[0]; |
||||
} |
||||
} |
||||
|
||||
if (apiAnnotation != null && !apiAnnotation.hidden()) { |
||||
parentResource = new Resource(); |
||||
groupName = aClass.getName(); |
||||
|
||||
parentResource.setLabel(apiAnnotation.description()); |
||||
parentResource.setName(groupName); |
||||
parentResource.setUrl(baseUrl); |
||||
parentResource.setMethod("ALL"); |
||||
|
||||
Integer parentResourceId = resourceMapper.getIdByName(groupName); |
||||
|
||||
if (parentResourceId == null) { |
||||
resourceMapper.insert(parentResource); |
||||
} else { |
||||
parentResource.setId(parentResourceId); |
||||
resourceMapper.updateById(parentResource); |
||||
} |
||||
|
||||
resources.add(parentResource); |
||||
} |
||||
|
||||
// 获取该类的所有方法 (不包括父类)
|
||||
Method[] methods = aClass.getDeclaredMethods(); |
||||
|
||||
for (Method method : methods) { |
||||
if (!Modifier.isPublic(method.getModifiers())) { |
||||
continue; |
||||
} |
||||
|
||||
ApiOperation operationAnnotation = method.getAnnotation(ApiOperation.class); |
||||
// LoginUserScope scopeAnnotation = method.getAnnotation(LoginUserScope.class);
|
||||
PermitAll permitAllAnnotation = method.getAnnotation(PermitAll.class); |
||||
|
||||
if (operationAnnotation == null || operationAnnotation.hidden()) { |
||||
continue; |
||||
} |
||||
|
||||
List<String> interfaceMethods = new ArrayList<>(); |
||||
String url = getInterfaceMethods(method, interfaceMethods); |
||||
String interfaceName = operationAnnotation.value(); |
||||
|
||||
// 添加到接口信息列表
|
||||
if (!"".equals(interfaceName) && url != null) { |
||||
for (String requestMethod : interfaceMethods) { |
||||
Resource resource = new Resource(); |
||||
|
||||
resource.setLabel(interfaceName); |
||||
resource.setName(groupName + "." + method.getName()); |
||||
resource.setMethod(requestMethod); |
||||
resource.setUrl(baseUrl + url); |
||||
|
||||
if (permitAllAnnotation != null) { |
||||
resource.setPermitAll(true); |
||||
} |
||||
|
||||
// if (scopeAnnotation != null) {
|
||||
// resource.setUserScope(true);
|
||||
// }
|
||||
|
||||
if (parentResource != null) { |
||||
resource.setParentId(parentResource.getId()); |
||||
} |
||||
|
||||
resources.add(resource); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return resources; |
||||
} |
||||
|
||||
/** |
||||
* 判断资源元信息是否发生变更 |
||||
*/ |
||||
private boolean isResourceChange(Resource newResource, Resource oldResource) { |
||||
if (newResource == null && oldResource == null) return true; |
||||
if (newResource == null || oldResource == null) return false; |
||||
|
||||
return !Objects.equals(newResource.getName(), oldResource.getName()) || |
||||
!Objects.equals(newResource.getParentId(), oldResource.getParentId()) || |
||||
!Objects.equals(newResource.getLabel(), oldResource.getLabel()) || |
||||
!Objects.equals(newResource.getMethod(), oldResource.getMethod()) || |
||||
!Objects.equals(newResource.getPermitAll(), oldResource.getPermitAll()) || |
||||
!Objects.equals(newResource.getUrl(), oldResource.getUrl()) || |
||||
!Objects.equals(newResource.getUserScope(), oldResource.getUserScope()); |
||||
} |
||||
|
||||
/** |
||||
* 获取接口的请求方法和URL |
||||
*/ |
||||
private String getInterfaceMethods(Method method, List<String> interfaceMethods) { |
||||
RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class); |
||||
GetMapping getMapping = method.getDeclaredAnnotation(GetMapping.class); |
||||
PostMapping postMapping = method.getDeclaredAnnotation(PostMapping.class); |
||||
PutMapping putMapping = method.getDeclaredAnnotation(PutMapping.class); |
||||
DeleteMapping deleteMapping = method.getDeclaredAnnotation(DeleteMapping.class); |
||||
|
||||
String url = null; |
||||
|
||||
if (requestMapping != null) { |
||||
String[] urls = requestMapping.value(); |
||||
if (urls.length > 0) { |
||||
url = urls[0]; |
||||
} |
||||
|
||||
// 获取请求方法类型
|
||||
RequestMethod[] requestMethods = requestMapping.method(); |
||||
|
||||
if (requestMethods.length > 0) { |
||||
for (RequestMethod requestMethod : requestMethods) { |
||||
interfaceMethods.add(requestMethod.name()); |
||||
} |
||||
} |
||||
} else if (getMapping != null) { |
||||
String[] urls = getMapping.value(); |
||||
if (urls.length > 0) { |
||||
url = urls[0]; |
||||
} |
||||
interfaceMethods.add("GET"); |
||||
} else if (postMapping != null) { |
||||
String[] urls = postMapping.value(); |
||||
if (urls.length > 0) { |
||||
url = urls[0]; |
||||
} |
||||
interfaceMethods.add("POST"); |
||||
} else if (putMapping != null) { |
||||
String[] urls = putMapping.value(); |
||||
if (urls.length > 0) { |
||||
url = urls[0]; |
||||
} |
||||
interfaceMethods.add("PUT"); |
||||
} else if (deleteMapping != null) { |
||||
String[] urls = deleteMapping.value(); |
||||
if (urls.length > 0) { |
||||
url = urls[0]; |
||||
} |
||||
interfaceMethods.add("DELETE"); |
||||
} |
||||
|
||||
return url; |
||||
} |
||||
|
||||
/** |
||||
* 扫描指定包下的所有类 |
||||
* @return 类对象列表 |
||||
*/ |
||||
private List<Class<?>> findClassLocal() { |
||||
ClassLoader classLoader = SystemService.class.getClassLoader();// 类加载器
|
||||
List<Class<?>> result = new ArrayList<>(); |
||||
|
||||
findClassLocalHelper(SCAN_PACKAGE, classLoader, result); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
private void findClassLocalHelper(String packageName, ClassLoader classLoader, List<Class<?>> result) { |
||||
URI uri = null; |
||||
|
||||
try { |
||||
uri = Objects.requireNonNull(classLoader.getResource(packageName.replace(".", "/"))).toURI(); |
||||
} catch (URISyntaxException e) { |
||||
throw new BusinessException(CodeMsg.SERVER_ERROR); |
||||
} |
||||
|
||||
File file = new File(uri); |
||||
file.listFiles(new FileFilter() { |
||||
@Override |
||||
public boolean accept(File pathname) { |
||||
if (pathname.isDirectory()) { |
||||
findClassLocalHelper(packageName + "." + pathname.getName(), classLoader, result); |
||||
} |
||||
|
||||
if (pathname.getName().endsWith(".class")) { |
||||
Class<?> clazz = null; |
||||
|
||||
try { |
||||
clazz = classLoader.loadClass(packageName + "." + pathname.getName().replace(".class", "")); |
||||
} catch (ClassNotFoundException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
|
||||
result.add(clazz); |
||||
|
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* 获取资源列表 |
||||
*/ |
||||
@Override |
||||
public ListResultVO<Resource> getResourceList(Query query) { |
||||
Page<Resource> pageRequest = query.toPageRequest(); |
||||
QueryWrapper<Resource> queryWrapper = new QueryWrapper<>(); |
||||
Page<Resource> resourcePage = resourceMapper.selectPage(pageRequest, queryWrapper); |
||||
|
||||
ListResultVO<Resource> listResultVO = new ListResultVO<>(); |
||||
listResultVO.setList(resourcePage.getRecords()); |
||||
listResultVO.setTotal(resourcePage.getTotal()); |
||||
|
||||
return listResultVO; |
||||
} |
||||
|
||||
/** |
||||
* 获取资源树 |
||||
*/ |
||||
@Override |
||||
public List<Resource> getResourceTree() { |
||||
return resourceMapper.getResourceTree(); |
||||
} |
||||
|
||||
/** |
||||
* 获取角色能够访问的资源列表 |
||||
*/ |
||||
@Override |
||||
public List<Integer> getRoleResourceIds(Integer roleId) { |
||||
return resourceMapper.getRoleResourceIds(roleId); |
||||
} |
||||
|
||||
/** |
||||
* 获取多个角色能够访问的资源列表 |
||||
*/ |
||||
@Override |
||||
public List<String> getRolesResourceList(List<String> roleNames) { |
||||
//resourceMapper.getRolesResourceUrls(roleNames);
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* 获取不受限的接口 |
||||
*/ |
||||
@Cacheable(cacheNames = "permitAllResourceList") |
||||
@Override |
||||
public List<Resource> getPermitAllResourceList() { |
||||
return resourceMapper.getPermitAllResourceList(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 获取不受限的URL |
||||
*/ |
||||
@Cacheable(cacheNames = "permitAllResourceURLs") |
||||
@Override |
||||
public List<String> getPermitAllResourceURLs() { |
||||
List<String> strings = new ArrayList<>(); |
||||
strings.add("/api/user/current"); |
||||
strings.add("/api/auth/logout"); |
||||
//resourceMapper.getPermitAllResourceURLs();
|
||||
return strings; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,15 @@ |
||||
package edu.ncst.award.service.impl.system; |
||||
|
||||
import edu.ncst.award.service.ISystemService; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
/** |
||||
* 系统核心服务 |
||||
*/ |
||||
@Service |
||||
public class SystemService implements ISystemService { |
||||
|
||||
|
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,69 @@ |
||||
package edu.ncst.award.service.impl.system; |
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
||||
import edu.ncst.award.mapper.system.UserMapper; |
||||
import edu.ncst.award.model.system.Role; |
||||
import edu.ncst.award.model.system.User; |
||||
import edu.ncst.award.service.IUserService; |
||||
import edu.ncst.award.vo.ListResultVO; |
||||
import edu.ncst.award.vo.param.CurrentUserDTO; |
||||
import edu.ncst.award.vo.param.query.UserQuery; |
||||
import edu.ncst.award.vo.result.UserInfoVO; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
@Service |
||||
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserDetailsService { |
||||
@Override |
||||
public UserInfoVO getUserInfo(UserQuery query) { |
||||
UserInfoVO vo = new UserInfoVO(); |
||||
vo.setAddress("address 1111111"); |
||||
vo.setUsername("zhangsan"); |
||||
vo.setFullName("张三"); |
||||
return vo; |
||||
} |
||||
|
||||
@Override |
||||
public ListResultVO<User> getUserList(UserQuery query) { |
||||
User user = new User(); |
||||
user.setPassword("$2a$10$1cDN8G8bEpqkvOHZUxrAk.qvnZzP8IAuCWzezPXCdZYOL4jKVgEb."); |
||||
user.setAddress("address 1111111"); |
||||
user.setUsername("zhangsan"); |
||||
user.setFullName("张三"); |
||||
user.setEnable(true); |
||||
ListResultVO listResultVO = new ListResultVO(); |
||||
List list = new ArrayList<>(); |
||||
list.add(user); |
||||
listResultVO.setList(list); |
||||
return listResultVO; |
||||
} |
||||
|
||||
@Override |
||||
public CurrentUserDTO getCurrentUser() { |
||||
CurrentUserDTO user = new CurrentUserDTO(); |
||||
user.setUsername("zhangsan"); |
||||
user.setNickname("张三"); |
||||
return user; |
||||
} |
||||
|
||||
@Override |
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
||||
User user = new User(); |
||||
user.setPassword("$2a$10$1cDN8G8bEpqkvOHZUxrAk.qvnZzP8IAuCWzezPXCdZYOL4jKVgEb."); |
||||
user.setAddress("address 1111111"); |
||||
user.setUsername("zhangsan"); |
||||
user.setFullName("张三"); |
||||
Role role=new Role(); |
||||
role.setName("NORMAL"); |
||||
List<Role> roles = new ArrayList<>(); |
||||
roles.add(role); |
||||
user.setRoles(roles); |
||||
user.setEnable(true); |
||||
return user; |
||||
} |
||||
} |
||||
@ -0,0 +1,79 @@ |
||||
package edu.ncst.award.utils; |
||||
|
||||
import lombok.experimental.UtilityClass; |
||||
|
||||
import java.awt.*; |
||||
import java.awt.image.BufferedImage; |
||||
import java.util.Random; |
||||
|
||||
@UtilityClass |
||||
public class CaptchaUtil { |
||||
|
||||
private int width = 200; |
||||
private int height = 50; |
||||
|
||||
public BufferedImage createImage() { |
||||
return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); |
||||
} |
||||
|
||||
public String drawRandomText(BufferedImage verifyImg) { |
||||
Graphics2D graphics = (Graphics2D) verifyImg.getGraphics(); |
||||
|
||||
graphics.setColor(Color.WHITE); |
||||
graphics.fillRect(0, 0, width, height); |
||||
Font font = new Font("Bitmap", Font.PLAIN, 54); |
||||
graphics.setFont(font); |
||||
|
||||
String baseNumLetter = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; |
||||
StringBuilder stringBuffer = new StringBuilder(); |
||||
|
||||
int x = 10; |
||||
String ch = ""; |
||||
Random random = new Random(); |
||||
|
||||
for (int i = 0; i < 4; i++) { |
||||
graphics.setColor(getRandomColor()); |
||||
|
||||
// 设置字体旋转角度(控制小于30度)
|
||||
int degree = random.nextInt() % 30; |
||||
int dot = random.nextInt(baseNumLetter.length()); |
||||
|
||||
ch = baseNumLetter.charAt(dot) + ""; |
||||
stringBuffer.append(ch); |
||||
|
||||
// 正向旋转
|
||||
graphics.rotate(degree * Math.PI / 180, x, 45); |
||||
graphics.drawString(ch, x, 45); |
||||
|
||||
// 反向旋转
|
||||
graphics.rotate(-degree * Math.PI / 180, x, 45); |
||||
x += 48; |
||||
} |
||||
|
||||
// 绘制干扰线
|
||||
for (int i = 0; i < 6; i++) { |
||||
graphics.setColor(getRandomColor()); |
||||
graphics.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height)); |
||||
} |
||||
|
||||
// 绘制噪点
|
||||
for (int i = 0; i < 30; i++) { |
||||
int x1 = random.nextInt(width); |
||||
int y1 = random.nextInt(height); |
||||
|
||||
graphics.setColor(getRandomColor()); |
||||
graphics.fillRect(x1, y1, 2, 1); |
||||
} |
||||
|
||||
return stringBuffer.toString(); |
||||
} |
||||
|
||||
/*** |
||||
* 取随机颜色 |
||||
*/ |
||||
private Color getRandomColor() { |
||||
Random random = new Random(); |
||||
return new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
package edu.ncst.award.utils; |
||||
|
||||
import javax.servlet.http.Cookie; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
|
||||
public class CookieUtils { |
||||
|
||||
/** |
||||
* 获取指定的 Cookie |
||||
*/ |
||||
public static Cookie getCookie(HttpServletRequest request, String name) { |
||||
if (name == null || "".equalsIgnoreCase(name)) { |
||||
return null; |
||||
} |
||||
|
||||
Cookie[] cookies = request.getCookies(); |
||||
|
||||
if (cookies != null) { |
||||
for (Cookie cookie : cookies) { |
||||
if (cookie.getName() != null && name.equals(cookie.getName())) { |
||||
return cookie; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,91 @@ |
||||
package edu.ncst.award.utils; |
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.vo.ResultVO; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.validation.BindingResult; |
||||
import org.springframework.validation.FieldError; |
||||
|
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
import java.io.PrintWriter; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 消息类响应工具类 |
||||
*/ |
||||
public class ResponseUtils { |
||||
|
||||
// 成功
|
||||
// 成功
|
||||
public static <T> ResponseEntity<?> success(String msg, T data) { |
||||
ResultVO<T> resultVO = new ResultVO<>(); |
||||
resultVO.setCode(0); |
||||
resultVO.setMsg(msg); |
||||
resultVO.setData(data); |
||||
|
||||
return ResponseEntity.ok(resultVO); |
||||
} |
||||
|
||||
public static ResponseEntity<?> success(Object data) { |
||||
return success("success", data); |
||||
} |
||||
|
||||
public static ResponseEntity<?> success(String msg) { |
||||
return success(msg, null); |
||||
} |
||||
|
||||
public static ResponseEntity<?> success() { |
||||
return success("success", null); |
||||
} |
||||
|
||||
// 失败
|
||||
public static <T> ResponseEntity<?> error(Integer code, String msg) { |
||||
ResultVO<T> resultVO = new ResultVO<>(); |
||||
resultVO.setCode(code); |
||||
resultVO.setMsg(msg); |
||||
return ResponseEntity.ok().body(resultVO); |
||||
} |
||||
|
||||
public static ResponseEntity<?> error(CodeMsg codeMsg) { |
||||
return error(codeMsg.getCode(), codeMsg.getMsg()); |
||||
} |
||||
|
||||
// 输出字段错误信息
|
||||
public static <T> ResponseEntity<?> fieldError(BindingResult bindingResult) { |
||||
ResultVO<T> resultVO = new ResultVO<>(); |
||||
String msg = "未知字段错误"; |
||||
|
||||
if (bindingResult.hasFieldErrors()) { |
||||
List<FieldError> fieldErrors = bindingResult.getFieldErrors(); |
||||
int count = bindingResult.getFieldErrorCount(); |
||||
|
||||
if (count > 0) { |
||||
msg = fieldErrors.get(0).getDefaultMessage(); |
||||
} |
||||
} |
||||
|
||||
resultVO.setMsg(msg); |
||||
|
||||
return ResponseEntity.ok().body(resultVO); |
||||
} |
||||
|
||||
// 调用
|
||||
public static void output(HttpServletResponse response, Object data) { |
||||
try(OutputStream out = response.getOutputStream()) { |
||||
response.setContentType("application/json;charset=utf-8"); |
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper(); |
||||
String s = objectMapper.writeValueAsString(data); |
||||
PrintWriter writer = new PrintWriter(out); |
||||
writer.print(s); |
||||
writer.flush(); |
||||
writer.close(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,55 @@ |
||||
package edu.ncst.award.utils; |
||||
|
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.vo.ResultVO; |
||||
|
||||
public class ResultUtils { |
||||
|
||||
// 成功
|
||||
public static <T> ResultVO<T> success(String msg, T data) { |
||||
ResultVO<T> resultVO = new ResultVO<>(); |
||||
resultVO.setCode(0); |
||||
resultVO.setMsg(msg); |
||||
resultVO.setData(data); |
||||
|
||||
return resultVO; |
||||
} |
||||
public static <T> ResultVO<T> success(String msg) { |
||||
ResultVO<T> resultVO = new ResultVO<>(); |
||||
resultVO.setCode(0); |
||||
resultVO.setMsg(msg); |
||||
resultVO.setData(null); |
||||
|
||||
return resultVO; |
||||
} |
||||
|
||||
// 成功
|
||||
public static <T> ResultVO<T> success(T data) { |
||||
ResultVO<T> resultVO = new ResultVO<>(); |
||||
resultVO.setCode(0); |
||||
resultVO.setMsg("success"); |
||||
resultVO.setData(data); |
||||
|
||||
return resultVO; |
||||
} |
||||
// 错误
|
||||
public static <T> ResultVO<T> error(CodeMsg codeMsg) { |
||||
ResultVO<T> resultVO = new ResultVO<>(); |
||||
resultVO.setCode(codeMsg.getCode()); |
||||
resultVO.setMsg(codeMsg.getMsg()); |
||||
resultVO.setData(null); |
||||
|
||||
return resultVO; |
||||
} |
||||
|
||||
// 错误
|
||||
public static <T> ResultVO<T> error(String codeMsg) { |
||||
ResultVO<T> resultVO = new ResultVO<>(); |
||||
resultVO.setCode(500); |
||||
resultVO.setMsg(codeMsg); |
||||
resultVO.setData(null); |
||||
|
||||
return resultVO; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,55 @@ |
||||
package edu.ncst.award.utils; |
||||
|
||||
import edu.ncst.award.exception.BusinessException; |
||||
import edu.ncst.award.exception.CodeMsg; |
||||
import edu.ncst.award.service.IUserService; |
||||
import edu.ncst.award.vo.result.UserInfoVO; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.core.context.SecurityContext; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.context.request.RequestContextHolder; |
||||
import org.springframework.web.context.request.ServletRequestAttributes; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
import java.util.Objects; |
||||
|
||||
/** |
||||
* 会话工具类 |
||||
*/ |
||||
@Component |
||||
public class SessionUtils { |
||||
|
||||
@Autowired |
||||
private IUserService userService; |
||||
|
||||
// 获取当前用户
|
||||
public static UserInfoVO getCurrentUser() { |
||||
SecurityContext context = SecurityContextHolder.getContext(); |
||||
|
||||
if (context == null || context.getAuthentication() == null || context.getAuthentication().getDetails() == null) { |
||||
throw new BusinessException(CodeMsg.NOT_GET_CURRENT_USER_INFO); |
||||
} |
||||
|
||||
return (UserInfoVO) context.getAuthentication().getDetails(); |
||||
} |
||||
|
||||
// 获取当前用户标识
|
||||
public static Object getCurrentPrincipal() { |
||||
SecurityContext context = SecurityContextHolder.getContext(); |
||||
|
||||
return context.getAuthentication().getPrincipal(); |
||||
} |
||||
|
||||
// 获取当前 Request
|
||||
public static HttpServletRequest getRequest() { |
||||
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); |
||||
} |
||||
|
||||
// 获取当前 Response
|
||||
public static HttpServletResponse getResponse() { |
||||
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,27 @@ |
||||
package edu.ncst.award.utils; |
||||
|
||||
public class URLUtils { |
||||
|
||||
public static String removeQueryString(String url) { |
||||
int a = url.indexOf("?"); |
||||
int b = url.indexOf("#"); |
||||
int deleteStart = 0; |
||||
|
||||
if (a == -1 && b == -1) { |
||||
deleteStart = -1; |
||||
} else if (a != -1 && b != -1) { |
||||
deleteStart = Math.min(a, b); |
||||
} else if (a == -1) { |
||||
deleteStart = b; |
||||
} else { |
||||
deleteStart = a; |
||||
} |
||||
|
||||
if (deleteStart != -1) { |
||||
return url.substring(0, deleteStart); |
||||
} else { |
||||
return url; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,30 @@ |
||||
package edu.ncst.award.vo; |
||||
|
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
|
||||
import java.io.Serializable; |
||||
import java.util.List; |
||||
|
||||
@ApiModel(value = "ListResultVO", description = "分页列表数据") |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = false) |
||||
public class ListResultVO<T> implements Serializable { |
||||
|
||||
private static final long serialVersionUID = -8896090499552990782L; |
||||
|
||||
@ApiModelProperty(value = "列表数据") |
||||
private List<T> list; |
||||
|
||||
@ApiModelProperty(value = "记录总数") |
||||
private Long total; |
||||
|
||||
@ApiModelProperty(value = "每页记录数") |
||||
private Long pageSize; |
||||
|
||||
@ApiModelProperty(value = "当前页码") |
||||
private Long current; |
||||
|
||||
} |
||||
@ -0,0 +1,61 @@ |
||||
package edu.ncst.award.vo; |
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils; |
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
||||
import io.swagger.annotations.ApiModel; |
||||
import lombok.Data; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
@ApiModel(description = "列表查询参数") |
||||
@Data |
||||
public class Query implements Serializable { |
||||
|
||||
private static final long serialVersionUID = 4617734867110140874L; |
||||
|
||||
// 排序方式
|
||||
private String sorter; |
||||
|
||||
// 页码
|
||||
private Integer current = 1; |
||||
|
||||
// 每页记录数
|
||||
private Integer pageSize = 10; |
||||
|
||||
// 转换为 Pageable
|
||||
public <T> Page<T> toPageRequest() { |
||||
return new Page<>(current, pageSize); |
||||
} |
||||
|
||||
// 转换为 Sorter 对象
|
||||
public Sorter toSorter() { |
||||
if (sorter == null) { |
||||
return null; |
||||
} |
||||
|
||||
if (sorter.endsWith("_ascend")) { |
||||
String key = sorter.replace("_ascend", ""); |
||||
String column = StringUtils.camelToUnderline(key); |
||||
Sorter _sorter = new Sorter(); |
||||
|
||||
_sorter.setColumn(column); |
||||
_sorter.setKey(key); |
||||
_sorter.setIsAsc(true); |
||||
|
||||
return _sorter; |
||||
} else if (sorter.endsWith("_descend")){ |
||||
String key = sorter.replace("_descend", ""); |
||||
String column = StringUtils.camelToUnderline(key); |
||||
Sorter _sorter = new Sorter(); |
||||
|
||||
_sorter.setColumn(column); |
||||
_sorter.setKey(key); |
||||
_sorter.setIsAsc(false); |
||||
|
||||
return _sorter; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,19 @@ |
||||
package edu.ncst.award.vo; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude; |
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
/** |
||||
* 统一的接口返回数据格式 |
||||
*/ |
||||
@ApiModel(value = "ResultVO", description = "返回信息") |
||||
@Data |
||||
public class ResultVO<T> { |
||||
private Integer code=0; |
||||
private String msg="success"; |
||||
// 数据
|
||||
@ApiModelProperty(value = "数据", dataType = "Object",name = "data") |
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY) |
||||
private T data; |
||||
} |
||||
@ -0,0 +1,20 @@ |
||||
package edu.ncst.award.vo; |
||||
|
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
|
||||
@ApiModel(description = "排序器") |
||||
@Data |
||||
public class Sorter { |
||||
|
||||
@ApiModelProperty(value = "排序关键字") |
||||
private String key; |
||||
|
||||
@ApiModelProperty(value = "是否为升序") |
||||
private Boolean isAsc = true; |
||||
|
||||
@ApiModelProperty(value = "排序列名", notes = "根据该列进行排序") |
||||
private String column; |
||||
|
||||
} |
||||
@ -0,0 +1,41 @@ |
||||
package edu.ncst.award.vo.param; |
||||
|
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
|
||||
@ApiModel(description = "当前用户信息") |
||||
@Data |
||||
public class CurrentUserDTO { |
||||
|
||||
@ApiModelProperty("头像链接") |
||||
private String avatar; |
||||
|
||||
@ApiModelProperty("姓名") |
||||
private String username; |
||||
|
||||
@ApiModelProperty("昵称") |
||||
private String nickname; |
||||
|
||||
@ApiModelProperty("全名") |
||||
private String fullname; |
||||
|
||||
@ApiModelProperty("角色") |
||||
private String role; |
||||
|
||||
@ApiModelProperty("头衔") |
||||
private String title; |
||||
|
||||
@ApiModelProperty("用户组") |
||||
private String group; |
||||
|
||||
@ApiModelProperty("用户ID") |
||||
private Integer userId; |
||||
|
||||
@ApiModelProperty("所在单位ID") |
||||
private Integer unitId; |
||||
|
||||
@ApiModelProperty("所在单位名称") |
||||
private Integer unitName; |
||||
|
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
package edu.ncst.award.vo.param; |
||||
|
||||
import edu.ncst.award.model.system.Menu; |
||||
import lombok.Data; |
||||
|
||||
import java.util.List; |
||||
|
||||
@Data |
||||
public class ResetMenuDTO { |
||||
|
||||
private List<Menu> menus; |
||||
|
||||
} |
||||
@ -0,0 +1,32 @@ |
||||
package edu.ncst.award.vo.param; |
||||
|
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
|
||||
@ApiModel(description = "用户登录表单") |
||||
@Data |
||||
public class UserLoginDTO { |
||||
|
||||
@ApiModelProperty(value = "登录方式", notes = "可选:MOBILE / PASSWORD") |
||||
private String type; |
||||
|
||||
@ApiModelProperty("用户名") |
||||
private String username; |
||||
|
||||
@ApiModelProperty("密码") |
||||
private String password; |
||||
|
||||
@ApiModelProperty("手机号") |
||||
private String phoneNumber; |
||||
|
||||
@ApiModelProperty("手机验证码") |
||||
private String mobileCaptcha; |
||||
|
||||
@ApiModelProperty("验证码") |
||||
private String loginCaptcha; |
||||
|
||||
@ApiModelProperty("临时TOKEN") |
||||
private String tempToken; |
||||
|
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
package edu.ncst.award.vo.param.query; |
||||
|
||||
import edu.ncst.award.vo.Query; |
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
import org.springframework.format.annotation.DateTimeFormat; |
||||
|
||||
import java.util.Date; |
||||
|
||||
@ApiModel(description = "登录日志查询对象") |
||||
@Data |
||||
public class LoginLogQuery extends Query { |
||||
|
||||
@ApiModelProperty(value = "关键字", notes = "用于对用户名、用户ID、用户姓名进行模糊查询") |
||||
private String keyword; |
||||
|
||||
@ApiModelProperty(value = "登录类型") |
||||
private String type; |
||||
|
||||
@ApiModelProperty(value = "开始日期") |
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
||||
private Date startTime; |
||||
|
||||
@ApiModelProperty(value = "结束日期") |
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
||||
private Date endTime; |
||||
|
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
package edu.ncst.award.vo.param.query; |
||||
|
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
|
||||
@Data |
||||
@ApiModel(description = "用户信息查询") |
||||
public class UserQuery { |
||||
@ApiModelProperty("手机号") |
||||
private String phone; |
||||
@ApiModelProperty("姓名") |
||||
private String name; |
||||
} |
||||
@ -0,0 +1,32 @@ |
||||
package edu.ncst.award.vo.result; |
||||
|
||||
import edu.ncst.award.model.system.Role; |
||||
import io.swagger.annotations.ApiModel; |
||||
import io.swagger.annotations.ApiModelProperty; |
||||
import lombok.Data; |
||||
|
||||
import java.util.List; |
||||
|
||||
@Data |
||||
@ApiModel(description = "用户信息") |
||||
public class UserInfoVO { |
||||
@ApiModelProperty(value = "用户id") |
||||
private Integer id; |
||||
@ApiModelProperty(value = "用户名") |
||||
private String username; |
||||
@ApiModelProperty(value = "用户真实名") |
||||
private String fullName; |
||||
@ApiModelProperty(value = "地址信息") |
||||
private String address; |
||||
@ApiModelProperty(value = "手机号") |
||||
private String phone; |
||||
@ApiModelProperty(value = "所属单位") |
||||
private Integer unitId; |
||||
|
||||
@ApiModelProperty(value = "角色列表") |
||||
private List<Role> roles; |
||||
@ApiModelProperty(value = "登录方式") |
||||
private String loginType; |
||||
@ApiModelProperty(value = "是否启用") |
||||
private boolean enable; |
||||
} |
||||
@ -0,0 +1,61 @@ |
||||
spring: |
||||
datasource: |
||||
url: jdbc:mysql://localhost:3307/award_sys?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai |
||||
username: smart |
||||
password: smart3306 |
||||
driver-class-name: com.mysql.cj.jdbc.Driver |
||||
hikari: |
||||
max-lifetime: 300000 |
||||
devtools: |
||||
restart: |
||||
enabled: true #设置开启热部署 |
||||
additional-paths: src/main/java #重启目录 |
||||
exclude: WEB-INF/** |
||||
application: |
||||
name: springfox-swagger |
||||
mvc: |
||||
pathmatch: |
||||
matching-strategy: ant_path_matcher |
||||
async: |
||||
request-timeout: 120000 #设置restful的api超时时间为60秒 |
||||
server: |
||||
port: 8083 |
||||
tomcat: |
||||
connection-timeout: 300000 |
||||
servlet: |
||||
encoding: |
||||
charset: utf-8 |
||||
#mybatis: |
||||
# mapper-locations: classpath:mapping/*.xml |
||||
# type-aliases-package: edu.ncst.award.model |
||||
|
||||
swagger: |
||||
enable: true |
||||
title: API文档 |
||||
description: 科技奖申报系统API文档 |
||||
base-package: edu.ncst.award.controller |
||||
exclude-path: /error/** |
||||
termsOfServiceUrl: https://meander.net.cn |
||||
contact: |
||||
name: sage |
||||
url: www.meander.net.cn |
||||
email: sage@meander.net.cn |
||||
|
||||
logging: |
||||
config: classpath:logback.xml |
||||
level: |
||||
edu.ncst.equmanager: info |
||||
org.springframework: info |
||||
java.sql: info |
||||
# |
||||
## MyBatis Plus |
||||
#mybatis-plus: |
||||
# global-config: |
||||
# db-config: |
||||
# logic-delete-field: deleteAt |
||||
# logic-delete-value: now() |
||||
# logic-not-delete-value: null |
||||
# banner: false # 关闭控制台的 LOGO |
||||
# mapper-locations: classpath*:/mapping/**/*.xml |
||||
# configuration: |
||||
# log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl |
||||
@ -0,0 +1,57 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<configuration debug="false"> |
||||
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--> |
||||
<property name="LOG_PATH" value="D:/log"/> |
||||
<!-- 定义日志格式 --> |
||||
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS,CTT} [%-5level] [%thread] [%-30.30logger{30}] %msg%n"/> |
||||
<property name="MAX_FILE_SIZE" value="10MB"/> |
||||
<property name="MAX_HISTORY" value="30"/> |
||||
<property name="CONTEXT_NAME" value="sbgl"/> |
||||
|
||||
<contextName>${CONTEXT_NAME}</contextName> |
||||
<!-- 彩色日志 --> |
||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /> |
||||
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /> |
||||
<!-- 控制台输出 --> |
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> |
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> |
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> |
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS,CTT} [%thread] %-5level %logger{50} - %msg%n</pattern> |
||||
</encoder> |
||||
</appender> |
||||
<!-- 按照每天生成日志文件 --> |
||||
<!-- 控制台日志样式 --> |
||||
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS,CTT}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([%15.15t]){faint} [%X{requestId}] %clr(%-40.40logger{39}){cyan} [%L] %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> |
||||
<!-- 文件日志样式 --> |
||||
<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS,CTT}} ${LOG_LEVEL_PATTERN:-%5p} [%t] [%X{requestId}] %-40.40logger{39} %L : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> |
||||
|
||||
<!-- 禁用logback自身日志输出 --> |
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" /> |
||||
|
||||
<!-- 控制台 --> |
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> |
||||
<encoder> |
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern> |
||||
</encoder> |
||||
</appender> |
||||
|
||||
<!-- 运行日志文件 --> |
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
||||
<encoder> |
||||
<pattern>${FILE_LOG_PATTERN}</pattern> |
||||
</encoder> |
||||
<file>${LOG_PATH}/sbgl.log</file> |
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
||||
<fileNamePattern>${LOG_PATH}/sbgl-%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
||||
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize> |
||||
<maxHistory>${MAX_HISTORY}</maxHistory> |
||||
</rollingPolicy> |
||||
</appender> |
||||
<!-- |
||||
日志输出级别 |
||||
--> |
||||
<root level="INFO"> |
||||
<appender-ref ref="CONSOLE"/> |
||||
<appender-ref ref="FILE"/> |
||||
</root> |
||||
</configuration> |
||||
@ -0,0 +1,13 @@ |
||||
package edu.ncst.award; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
||||
@SpringBootTest |
||||
class AwardApplicationTests { |
||||
|
||||
@Test |
||||
void contextLoads() { |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,16 @@ |
||||
# http://editorconfig.org |
||||
root = true |
||||
|
||||
[*] |
||||
indent_style = space |
||||
indent_size = 2 |
||||
end_of_line = lf |
||||
charset = utf-8 |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = true |
||||
|
||||
[*.md] |
||||
trim_trailing_whitespace = false |
||||
|
||||
[Makefile] |
||||
indent_style = tab |
||||
@ -0,0 +1,8 @@ |
||||
/lambda/ |
||||
/scripts |
||||
/config |
||||
.history |
||||
public |
||||
dist |
||||
.umi |
||||
mock |
||||
@ -0,0 +1,8 @@ |
||||
module.exports = { |
||||
extends: [require.resolve('@umijs/fabric/dist/eslint')], |
||||
globals: { |
||||
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, |
||||
page: true, |
||||
REACT_APP_ENV: true, |
||||
}, |
||||
}; |
||||
@ -0,0 +1,42 @@ |
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. |
||||
|
||||
# dependencies |
||||
**/node_modules |
||||
# roadhog-api-doc ignore |
||||
/src/utils/request-temp.js |
||||
_roadhog-api-doc |
||||
|
||||
# production |
||||
/dist |
||||
/.vscode |
||||
|
||||
# misc |
||||
.DS_Store |
||||
npm-debug.log* |
||||
yarn-error.log |
||||
|
||||
/coverage |
||||
.idea |
||||
yarn.lock |
||||
# package-lock.json |
||||
*bak |
||||
.vscode |
||||
|
||||
# visual studio code |
||||
.history |
||||
*.log |
||||
functions/* |
||||
.temp/** |
||||
|
||||
# umi |
||||
.umi |
||||
.umi-production |
||||
|
||||
# screenshot |
||||
screenshot |
||||
.firebase |
||||
.eslintcache |
||||
|
||||
build |
||||
|
||||
.mocks/ |
||||
@ -0,0 +1,23 @@ |
||||
**/*.svg |
||||
package.json |
||||
.umi |
||||
.umi-production |
||||
/dist |
||||
.dockerignore |
||||
.DS_Store |
||||
.eslintignore |
||||
*.png |
||||
*.toml |
||||
docker |
||||
.editorconfig |
||||
Dockerfile* |
||||
.gitignore |
||||
.prettierignore |
||||
LICENSE |
||||
.eslintcache |
||||
*.lock |
||||
yarn-error.log |
||||
.history |
||||
CNAME |
||||
/build |
||||
/public |
||||
@ -0,0 +1,5 @@ |
||||
const fabric = require('@umijs/fabric'); |
||||
|
||||
module.exports = { |
||||
...fabric.prettier, |
||||
}; |
||||
@ -0,0 +1,5 @@ |
||||
const fabric = require('@umijs/fabric'); |
||||
|
||||
module.exports = { |
||||
...fabric.stylelint, |
||||
}; |
||||
@ -0,0 +1,57 @@ |
||||
# Award Manage |
||||
|
||||
This project is initialized with [Award Manage](https://pro.ant.design). Follow is the quick guide for how to use. |
||||
|
||||
## Environment Prepare |
||||
|
||||
Install `node_modules`: |
||||
|
||||
```bash |
||||
npm install |
||||
``` |
||||
|
||||
or |
||||
|
||||
```bash |
||||
yarn |
||||
``` |
||||
|
||||
## Provided Scripts |
||||
|
||||
Award Manage provides some useful script to help you quick start and build with web project, code style check and test. |
||||
|
||||
Scripts provided in `package.json`. It's safe to modify or add additional script: |
||||
|
||||
### Start project |
||||
|
||||
```bash |
||||
npm start |
||||
``` |
||||
|
||||
### Build project |
||||
|
||||
```bash |
||||
npm run build |
||||
``` |
||||
|
||||
### Check code style |
||||
|
||||
```bash |
||||
npm run lint |
||||
``` |
||||
|
||||
You can also use script to auto fix some lint error: |
||||
|
||||
```bash |
||||
npm run lint:fix |
||||
``` |
||||
|
||||
### Test code |
||||
|
||||
```bash |
||||
npm test |
||||
``` |
||||
|
||||
## More |
||||
|
||||
You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro). |
||||
@ -0,0 +1,15 @@ |
||||
// https://umijs.org/config/
|
||||
import { defineConfig } from 'umi'; |
||||
|
||||
export default defineConfig({ |
||||
plugins: [ |
||||
// https://github.com/zthxxx/react-dev-inspector
|
||||
'react-dev-inspector/plugins/umi/react-inspector', |
||||
], |
||||
// https://github.com/zthxxx/react-dev-inspector#inspector-loader-props
|
||||
inspectorConfig: { |
||||
exclude: [], |
||||
babelPlugins: [], |
||||
babelOptions: {}, |
||||
}, |
||||
}); |
||||
@ -0,0 +1,67 @@ |
||||
// https://umijs.org/config/
|
||||
import { defineConfig } from 'umi'; |
||||
import { join } from 'path'; |
||||
|
||||
import defaultSettings from './defaultSettings'; |
||||
import proxy from './proxy'; |
||||
import routes from './routes'; |
||||
|
||||
const { REACT_APP_ENV } = process.env; |
||||
|
||||
export default defineConfig({ |
||||
hash: true, |
||||
antd: {}, |
||||
dva: { |
||||
hmr: true, |
||||
}, |
||||
|
||||
// https://umijs.org/zh-CN/plugins/plugin-locale
|
||||
locale: { |
||||
// default zh-CN
|
||||
default: 'zh-CN', |
||||
antd: true, |
||||
// default true, when it is true, will use `navigator.language` overwrite default
|
||||
baseNavigator: false, |
||||
}, |
||||
dynamicImport: { |
||||
loading: '@ant-design/pro-layout/es/PageLoading', |
||||
}, |
||||
targets: { |
||||
ie: 11, |
||||
}, |
||||
// umi routes: https://umijs.org/docs/routing
|
||||
routes, |
||||
// Theme for antd: https://ant.design/docs/react/customize-theme-cn
|
||||
theme: { |
||||
'primary-color': defaultSettings.primaryColor, |
||||
}, |
||||
// esbuild is father build tools
|
||||
// https://umijs.org/plugins/plugin-esbuild
|
||||
esbuild: {}, |
||||
title: false, |
||||
ignoreMomentLocale: true, |
||||
proxy: proxy[REACT_APP_ENV || 'dev'], |
||||
manifest: { |
||||
basePath: '/', |
||||
}, |
||||
// Fast Refresh 热更新
|
||||
fastRefresh: {}, |
||||
openAPI: [ |
||||
{ |
||||
requestLibPath: "import { request } from 'umi'", |
||||
// 或者使用在线的版本
|
||||
// schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
|
||||
schemaPath: join(__dirname, 'oneapi.json'), |
||||
mock: false, |
||||
}, |
||||
{ |
||||
requestLibPath: "import { request } from 'umi'", |
||||
schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json', |
||||
projectName: 'swagger', |
||||
}, |
||||
], |
||||
nodeModulesTransform: { type: 'none' }, |
||||
mfsu: {}, |
||||
webpack5: {}, |
||||
exportStatic: {}, |
||||
}); |
||||
@ -0,0 +1,61 @@ |
||||
import { MenuTheme } from 'antd/es/menu/MenuContext'; |
||||
|
||||
export type ContentWidth = 'Fluid' | 'Fixed'; |
||||
|
||||
export interface DefaultSettings { |
||||
/** |
||||
* theme for nav menu |
||||
*/ |
||||
navTheme: MenuTheme; |
||||
/** |
||||
* primary color of ant design |
||||
*/ |
||||
primaryColor: string; |
||||
/** |
||||
* nav menu position: `sidemenu` or `topmenu` |
||||
*/ |
||||
layout: 'sidemenu' | 'topmenu'; |
||||
/** |
||||
* layout of content: `Fluid` or `Fixed`, only works when layout is topmenu |
||||
*/ |
||||
contentWidth: ContentWidth; |
||||
/** |
||||
* sticky header |
||||
*/ |
||||
fixedHeader: boolean; |
||||
/** |
||||
* auto hide header |
||||
*/ |
||||
autoHideHeader: boolean; |
||||
/** |
||||
* sticky siderbar |
||||
*/ |
||||
fixSiderbar: boolean; |
||||
menu: { locale: boolean }; |
||||
title: string; |
||||
pwa: boolean; |
||||
// Your custom iconfont Symbol script Url
|
||||
// eg://at.alicdn.com/t/font_1039637_btcrd5co4w.js
|
||||
// 注意:如果需要图标多色,Iconfont 图标项目里要进行批量去色处理
|
||||
// Usage: https://github.com/ant-design/ant-design-pro/pull/3517
|
||||
iconfontUrl: string; |
||||
colorWeak: boolean; |
||||
} |
||||
|
||||
export default { |
||||
navTheme: 'light', |
||||
// 拂晓蓝
|
||||
primaryColor: '#1890ff', |
||||
layout: 'sidemenu', |
||||
contentWidth: 'Fluid', |
||||
fixedHeader: false, |
||||
autoHideHeader: false, |
||||
fixSiderbar: true, |
||||
colorWeak: false, |
||||
menu: { |
||||
locale: true, |
||||
}, |
||||
title: '科技奖评审系统', |
||||
pwa: false, |
||||
iconfontUrl: '', |
||||
} as DefaultSettings; |
||||
@ -0,0 +1,593 @@ |
||||
{ |
||||
"openapi": "3.0.1", |
||||
"info": { |
||||
"title": "Award Manage", |
||||
"version": "1.0.0" |
||||
}, |
||||
"servers": [ |
||||
{ |
||||
"url": "http://localhost:8000/" |
||||
}, |
||||
{ |
||||
"url": "https://localhost:8000/" |
||||
} |
||||
], |
||||
"paths": { |
||||
"/api/currentUser": { |
||||
"get": { |
||||
"tags": ["api"], |
||||
"description": "获取当前的用户", |
||||
"operationId": "currentUser", |
||||
"responses": { |
||||
"200": { |
||||
"description": "Success", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/CurrentUser" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"401": { |
||||
"description": "Error", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/ErrorResponse" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"x-swagger-router-controller": "api" |
||||
}, |
||||
"/api/login/captcha": { |
||||
"post": { |
||||
"description": "发送验证码", |
||||
"operationId": "getFakeCaptcha", |
||||
"tags": ["login"], |
||||
"parameters": [ |
||||
{ |
||||
"name": "phone", |
||||
"in": "query", |
||||
"description": "手机号", |
||||
"schema": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
], |
||||
"responses": { |
||||
"200": { |
||||
"description": "Success", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/FakeCaptcha" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"/api/login/outLogin": { |
||||
"post": { |
||||
"description": "登录接口", |
||||
"operationId": "outLogin", |
||||
"tags": ["login"], |
||||
"responses": { |
||||
"200": { |
||||
"description": "Success", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"type": "object" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"401": { |
||||
"description": "Error", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/ErrorResponse" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"x-swagger-router-controller": "api" |
||||
}, |
||||
"/api/login/account": { |
||||
"post": { |
||||
"tags": ["login"], |
||||
"description": "登录接口", |
||||
"operationId": "login", |
||||
"requestBody": { |
||||
"description": "登录系统", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/LoginParams" |
||||
} |
||||
} |
||||
}, |
||||
"required": true |
||||
}, |
||||
"responses": { |
||||
"200": { |
||||
"description": "Success", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/LoginResult" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"401": { |
||||
"description": "Error", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/ErrorResponse" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"x-codegen-request-body-name": "body" |
||||
}, |
||||
"x-swagger-router-controller": "api" |
||||
}, |
||||
"/api/notices": { |
||||
"summary": "getNotices", |
||||
"description": "NoticeIconItem", |
||||
"get": { |
||||
"tags": ["api"], |
||||
"operationId": "getNotices", |
||||
"responses": { |
||||
"200": { |
||||
"description": "Success", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/NoticeIconList" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"/api/rule": { |
||||
"get": { |
||||
"tags": ["rule"], |
||||
"description": "获取规则列表", |
||||
"operationId": "rule", |
||||
"parameters": [ |
||||
{ |
||||
"name": "current", |
||||
"in": "query", |
||||
"description": "当前的页码", |
||||
"schema": { |
||||
"type": "number" |
||||
} |
||||
}, |
||||
{ |
||||
"name": "pageSize", |
||||
"in": "query", |
||||
"description": "页面的容量", |
||||
"schema": { |
||||
"type": "number" |
||||
} |
||||
} |
||||
], |
||||
"responses": { |
||||
"200": { |
||||
"description": "Success", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/RuleList" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"401": { |
||||
"description": "Error", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/ErrorResponse" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"post": { |
||||
"tags": ["rule"], |
||||
"description": "新建规则", |
||||
"operationId": "addRule", |
||||
"responses": { |
||||
"200": { |
||||
"description": "Success", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/RuleListItem" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"401": { |
||||
"description": "Error", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/ErrorResponse" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"put": { |
||||
"tags": ["rule"], |
||||
"description": "新建规则", |
||||
"operationId": "updateRule", |
||||
"responses": { |
||||
"200": { |
||||
"description": "Success", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/RuleListItem" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"401": { |
||||
"description": "Error", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/ErrorResponse" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"delete": { |
||||
"tags": ["rule"], |
||||
"description": "删除规则", |
||||
"operationId": "removeRule", |
||||
"responses": { |
||||
"200": { |
||||
"description": "Success", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"type": "object" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"401": { |
||||
"description": "Error", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"$ref": "#/components/schemas/ErrorResponse" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"x-swagger-router-controller": "api" |
||||
}, |
||||
"/swagger": { |
||||
"x-swagger-pipe": "swagger_raw" |
||||
} |
||||
}, |
||||
"components": { |
||||
"schemas": { |
||||
"CurrentUser": { |
||||
"type": "object", |
||||
"properties": { |
||||
"name": { |
||||
"type": "string" |
||||
}, |
||||
"avatar": { |
||||
"type": "string" |
||||
}, |
||||
"userid": { |
||||
"type": "string" |
||||
}, |
||||
"email": { |
||||
"type": "string" |
||||
}, |
||||
"signature": { |
||||
"type": "string" |
||||
}, |
||||
"title": { |
||||
"type": "string" |
||||
}, |
||||
"group": { |
||||
"type": "string" |
||||
}, |
||||
"tags": { |
||||
"type": "array", |
||||
"items": { |
||||
"type": "object", |
||||
"properties": { |
||||
"key": { |
||||
"type": "string" |
||||
}, |
||||
"label": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"notifyCount": { |
||||
"type": "integer", |
||||
"format": "int32" |
||||
}, |
||||
"unreadCount": { |
||||
"type": "integer", |
||||
"format": "int32" |
||||
}, |
||||
"country": { |
||||
"type": "string" |
||||
}, |
||||
"access": { |
||||
"type": "string" |
||||
}, |
||||
"geographic": { |
||||
"type": "object", |
||||
"properties": { |
||||
"province": { |
||||
"type": "object", |
||||
"properties": { |
||||
"label": { |
||||
"type": "string" |
||||
}, |
||||
"key": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
}, |
||||
"city": { |
||||
"type": "object", |
||||
"properties": { |
||||
"label": { |
||||
"type": "string" |
||||
}, |
||||
"key": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"address": { |
||||
"type": "string" |
||||
}, |
||||
"phone": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
}, |
||||
"LoginResult": { |
||||
"type": "object", |
||||
"properties": { |
||||
"status": { |
||||
"type": "string" |
||||
}, |
||||
"type": { |
||||
"type": "string" |
||||
}, |
||||
"currentAuthority": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
}, |
||||
"PageParams": { |
||||
"type": "object", |
||||
"properties": { |
||||
"current": { |
||||
"type": "number" |
||||
}, |
||||
"pageSize": { |
||||
"type": "number" |
||||
} |
||||
} |
||||
}, |
||||
"RuleListItem": { |
||||
"type": "object", |
||||
"properties": { |
||||
"key": { |
||||
"type": "integer", |
||||
"format": "int32" |
||||
}, |
||||
"disabled": { |
||||
"type": "boolean" |
||||
}, |
||||
"href": { |
||||
"type": "string" |
||||
}, |
||||
"avatar": { |
||||
"type": "string" |
||||
}, |
||||
"name": { |
||||
"type": "string" |
||||
}, |
||||
"owner": { |
||||
"type": "string" |
||||
}, |
||||
"desc": { |
||||
"type": "string" |
||||
}, |
||||
"callNo": { |
||||
"type": "integer", |
||||
"format": "int32" |
||||
}, |
||||
"status": { |
||||
"type": "integer", |
||||
"format": "int32" |
||||
}, |
||||
"updatedAt": { |
||||
"type": "string", |
||||
"format": "datetime" |
||||
}, |
||||
"createdAt": { |
||||
"type": "string", |
||||
"format": "datetime" |
||||
}, |
||||
"progress": { |
||||
"type": "integer", |
||||
"format": "int32" |
||||
} |
||||
} |
||||
}, |
||||
"RuleList": { |
||||
"type": "object", |
||||
"properties": { |
||||
"data": { |
||||
"type": "array", |
||||
"items": { |
||||
"$ref": "#/components/schemas/RuleListItem" |
||||
} |
||||
}, |
||||
"total": { |
||||
"type": "integer", |
||||
"description": "列表的内容总数", |
||||
"format": "int32" |
||||
}, |
||||
"success": { |
||||
"type": "boolean" |
||||
} |
||||
} |
||||
}, |
||||
"FakeCaptcha": { |
||||
"type": "object", |
||||
"properties": { |
||||
"code": { |
||||
"type": "integer", |
||||
"format": "int32" |
||||
}, |
||||
"status": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
}, |
||||
"LoginParams": { |
||||
"type": "object", |
||||
"properties": { |
||||
"username": { |
||||
"type": "string" |
||||
}, |
||||
"password": { |
||||
"type": "string" |
||||
}, |
||||
"autoLogin": { |
||||
"type": "boolean" |
||||
}, |
||||
"type": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
}, |
||||
"ErrorResponse": { |
||||
"required": ["errorCode"], |
||||
"type": "object", |
||||
"properties": { |
||||
"errorCode": { |
||||
"type": "string", |
||||
"description": "业务约定的错误码" |
||||
}, |
||||
"errorMessage": { |
||||
"type": "string", |
||||
"description": "业务上的错误信息" |
||||
}, |
||||
"success": { |
||||
"type": "boolean", |
||||
"description": "业务上的请求是否成功" |
||||
} |
||||
} |
||||
}, |
||||
"NoticeIconList": { |
||||
"type": "object", |
||||
"properties": { |
||||
"data": { |
||||
"type": "array", |
||||
"items": { |
||||
"$ref": "#/components/schemas/NoticeIconItem" |
||||
} |
||||
}, |
||||
"total": { |
||||
"type": "integer", |
||||
"description": "列表的内容总数", |
||||
"format": "int32" |
||||
}, |
||||
"success": { |
||||
"type": "boolean" |
||||
} |
||||
} |
||||
}, |
||||
"NoticeIconItemType": { |
||||
"title": "NoticeIconItemType", |
||||
"description": "已读未读列表的枚举", |
||||
"type": "string", |
||||
"properties": {}, |
||||
"enum": ["notification", "message", "event"] |
||||
}, |
||||
"NoticeIconItem": { |
||||
"type": "object", |
||||
"properties": { |
||||
"id": { |
||||
"type": "string" |
||||
}, |
||||
"extra": { |
||||
"type": "string", |
||||
"format": "any" |
||||
}, |
||||
"key": { "type": "string" }, |
||||
"read": { |
||||
"type": "boolean" |
||||
}, |
||||
"avatar": { |
||||
"type": "string" |
||||
}, |
||||
"title": { |
||||
"type": "string" |
||||
}, |
||||
"status": { |
||||
"type": "string" |
||||
}, |
||||
"datetime": { |
||||
"type": "string", |
||||
"format": "date" |
||||
}, |
||||
"description": { |
||||
"type": "string" |
||||
}, |
||||
"type": { |
||||
"extensions": { |
||||
"x-is-enum": true |
||||
}, |
||||
"$ref": "#/components/schemas/NoticeIconItemType" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,35 @@ |
||||
/** |
||||
* 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 |
||||
* ------------------------------- |
||||
* The agent cannot take effect in the production environment |
||||
* so there is no configuration of the production environment |
||||
* For details, please see |
||||
* https://pro.ant.design/docs/deploy
|
||||
*/ |
||||
export default { |
||||
dev: { |
||||
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
||||
'/api/': { |
||||
// 要代理的地址
|
||||
// target: 'https://preview.pro.ant.design',
|
||||
// 配置了这个可以从 http 代理到 https
|
||||
// 依赖 origin 的功能可能需要这个,比如 cookie
|
||||
target: 'http://localhost:8083', |
||||
changeOrigin: true, |
||||
}, |
||||
}, |
||||
test: { |
||||
'/api/': { |
||||
target: 'https://proapi.azurewebsites.net', |
||||
changeOrigin: true, |
||||
pathRewrite: { '^': '' }, |
||||
}, |
||||
}, |
||||
pre: { |
||||
'/api/': { |
||||
target: 'your pre url', |
||||
changeOrigin: true, |
||||
pathRewrite: { '^': '' }, |
||||
}, |
||||
}, |
||||
}; |
||||
@ -0,0 +1,9 @@ |
||||
module.exports = { |
||||
testURL: 'http://localhost:8000', |
||||
verbose: false, |
||||
extraSetupFiles: ['./tests/setupTests.js'], |
||||
globals: { |
||||
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false, |
||||
localStorage: null, |
||||
}, |
||||
}; |
||||
@ -0,0 +1,11 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"jsx": "react-jsx", |
||||
"emitDecoratorMetadata": true, |
||||
"experimentalDecorators": true, |
||||
"baseUrl": ".", |
||||
"paths": { |
||||
"@/*": ["./src/*"] |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,174 @@ |
||||
import { Request, Response } from 'express'; |
||||
import moment from 'moment'; |
||||
import { parse } from 'url'; |
||||
|
||||
// mock tableListDataSource
|
||||
const genList = (current: number, pageSize: number) => { |
||||
const tableListDataSource: APID.RuleListItem[] = []; |
||||
|
||||
for (let i = 0; i < pageSize; i += 1) { |
||||
const index = (current - 1) * 10 + i; |
||||
tableListDataSource.push({ |
||||
key: index, |
||||
disabled: i % 6 === 0, |
||||
href: 'https://ant.design', |
||||
avatar: [ |
||||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
][i % 2], |
||||
name: `TradeCode ${index}`, |
||||
owner: '曲丽丽', |
||||
desc: '这是一段描述', |
||||
callNo: Math.floor(Math.random() * 1000), |
||||
status: Math.floor(Math.random() * 10) % 4, |
||||
updatedAt: moment().format('YYYY-MM-DD'), |
||||
createdAt: moment().format('YYYY-MM-DD'), |
||||
progress: Math.ceil(Math.random() * 100), |
||||
}); |
||||
} |
||||
tableListDataSource.reverse(); |
||||
return tableListDataSource; |
||||
}; |
||||
|
||||
let tableListDataSource = genList(1, 100); |
||||
|
||||
function getRule(req: Request, res: Response, u: string) { |
||||
let realUrl = u; |
||||
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { |
||||
realUrl = req.url; |
||||
} |
||||
const { current = 1, pageSize = 10 } = req.query; |
||||
const params = parse(realUrl, true).query as unknown as APID.PageParams & |
||||
APID.RuleListItem & { |
||||
sorter: any; |
||||
filter: any; |
||||
}; |
||||
|
||||
let dataSource = [...tableListDataSource].slice( |
||||
((current as number) - 1) * (pageSize as number), |
||||
(current as number) * (pageSize as number), |
||||
); |
||||
if (params.sorter) { |
||||
const sorter = JSON.parse(params.sorter); |
||||
dataSource = dataSource.sort((prev, next) => { |
||||
let sortNumber = 0; |
||||
Object.keys(sorter).forEach((key) => { |
||||
if (sorter[key] === 'descend') { |
||||
if (prev[key] - next[key] > 0) { |
||||
sortNumber += -1; |
||||
} else { |
||||
sortNumber += 1; |
||||
} |
||||
return; |
||||
} |
||||
if (prev[key] - next[key] > 0) { |
||||
sortNumber += 1; |
||||
} else { |
||||
sortNumber += -1; |
||||
} |
||||
}); |
||||
return sortNumber; |
||||
}); |
||||
} |
||||
if (params.filter) { |
||||
const filter = JSON.parse(params.filter as any) as { |
||||
[key: string]: string[]; |
||||
}; |
||||
if (Object.keys(filter).length > 0) { |
||||
dataSource = dataSource.filter((item) => { |
||||
return Object.keys(filter).some((key) => { |
||||
if (!filter[key]) { |
||||
return true; |
||||
} |
||||
if (filter[key].includes(`${item[key]}`)) { |
||||
return true; |
||||
} |
||||
return false; |
||||
}); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
if (params.name) { |
||||
dataSource = dataSource.filter((data) => data?.name?.includes(params.name || '')); |
||||
} |
||||
const result = { |
||||
data: dataSource, |
||||
total: tableListDataSource.length, |
||||
success: true, |
||||
pageSize, |
||||
current: parseInt(`${params.current}`, 10) || 1, |
||||
}; |
||||
|
||||
return res.json(result); |
||||
} |
||||
|
||||
function postRule(req: Request, res: Response, u: string, b: Request) { |
||||
let realUrl = u; |
||||
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') { |
||||
realUrl = req.url; |
||||
} |
||||
|
||||
const body = (b && b.body) || req.body; |
||||
const { method, name, desc, key } = body; |
||||
|
||||
switch (method) { |
||||
/* eslint no-case-declarations:0 */ |
||||
case 'delete': |
||||
tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1); |
||||
break; |
||||
case 'post': |
||||
(() => { |
||||
const i = Math.ceil(Math.random() * 10000); |
||||
const newRule: APID.RuleListItem = { |
||||
key: tableListDataSource.length, |
||||
href: 'https://ant.design', |
||||
avatar: [ |
||||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
||||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
||||
][i % 2], |
||||
name, |
||||
owner: '曲丽丽', |
||||
desc, |
||||
callNo: Math.floor(Math.random() * 1000), |
||||
status: Math.floor(Math.random() * 10) % 2, |
||||
updatedAt: moment().format('YYYY-MM-DD'), |
||||
createdAt: moment().format('YYYY-MM-DD'), |
||||
progress: Math.ceil(Math.random() * 100), |
||||
}; |
||||
tableListDataSource.unshift(newRule); |
||||
return res.json(newRule); |
||||
})(); |
||||
return; |
||||
|
||||
case 'update': |
||||
(() => { |
||||
let newRule = {}; |
||||
tableListDataSource = tableListDataSource.map((item) => { |
||||
if (item.key === key) { |
||||
newRule = { ...item, desc, name }; |
||||
return { ...item, desc, name }; |
||||
} |
||||
return item; |
||||
}); |
||||
return res.json(newRule); |
||||
})(); |
||||
return; |
||||
default: |
||||
break; |
||||
} |
||||
|
||||
const result = { |
||||
list: tableListDataSource, |
||||
pagination: { |
||||
total: tableListDataSource.length, |
||||
}, |
||||
}; |
||||
|
||||
res.json(result); |
||||
} |
||||
|
||||
export default { |
||||
'GET /api/rule': getRule, |
||||
'POST /api/rule': postRule, |
||||
}; |
||||
@ -0,0 +1,107 @@ |
||||
import { Request, Response } from 'express'; |
||||
|
||||
const getNotices = (req: Request, res: Response) => { |
||||
res.json({ |
||||
data: [ |
||||
{ |
||||
id: '000000001', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', |
||||
title: '你收到了 14 份新周报', |
||||
datetime: '2017-08-09', |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000002', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', |
||||
title: '你推荐的 曲妮妮 已通过第三轮面试', |
||||
datetime: '2017-08-08', |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000003', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', |
||||
title: '这种模板可以区分多种通知类型', |
||||
datetime: '2017-08-07', |
||||
read: true, |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000004', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', |
||||
title: '左侧图标用于区分不同的类型', |
||||
datetime: '2017-08-07', |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000005', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', |
||||
title: '内容不要超过两行字,超出时自动截断', |
||||
datetime: '2017-08-07', |
||||
type: 'notification', |
||||
}, |
||||
{ |
||||
id: '000000006', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
title: '曲丽丽 评论了你', |
||||
description: '描述信息描述信息描述信息', |
||||
datetime: '2017-08-07', |
||||
type: 'message', |
||||
clickClose: true, |
||||
}, |
||||
{ |
||||
id: '000000007', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
title: '朱偏右 回复了你', |
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', |
||||
datetime: '2017-08-07', |
||||
type: 'message', |
||||
clickClose: true, |
||||
}, |
||||
{ |
||||
id: '000000008', |
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
title: '标题', |
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', |
||||
datetime: '2017-08-07', |
||||
type: 'message', |
||||
clickClose: true, |
||||
}, |
||||
{ |
||||
id: '000000009', |
||||
title: '任务名称', |
||||
description: '任务需要在 2017-01-12 20:00 前启动', |
||||
extra: '未开始', |
||||
status: 'todo', |
||||
type: 'event', |
||||
}, |
||||
{ |
||||
id: '000000010', |
||||
title: '第三方紧急代码变更', |
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', |
||||
extra: '马上到期', |
||||
status: 'urgent', |
||||
type: 'event', |
||||
}, |
||||
{ |
||||
id: '000000011', |
||||
title: '信息安全考试', |
||||
description: '指派竹尔于 2017-01-09 前完成更新并发布', |
||||
extra: '已耗时 8 天', |
||||
status: 'doing', |
||||
type: 'event', |
||||
}, |
||||
{ |
||||
id: '000000012', |
||||
title: 'ABCD 版本发布', |
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', |
||||
extra: '进行中', |
||||
status: 'processing', |
||||
type: 'event', |
||||
}, |
||||
], |
||||
}); |
||||
}; |
||||
|
||||
export default { |
||||
'GET /api/notices': getNotices, |
||||
}; |
||||
@ -0,0 +1,5 @@ |
||||
export default { |
||||
'/api/auth_routes': { |
||||
'/form/advanced-form': { authority: ['admin', 'user'] }, |
||||
}, |
||||
}; |
||||
@ -0,0 +1,203 @@ |
||||
import { Request, Response } from 'express'; |
||||
|
||||
const waitTime = (time: number = 100) => { |
||||
return new Promise((resolve) => { |
||||
setTimeout(() => { |
||||
resolve(true); |
||||
}, time); |
||||
}); |
||||
}; |
||||
|
||||
async function getFakeCaptcha(req: Request, res: Response) { |
||||
await waitTime(2000); |
||||
return res.json('captcha-xxx'); |
||||
} |
||||
|
||||
const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env; |
||||
|
||||
/** |
||||
* 当前用户的权限,如果为空代表没登录 |
||||
* current user access, if is '', user need login |
||||
* 如果是 pro 的预览,默认是有权限的 |
||||
*/ |
||||
let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : ''; |
||||
|
||||
const getAccess = () => { |
||||
return access; |
||||
}; |
||||
|
||||
// 代码中会兼容本地 service mock 以及部署站点的静态数据
|
||||
export default { |
||||
// 支持值为 Object 和 Array
|
||||
'GET /api/currentUser': (req: Request, res: Response) => { |
||||
if (!getAccess()) { |
||||
res.status(401).send({ |
||||
data: { |
||||
isLogin: false, |
||||
}, |
||||
errorCode: '401', |
||||
errorMessage: '请先登录!', |
||||
success: true, |
||||
}); |
||||
return; |
||||
} |
||||
res.send({ |
||||
success: true, |
||||
data: { |
||||
name: 'Serati Ma', |
||||
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', |
||||
userid: '00000001', |
||||
email: 'antdesign@alipay.com', |
||||
signature: '海纳百川,有容乃大', |
||||
title: '交互专家', |
||||
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', |
||||
tags: [ |
||||
{ |
||||
key: '0', |
||||
label: '很有想法的', |
||||
}, |
||||
{ |
||||
key: '1', |
||||
label: '专注设计', |
||||
}, |
||||
{ |
||||
key: '2', |
||||
label: '辣~', |
||||
}, |
||||
{ |
||||
key: '3', |
||||
label: '大长腿', |
||||
}, |
||||
{ |
||||
key: '4', |
||||
label: '川妹子', |
||||
}, |
||||
{ |
||||
key: '5', |
||||
label: '海纳百川', |
||||
}, |
||||
], |
||||
notifyCount: 12, |
||||
unreadCount: 11, |
||||
country: 'China', |
||||
access: getAccess(), |
||||
geographic: { |
||||
province: { |
||||
label: '浙江省', |
||||
key: '330000', |
||||
}, |
||||
city: { |
||||
label: '杭州市', |
||||
key: '330100', |
||||
}, |
||||
}, |
||||
address: '西湖区工专路 77 号', |
||||
phone: '0752-268888888', |
||||
}, |
||||
}); |
||||
}, |
||||
// GET POST 可省略
|
||||
'GET /api/users': [ |
||||
{ |
||||
key: '1', |
||||
name: 'John Brown', |
||||
age: 32, |
||||
address: 'New York No. 1 Lake Park', |
||||
}, |
||||
{ |
||||
key: '2', |
||||
name: 'Jim Green', |
||||
age: 42, |
||||
address: 'London No. 1 Lake Park', |
||||
}, |
||||
{ |
||||
key: '3', |
||||
name: 'Joe Black', |
||||
age: 32, |
||||
address: 'Sidney No. 1 Lake Park', |
||||
}, |
||||
], |
||||
'POST /api/login/account': async (req: Request, res: Response) => { |
||||
const { password, username, type } = req.body; |
||||
await waitTime(2000); |
||||
if (password === 'ant.design' && username === 'admin') { |
||||
res.send({ |
||||
status: 'ok', |
||||
type, |
||||
currentAuthority: 'admin', |
||||
}); |
||||
access = 'admin'; |
||||
return; |
||||
} |
||||
if (password === 'ant.design' && username === 'user') { |
||||
res.send({ |
||||
status: 'ok', |
||||
type, |
||||
currentAuthority: 'user', |
||||
}); |
||||
access = 'user'; |
||||
return; |
||||
} |
||||
if (type === 'mobile') { |
||||
res.send({ |
||||
status: 'ok', |
||||
type, |
||||
currentAuthority: 'admin', |
||||
}); |
||||
access = 'admin'; |
||||
return; |
||||
} |
||||
|
||||
res.send({ |
||||
status: 'error', |
||||
type, |
||||
currentAuthority: 'guest', |
||||
}); |
||||
access = 'guest'; |
||||
}, |
||||
'POST /api/login/outLogin': (req: Request, res: Response) => { |
||||
access = ''; |
||||
res.send({ data: {}, success: true }); |
||||
}, |
||||
'POST /api/register': (req: Request, res: Response) => { |
||||
res.send({ status: 'ok', currentAuthority: 'user', success: true }); |
||||
}, |
||||
'GET /api/500': (req: Request, res: Response) => { |
||||
res.status(500).send({ |
||||
timestamp: 1513932555104, |
||||
status: 500, |
||||
error: 'error', |
||||
message: 'error', |
||||
path: '/base/category/list', |
||||
}); |
||||
}, |
||||
'GET /api/404': (req: Request, res: Response) => { |
||||
res.status(404).send({ |
||||
timestamp: 1513932643431, |
||||
status: 404, |
||||
error: 'Not Found', |
||||
message: 'No message available', |
||||
path: '/base/category/list/2121212', |
||||
}); |
||||
}, |
||||
'GET /api/403': (req: Request, res: Response) => { |
||||
res.status(403).send({ |
||||
timestamp: 1513932555104, |
||||
status: 403, |
||||
error: 'Forbidden', |
||||
message: 'Forbidden', |
||||
path: '/base/category/list', |
||||
}); |
||||
}, |
||||
'GET /api/401': (req: Request, res: Response) => { |
||||
res.status(401).send({ |
||||
timestamp: 1513932555104, |
||||
status: 401, |
||||
error: 'Unauthorized', |
||||
message: 'Unauthorized', |
||||
path: '/base/category/list', |
||||
}); |
||||
}, |
||||
|
||||
'GET /api/login/captcha': getFakeCaptcha, |
||||
}; |
||||
@ -0,0 +1,116 @@ |
||||
{ |
||||
"name": "ant-design-pro", |
||||
"version": "5.2.0", |
||||
"private": true, |
||||
"description": "An out-of-box UI solution for enterprise applications", |
||||
"scripts": { |
||||
"analyze": "cross-env ANALYZE=1 umi build", |
||||
"build": "umi build", |
||||
"deploy": "npm run build && npm run gh-pages", |
||||
"dev": "npm run start:dev", |
||||
"gh-pages": "gh-pages -d dist", |
||||
"i18n-remove": "pro i18n-remove --locale=zh-CN --write", |
||||
"postinstall": "umi g tmp", |
||||
"lint": "umi g tmp && npm run lint:js && npm run lint:style && npm run lint:prettier && npm run tsc", |
||||
"lint-staged": "lint-staged", |
||||
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ", |
||||
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style", |
||||
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src", |
||||
"lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto", |
||||
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less", |
||||
"openapi": "umi openapi", |
||||
"playwright": "playwright install && playwright test", |
||||
"precommit": "lint-staged", |
||||
"prettier": "prettier -c --write \"src/**/*\"", |
||||
"serve": "umi-serve", |
||||
"start": "cross-env UMI_ENV=dev umi dev", |
||||
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev umi dev", |
||||
"start:no-mock": "cross-env MOCK=none UMI_ENV=dev umi dev", |
||||
"start:no-ui": "cross-env UMI_UI=none UMI_ENV=dev umi dev", |
||||
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev umi dev", |
||||
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev umi dev", |
||||
"test": "umi test", |
||||
"test:component": "umi test ./src/components", |
||||
"test:e2e": "node ./tests/run-tests.js", |
||||
"tsc": "tsc --noEmit" |
||||
}, |
||||
"lint-staged": { |
||||
"**/*.less": "stylelint --syntax less", |
||||
"**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js", |
||||
"**/*.{js,jsx,tsx,ts,less,md,json}": [ |
||||
"prettier --write" |
||||
] |
||||
}, |
||||
"browserslist": [ |
||||
"> 1%", |
||||
"last 2 versions", |
||||
"not ie <= 10" |
||||
], |
||||
"dependencies": { |
||||
"@ant-design/icons": "^4.7.0", |
||||
"@ant-design/pro-descriptions": "^1.10.0", |
||||
"@ant-design/pro-form": "^1.52.0", |
||||
"@ant-design/pro-layout": "^6.32.0", |
||||
"@ant-design/pro-table": "^2.61.0", |
||||
"@umijs/route-utils": "^2.0.0", |
||||
"antd": "^4.17.0", |
||||
"classnames": "^2.3.0", |
||||
"lodash": "^4.17.0", |
||||
"md5": "^2.3.0", |
||||
"moment": "^2.29.0", |
||||
"omit.js": "^2.0.2", |
||||
"rc-menu": "^9.1.0", |
||||
"rc-util": "^5.16.0", |
||||
"react": "^17.0.0", |
||||
"react-dev-inspector": "^1.7.0", |
||||
"react-dom": "^17.0.0", |
||||
"react-helmet-async": "^1.2.0", |
||||
"umi": "^3.5.0", |
||||
"umi-request": "^1.4.0", |
||||
"use-merge-value": "^1.0.2" |
||||
}, |
||||
"devDependencies": { |
||||
"@ant-design/pro-cli": "^2.1.0", |
||||
"@playwright/test": "^1.17.0", |
||||
"@types/express": "^4.17.0", |
||||
"@types/history": "^4.7.0", |
||||
"@types/jest": "^26.0.0", |
||||
"@types/lodash": "^4.14.0", |
||||
"@types/react": "^17.0.0", |
||||
"@types/react-dom": "^17.0.0", |
||||
"@types/react-helmet": "^6.1.0", |
||||
"@umijs/fabric": "^2.8.0", |
||||
"@umijs/openapi": "^1.3.0", |
||||
"@umijs/plugin-blocks": "^2.2.0", |
||||
"@umijs/plugin-esbuild": "^1.4.0", |
||||
"@umijs/plugin-openapi": "^1.3.0", |
||||
"@umijs/preset-ant-design-pro": "^1.3.0", |
||||
"@umijs/preset-dumi": "^1.1.0", |
||||
"@umijs/preset-react": "^1.8.17", |
||||
"@umijs/yorkie": "^2.0.5", |
||||
"carlo": "^0.9.46", |
||||
"cross-env": "^7.0.0", |
||||
"cross-port-killer": "^1.3.0", |
||||
"detect-installer": "^1.0.0", |
||||
"enzyme": "^3.11.0", |
||||
"eslint": "^7.32.0", |
||||
"express": "^4.17.0", |
||||
"gh-pages": "^3.2.0", |
||||
"jsdom-global": "^3.0.0", |
||||
"lint-staged": "^10.0.0", |
||||
"mockjs": "^1.1.0", |
||||
"pont-engine": "1.0.13", |
||||
"prettier": "^2.5.0", |
||||
"puppeteer-core": "^8.0.0", |
||||
"stylelint": "^13.0.0", |
||||
"swagger-ui-react": "^3.52.0", |
||||
"typescript": "^4.5.0", |
||||
"umi-serve": "^1.9.10" |
||||
}, |
||||
"engines": { |
||||
"node": ">=10.0.0" |
||||
}, |
||||
"gitHooks": { |
||||
"commit-msg": "fabric verify-commit" |
||||
} |
||||
} |
||||
@ -0,0 +1,9 @@ |
||||
{ |
||||
"originUrl": "http://localhost:8083/v2/api-docs", |
||||
"templatePath": "./pontTemplate", |
||||
"outDir": "./src/services", |
||||
"surrounding": "typescript", |
||||
"mocks": { |
||||
"enable": true |
||||
} |
||||
} |
||||
@ -0,0 +1,192 @@ |
||||
import * as Pont from 'pont-engine'; |
||||
import { CodeGenerator, Interface, Property, Mod, Surrounding } from 'pont-engine'; |
||||
|
||||
/** |
||||
* 生成代码 |
||||
*/ |
||||
export default class MyGenerator extends CodeGenerator { |
||||
/** |
||||
* 类型声明 api.d.ts |
||||
*/ |
||||
|
||||
/** 获取公共的类型定义代码 */ |
||||
// getCommonDeclaration() {
|
||||
// return `
|
||||
// import { RequestOptionsInit } from 'umi-request';
|
||||
// `;
|
||||
// }
|
||||
|
||||
/** 获取所有基类的类型定义代码,一个 namespace */ |
||||
getBaseClassesInDeclaration() { |
||||
const content = `namespace ${this.dataSource.name || 'defs'} {
|
||||
${this.dataSource.baseClasses |
||||
.map( |
||||
(base) => ` |
||||
export ${this.getBaseClassInDeclaration(base)} |
||||
`,
|
||||
) |
||||
.join('\n')} |
||||
} |
||||
`;
|
||||
|
||||
return content; |
||||
} |
||||
|
||||
/** 获取接口内容的类型定义代码 */ |
||||
getInterfaceContentInDeclaration(inter: Interface) { |
||||
// 请求体(Body)类型参数列表
|
||||
const bodyParams = inter.parameters.filter((param) => param.in === 'body'); |
||||
// 路径参数(PathVariable)类型参数列表
|
||||
const pathParams = inter.parameters.filter((param) => param.in === 'path'); |
||||
// 查询字符串(QueryString)类型参数列表
|
||||
const queryParams = inter.parameters.filter((param) => param.in === 'query'); |
||||
|
||||
// 获取请求体类型参数类代码
|
||||
const bodyParamsCode = inter.getBodyParamsCode(); |
||||
// 获取查询字符串参数类代码
|
||||
const paramsCode = inter.getParamsCode(); |
||||
// 获取路径参数类代码
|
||||
const pathParamsCode = this.getPathParamsCodeInDeclaration(pathParams); |
||||
|
||||
// 接口方法参数列表
|
||||
const requestArgs: string[] = []; |
||||
|
||||
pathParams.length > 0 && requestArgs.push(`pathParams: PathParams`); |
||||
queryParams.length > 0 && requestArgs.push(`params: Params`); |
||||
bodyParams.length > 0 && |
||||
requestArgs.push(`bodyParams:${bodyParams.length > 0 ? bodyParamsCode : ''}`); |
||||
requestArgs.push('options?: any'); |
||||
|
||||
const requestParams = requestArgs.join(', '); |
||||
|
||||
// 返回数据类型
|
||||
const { responseType } = inter; |
||||
|
||||
return ` |
||||
${queryParams.length > 0 ? paramsCode : ''} |
||||
${pathParams.length > 0 ? pathParamsCode : ''} |
||||
|
||||
export type ResponseType = Promise<${responseType}> |
||||
|
||||
export const URL:string; |
||||
|
||||
export function request(${requestParams}): ResponseType; |
||||
`;
|
||||
} |
||||
|
||||
/** |
||||
* 接口实现 /mods/<module_name>/<method_name>.ts |
||||
*/ |
||||
|
||||
/** 获取接口实现内容的代码 */ |
||||
getInterfaceContent(inter: Interface) { |
||||
// 接口请求类型
|
||||
const method = inter.method.toUpperCase(); |
||||
|
||||
// 请求体(Body)类型参数列表
|
||||
const bodyParams = inter.parameters.filter((param) => param.in === 'body'); |
||||
// 路径参数(PathVariable)类型参数列表
|
||||
const pathParams = inter.parameters.filter((param) => param.in === 'path'); |
||||
// 查询字符串(QueryString)类型参数列表
|
||||
const queryParams = inter.parameters.filter((param) => param.in === 'query'); |
||||
|
||||
// 获取请求体类型参数类代码
|
||||
const bodyParamsCode = inter.getBodyParamsCode(); |
||||
// 获取查询字符串参数类代码
|
||||
const paramsCode = this.getParamsCode(queryParams); |
||||
// 获取路径参数类代码
|
||||
const pathParamsCode = this.getPathParamsCode(pathParams); |
||||
|
||||
// 请求的 MIME 类型
|
||||
const contentType = |
||||
inter.consumes && inter.consumes.length ? inter.consumes[0] : 'application/json'; |
||||
|
||||
// 接口方法参数列表
|
||||
const requestArgs: string[] = []; |
||||
|
||||
pathParams.length > 0 && requestArgs.push(`pathParams: PathParams`); |
||||
queryParams.length > 0 && requestArgs.push(`params: Params`); |
||||
bodyParams.length > 0 && |
||||
requestArgs.push(`bodyParams:${bodyParams.length > 0 ? bodyParamsCode : ''}`); |
||||
requestArgs.push('options?: RequestOptionsInit'); |
||||
|
||||
const requestParams = requestArgs.join(', '); |
||||
|
||||
// 数据源前缀
|
||||
let dataSourcePrefix = ''; |
||||
if (this.dataSource.name) { |
||||
dataSourcePrefix = `/${this.dataSource.name}`; |
||||
} |
||||
|
||||
return ` |
||||
import fetch from '../../../utils/request'; |
||||
import { RequestOptionsInit } from 'umi-request'; |
||||
|
||||
${pathParams.length > 0 ? `export ${pathParamsCode}` : ''} |
||||
${queryParams.length > 0 ? `export ${paramsCode}` : ''} |
||||
|
||||
export const URL = '${dataSourcePrefix}${inter.path}'; |
||||
|
||||
/** |
||||
* @desc ${inter.description} |
||||
*/ |
||||
export function request (${requestParams}) { |
||||
const fetchOption = Object.assign ({ |
||||
method: '${method}', |
||||
headers: { |
||||
'Content-Type': '${contentType}' |
||||
}, |
||||
${queryParams.length > 0 ? 'params: params,' : ''} |
||||
${bodyParamsCode ? 'data: bodyParams' : ''} |
||||
}, |
||||
options) |
||||
return fetch('${dataSourcePrefix}${inter.path}', fetchOption); |
||||
} |
||||
`;
|
||||
} |
||||
|
||||
// 生成参数类(Params)
|
||||
getParamsCode(params: Property[]): string { |
||||
if (!Array.isArray(params) || params.length === 0) return ''; |
||||
|
||||
return ` |
||||
export class Params { |
||||
${params |
||||
.map((param) => { |
||||
return `${param.toPropertyCodeWithInitValue('')}`; |
||||
}) |
||||
.join('\n')} |
||||
} |
||||
`;
|
||||
} |
||||
|
||||
// 生成路径参数类(PathParams)
|
||||
getPathParamsCode(pathParams: Property[]): string { |
||||
if (!Array.isArray(pathParams) || pathParams.length === 0) return ''; |
||||
|
||||
return ` |
||||
class PathParams { |
||||
${pathParams |
||||
.map((param) => { |
||||
return `${param.toPropertyCodeWithInitValue('')}`; |
||||
}) |
||||
.join('\n')} |
||||
} |
||||
`;
|
||||
} |
||||
|
||||
// 生成路径参数类(PathParams)
|
||||
getPathParamsCodeInDeclaration(pathParams: Property[]): string { |
||||
if (!Array.isArray(pathParams) || pathParams.length === 0) return ''; |
||||
|
||||
return ` |
||||
class PathParams { |
||||
${pathParams |
||||
.map((param) => { |
||||
return `${param.toPropertyCode(Surrounding.typeScript, param.required)}`; |
||||
}) |
||||
.join('\n')} |
||||
} |
||||
`;
|
||||
} |
||||
} |
||||
@ -0,0 +1 @@ |
||||
preview.pro.ant.design |
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 677 B |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1,36 @@ |
||||
import React from 'react'; |
||||
import { Result } from 'antd'; |
||||
import type { IAuthorityType } from './CheckPermissions'; |
||||
import check from './CheckPermissions'; |
||||
|
||||
import type AuthorizedRoute from './AuthorizedRoute'; |
||||
import type Secured from './Secured'; |
||||
|
||||
interface AuthorizedProps { |
||||
authority: IAuthorityType; |
||||
noMatch?: React.ReactNode; |
||||
} |
||||
|
||||
type IAuthorizedType = React.FunctionComponent<AuthorizedProps> & { |
||||
Secured: typeof Secured; |
||||
check: typeof check; |
||||
AuthorizedRoute: typeof AuthorizedRoute; |
||||
}; |
||||
|
||||
const Authorized: React.FunctionComponent<AuthorizedProps> = ({ |
||||
children, |
||||
authority, |
||||
noMatch = ( |
||||
<Result |
||||
status="403" |
||||
title="403" |
||||
subTitle="Sorry, you are not authorized to access this page." |
||||
/> |
||||
), |
||||
}) => { |
||||
const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children; |
||||
const dom = check(authority, childrenRender, noMatch); |
||||
return <>{dom}</>; |
||||
}; |
||||
|
||||
export default Authorized as IAuthorizedType; |
||||